diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java index b669ed6b..37a32777 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java @@ -150,6 +150,24 @@ public class AgentController { } } + @PostMapping("/chat-title/{sessionId}/generate") + @Operation(summary = "根据会话ID生成聊天标题(异步执行)") + public Result generateAndSaveChatTitle(@PathVariable String sessionId) { + try { + new Thread(() -> { + try { + agentChatSummaryService.generateAndSaveChatTitle(sessionId); + System.out.println("异步执行会话 " + sessionId + " 的标题生成完成"); + } catch (Exception e) { + System.err.println("异步执行会话 " + sessionId + " 的标题生成失败: " + e.getMessage()); + } + }).start(); + return new Result().ok(null); + } catch (Exception e) { + return new Result().error("启动异步标题生成任务失败: " + e.getMessage()); + } + } + @PutMapping("/{id}") @Operation(summary = "更新智能体") @RequiresPermissions("sys:role:normal") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java index 418ee9a0..33605849 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java @@ -12,4 +12,12 @@ public interface AgentChatSummaryService { * @return 保存结果 */ boolean generateAndSaveChatSummary(String sessionId); + + /** + * 根据会话ID生成聊天标题并保存 + * + * @param sessionId 会话ID + * @return 是否成功 + */ + boolean generateAndSaveChatTitle(String sessionId); } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java index 02e54145..a3b78b18 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java @@ -104,7 +104,6 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { if (memModelId == null || memModelId.equals(Constant.MEMORY_MEM_REPORT_ONLY)) { log.info("会话 {} 使用仅上报聊天记录模式,跳过记忆总结", sessionId); - generateAndSaveChatTitle(sessionId, agentId); return true; } @@ -128,7 +127,6 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { log.info("会话 {} 使用 {} 模式,跳过记忆总结", sessionId, memModelId); } - generateAndSaveChatTitle(sessionId, agentId); return true; } catch (Exception e) { @@ -137,16 +135,24 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { } } - private void generateAndSaveChatTitle(String sessionId, String agentId) { + @Override + public boolean generateAndSaveChatTitle(String sessionId) { try { + // 自动获取agentId + String agentId = findAgentIdBySessionId(sessionId); + if (StringUtils.isBlank(agentId)) { + log.warn("会话 {} 无法获取智能体信息,跳过标题生成", sessionId); + return false; + } + List chatHistory = getChatHistoryBySessionId(sessionId); if (chatHistory == null || chatHistory.isEmpty()) { - return; + return false; } List meaningfulMessages = extractMeaningfulMessages(chatHistory); if (meaningfulMessages.isEmpty()) { - return; + return false; } StringBuilder conversation = new StringBuilder(); @@ -160,9 +166,12 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { if (StringUtils.isNotBlank(title)) { agentChatTitleService.saveOrUpdateTitle(sessionId, title); log.info("成功保存会话 {} 的标题: {}", sessionId, title); + return true; } + return false; } catch (Exception e) { log.error("生成会话 {} 的标题时发生错误: {}", sessionId, e.getMessage()); + return false; } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java index 29b9ad2f..80b93c6d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java @@ -90,6 +90,7 @@ public class ShiroConfig { filterMap.put("/agent/chat-history/report", "server"); filterMap.put("/agent/chat-history/download/**", "anon"); filterMap.put("/agent/chat-summary/**", "server"); + filterMap.put("/agent/chat-title/**", "server"); filterMap.put("/agent/play/**", "anon"); filterMap.put("/voiceClone/play/**", "anon"); filterMap.put("/**", "oauth2"); diff --git a/main/xiaozhi-server/config/manage_api_client.py b/main/xiaozhi-server/config/manage_api_client.py index a9d8d601..e6f7c918 100644 --- a/main/xiaozhi-server/config/manage_api_client.py +++ b/main/xiaozhi-server/config/manage_api_client.py @@ -195,6 +195,18 @@ async def generate_and_save_chat_summary(session_id: str) -> Optional[Dict]: return None +async def generate_and_save_chat_title(session_id: str) -> Optional[Dict]: + """生成并保存聊天标题""" + try: + return await ManageApiClient._instance._execute_async_request( + "POST", + f"/agent/chat-title/{session_id}/generate", + ) + except Exception as e: + print(f"生成并保存聊天标题失败: {e}") + return None + + async def report( mac_address: str, session_id: str, chat_type: int, content: str, audio, report_time ) -> Optional[Dict]: diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 31c79f5f..501b52e0 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -37,7 +37,7 @@ from core.auth import AuthenticationError from config.config_loader import get_private_config_from_api from core.providers.tts.dto.dto import ContentType, TTSMessageDTO, SentenceType from config.logger import setup_logging, build_module_string, create_connection_logger -from config.manage_api_client import DeviceNotFoundException, DeviceBindException +from config.manage_api_client import DeviceNotFoundException, DeviceBindException, generate_and_save_chat_title from core.utils.prompt_manager import PromptManager from core.utils.voiceprint_provider import VoiceprintProvider from core.utils.util import get_system_error_response @@ -284,6 +284,26 @@ class ConnectionHandler: async def _save_and_close(self, ws): """保存记忆并关闭连接""" try: + # 守护线程1:独立生成标题(不依赖记忆模型) + if self.session_id: + def generate_title_task(): + try: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete( + generate_and_save_chat_title(self.session_id) + ) + except Exception as e: + self.logger.bind(tag=TAG).error(f"生成标题失败: {e}") + finally: + try: + loop.close() + except Exception: + pass + + threading.Thread(target=generate_title_task, daemon=True).start() + + # 守护线程2:走老流程记忆保存(仅记忆,不含标题) if self.memory: # 使用线程池异步保存记忆 def save_memory_task(): diff --git a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py index 8c593669..79d0770a 100644 --- a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py +++ b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py @@ -2,7 +2,6 @@ import json import traceback from ..base import MemoryProviderBase, logger -from config.manage_api_client import generate_and_save_chat_summary from mem0 import MemoryClient from core.utils.util import check_model_key @@ -59,11 +58,6 @@ class MemoryProvider(MemoryProviderBase): except Exception as e: logger.bind(tag=TAG).error(f"保存记忆失败: {str(e)}") - # Generate and save chat summary (SLM summarizes session title) - # This is independent of Mem0AI's memory saving - if session_id: - await generate_and_save_chat_summary(session_id) - return None async def query_memory(self, query: str) -> str: diff --git a/main/xiaozhi-server/core/providers/memory/mem_report_only/mem_report_only.py b/main/xiaozhi-server/core/providers/memory/mem_report_only/mem_report_only.py index 7654b0c5..2fd1fdff 100644 --- a/main/xiaozhi-server/core/providers/memory/mem_report_only/mem_report_only.py +++ b/main/xiaozhi-server/core/providers/memory/mem_report_only/mem_report_only.py @@ -3,7 +3,6 @@ """ from ..base import MemoryProviderBase, logger -from config.manage_api_client import generate_and_save_chat_summary TAG = __name__ @@ -14,8 +13,6 @@ class MemoryProvider(MemoryProviderBase): async def save_memory(self, msgs, session_id=None): logger.bind(tag=TAG).debug("mem_report_only mode: No memory saving or summarization is performed.") - if session_id: - await generate_and_save_chat_summary(session_id) return None async def query_memory(self, query: str) -> str: diff --git a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py index d7787f5a..cd55caf8 100644 --- a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py +++ b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py @@ -16,7 +16,6 @@ import traceback from typing import Optional, Dict, Any from ..base import MemoryProviderBase, logger -from config.manage_api_client import generate_and_save_chat_summary TAG = __name__ @@ -211,11 +210,6 @@ class MemoryProvider(MemoryProviderBase): logger.bind(tag=TAG).error(f"Error saving memory: {str(e)}") logger.bind(tag=TAG).debug(f"Detailed error: {traceback.format_exc()}") - # Generate and save chat summary (SLM summarizes session title) - # This is independent of PowerMem's memory saving - if session_id: - await generate_and_save_chat_summary(session_id) - return None async def query_memory(self, query: str) -> str: