得物以任务评测选择长期记忆方案
概述
在 MultiAgent 平台中,一次请求可能同时经过模型、MCP/A2A 工具、RAG、Workflow 与 Sandbox,还要在多轮对话和跨会话协作中记住用户偏好、任务进展与协作约定,因此记忆模块不是独立外挂而是执行链路的一部分。团队建立四层记忆模型:Working Memory 只服务当前一步推理,Session Memory 保存会话内消息历史,User Memory 记录跨 Agent 共享的偏好与稳定事实(对应 MemOS 的 user_profile),Agent Memory 保存某 Agent 的任务经验与协作约定(对应 agent_{agentId});会话结束后新增消息经判断与去重从 Session 层沉淀到 User 或 Agent 层。整体架构后端基于 Spring Boot 3 与 Java 17,Agent 编排采用 AgentScope;新建 Agent 默认 longMemoryProvider=MEMOS 但 openLongMemory 默认关闭,开启后请求开始时短期会话历史与长期记忆并行加载,会话结束由 onSessionEndAsync 异步完成筛选、去重与长期沉淀;短期记忆以 MySQL 为持久化底座、Redis 为热点缓存,长期记忆以 MemOS 为主路径并保留 MySQL/Mem0 兼容路由(MemOS 查询异常由 Provider 记录日志并返回空结果,不会自动切回 MySQL)。选型 MemOS 的依据是 1540 个问题、10 个测试用户的评测中综合得分 74.33%、单跳与多跳表现均达标,正文注明该评测仅作选型参考、不代表生产 SLA。 短期记忆保存当前会话中按时间排列的对话历史,是指代消解与多轮推理的直接上下文。加载侧由 AgentExecutor 取 contextRounds,调用 chatMemory.get(conversationId, contextRounds*3),实现类优先读 Redis List(leftPush 写入、最新消息在 index 0、应用层窗口裁剪、rightPop 淘汰最旧消息、超过 200 条即淘汰),未命中或读取异常则查询 MySQL 并回写;Token 窗口以 MAX_TOKEN_WINDOW_SIZE 减去摘要预算为界,从最新消息向前累计,未超上限时直接合并 system 与历史消息,超限则保留最近窗口并前置会话摘要。写入侧采用双写:先落 MySQL 持久化,再把 Redis 作为可失效的热点缓存(缓存失败不回滚已完成的 MySQL 写入,读取时回源数据库);子 Agent 会话(conversationId 以 agent: 开头)的非 ChatMessage 不写入主会话,避免虚拟会话污染主链路。会话结束后,未被摘要覆盖的消息达到 20 条才触发摘要生成,摘要注入预算约 2000 tokens 并在 Redis 缓存 1 小时,以此把高频写入与低频摘要合并拆开、避免每轮调用摘要模型。 请求入口 AgentExecutor#execute 用专用线程池 memoryLoadExecutor 以 CompletableFuture 并行加载短期与长期记忆,并在 join 处汇合;长期链路在 openLongMemory 未开启、Agent 未绑定模型(targetId 为空)时直接返回空 Map,查询异常也只记录 warning 并让主对话继续,长期记忆定位为增强能力。MemOS Search 一次覆盖 user_profile 与 agent_{agentId} 两个可读 cube,参数含 topK(DEFAULT_TOP_K)、mode=fast、relativity=0.45、dedup=mmr、includePreference 与 prefTopK=6;context 非空时只取前 200 个字符拼到 userMessage 后作为 query,且当前实现未根据 justKeywordSearch 切换检索模式、仍固定调用 MemOS Search。返回结果经 score<0.3 过滤与单条 1000 字符截断后按 score 降序,避免低相关或过长内容挤占上下文。注入预算默认 4000 tokens:user_profile 最多占 60%,实际未用满时剩余空间分给 Agent 记忆,两类内容均按行累加 token、达到预算即停止追加,避免拆散单条记忆。 会话结束由 MemoryRpcServiceImpl 编排:onSessionEndAsync 先取会话级 Redis 锁(10 分钟),由 processSessionEnd 按 MD5(role:content) 消息 hash 过滤本次新增内容并提前记录已处理 hash(TTL 7 天),用于在 LLM 判断与 MemOS 持久化之前挡住重复结束事件——正文说明这层属 best-effort,Redis 读取失败时会把整段会话作为新增内容继续处理,hash 写入失败只记录日志,外部服务失败后无自动回放。MemOS 路径先用 LLM 做记忆判断:以 [[NEW]] 标记本次新增消息、按 5 类信息定义 importance 1-10 区间、明确 scope 归属并要求 JSON 输出;调用异常或返回为空时降级到仅基于新增消息的规则判断,返回非空时统一补齐缺省字段并截断单条记忆到 200 字符。持久化前用 exact、contains、字符级 Jaccard(阈值 0.7)与短文本 Levenshtein(阈值 0.8)依次做本地相似度预去重;再按 memCubeId 分组调 batchDetectConflict,逐条决定写入新记忆、跳过或标记旧记忆失效。MemOS 侧以 async、fine 模式调用 addMemory,采用“先写新记忆、后删旧记忆”策略:只有返回含新增记忆时才把冲突旧记忆 ID 交给 deleteMemories,删除异常只记 warning,属 best-effort 而非事务级一致性。