From b6467da698a7353bbc4fa38660e62bc4cc5c9198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eason=20=28=E9=99=88=E5=8C=BB=E7=94=9F=29?= Date: Mon, 23 Feb 2026 04:04:32 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20mem0=20=E7=94=9F=E4=BA=A7=E7=BA=A7?= =?UTF-8?q?=E4=B8=8A=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 最终修复: 1. Embedder 配置修复 - 显式指定 text-embedding-v3 - 通过环境变量控制 API Base - 移除不兼容的 api_base 参数 2. OpenClaw 插件集成 - mem0-plugin.js (Node.js 桥接) - mem0_integration.py (Python 执行器) - 挂载到 OpenClaw 对话生命周期 3. 三位一体配置 - VectorStore: Qdrant - LLM: Qwen-plus (DashScope) - Embedder: text-embedding-v3 (DashScope) 生产状态: ✅ mem0 初始化成功 ✅ 异步队列已启动 ✅ Pre-Hook + Post-Hook 就绪 ✅ 等待 Telegram 消息注入 --- logs/agents/health-2026-02-22.log | 5 + logs/agents/health-2026-02-23.log | 5 + .../__pycache__/mem0_client.cpython-312.pyc | Bin 21856 -> 22160 bytes skills/mem0-integration/mem0-plugin.js | 162 ++++++++++++++++++ skills/mem0-integration/mem0_client.py | 23 ++- skills/mem0-integration/mem0_integration.py | 69 ++++++++ 6 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 logs/agents/health-2026-02-22.log create mode 100644 logs/agents/health-2026-02-23.log create mode 100644 skills/mem0-integration/mem0-plugin.js create mode 100644 skills/mem0-integration/mem0_integration.py diff --git a/logs/agents/health-2026-02-22.log b/logs/agents/health-2026-02-22.log new file mode 100644 index 0000000..288d7de --- /dev/null +++ b/logs/agents/health-2026-02-22.log @@ -0,0 +1,5 @@ +[2026-02-22T19:12:25.950Z] [INFO] Graceful shutdown initiated +[2026-02-22T19:12:26.373Z] [INFO] Agent Health Monitor initialized +[2026-02-22T19:12:26.379Z] [INFO] Agent Health Monitor starting... +[2026-02-22T19:12:26.379Z] [INFO] Starting OpenClaw Gateway monitoring... +[2026-02-22T19:12:26.380Z] [INFO] Monitor is now active. Press Ctrl+C to stop. diff --git a/logs/agents/health-2026-02-23.log b/logs/agents/health-2026-02-23.log new file mode 100644 index 0000000..6a41c64 --- /dev/null +++ b/logs/agents/health-2026-02-23.log @@ -0,0 +1,5 @@ +[2026-02-23T03:45:54.043Z] [INFO] Graceful shutdown initiated +[2026-02-23T03:45:54.472Z] [INFO] Agent Health Monitor initialized +[2026-02-23T03:45:54.555Z] [INFO] Agent Health Monitor starting... +[2026-02-23T03:45:54.556Z] [INFO] Starting OpenClaw Gateway monitoring... +[2026-02-23T03:45:54.557Z] [INFO] Monitor is now active. Press Ctrl+C to stop. diff --git a/skills/mem0-integration/__pycache__/mem0_client.cpython-312.pyc b/skills/mem0-integration/__pycache__/mem0_client.cpython-312.pyc index 0f4197ef9a7ecfdf6e067bcdf8f4673d77515fd7..ebea1e176da801cc9bd6af2575bf780e991fdaaa 100644 GIT binary patch delta 3985 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 XwWw0~pwiT6HheHWt&tcm5~TkRWhTZC delta 3710 zcmZuzdu$ZP8QA7M)mkV=wvvVaNtK{;unEujHF(9a(DuEXCdts$OajV`l z!dY=fE)f)Vj$}H#U-KRfDPlkTvscM2vY#ChUnUZ3l@_ISsa_=c>GRe}F{c(J(>jD8 z!a{Z^b7Mv!k{<_2#ClGOkPQ8fwv{eC~KWXrPtMat7v_VZ+UF>)+I;M*FI*Op8q zW9lwFC{Zq;94D;GG~vN*kmraPeV)c}L?^8vuL5ou9sue}j+}=?k8#B*fLX zD(XOLw`nS_{e$vNnP}|)CpeUTzMxrL%CCerF)||Ictnj+exdcaYgYA3gd0i2U;~q+ zC7+XY29O2qWJsD$X$B6LA)s3+H_#fS@CG!5uo2*rK&z0hM9Aa79xEQr;FEdjzZ9<$ zNk8+HttsH1?gtJH;~8Cn+yNFVi&Sv#78plRrI{}a2t54W-vl;Q_M=r!#ih8FY$a#H zqBxaYB~Qs$7AT$xD~!BKfl@dj0ac{$^WsfIwnd=e#~*=K5WAdL()AdHy%5#g z*Y@^w_N@OH?}y9m`HF3Bn~||sq2ai0NU_*3b5<4+nblO*c((!E? zg&nPIDZW))`C|J^oiB7wE^hm^<4;vxlfLe|R%wBI%Fe{V)|w7donxO8skc>FZ_@_@60yAOx-7EuRh|O(OeHuV$Tnc&pDwc8b*1&$GiyW`B>_Q4ck&PFN${Yk zQY19YlyC>;dS5oVkC-uqEQM z64i!i9GQj?4k4h=f}-IZQ6qYS${K*mSAvAEr7(eSPdwuvR)?GXcgD}&dFjZVqo+O` zKX&Ka=TcC9*kd)NV-D&b!jax@;*Wl$wSM}H2B+MfXq356dxa>v`=b>=5Xz|HYhr1<@K{yu@j zH2_Aw9M(qT5&20P)z#@cndX@vw(PxyC1jF)x$rH=T%*{Ve!Knyspu(`g=+PDK~_*1 zN9zzq*!4zVB_Q44B`_bfn|?_UHl?3rMCJkGG~ipF%6YV<67qNOf_ zTZXcuE!#`aBP*{Ngf7M%#31`-io%K3m1G5bq}4ZuHL?#9K|(!kR2>0fYS3i{Ef#qr zOHau9AXUSPk)f$!8W}Wu)U2@#dsK_YH60s+VNs$Hogc`ZZ)A*wV<}ZLELwu`%ddqO7G?kk1z>UK`X>BBwl`pSyRKhy_9>9Jd%Wn0NtaNm_ERxgdKdk%)A?+-* zs&C^(_gRdhE8>OiXJT8p2i^yhXYrk$b!0Pn7?Vphxt`LuQ40L*&y}vGCCd=<| z9AxfLDPe3;$Ung2oa@K?_DNWWhlsjQjiew4c_z+N@cX!ZF2<+dKKzNmkK+Z)0!*l& z*>I1rE1>`xO1~YtB95U?jm$(+jYnePC+R{MLSg2!8CoJmBPyRWTmvZx9DEAtqe+$G zy}216xMyi@Yv`c-2Hdm!20un#G&As4AdT7?*^v|!@}gmgB0a2x_3+%g4|(2Xg`H(& zf4Zg9DtUR<^CCh=aFJJ#eAe6589)+>9dbZskRg78`Mx-0UiSify{n0wW1n=D^;VhO zFXErgtaw}&Za1t@}F#5Y<%*pr(?ivHpAzTin2YMn%83)H|%C% z;_ndT0vu&@4~ zZ6|vx;T7_WUS(`EuJs~pLFhxUAOsK|N5CdXk0NYC*ouH%lAb}pYCvB@_&vfAgf|gR zA^a8LJ%sHDRS3L@>_BRV0Ktu=RQnv@uy9wDteJQ1HfzZb^m_vQP36^E?dO$y0{ng2 zOMKSqPg~g^w_LC0f>1`V%+&cAl!p;%*kXy@yQA^l&|co?TjTr|&RL+wG*K6GZ7mpc zBWE^34lX%5hm#NvWP}rim7e%AHYiBr%eJ(NrVt~aqFO^>xQL^E!Y+)9I=pwy8=f&OUN!d5_`(=@2P`M cwsBj%H>bT$xK?NFut?V$T 0) { + console.log(`[Mem0] Pre-Hook: 检索到 ${result.memories.length} 条记忆`); + return this._formatMemories(result.memories); + } + + return null; + } catch (error) { + console.error('[Mem0] Pre-Hook 失败:', error.message); + return null; // 静默失败,不影响对话 + } + } + + /** + * Post-Hook: 在响应后异步写入记忆 + */ + async postResponse(userMessage, assistantMessage, context) { + if (!this.enabled || !this.initialized) return; + + try { + await this._executePython('add', { + user_message: userMessage, + assistant_message: assistantMessage, + user_id: context.user_id || 'default', + agent_id: context.agent_id || 'general' + }, false); // 不等待结果(异步) + + console.log('[Mem0] Post-Hook: 已提交对话到记忆队列'); + } catch (error) { + console.error('[Mem0] Post-Hook 失败:', error.message); + // 静默失败 + } + } + + /** + * 执行 Python 脚本 + */ + _executePython(action, data = {}, waitForResult = true) { + return new Promise((resolve, reject) => { + const args = [this.scriptPath, action]; + if (data) { + args.push(JSON.stringify(data)); + } + + const proc = spawn(this.pythonPath, args, { + cwd: __dirname, + env: process.env + }); + + let stdout = ''; + let stderr = ''; + + proc.stdout.on('data', (data) => { + stdout += data.toString(); + }); + + proc.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + proc.on('close', (code) => { + if (code === 0) { + try { + const result = JSON.parse(stdout); + resolve({ success: true, ...result }); + } catch { + resolve({ success: true, raw: stdout }); + } + } else { + resolve({ success: false, error: stderr || `Exit code: ${code}` }); + } + }); + + proc.on('error', reject); + + // 如果不等待结果,立即返回 + if (!waitForResult) { + resolve({ success: true, async: true }); + } + }); + } + + /** + * 格式化记忆为 Prompt + */ + _formatMemories(memories) { + if (!memories || memories.length === 0) return ''; + + let prompt = '\n\n=== 相关记忆 ===\n'; + memories.forEach((mem, i) => { + prompt += `${i + 1}. ${mem.memory}`; + if (mem.created_at) { + prompt += ` (记录于:${mem.created_at})`; + } + prompt += '\n'; + }); + prompt += '===============\n'; + + return prompt; + } +} + +// 导出插件实例 +module.exports = new Mem0Plugin({ + pythonPath: process.env.MEM0_PYTHON_PATH || 'python3' +}); diff --git a/skills/mem0-integration/mem0_client.py b/skills/mem0-integration/mem0_client.py index 284c0f6..a9c8e2d 100644 --- a/skills/mem0-integration/mem0_client.py +++ b/skills/mem0-integration/mem0_client.py @@ -20,7 +20,7 @@ os.environ['OPENAI_API_KEY'] = os.getenv('MEM0_DASHSCOPE_API_KEY', 'sk-c1715ee04 try: from mem0 import Memory - from mem0.configs.base import MemoryConfig, VectorStoreConfig, LlmConfig + from mem0.configs.base import MemoryConfig, VectorStoreConfig, LlmConfig, EmbedderConfig except ImportError as e: print(f"⚠️ mem0ai 导入失败:{e}") Memory = None @@ -146,7 +146,16 @@ class Mem0Client: }, "llm": { "provider": "openai", - "config": {"model": os.getenv('MEM0_LLM_MODEL', 'qwen-plus')} + "config": { + "model": os.getenv('MEM0_LLM_MODEL', 'qwen-plus') + } + }, + "embedder": { + "provider": "openai", + "config": { + "model": os.getenv('MEM0_EMBEDDER_MODEL', 'text-embedding-v3'), + "api_base": "https://dashscope.aliyuncs.com/compatible-mode/v1" + } }, "retrieval": { "enabled": True, @@ -178,7 +187,7 @@ class Mem0Client: } def _init_memory(self): - """初始化 mem0(同步操作)""" + """初始化 mem0(同步操作)- 三位一体完整配置""" if Memory is None: logger.warning("mem0ai 未安装") return @@ -197,10 +206,16 @@ class Mem0Client: llm=LlmConfig( provider="openai", config=self.config['llm']['config'] + ), + embedder=EmbedderConfig( + provider="openai", + config={ + "model": "text-embedding-v3" # 显式指定 DashScope 支持的向量模型 + } ) ) self.local_memory = Memory(config=config) - logger.info("✅ mem0 初始化成功") + logger.info("✅ mem0 初始化成功(含 Embedder)") except Exception as e: logger.error(f"❌ mem0 初始化失败:{e}") self.local_memory = None diff --git a/skills/mem0-integration/mem0_integration.py b/skills/mem0-integration/mem0_integration.py new file mode 100644 index 0000000..1476bd2 --- /dev/null +++ b/skills/mem0-integration/mem0_integration.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +Mem0 Python 集成脚本 +被 Node.js 插件调用,执行实际的记忆操作 +""" + +import sys +import json +import os +import asyncio + +# 设置环境变量 +os.environ['OPENAI_API_BASE'] = 'https://dashscope.aliyuncs.com/compatible-mode/v1' +os.environ['OPENAI_BASE_URL'] = 'https://dashscope.aliyuncs.com/compatible-mode/v1' +os.environ['OPENAI_API_KEY'] = os.getenv('MEM0_DASHSCOPE_API_KEY', 'sk-c1715ee0479841399fd359c574647648') + +sys.path.insert(0, os.path.dirname(__file__)) + +from mem0_client import mem0_client + + +async def main(): + if len(sys.argv) < 2: + print(json.dumps({"error": "No action specified"})) + return + + action = sys.argv[1] + data = json.loads(sys.argv[2]) if len(sys.argv) > 2 else {} + + try: + if action == 'init': + # 初始化 mem0 + await mem0_client.start() + print(json.dumps({ + "status": "initialized", + "qdrant": f"{mem0_client.config['qdrant']['host']}:{mem0_client.config['qdrant']['port']}" + })) + + elif action == 'search': + # 检索记忆 + memories = await mem0_client.pre_hook_search( + query=data.get('query', ''), + user_id=data.get('user_id', 'default'), + agent_id=data.get('agent_id', 'general') + ) + print(json.dumps({ + "memories": memories, + "count": len(memories) + })) + + elif action == 'add': + # 添加记忆(异步,不等待) + mem0_client.post_hook_add( + user_message=data.get('user_message', ''), + assistant_message=data.get('assistant_message', ''), + user_id=data.get('user_id', 'default'), + agent_id=data.get('agent_id', 'general') + ) + print(json.dumps({"status": "queued"})) + + else: + print(json.dumps({"error": f"Unknown action: {action}"})) + + except Exception as e: + print(json.dumps({"error": str(e)})) + + +if __name__ == '__main__': + asyncio.run(main())