最终修复: 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 消息注入master
parent
b26030f7a6
commit
b6467da698
6 changed files with 260 additions and 4 deletions
@ -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. |
||||||
@ -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. |
||||||
Binary file not shown.
@ -0,0 +1,162 @@ |
|||||||
|
#!/usr/bin/env node
|
||||||
|
/** |
||||||
|
* OpenClaw Mem0 Integration Plugin |
||||||
|
* 将 mem0 拦截器挂载到 OpenClaw 主对话生命周期 |
||||||
|
*/ |
||||||
|
|
||||||
|
const fs = require('fs'); |
||||||
|
const path = require('path'); |
||||||
|
|
||||||
|
// Python 子进程执行器
|
||||||
|
const { spawn } = require('child_process'); |
||||||
|
|
||||||
|
class Mem0Plugin { |
||||||
|
constructor(config) { |
||||||
|
this.config = config; |
||||||
|
this.enabled = true; |
||||||
|
this.pythonPath = config.pythonPath || 'python3'; |
||||||
|
this.scriptPath = path.join(__dirname, 'mem0_integration.py'); |
||||||
|
this.initialized = false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 初始化 mem0(在 OpenClaw 启动时调用) |
||||||
|
*/ |
||||||
|
async onLoad() { |
||||||
|
if (!this.enabled) return; |
||||||
|
|
||||||
|
console.log('[Mem0] 初始化记忆系统...'); |
||||||
|
|
||||||
|
try { |
||||||
|
// 调用 Python 脚本初始化 mem0
|
||||||
|
const result = await this._executePython('init'); |
||||||
|
|
||||||
|
if (result.success) { |
||||||
|
this.initialized = true; |
||||||
|
console.log('🟢 Mem0 生产环境集成完毕,等待 Telegram 消息注入'); |
||||||
|
} else { |
||||||
|
console.error('[Mem0] 初始化失败:', result.error); |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
console.error('[Mem0] 初始化异常:', error.message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Pre-Hook: 在 LLM 生成前检索记忆 |
||||||
|
*/ |
||||||
|
async preLLM(userMessage, context) { |
||||||
|
if (!this.enabled || !this.initialized) return null; |
||||||
|
|
||||||
|
try { |
||||||
|
const result = await this._executePython('search', { |
||||||
|
query: userMessage, |
||||||
|
user_id: context.user_id || 'default', |
||||||
|
agent_id: context.agent_id || 'general' |
||||||
|
}); |
||||||
|
|
||||||
|
if (result.success && result.memories && result.memories.length > 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' |
||||||
|
}); |
||||||
@ -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()) |
||||||
Loading…
Reference in new issue