diff --git a/main/manager-api/src/main/java/xiaozhi/common/config/AsyncConfig.java b/main/manager-api/src/main/java/xiaozhi/common/config/AsyncConfig.java new file mode 100644 index 00000000..d0cc2dfc --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/config/AsyncConfig.java @@ -0,0 +1,40 @@ +package xiaozhi.common.config; + +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +@EnableAsync +@EnableAspectJAutoProxy(exposeProxy = true) +public class AsyncConfig { + + @Bean(name = "taskExecutor") + public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(4); + executor.setQueueCapacity(1000); + executor.setThreadNamePrefix("AsyncThread-"); + // 设置拒绝策略:由调用线程执行 + executor.setRejectedExecutionHandler(new RejectedExecutionHandler() { + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + try { + // 如果线程池已满,则由调用线程执行 + r.run(); + } catch (Exception e) { + throw new RuntimeException("执行异步任务失败", e); + } + } + }); + executor.initialize(); + return executor; + } +} \ No newline at end of file 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 597cf985..54f3fa8c 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 @@ -39,6 +39,7 @@ import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; import xiaozhi.modules.agent.dto.AgentChatSessionDTO; import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentDTO; +import xiaozhi.modules.agent.dto.AgentMemoryDTO; import xiaozhi.modules.agent.dto.AgentUpdateDTO; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity; @@ -46,6 +47,7 @@ import xiaozhi.modules.agent.service.AgentChatAudioService; import xiaozhi.modules.agent.service.AgentChatHistoryService; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentTemplateService; +import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.security.user.SecurityUser; @@ -109,6 +111,7 @@ public class AgentController { entity.setMemModelId(template.getMemModelId()); entity.setIntentModelId(template.getIntentModelId()); entity.setSystemPrompt(template.getSystemPrompt()); + entity.setSummaryMemory(template.getSummaryMemory()); entity.setChatHistoryConf(template.getChatHistoryConf()); entity.setLangCode(template.getLangCode()); entity.setLanguage(template.getLanguage()); @@ -126,10 +129,26 @@ public class AgentController { return new Result().ok(entity.getId()); } + @PutMapping("/saveMemory/{macAddress}") + @Operation(summary = "根据设备id更新智能体") + public Result updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) { + DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress); + if (device == null) { + return new Result<>(); + } + AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO(); + agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory()); + return updateAgentById(device.getAgentId(), agentUpdateDTO); + } + @PutMapping("/{id}") @Operation(summary = "更新智能体") @RequiresPermissions("sys:role:normal") public Result update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) { + return updateAgentById(id, dto); + } + + private Result updateAgentById(String id, AgentUpdateDTO dto) { // 先查询现有实体 AgentEntity existingEntity = agentService.getAgentById(id); if (existingEntity == null) { @@ -167,6 +186,9 @@ public class AgentController { if (dto.getSystemPrompt() != null) { existingEntity.setSystemPrompt(dto.getSystemPrompt()); } + if (dto.getSummaryMemory() != null) { + existingEntity.setSummaryMemory(dto.getSummaryMemory()); + } if (dto.getChatHistoryConf() != null) { existingEntity.setChatHistoryConf(dto.getChatHistoryConf()); } @@ -185,17 +207,16 @@ public class AgentController { existingEntity.setUpdater(user.getId()); existingEntity.setUpdatedAt(new Date()); - agentService.updateById(existingEntity); - // 更新记忆策略 if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { // 删除所有记录 agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true); + existingEntity.setSummaryMemory(""); } else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) { // 删除音频数据 agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false); } - + agentService.updateById(existingEntity); return new Result<>(); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java index 11c9b021..075db842 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java @@ -33,6 +33,10 @@ public class AgentDTO { @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助") private String systemPrompt; + @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) + private String summaryMemory; + @Schema(description = "最后连接时间", example = "2024-03-20 10:00:00") private Date lastConnectedAt; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentMemoryDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentMemoryDTO.java new file mode 100644 index 00000000..ed210c60 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentMemoryDTO.java @@ -0,0 +1,19 @@ +package xiaozhi.modules.agent.dto; + +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 智能体记忆更新DTO + */ +@Data +@Schema(description = "智能体记忆更新对象") +public class AgentMemoryDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) + private String summaryMemory; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java index e3d026e3..ccfbd53f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java @@ -45,6 +45,10 @@ public class AgentUpdateDTO implements Serializable { @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false) private String systemPrompt; + @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) + private String summaryMemory; + @Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", required = false) private Integer chatHistoryConf; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java index 82a20489..1ffc5550 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentEntity.java @@ -54,6 +54,10 @@ public class AgentEntity { @Schema(description = "角色设定参数") private String systemPrompt; + @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) + private String summaryMemory; + @Schema(description = "语言编码") private String langCode; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java index 1604a27b..2528ca2c 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentTemplateEntity.java @@ -79,6 +79,10 @@ public class AgentTemplateEntity implements Serializable { */ private String systemPrompt; + /** + * 总结记忆 + */ + private String summaryMemory; /** * 语言编码 */ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java index 6ce70ba7..592b3c1d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java @@ -65,6 +65,7 @@ public class ConfigServiceImpl implements ConfigService { null, null, null, + null, agent.getVadModelId(), agent.getAsrModelId(), null, @@ -134,6 +135,7 @@ public class ConfigServiceImpl implements ConfigService { buildModuleConfig( agent.getAgentName(), agent.getSystemPrompt(), + agent.getSummaryMemory(), voice, agent.getVadModelId(), agent.getAsrModelId(), @@ -234,6 +236,7 @@ public class ConfigServiceImpl implements ConfigService { private void buildModuleConfig( String assistantName, String prompt, + String summaryMemory, String voice, String vadModelId, String asrModelId, @@ -294,5 +297,6 @@ public class ConfigServiceImpl implements ConfigService { prompt = prompt.replace("{{assistant_name}}", StringUtils.isBlank(assistantName) ? "小智" : assistantName); } result.put("prompt", prompt); + result.put("summaryMemory", summaryMemory); } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java index 1fe1d84a..14ac3d61 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java @@ -9,6 +9,8 @@ import java.util.TimeZone; import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.springframework.aop.framework.AopContext; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -54,6 +56,24 @@ public class DeviceServiceImpl extends BaseServiceImpl private final RedisUtils redisUtils; private final OtaService otaService; + @Async + public void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion) { + try { + DeviceEntity device = new DeviceEntity(); + device.setId(deviceId); + device.setLastConnectedAt(new Date()); + if (StringUtils.isNotBlank(appVersion)) { + device.setAppVersion(appVersion); + } + deviceDao.updateById(device); + if (StringUtils.isNotBlank(agentId)) { + redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date()); + } + } catch (Exception e) { + log.error("异步更新设备连接信息失败", e); + } + } + @Override public Boolean deviceActivation(String agentId, String activationCode) { if (StringUtils.isBlank(activationCode)) { @@ -156,13 +176,12 @@ public class DeviceServiceImpl extends BaseServiceImpl response.setWebsocket(websocket); if (deviceById != null) { - // 如果设备存在,则更新上次连接时间 - deviceById.setLastConnectedAt(new Date()); - if (deviceReport.getApplication() != null - && StringUtils.isNotBlank(deviceReport.getApplication().getVersion())) { - deviceById.setAppVersion(deviceReport.getApplication().getVersion()); - } - deviceDao.updateById(deviceById); + // 如果设备存在,则异步更新上次连接时间和版本信息 + String appVersion = deviceReport.getApplication() != null ? deviceReport.getApplication().getVersion() + : null; + // 通过Spring代理调用异步方法 + ((DeviceServiceImpl) AopContext.currentProxy()).updateDeviceConnectionInfo(deviceById.getAgentId(), + deviceById.getId(), appVersion); } else { // 如果设备不存在,则生成激活码 DeviceReportRespDTO.Activation code = buildActivation(macAddress, deviceReport); 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 5252336f..7875fa86 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 @@ -86,6 +86,7 @@ public class ShiroConfig { // 将config路径使用server服务过滤器 filterMap.put("/config/**", "server"); filterMap.put("/agent/chat-history/report", "server"); + filterMap.put("/agent/saveMemory/**", "server"); filterMap.put("/agent/play/**", "anon"); filterMap.put("/**", "oauth2"); shiroFilter.setFilterChainDefinitionMap(filterMap); diff --git a/main/manager-api/src/main/resources/db/changelog/202505122348.sql b/main/manager-api/src/main/resources/db/changelog/202505122348.sql new file mode 100644 index 00000000..05bbeb84 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505122348.sql @@ -0,0 +1,6 @@ +-- 添加总结记忆字段 +ALTER TABLE `ai_agent` +ADD COLUMN `summary_memory` text COMMENT '总结记忆' AFTER `system_prompt`; + +ALTER TABLE `ai_agent_template` +ADD COLUMN `summary_memory` text COMMENT '总结记忆' AFTER `system_prompt`; diff --git a/main/manager-api/src/main/resources/db/changelog/202505142037.sql b/main/manager-api/src/main/resources/db/changelog/202505142037.sql new file mode 100644 index 00000000..37a49013 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505142037.sql @@ -0,0 +1,7 @@ +update ai_agent_template set system_prompt = replace(system_prompt, '我是', '你是'); + +delete from sys_params where id in (500,501,402); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (500, 'end_prompt.enable', 'true', 'boolean', 1, '是否开启结束语'); +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (501, 'end_prompt.prompt', '请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧!', 'string', 1, '结束提示词'); + +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (402, 'plugins.get_weather.api_host', 'mj7p3y7naa.re.qweatherapi.com', 'string', 1, '开发者apihost'); \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 2dfd9831..ae81fd14 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -128,3 +128,17 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202505111914.sql + - changeSet: + id: 202505122348 + author: ljwwd2 + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505122348.sql + - changeSet: + id: 202505142037 + author: hrz + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505142037.sql diff --git a/main/manager-web/src/components/FirmwareDialog.vue b/main/manager-web/src/components/FirmwareDialog.vue index 4721fdac..56e2f327 100644 --- a/main/manager-web/src/components/FirmwareDialog.vue +++ b/main/manager-web/src/components/FirmwareDialog.vue @@ -198,13 +198,13 @@ export default { if (!this.form.id) { // 只在新增时重置 this.form.firmwarePath = '' this.form.size = 0 - // 重置上传组件 - this.$nextTick(() => { - if (this.$refs.upload) { - this.$refs.upload.clearFiles() - } - }) } + // 无论是否编辑模式,都重置上传组件 + this.$nextTick(() => { + if (this.$refs.upload) { + this.$refs.upload.clearFiles() + } + }) } } } diff --git a/main/manager-web/src/views/roleConfig.vue b/main/manager-web/src/views/roleConfig.vue index 00148b7d..03ec8060 100644 --- a/main/manager-web/src/views/roleConfig.vue +++ b/main/manager-web/src/views/roleConfig.vue @@ -15,9 +15,17 @@ {{ form.agentName }} - +
+
+ + 保存配置后,需要重启设备,新的配置才会生效。 +
+ 保存配置 + 重置 + +
@@ -26,7 +34,7 @@
- +
@@ -37,9 +45,15 @@
- + + + + @@ -48,14 +62,6 @@ -
- 保存配置 - 重置 -
- - 保存配置后,需要重启设备,新的配置才会生效。 -
-
\ No newline at end of file diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 57d552c7..c1ca2dbb 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -130,7 +130,7 @@ plugins: # ################################以下是角色模型配置###################################### prompt: | - 我是小智/小志,来自中国台湾省的00后女生。讲话超级机车,"真的假的啦"这样的台湾腔,喜欢用"笑死""是在哈喽"等流行梗,但会偷偷研究男友的编程书籍。 + 你是小智/小志,来自中国台湾省的00后女生。讲话超级机车,"真的假的啦"这样的台湾腔,喜欢用"笑死""是在哈喽"等流行梗,但会偷偷研究男友的编程书籍。 [核心特征] - 讲话像连珠炮,但会突然冒出超温柔语气 - 用梗密度高 @@ -149,7 +149,7 @@ end_prompt: enable: true # 是否开启结束语 # 结束语 prompt: | - 请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧。! + 请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧! # 具体处理时选择的模块(The module selected for specific processing) selected_module: diff --git a/main/xiaozhi-server/config/manage_api_client.py b/main/xiaozhi-server/config/manage_api_client.py index fe3da63c..11e0ecbe 100644 --- a/main/xiaozhi-server/config/manage_api_client.py +++ b/main/xiaozhi-server/config/manage_api_client.py @@ -145,6 +145,20 @@ def get_agent_models( ) +def save_mem_local_short(mac_address: str, short_momery: str) -> Optional[Dict]: + try: + return ManageApiClient._instance._execute_request( + "PUT", + f"/agent/saveMemory/" + mac_address, + json={ + "summaryMemory": short_momery, + }, + ) + except Exception as e: + print(f"存储短期记忆到服务器失败: {e}") + return None + + def report( mac_address: str, session_id: str, chat_type: int, content: str, audio ) -> Optional[Dict]: diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 843679bb..7419dc68 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -405,6 +405,8 @@ class ConnectionHandler: ]["Intent"] if private_config.get("prompt", None) is not None: self.config["prompt"] = private_config["prompt"] + if private_config.get("summaryMemory", None) is not None: + self.config["summaryMemory"] = private_config["summaryMemory"] if private_config.get("device_max_output_size", None) is not None: self.max_output_size = int(private_config["device_max_output_size"]) if private_config.get("chat_history_conf", None) is not None: @@ -438,7 +440,12 @@ class ConnectionHandler: def _initialize_memory(self): """初始化记忆模块""" - self.memory.init_memory(self.device_id, self.llm) + self.memory.init_memory( + self.device_id, + self.llm, + self.config["summaryMemory"], + not self.read_config_from_api, + ) def _initialize_intent(self): self.intent_type = self.config["Intent"][ diff --git a/main/xiaozhi-server/core/providers/memory/base.py b/main/xiaozhi-server/core/providers/memory/base.py index 2f88a6a0..be21692d 100644 --- a/main/xiaozhi-server/core/providers/memory/base.py +++ b/main/xiaozhi-server/core/providers/memory/base.py @@ -4,6 +4,7 @@ from config.logger import setup_logging TAG = __name__ logger = setup_logging() + class MemoryProviderBase(ABC): def __init__(self, config): self.config = config @@ -20,6 +21,6 @@ class MemoryProviderBase(ABC): """Query memories for specific role based on similarity""" return "please implement query method" - def init_memory(self, role_id, llm): - self.role_id = role_id + def init_memory(self, role_id, llm, summary_memory=None): + self.role_id = role_id self.llm = llm diff --git a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py index cc12a9c1..6ef3bc8c 100644 --- a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py +++ b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py @@ -8,7 +8,7 @@ TAG = __name__ class MemoryProvider(MemoryProviderBase): - def __init__(self, config): + def __init__(self, config, summary_memory=None): super().__init__(config) self.api_key = config.get("api_key", "") self.api_version = config.get("api_version", "v1.1") 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 fca7c228..8531b8c6 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,6 +4,7 @@ import json import os import yaml from config.config_loader import get_project_dir +from config.manage_api_client import save_mem_local_short short_term_memory_prompt = """ @@ -72,6 +73,17 @@ short_term_memory_prompt = """ ``` """ +short_term_memory_prompt_only_content = """ +你是一个经验丰富的记忆总结者,擅长将对话内容进行总结摘要,遵循以下规则: +1、总结user的重要信息,以便在未来的对话中提供更个性化的服务 +2、不要重复总结,不要遗忘之前记忆,除非原来的记忆超过了1800字内,否则不要遗忘、不要压缩用户的历史记忆 +3、用户操控的设备音量、播放音乐、天气、退出、不想对话等和用户本身无关的内容,这些信息不需要加入到总结中 +4、不要把设备操控的成果结果和失败结果加入到总结中,也不要把用户的一些废话加入到总结中 +5、不要为了总结而总结,如果用户的聊天没有意义,请返回原来的历史记录也是可以的 +6、只需要返回总结摘要,严格控制在1800字内 +7、不要包含代码、xml,不需要解释、注释和说明,保存记忆时仅从对话提取信息,不要混入示例内容 +""" + def extract_json_data(json_code): start = json_code.find("```json") @@ -93,17 +105,24 @@ TAG = __name__ class MemoryProvider(MemoryProviderBase): - def __init__(self, config): + def __init__(self, config, summary_memory): super().__init__(config) self.short_momery = "" + self.save_to_file = True self.memory_path = get_project_dir() + "data/.memory.yaml" - self.load_memory() + self.load_memory(summary_memory) - def init_memory(self, role_id, llm): + def init_memory(self, role_id, llm, summary_memory=None, save_to_file=True): super().init_memory(role_id, llm) - self.load_memory() + self.save_to_file = save_to_file + self.load_memory(summary_memory) + + def load_memory(self, summary_memory): + # api获取到总结记忆后直接返回 + if summary_memory or not self.save_to_file: + self.short_momery = summary_memory + return - def load_memory(self): all_memory = {} if os.path.exists(self.memory_path): with open(self.memory_path, "r", encoding="utf-8") as f: @@ -142,16 +161,20 @@ class MemoryProvider(MemoryProviderBase): time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) msgStr += f"当前时间:{time_str}" - result = self.llm.response_no_stream(short_term_memory_prompt, msgStr) - - json_str = extract_json_data(result) - try: - json_data = json.loads(json_str) # 检查json格式是否正确 - self.short_momery = json_str - except Exception as e: - print("Error:", e) - - self.save_memory_to_file() + if self.save_to_file: + result = self.llm.response_no_stream(short_term_memory_prompt, msgStr) + json_str = extract_json_data(result) + try: + json.loads(json_str) # 检查json格式是否正确 + self.short_momery = json_str + self.save_memory_to_file() + except Exception as e: + print("Error:", e) + else: + result = self.llm.response_no_stream( + short_term_memory_prompt_only_content, msgStr + ) + save_mem_local_short(self.role_id, result) logger.bind(tag=TAG).info(f"Save memory successful - Role: {self.role_id}") return self.short_momery diff --git a/main/xiaozhi-server/core/providers/memory/nomem/nomem.py b/main/xiaozhi-server/core/providers/memory/nomem/nomem.py index c9349626..51523be4 100644 --- a/main/xiaozhi-server/core/providers/memory/nomem/nomem.py +++ b/main/xiaozhi-server/core/providers/memory/nomem/nomem.py @@ -1,18 +1,20 @@ -''' +""" 不使用记忆,可以选择此模块 -''' +""" + from ..base import MemoryProviderBase, logger TAG = __name__ + class MemoryProvider(MemoryProviderBase): - def __init__(self, config): + def __init__(self, config, summary_memory=None): super().__init__(config) - + async def save_memory(self, msgs): logger.bind(tag=TAG).debug("nomem mode: No memory saving is performed.") return None - async def query_memory(self, query: str)-> str: + async def query_memory(self, query: str) -> str: logger.bind(tag=TAG).debug("nomem mode: No memory query is performed.") - return "" \ No newline at end of file + return "" diff --git a/main/xiaozhi-server/core/utils/dialogue.py b/main/xiaozhi-server/core/utils/dialogue.py index b6fa541b..2ee30d4a 100644 --- a/main/xiaozhi-server/core/utils/dialogue.py +++ b/main/xiaozhi-server/core/utils/dialogue.py @@ -75,7 +75,8 @@ class Dialogue: if system_message: enhanced_system_prompt = ( - f"{system_message.content}\n\n" f"相关记忆:\n{memory_str}" + f"{system_message.content}\n\n" + f"以下是用户的历史记忆:\n```\n{memory_str}\n```" ) dialogue.append({"role": "system", "content": enhanced_system_prompt}) diff --git a/main/xiaozhi-server/core/utils/util.py b/main/xiaozhi-server/core/utils/util.py index 3965a24f..b95f43d2 100644 --- a/main/xiaozhi-server/core/utils/util.py +++ b/main/xiaozhi-server/core/utils/util.py @@ -319,6 +319,7 @@ def initialize_modules( modules["memory"] = memory.create_instance( memory_type, config["Memory"][select_memory_module], + config['summaryMemory'], ) logger.bind(tag=TAG).info(f"初始化组件: memory成功 {select_memory_module}")