From d6697948c2f1c0b0a6a86be3428003bae6a6c624 Mon Sep 17 00:00:00 2001 From: rainv123 <2148537152@qq.com> Date: Fri, 12 Dec 2025 12:53:59 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E4=BD=BF=E7=94=A8java=E7=AB=AF?= =?UTF-8?q?=E5=81=9A=E8=81=8A=E5=A4=A9=E8=AE=B0=E5=BD=95=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/controller/AgentController.java | 33 ++ .../agent/dto/AgentChatSummaryDTO.java | 48 ++ .../service/AgentChatSummaryService.java | 25 ++ .../impl/AgentChatHistoryBizServiceImpl.java | 34 +- .../impl/AgentChatSummaryServiceImpl.java | 424 ++++++++++++++++++ .../modules/llm/service/LLMService.java | 70 +++ .../impl/OpenAIStyleLLMServiceImpl.java | 306 +++++++++++++ .../model/service/ModelConfigService.java | 8 + .../service/impl/ModelConfigServiceImpl.java | 18 + .../config/manage_api_client.py | 31 +- main/xiaozhi-server/core/connection.py | 4 +- .../core/providers/memory/base.py | 2 +- .../core/providers/memory/mem0ai/mem0ai.py | 6 +- .../memory/mem_local_short/mem_local_short.py | 23 +- .../core/providers/memory/nomem/nomem.py | 2 +- 15 files changed, 1013 insertions(+), 21 deletions(-) create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java 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 c09aa141..ac1852a5 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 @@ -36,6 +36,7 @@ import xiaozhi.common.utils.Result; import xiaozhi.common.utils.ResultUtils; import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; import xiaozhi.modules.agent.dto.AgentChatSessionDTO; +import xiaozhi.modules.agent.dto.AgentChatSummaryDTO; import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.dto.AgentMemoryDTO; @@ -44,6 +45,7 @@ import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity; import xiaozhi.modules.agent.service.AgentChatAudioService; import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentChatSummaryService; import xiaozhi.modules.agent.service.AgentContextProviderService; import xiaozhi.modules.agent.service.AgentPluginMappingService; import xiaozhi.modules.agent.service.AgentService; @@ -66,6 +68,7 @@ public class AgentController { private final AgentChatAudioService agentChatAudioService; private final AgentPluginMappingService agentPluginMappingService; private final AgentContextProviderService agentContextProviderService; + private final AgentChatSummaryService agentChatSummaryService; private final RedisUtils redisUtils; @GetMapping("/list") @@ -119,6 +122,36 @@ public class AgentController { return new Result<>(); } + @PostMapping("/chat-summary/{sessionId}") + @Operation(summary = "根据会话ID生成聊天记录总结") + public Result generateChatSummary(@PathVariable String sessionId) { + try { + AgentChatSummaryDTO summary = agentChatSummaryService.generateChatSummary(sessionId); + if (summary.isSuccess()) { + return new Result().ok(summary); + } else { + return new Result().error(summary.getErrorMessage()); + } + } catch (Exception e) { + return new Result().error("生成聊天记录总结失败: " + e.getMessage()); + } + } + + @PostMapping("/chat-summary/{sessionId}/save") + @Operation(summary = "根据会话ID生成聊天记录总结并保存") + public Result generateAndSaveChatSummary(@PathVariable String sessionId) { + try { + boolean success = agentChatSummaryService.generateAndSaveChatSummary(sessionId); + if (success) { + return new Result().ok(null); + } else { + return new Result().error("保存聊天记录总结失败"); + } + } 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/dto/AgentChatSummaryDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java new file mode 100644 index 00000000..e0bf12a7 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java @@ -0,0 +1,48 @@ +package xiaozhi.modules.agent.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 智能体聊天记录总结DTO + */ +@Data +@Schema(description = "智能体聊天记录总结对象") +public class AgentChatSummaryDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "会话ID") + private String sessionId; + + @Schema(description = "智能体ID") + private String agentId; + + @Schema(description = "总结内容") + private String summary; + + @Schema(description = "总结状态") + private boolean success; + + @Schema(description = "错误信息") + private String errorMessage; + + public AgentChatSummaryDTO() { + this.success = true; + } + + public AgentChatSummaryDTO(String sessionId, String agentId, String summary) { + this.sessionId = sessionId; + this.agentId = agentId; + this.summary = summary; + this.success = true; + } + + public AgentChatSummaryDTO(String sessionId, String errorMessage) { + this.sessionId = sessionId; + this.errorMessage = errorMessage; + this.success = false; + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..c442deb8 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java @@ -0,0 +1,25 @@ +package xiaozhi.modules.agent.service; + +import xiaozhi.modules.agent.dto.AgentChatSummaryDTO; + +/** + * 智能体聊天记录总结服务接口 + */ +public interface AgentChatSummaryService { + + /** + * 根据会话ID生成聊天记录总结 + * + * @param sessionId 会话ID + * @return 总结结果 + */ + AgentChatSummaryDTO generateChatSummary(String sessionId); + + /** + * 根据会话ID生成聊天记录总结并保存到智能体记忆 + * + * @param sessionId 会话ID + * @return 保存结果 + */ + boolean generateAndSaveChatSummary(String sessionId); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java index 36b59993..16bcce50 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java @@ -17,6 +17,7 @@ import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.service.AgentChatAudioService; import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentChatSummaryService; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService; import xiaozhi.modules.device.entity.DeviceEntity; @@ -36,6 +37,7 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic private final AgentService agentService; private final AgentChatHistoryService agentChatHistoryService; private final AgentChatAudioService agentChatAudioService; + private final AgentChatSummaryService agentChatSummaryService; private final RedisUtils redisUtils; private final DeviceService deviceService; @@ -50,7 +52,8 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic public Boolean report(AgentChatHistoryReportDTO report) { String macAddress = report.getMacAddress(); Byte chatType = report.getChatType(); - Long reportTimeMillis = null != report.getReportTime() ? report.getReportTime() * 1000 : System.currentTimeMillis(); + Long reportTimeMillis = null != report.getReportTime() ? report.getReportTime() * 1000 + : System.currentTimeMillis(); log.info("小智设备聊天上报请求: macAddress={}, type={} reportTime={}", macAddress, chatType, reportTimeMillis); // 根据设备MAC地址查询对应的默认智能体,判断是否需要上报 @@ -80,6 +83,9 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic log.warn("聊天记录上报时,未找到mac地址为 {} 的设备", macAddress); } + // 异步触发聊天记录总结(仅在对话结束时触发) + triggerChatSummaryAsync(report.getSessionId(), chatType); + return Boolean.TRUE; } @@ -105,7 +111,8 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic /** * 组装上报数据 */ - private void saveChatText(AgentChatHistoryReportDTO report, String agentId, String macAddress, String audioId, Long reportTime) { + private void saveChatText(AgentChatHistoryReportDTO report, String agentId, String macAddress, String audioId, + Long reportTime) { // 构建聊天记录实体 AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder() .macAddress(macAddress) @@ -123,4 +130,27 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic log.info("设备 {} 对应智能体 {} 上报成功", macAddress, agentId); } + + /** + * 异步触发聊天记录总结 + * 仅在对话结束时(chatType=2)触发总结,避免频繁总结 + */ + private void triggerChatSummaryAsync(String sessionId, Byte chatType) { + // 仅在对话结束时触发总结(chatType=2表示对话结束) + if (chatType != null && chatType == 2) { + new Thread(() -> { + try { + log.info("开始为会话 {} 生成聊天记录总结", sessionId); + boolean success = agentChatSummaryService.generateAndSaveChatSummary(sessionId); + if (success) { + log.info("会话 {} 的聊天记录总结生成并保存成功", sessionId); + } else { + log.warn("会话 {} 的聊天记录总结生成失败", sessionId); + } + } catch (Exception e) { + log.error("触发会话 {} 的聊天记录总结时发生异常", sessionId, e); + } + }).start(); + } + } } 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 new file mode 100644 index 00000000..80ad03d6 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java @@ -0,0 +1,424 @@ +package xiaozhi.modules.agent.service.impl; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; +import xiaozhi.modules.agent.dto.AgentChatSummaryDTO; +import xiaozhi.modules.agent.dto.AgentMemoryDTO; +import xiaozhi.modules.agent.dto.AgentUpdateDTO; +import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; +import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentChatSummaryService; +import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.config.service.ConfigService; +import xiaozhi.modules.device.entity.DeviceEntity; +import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.llm.service.LLMService; +import xiaozhi.modules.agent.vo.AgentInfoVO; +import xiaozhi.modules.model.entity.ModelConfigEntity; +import xiaozhi.modules.model.service.ModelConfigService; +import java.util.Map; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 智能体聊天记录总结服务实现类 + * 实现Python端mem_local_short.py中的总结逻辑 + */ +@Service +@RequiredArgsConstructor +public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { + + private static final Logger log = LoggerFactory.getLogger(AgentChatSummaryServiceImpl.class); + + private final AgentChatHistoryService agentChatHistoryService; + private final AgentService agentService; + private final DeviceService deviceService; + private final LLMService llmService; + private final ConfigService configService; + private final ModelConfigService modelConfigService; + + // 总结规则常量 + private static final int MAX_SUMMARY_LENGTH = 1800; // 最大总结长度 + private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*?\\}", Pattern.DOTALL); + private static final Pattern DEVICE_CONTROL_PATTERN = Pattern.compile("设备控制|设备操作|控制设备|设备状态", + Pattern.CASE_INSENSITIVE); + private static final Pattern WEATHER_PATTERN = Pattern.compile("天气|温度|湿度|降雨|气象", Pattern.CASE_INSENSITIVE); + private static final Pattern DATE_PATTERN = Pattern.compile("日期|时间|星期|月份|年份", Pattern.CASE_INSENSITIVE); + + @Override + public AgentChatSummaryDTO generateChatSummary(String sessionId) { + try { + System.out.println("开始生成会话 " + sessionId + " 的聊天记录总结"); + + // 1. 根据sessionId获取聊天记录 + List chatHistory = getChatHistoryBySessionId(sessionId); + if (chatHistory == null || chatHistory.isEmpty()) { + return new AgentChatSummaryDTO(sessionId, "未找到该会话的聊天记录"); + } + + // 2. 获取智能体信息 + String agentId = getAgentIdFromSession(sessionId, chatHistory); + if (StringUtils.isBlank(agentId)) { + return new AgentChatSummaryDTO(sessionId, "无法获取智能体信息"); + } + + // 3. 提取关键对话内容 + List meaningfulMessages = extractMeaningfulMessages(chatHistory); + if (meaningfulMessages.isEmpty()) { + return new AgentChatSummaryDTO(sessionId, "没有有效的对话内容可总结"); + } + + // 4. 生成总结(generateSummaryFromMessages方法已包含长度限制逻辑) + String summary = generateSummaryFromMessages(meaningfulMessages, agentId); + + System.out.println("成功生成会话 " + sessionId + " 的聊天记录总结,长度: " + summary.length() + " 字符"); + return new AgentChatSummaryDTO(sessionId, agentId, summary); + + } catch (Exception e) { + System.err.println("生成会话 " + sessionId + " 的聊天记录总结时发生错误: " + e.getMessage()); + return new AgentChatSummaryDTO(sessionId, "生成总结时发生错误: " + e.getMessage()); + } + } + + @Override + public boolean generateAndSaveChatSummary(String sessionId) { + try { + // 1. 生成总结 + AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId); + if (!summaryDTO.isSuccess()) { + System.err.println("生成总结失败: " + summaryDTO.getErrorMessage()); + return false; + } + + // 2. 获取设备信息(通过会话关联的设备) + DeviceEntity device = getDeviceBySessionId(sessionId); + if (device == null) { + System.err.println("未找到与会话 " + sessionId + " 关联的设备"); + return false; + } + + // 3. 更新智能体记忆 + AgentMemoryDTO memoryDTO = new AgentMemoryDTO(); + memoryDTO.setSummaryMemory(summaryDTO.getSummary()); + + // 调用现有接口更新记忆 + agentService.updateAgentById(device.getAgentId(), + new AgentUpdateDTO() { + { + setSummaryMemory(summaryDTO.getSummary()); + } + }); + + System.out.println("成功保存会话 " + sessionId + " 的聊天记录总结到智能体 " + device.getAgentId()); + return true; + + } catch (Exception e) { + System.err.println("保存会话 " + sessionId + " 的聊天记录总结时发生错误: " + e.getMessage()); + return false; + } + } + + /** + * 根据会话ID获取聊天记录 + */ + private List getChatHistoryBySessionId(String sessionId) { + try { + // 这里需要根据sessionId获取聊天记录 + // 由于现有接口需要agentId,我们需要先找到关联的agentId + String agentId = findAgentIdBySessionId(sessionId); + if (StringUtils.isBlank(agentId)) { + return null; + } + return agentChatHistoryService.getChatHistoryBySessionId(agentId, sessionId); + } catch (Exception e) { + System.err.println("获取会话 " + sessionId + " 的聊天记录失败: " + e.getMessage()); + return null; + } + } + + /** + * 根据会话ID查找关联的智能体ID + */ + private String findAgentIdBySessionId(String sessionId) { + try { + // 查询该会话的第一条记录获取agentId + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("agent_id") + .eq("session_id", sessionId) + .last("LIMIT 1"); + + AgentChatHistoryEntity entity = agentChatHistoryService.getOne(wrapper); + return entity != null ? entity.getAgentId() : null; + } catch (Exception e) { + System.err.println("根据会话ID " + sessionId + " 查找智能体ID失败: " + e.getMessage()); + return null; + } + } + + /** + * 从会话中获取智能体ID + */ + private String getAgentIdFromSession(String sessionId, List chatHistory) { + // 直接从数据库查询智能体ID + return findAgentIdBySessionId(sessionId); + } + + /** + * 提取有意义的对话内容(只提取用户消息,排除AI回复) + */ + private List extractMeaningfulMessages(List chatHistory) { + List meaningfulMessages = new ArrayList<>(); + + for (AgentChatHistoryDTO message : chatHistory) { + // 只处理用户消息(chatType = 1) + if (message.getChatType() != null && message.getChatType() == 1) { + String content = extractContentFromMessage(message); + if (isMeaningfulMessage(content)) { + meaningfulMessages.add(content); + } + } + } + + return meaningfulMessages; + } + + /** + * 从消息中提取内容(处理JSON格式) + */ + private String extractContentFromMessage(AgentChatHistoryDTO message) { + String content = message.getContent(); + if (StringUtils.isBlank(content)) { + return ""; + } + + // 处理JSON格式内容(与前端ChatHistoryDialog.vue逻辑一致) + Matcher matcher = JSON_PATTERN.matcher(content); + if (matcher.find()) { + String jsonContent = matcher.group(); + // 简化处理:提取JSON中的文本内容 + return extractTextFromJson(jsonContent); + } + + return content; + } + + /** + * 从JSON中提取文本内容 + */ + private String extractTextFromJson(String jsonContent) { + // 简化处理:提取"content"字段的值 + Pattern contentPattern = Pattern.compile("\"content\"\s*:\s*\"([^\"]*)\""); + Matcher matcher = contentPattern.matcher(jsonContent); + if (matcher.find()) { + return matcher.group(1); + } + return jsonContent; + } + + /** + * 判断是否为有意义的消息 + */ + private boolean isMeaningfulMessage(String content) { + if (StringUtils.isBlank(content)) { + return false; + } + + // 排除设备控制信息 + if (DEVICE_CONTROL_PATTERN.matcher(content).find()) { + return false; + } + + // 排除日期天气等无关内容 + if (WEATHER_PATTERN.matcher(content).find() || DATE_PATTERN.matcher(content).find()) { + return false; + } + + // 排除过短的消息 + return content.length() >= 5; + } + + /** + * 从消息生成总结 + */ + private String generateSummaryFromMessages(List messages, String agentId) { + if (messages.isEmpty()) { + return "本次对话内容较少,没有需要总结的重要信息。"; + } + + // 构建完整的对话内容 + StringBuilder conversation = new StringBuilder(); + for (int i = 0; i < messages.size(); i++) { + conversation.append("消息").append(i + 1).append(": ").append(messages.get(i)).append("\n"); + } + + try { + // 获取当前智能体的历史记忆 + String historyMemory = getCurrentAgentMemory(agentId); + + // 调用LLM服务进行智能总结,传递agentId以获取正确的模型配置 + String summary = callJavaLLMForSummaryWithHistory(conversation.toString(), historyMemory, agentId); + + // 应用总结规则:限制最大长度 + if (summary.length() > MAX_SUMMARY_LENGTH) { + summary = summary.substring(0, MAX_SUMMARY_LENGTH) + "..."; + } + + return summary; + } catch (Exception e) { + System.err.println("调用Java端LLM服务失败: " + e.getMessage()); + throw new RuntimeException("LLM服务不可用,无法生成聊天总结"); + } + } + + /** + * 获取当前智能体的历史记忆 + */ + private String getCurrentAgentMemory(String agentId) { + try { + if (StringUtils.isBlank(agentId)) { + return null; + } + + // 获取智能体信息 + AgentInfoVO agentInfo = agentService.getAgentById(agentId); + if (agentInfo == null) { + return null; + } + + // 返回智能体的当前总结记忆 + return agentInfo.getSummaryMemory(); + } catch (Exception e) { + System.err.println("获取智能体历史记忆失败,agentId: " + agentId + ", 错误: " + e.getMessage()); + return null; + } + } + + /** + * 调用Java端LLM服务进行智能总结(支持历史记忆合并) + */ + private String callJavaLLMForSummaryWithHistory(String conversation, String historyMemory, String agentId) { + try { + // 获取智能体配置,从中提取记忆总结的模型ID + String modelId = getMemorySummaryModelId(agentId); + + if (StringUtils.isBlank(modelId)) { + System.out.println("未找到记忆总结的LLM模型配置,使用默认LLM服务"); + return llmService.generateSummaryWithHistory(conversation, historyMemory, null, null); + } + + // 使用指定的模型ID调用LLM服务(支持历史记忆合并) + String summary = llmService.generateSummaryWithHistory(conversation, historyMemory, null, modelId); + + if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) { + return summary; + } + + throw new RuntimeException("Java端LLM服务返回异常: " + summary); + + } catch (Exception e) { + System.err.println("调用Java端LLM服务异常,agentId: " + agentId + ", 错误: " + e.getMessage()); + throw e; + } + } + + /** + * 调用Java端LLM服务进行智能总结 + */ + private String callJavaLLMForSummary(String conversation, String agentId) { + try { + // 获取智能体配置,从中提取记忆总结的模型ID + String modelId = getMemorySummaryModelId(agentId); + + if (StringUtils.isBlank(modelId)) { + System.out.println("未找到记忆总结的LLM模型配置,使用默认LLM服务"); + return llmService.generateSummary(conversation); + } + + // 使用指定的模型ID调用LLM服务 + String summary = llmService.generateSummaryWithModel(conversation, modelId); + + if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) { + return summary; + } + + throw new RuntimeException("Java端LLM服务返回异常: " + summary); + + } catch (Exception e) { + System.err.println("调用Java端LLM服务异常,agentId: " + agentId + ", 错误: " + e.getMessage()); + throw e; + } + } + + /** + * 获取记忆总结的LLM模型ID + */ + private String getMemorySummaryModelId(String agentId) { + try { + if (StringUtils.isBlank(agentId)) { + return null; + } + + // 获取智能体信息 + AgentInfoVO agentInfo = agentService.getAgentById(agentId); + if (agentInfo == null) { + return null; + } + + // 获取智能体的记忆模型ID + String memModelId = agentInfo.getMemModelId(); + if (StringUtils.isBlank(memModelId)) { + return null; + } + + // 获取记忆模型配置 + ModelConfigEntity memModelConfig = modelConfigService.getModelByIdFromCache(memModelId); + if (memModelConfig == null || memModelConfig.getConfigJson() == null) { + return null; + } + + // 从记忆模型配置中提取对应的LLM模型ID + Map configMap = memModelConfig.getConfigJson(); + String llmModelId = (String) configMap.get("llm"); + + if (StringUtils.isBlank(llmModelId)) { + // 如果记忆模型没有配置独立的LLM,则使用智能体的默认LLM模型 + return agentInfo.getLlmModelId(); + } + + return llmModelId; + } catch (Exception e) { + System.err.println("获取记忆总结LLM模型ID失败,agentId: " + agentId + ", 错误: " + e.getMessage()); + return null; + } + } + + /** + * 根据会话ID获取设备信息 + */ + private DeviceEntity getDeviceBySessionId(String sessionId) { + try { + // 查询该会话的第一条记录获取macAddress + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("mac_address") + .eq("session_id", sessionId) + .last("LIMIT 1"); + + AgentChatHistoryEntity entity = agentChatHistoryService.getOne(wrapper); + if (entity != null && StringUtils.isNotBlank(entity.getMacAddress())) { + return deviceService.getDeviceByMacAddress(entity.getMacAddress()); + } + return null; + } catch (Exception e) { + System.err.println("根据会话ID " + sessionId + " 查找设备信息失败: " + e.getMessage()); + return null; + } + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java new file mode 100644 index 00000000..a3f10e9f --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java @@ -0,0 +1,70 @@ +package xiaozhi.modules.llm.service; + +/** + * LLM服务接口 + * 支持多种大模型调用 + */ +public interface LLMService { + + /** + * 生成聊天记录总结 + * + * @param conversation 对话内容 + * @param promptTemplate 提示词模板 + * @return 总结结果 + */ + String generateSummary(String conversation, String promptTemplate); + + /** + * 生成聊天记录总结(使用默认提示词) + * + * @param conversation 对话内容 + * @return 总结结果 + */ + String generateSummary(String conversation); + + /** + * 生成聊天记录总结(指定模型ID) + * + * @param conversation 对话内容 + * @param modelId 模型ID + * @return 总结结果 + */ + String generateSummaryWithModel(String conversation, String modelId); + + /** + * 生成聊天记录总结(指定模型ID和提示词模板) + * + * @param conversation 对话内容 + * @param promptTemplate 提示词模板 + * @param modelId 模型ID + * @return 总结结果 + */ + String generateSummary(String conversation, String promptTemplate, String modelId); + + /** + * 生成聊天记录总结(包含历史记忆合并) + * + * @param conversation 对话内容 + * @param historyMemory 历史记忆 + * @param promptTemplate 提示词模板 + * @param modelId 模型ID + * @return 总结结果 + */ + String generateSummaryWithHistory(String conversation, String historyMemory, String promptTemplate, String modelId); + + /** + * 检查服务是否可用 + * + * @return 是否可用 + */ + boolean isAvailable(); + + /** + * 检查指定模型的服务是否可用 + * + * @param modelId 模型ID + * @return 是否可用 + */ + boolean isAvailable(String modelId); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java new file mode 100644 index 00000000..e0b8a3b2 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java @@ -0,0 +1,306 @@ +package xiaozhi.modules.llm.service.impl; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import xiaozhi.modules.llm.service.LLMService; +import xiaozhi.modules.model.entity.ModelConfigEntity; +import xiaozhi.modules.model.service.ModelConfigService; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * OpenAI风格API的LLM服务实现 + * 支持阿里云、DeepSeek、ChatGLM等兼容OpenAI API的模型 + */ +@Slf4j +@Service +public class OpenAIStyleLLMServiceImpl implements LLMService { + + @Autowired + private ModelConfigService modelConfigService; + + private final RestTemplate restTemplate = new RestTemplate(); + + private static final String DEFAULT_SUMMARY_PROMPT = "你是一个经验丰富的记忆总结者,擅长将对话内容进行总结摘要,遵循以下规则:\n1、总结用户的重要信息,以便在未来的对话中提供更个性化的服务\n2、不要重复总结,不要遗忘之前记忆,除非原来的记忆超过了1800字,否则不要遗忘、不要压缩用户的历史记忆\n3、用户操控的设备音量、播放音乐、天气、退出、不想对话等和用户本身无关的内容,这些信息不需要加入到总结中\n4、聊天内容中的今天的日期时间、今天的天气情况与用户事件无关的数据,这些信息如果当成记忆存储会影响后续对话,这些信息不需要加入到总结中\n5、不要把设备操控的成果结果和失败结果加入到总结中,也不要把用户的一些废话加入到总结中\n6、不要为了总结而总结,如果用户的聊天没有意义,请返回原来的历史记录也是可以的\n7、只需要返回总结摘要,严格控制在1800字内\n8、不要包含代码、xml,不需要解释、注释和说明,保存记忆时仅从对话提取信息,不要混入示例内容\n9、如果提供了历史记忆,请将新对话内容与历史记忆进行智能合并,保留有价值的历史信息,同时添加新的重要信息\n\n历史记忆:\n{history_memory}\n\n新对话内容:\n{conversation}"; + + @Override + public String generateSummary(String conversation) { + return generateSummary(conversation, null, null); + } + + @Override + public String generateSummaryWithModel(String conversation, String modelId) { + return generateSummary(conversation, null, modelId); + } + + @Override + public String generateSummary(String conversation, String promptTemplate, String modelId) { + if (!isAvailable()) { + log.warn("LLM服务不可用,无法生成总结"); + return "LLM服务不可用,无法生成总结"; + } + + try { + // 从智控台获取LLM模型配置 + ModelConfigEntity llmConfig; + if (modelId != null && !modelId.trim().isEmpty()) { + // 通过具体模型ID获取配置 + llmConfig = modelConfigService.getModelByIdFromCache(modelId); + } else { + // 保持向后兼容,使用默认配置 + llmConfig = getDefaultLLMConfig(); + } + + if (llmConfig == null || llmConfig.getConfigJson() == null) { + log.error("未找到可用的LLM模型配置,modelId: {}", modelId); + return "未找到可用的LLM模型配置"; + } + + JSONObject configJson = llmConfig.getConfigJson(); + String baseUrl = configJson.getStr("base_url"); + String model = configJson.getStr("model_name"); + String apiKey = configJson.getStr("api_key"); + Double temperature = configJson.getDouble("temperature"); + Integer maxTokens = configJson.getInt("max_tokens"); + + if (StringUtils.isBlank(baseUrl) || StringUtils.isBlank(apiKey)) { + log.error("LLM配置不完整,baseUrl或apiKey为空"); + return "LLM配置不完整,无法生成总结"; + } + + // 构建提示词 + String prompt = (promptTemplate != null ? promptTemplate : DEFAULT_SUMMARY_PROMPT).replace("{conversation}", + conversation); + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("model", model != null ? model : "gpt-3.5-turbo"); + + Map[] messages = new Map[1]; + Map message = new HashMap<>(); + message.put("role", "user"); + message.put("content", prompt); + messages[0] = message; + + requestBody.put("messages", messages); + requestBody.put("temperature", temperature != null ? temperature : 0.7); + requestBody.put("max_tokens", maxTokens != null ? maxTokens : 2000); + + // 发送HTTP请求 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + apiKey); + + HttpEntity> entity = new HttpEntity<>(requestBody, headers); + + // 构建完整的API URL + String apiUrl = baseUrl; + if (!apiUrl.endsWith("/chat/completions")) { + if (!apiUrl.endsWith("/")) { + apiUrl += "/"; + } + apiUrl += "chat/completions"; + } + + ResponseEntity response = restTemplate.exchange( + apiUrl, HttpMethod.POST, entity, String.class); + + if (response.getStatusCode().is2xxSuccessful()) { + JSONObject responseJson = JSONUtil.parseObj(response.getBody()); + JSONArray choices = responseJson.getJSONArray("choices"); + if (choices != null && choices.size() > 0) { + JSONObject choice = choices.getJSONObject(0); + JSONObject messageObj = choice.getJSONObject("message"); + return messageObj.getStr("content"); + } + } else { + log.error("LLM API调用失败,状态码:{},响应:{}", response.getStatusCode(), response.getBody()); + } + } catch (Exception e) { + log.error("调用LLM服务生成总结时发生异常,modelId: {}", modelId, e); + } + + return "生成总结失败,请稍后重试"; + } + + @Override + public String generateSummary(String conversation, String promptTemplate) { + return generateSummary(conversation, promptTemplate, null); + } + + @Override + public String generateSummaryWithHistory(String conversation, String historyMemory, String promptTemplate, + String modelId) { + if (!isAvailable()) { + log.warn("LLM服务不可用,无法生成总结"); + return "LLM服务不可用,无法生成总结"; + } + + try { + // 从智控台获取LLM模型配置 + ModelConfigEntity llmConfig; + if (modelId != null && !modelId.trim().isEmpty()) { + // 通过具体模型ID获取配置 + llmConfig = modelConfigService.getModelByIdFromCache(modelId); + } else { + // 保持向后兼容,使用默认配置 + llmConfig = getDefaultLLMConfig(); + } + + if (llmConfig == null || llmConfig.getConfigJson() == null) { + log.error("未找到可用的LLM模型配置,modelId: {}", modelId); + return "未找到可用的LLM模型配置"; + } + + JSONObject configJson = llmConfig.getConfigJson(); + String baseUrl = configJson.getStr("base_url"); + String model = configJson.getStr("model_name"); + String apiKey = configJson.getStr("api_key"); + Double temperature = configJson.getDouble("temperature"); + Integer maxTokens = configJson.getInt("max_tokens"); + + if (StringUtils.isBlank(baseUrl) || StringUtils.isBlank(apiKey)) { + log.error("LLM配置不完整,baseUrl或apiKey为空"); + return "LLM配置不完整,无法生成总结"; + } + + // 构建提示词,包含历史记忆 + String prompt = (promptTemplate != null ? promptTemplate : DEFAULT_SUMMARY_PROMPT) + .replace("{history_memory}", historyMemory != null ? historyMemory : "无历史记忆") + .replace("{conversation}", conversation); + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("model", model != null ? model : "gpt-3.5-turbo"); + + Map[] messages = new Map[1]; + Map message = new HashMap<>(); + message.put("role", "user"); + message.put("content", prompt); + messages[0] = message; + + requestBody.put("messages", messages); + requestBody.put("temperature", temperature != null ? temperature : 0.7); + requestBody.put("max_tokens", maxTokens != null ? maxTokens : 2000); + + // 发送HTTP请求 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + apiKey); + + HttpEntity> entity = new HttpEntity<>(requestBody, headers); + + // 构建完整的API URL + String apiUrl = baseUrl; + if (!apiUrl.endsWith("/chat/completions")) { + if (!apiUrl.endsWith("/")) { + apiUrl += "/"; + } + apiUrl += "chat/completions"; + } + + ResponseEntity response = restTemplate.exchange( + apiUrl, HttpMethod.POST, entity, String.class); + + if (response.getStatusCode().is2xxSuccessful()) { + JSONObject responseJson = JSONUtil.parseObj(response.getBody()); + JSONArray choices = responseJson.getJSONArray("choices"); + if (choices != null && choices.size() > 0) { + JSONObject choice = choices.getJSONObject(0); + JSONObject messageObj = choice.getJSONObject("message"); + return messageObj.getStr("content"); + } + } else { + log.error("LLM API调用失败,状态码:{},响应:{}", response.getStatusCode(), response.getBody()); + } + } catch (Exception e) { + log.error("调用LLM服务生成总结时发生异常,modelId: {}", modelId, e); + } + + return "生成总结失败,请稍后重试"; + } + + @Override + public boolean isAvailable() { + try { + ModelConfigEntity defaultLLMConfig = getDefaultLLMConfig(); + if (defaultLLMConfig == null || defaultLLMConfig.getConfigJson() == null) { + return false; + } + + JSONObject configJson = defaultLLMConfig.getConfigJson(); + String baseUrl = configJson.getStr("base_url"); + String apiKey = configJson.getStr("api_key"); + + return baseUrl != null && !baseUrl.trim().isEmpty() && + apiKey != null && !apiKey.trim().isEmpty(); + } catch (Exception e) { + log.error("检查LLM服务可用性时发生异常:", e); + return false; + } + } + + @Override + public boolean isAvailable(String modelId) { + try { + if (modelId == null || modelId.trim().isEmpty()) { + return isAvailable(); + } + + // 通过具体模型ID获取配置 + ModelConfigEntity modelConfig = modelConfigService.getModelByIdFromCache(modelId); + if (modelConfig == null || modelConfig.getConfigJson() == null) { + log.warn("未找到指定的LLM模型配置,modelId: {}", modelId); + return false; + } + + JSONObject configJson = modelConfig.getConfigJson(); + String baseUrl = configJson.getStr("base_url"); + String apiKey = configJson.getStr("api_key"); + + return baseUrl != null && !baseUrl.trim().isEmpty() && + apiKey != null && !apiKey.trim().isEmpty(); + } catch (Exception e) { + log.error("检查LLM服务可用性时发生异常,modelId: {}", modelId, e); + return false; + } + } + + /** + * 从智控台获取默认的LLM模型配置 + */ + private ModelConfigEntity getDefaultLLMConfig() { + try { + // 获取所有启用的LLM模型配置 + List llmConfigs = modelConfigService.getEnabledModelsByType("LLM"); + if (llmConfigs == null || llmConfigs.isEmpty()) { + return null; + } + + // 优先返回默认配置,如果没有默认配置则返回第一个启用的配置 + for (ModelConfigEntity config : llmConfigs) { + if (config.getIsDefault() != null && config.getIsDefault() == 1) { + return config; + } + } + + return llmConfigs.get(0); + } catch (Exception e) { + log.error("获取LLM模型配置时发生异常:", e); + return null; + } + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java index 95b64b20..9ae10761 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java @@ -55,4 +55,12 @@ public interface ModelConfigService extends BaseService { * @return TTS平台列表(id和modelName) */ List> getTtsPlatformList(); + + /** + * 根据模型类型获取所有启用的模型配置 + * + * @param modelType 模型类型(如:LLM, TTS, ASR等) + * @return 启用的模型配置列表 + */ + List getEnabledModelsByType(String modelType); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java index fbfc7723..2a291cf9 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java @@ -502,4 +502,22 @@ public class ModelConfigServiceImpl extends BaseServiceImpl> getTtsPlatformList() { return modelConfigDao.getTtsPlatformList(); } + + /** + * 根据模型类型获取所有启用的模型配置 + */ + @Override + public List getEnabledModelsByType(String modelType) { + if (StringUtils.isBlank(modelType)) { + return null; + } + + List entities = modelConfigDao.selectList( + new QueryWrapper() + .eq("model_type", modelType) + .eq("is_enabled", 1) + .orderByAsc("sort")); + + return entities; + } } diff --git a/main/xiaozhi-server/config/manage_api_client.py b/main/xiaozhi-server/config/manage_api_client.py index 88995359..ae703815 100644 --- a/main/xiaozhi-server/config/manage_api_client.py +++ b/main/xiaozhi-server/config/manage_api_client.py @@ -53,6 +53,7 @@ class ManageApiClient: async def _ensure_async_client(cls): """确保异步客户端已创建(为每个事件循环创建独立的客户端)""" import asyncio + try: loop = asyncio.get_running_loop() loop_id = id(loop) @@ -115,6 +116,7 @@ class ManageApiClient: async def _execute_async_request(cls, method: str, endpoint: str, **kwargs) -> Dict: """带重试机制的异步请求执行器""" import asyncio + retry_count = 0 while retry_count <= cls.max_retries: @@ -138,6 +140,7 @@ class ManageApiClient: def safe_close(cls): """安全关闭所有异步连接池""" import asyncio + for client in list(cls._async_clients.values()): try: asyncio.run(client.aclose()) @@ -149,7 +152,9 @@ class ManageApiClient: async def get_server_config() -> Optional[Dict]: """获取服务器基础配置""" - return await ManageApiClient._instance._execute_async_request("POST", "/config/server-base") + return await ManageApiClient._instance._execute_async_request( + "POST", "/config/server-base" + ) async def get_agent_models( @@ -181,6 +186,30 @@ async def save_mem_local_short(mac_address: str, short_momery: str) -> Optional[ return None +async def generate_chat_summary(session_id: str) -> Optional[Dict]: + """生成聊天记录总结""" + try: + return await ManageApiClient._instance._execute_async_request( + "POST", + f"/agent/chat-summary/{session_id}", + ) + except Exception as e: + print(f"生成聊天记录总结失败: {e}") + return None + + +async def generate_and_save_chat_summary(session_id: str) -> Optional[Dict]: + """生成并保存聊天记录总结""" + try: + return await ManageApiClient._instance._execute_async_request( + "POST", + f"/agent/chat-summary/{session_id}/save", + ) + 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 6b34e32f..43c77f93 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -243,7 +243,9 @@ class ConnectionHandler: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete( - self.memory.save_memory(self.dialogue.dialogue) + self.memory.save_memory( + self.dialogue.dialogue, self.session_id + ) ) except Exception as e: self.logger.bind(tag=TAG).error(f"保存记忆失败: {e}") diff --git a/main/xiaozhi-server/core/providers/memory/base.py b/main/xiaozhi-server/core/providers/memory/base.py index 2ced898d..70c4b409 100644 --- a/main/xiaozhi-server/core/providers/memory/base.py +++ b/main/xiaozhi-server/core/providers/memory/base.py @@ -14,7 +14,7 @@ class MemoryProviderBase(ABC): self.llm = llm @abstractmethod - async def save_memory(self, msgs): + async def save_memory(self, msgs, session_id=None): """Save a new memory for specific role and return memory ID""" print("this is base func", msgs) diff --git a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py index 530aa317..7156ab72 100644 --- a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py +++ b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py @@ -28,7 +28,7 @@ class MemoryProvider(MemoryProviderBase): logger.bind(tag=TAG).error(f"详细错误: {traceback.format_exc()}") self.use_mem0 = False - async def save_memory(self, msgs): + async def save_memory(self, msgs, session_id=None): if not self.use_mem0: return None if len(msgs) < 2: @@ -41,9 +41,7 @@ class MemoryProvider(MemoryProviderBase): for message in msgs if message.role != "system" ] - result = self.client.add( - messages, user_id=self.role_id - ) + result = self.client.add(messages, user_id=self.role_id) logger.bind(tag=TAG).debug(f"Save memory result: {result}") except Exception as e: logger.bind(tag=TAG).error(f"保存记忆失败: {str(e)}") diff --git a/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py b/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py index a6b7dd36..f8b81aa8 100644 --- a/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py +++ b/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py @@ -4,7 +4,10 @@ import json import os import yaml from config.config_loader import get_project_dir -from config.manage_api_client import save_mem_local_short +from config.manage_api_client import ( + save_mem_local_short, + generate_and_save_chat_summary, +) import asyncio from core.utils.util import check_model_key @@ -144,7 +147,7 @@ class MemoryProvider(MemoryProviderBase): with open(self.memory_path, "w", encoding="utf-8") as f: yaml.dump(all_memory, f, allow_unicode=True) - async def save_memory(self, msgs): + async def save_memory(self, msgs, session_id=None): # 打印使用的模型信息 model_info = getattr(self.llm, "model_name", str(self.llm.__class__.__name__)) logger.bind(tag=TAG).debug(f"使用记忆保存模型: {model_info}") @@ -188,20 +191,18 @@ class MemoryProvider(MemoryProviderBase): except Exception as e: print("Error:", e) else: - result = self.llm.response_no_stream( - short_term_memory_prompt_only_content, - msgStr, - max_tokens=2000, - temperature=0.2, - ) + # 当save_to_file为False时,调用Java端的聊天记录总结接口 + summary_id = session_id if session_id else self.role_id # 使用异步版本,需要在事件循环中运行 try: loop = asyncio.get_running_loop() - loop.create_task(save_mem_local_short(self.role_id, result)) + loop.create_task(generate_and_save_chat_summary(summary_id)) except RuntimeError: # 如果没有运行中的事件循环,创建一个新的 - asyncio.run(save_mem_local_short(self.role_id, result)) - logger.bind(tag=TAG).info(f"Save memory successful - Role: {self.role_id}") + asyncio.run(generate_and_save_chat_summary(summary_id)) + logger.bind(tag=TAG).info( + f"Save memory successful - Role: {self.role_id}, Session: {session_id}" + ) return self.short_memory diff --git a/main/xiaozhi-server/core/providers/memory/nomem/nomem.py b/main/xiaozhi-server/core/providers/memory/nomem/nomem.py index 51523be4..6fe0a2be 100644 --- a/main/xiaozhi-server/core/providers/memory/nomem/nomem.py +++ b/main/xiaozhi-server/core/providers/memory/nomem/nomem.py @@ -11,7 +11,7 @@ class MemoryProvider(MemoryProviderBase): def __init__(self, config, summary_memory=None): super().__init__(config) - async def save_memory(self, msgs): + async def save_memory(self, msgs, session_id=None): logger.bind(tag=TAG).debug("nomem mode: No memory saving is performed.") return None