diff --git a/main/manager-api/src/main/resources/db/changelog/202506181501.sql b/main/manager-api/src/main/resources/db/changelog/202506181501.sql new file mode 100644 index 000000000..98bb41a6b --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202506181501.sql @@ -0,0 +1,12 @@ +INSERT INTO `ai_model_config` (`id`, `model_type`, `model_code`, `model_name`, `is_default`, + `is_enabled`, `config_json`, `doc_link`, `remark`, `sort`, `creator`, + `create_date`, `updater`, `update_date`) +VALUES ('Memory_mem0_milvus', 'memory', 'mem0_milvus', 'Mem0AI本地记忆', 0, 1, + '{\"llm\": {\"config\": {\"model\": \"qwen-plus\", \"top_p\": 1, \"api_key\": \"\", \"max_tokens\": 2000, \"temperature\": 0.2, \"openai_base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\"}, \"provider\": \"openai\"}, \"type\": \"mem0_milvus\", \"embedder\": {\"config\": {\"model\": \"text-embedding-v4\", \"api_key\": \"\", \"openai_base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1/\"}, \"provider\": \"openai\"}, \"vector_store\": {\"config\": {\"url\": \"http://127.0.0.1:19530\", \"collection_name\": \"mem0_collection\", \"embedding_model_dims\": 1024}, \"provider\": \"milvus\"}}', + 'https://app.mem0.ai/dashboard/get-started', 'Mem0AI记忆配置说明:\n1. 配置llm/embedder/vector_store\n', 3, NULL, + NULL, 1, '2025-06-18 17:02:49'); +INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, + `creator`, `create_date`, `updater`, `update_date`) +VALUES ('SYSTEM_Memory_mem0_milvus', 'Memory', 'mem0_milvus', 'mem0ai本地记忆', + '[{\"key\": \"llm\", \"type\": \"dict\", \"label\": \"llm\", \"default\": \"\", \"editing\": false, \"selected\": false}, {\"key\": \"embedder\", \"type\": \"dict\", \"label\": \"embedder\", \"default\": \"\", \"editing\": false, \"selected\": false}, {\"key\": \"vector_store\", \"type\": \"dict\", \"label\": \"vector_store\", \"default\": \"\", \"editing\": false, \"selected\": false}]', + 0, 1, '2025-06-18 14:00:57', 1, '2025-06-18 14:00:57'); 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 32ba9f307..712945815 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 @@ -226,6 +226,13 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202506191643.sql + - changeSet: + id: 202506181501 + author: chan + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202506181501.sql - changeSet: id: 202506251620 author: Tink @@ -239,4 +246,4 @@ databaseChangeLog: changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/202506261637.sql \ No newline at end of file + path: classpath:db/changelog/202506261637.sql diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 41ac96b75..c2ecf5a82 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -239,7 +239,33 @@ Memory: # 如果这里不填,则会默认使用selected_module.LLM的模型作为意图识别的思考模型 # 如果你的不想使用selected_module.LLM记忆存储,这里最好使用独立的LLM作为意图识别,例如使用免费的ChatGLMLLM llm: ChatGLMLLM - + mem0_milvus: + # 本地 mem0+milvus 记忆功能 + type: mem0_milvus + llm: + provider: openai + config: + model: qwen-plus + openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + # https://bailian.console.aliyun.com/?tab=model#/api-key + api_key: 阿里云百炼平台api_key + temperature: 0.2 + max_tokens: 2000 + top_p: 1.0 + embedder: + provider: openai + config: + model: text-embedding-v4 + api_key: 阿里云百炼平台api_key + openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1/ + vector_store: + #可以使用容器本地部署 milvus 向量数据库 + provider: milvus + config: + # 使用你的 milvus 向量数据库地址 + url: http://127.0.0.1:19530 + collection_name: mem0_collection + embedding_model_dims: 1024 ASR: FunASR: type: fun_local diff --git a/main/xiaozhi-server/core/providers/memory/mem0_milvus/mem0_milvus.py b/main/xiaozhi-server/core/providers/memory/mem0_milvus/mem0_milvus.py new file mode 100644 index 000000000..b3cdf7768 --- /dev/null +++ b/main/xiaozhi-server/core/providers/memory/mem0_milvus/mem0_milvus.py @@ -0,0 +1,78 @@ +from ..base import MemoryProviderBase, logger +from mem0 import Memory + +TAG = __name__ + + +class MemoryProvider(MemoryProviderBase): + def __init__(self, config, summary_memory=None): + super().__init__(config) + + try: + self.client = Memory.from_config(self.config) + self.use_mem0local = True + logger.bind(tag=TAG).info("成功使用Mem0_milvus服务") + except Exception as e: + logger.bind(tag=TAG).error(f"Mem0配置错误: {str(e)}") + self.use_mem0local = False + + async def save_memory(self, msgs): + if not self.use_mem0local: + return None + if len(msgs) < 2: + return None + + try: + # Format the content as a message list for mem0 + messages = [ + {"role": message.role, "content": message.content} + for message in msgs if message.role != "system" + ] + result = self.client.add(messages, user_id=self.role_id) + logger.bind(tag=TAG).info(f"Save memory result: {result}") + except Exception as e: + print(e) + logger.bind(tag=TAG).error(f"保存记忆失败: {str(e)}") + return None + + async def query_memory(self, query: str) -> str: + if not self.use_mem0local: + return "" + try: + results = self.client.search( + query, + user_id=self.role_id + ) + logger.bind(tag=TAG).info(f"get memory result: {results}") + if not results or 'results' not in results: + return "" + + # Format each memory entry with its update time up to minutes + memories = [] + for entry in results['results']: + timestamp = entry.get('updated_at', '') or entry.get('created_at', '') + if timestamp: + try: + # Parse and reformat the timestamp + dt = timestamp.split('.')[0] # Remove milliseconds + formatted_time = dt.replace('T', ' ') + except: + formatted_time = timestamp + memory = entry.get('memory', '') + if timestamp and memory: + # Store tuple of (timestamp, formatted_string) for sorting + memories.append((timestamp, f"[{formatted_time}] {memory}")) + + # Sort by timestamp in descending order (newest first) + memories.sort(key=lambda x: x[0], reverse=True) + + # Extract only the formatted strings + memories_str = "\n".join(f"- {memory[1]}" for memory in memories) + logger.bind(tag=TAG).debug(f"Query results: {memories_str}") + return memories_str + except Exception as e: + if "collection not found" in e: + return "" + else: + logger.bind(tag=TAG).error(f"查询记忆失败: {str(e)}") + return "" diff --git a/main/xiaozhi-server/requirements.txt b/main/xiaozhi-server/requirements.txt index 4ec8b6258..0870c7d9c 100755 --- a/main/xiaozhi-server/requirements.txt +++ b/main/xiaozhi-server/requirements.txt @@ -33,4 +33,5 @@ markitdown==0.1.1 mcp-proxy==0.8.0 PyJWT==2.8.0 psutil==7.0.0 -portalocker==2.10.1 \ No newline at end of file +portalocker==2.10.1 +pymilvus==2.5.11