Agent 记忆系统设计:让智能体记得住、想得起、用得上(续篇) Agent 记忆系统设计让智能体记得住、想得起、用得上续篇Agent 记住了你上次说我喜欢简短回答下次还是长篇大论——它的记忆存了但没用上。一、场景痛点你的 Agent 有记忆系统——用户说过的话会被存到向量数据库后续对话时检索相关记忆注入提示。但用户反馈我说过三遍了我不喜欢代码示例Agent 还是每条回复都带代码片段。你排查发现记忆确实存了向量数据库里有不喜欢代码示例的记录检索也命中了相似度 0.85但检索到的记忆被放在了提示的末尾模型优先响应了系统提示中的提供详细代码示例个人偏好的记忆被覆盖了。更深层的问题记忆的检索依赖向量相似度但相似度高不代表相关度高。不喜欢代码示例和不喜欢长代码的向量相似度是 0.92但语义完全不同——前者是不想要代码后者是不想要长代码。检索返回了不喜欢长代码Agent 理解成可以提供短代码又带了代码片段。核心矛盾Agent 记忆系统的三个问题存了但检索不准相似度≠相关度、检索了但排序不对记忆被系统提示覆盖、用上了但理解错了语义歧义。二、底层机制与原理剖析2.1 记忆系统的三层架构2.2 检索准确性相似度 ≠ 相关度向量相似度衡量的是两段文本的语义空间距离但这个距离不等于两段文本在当前对话中的有用程度。三个检索维度语义相似度文本内容是否相关向量距离意图相关性是否与当前查询的意图匹配关键词意图分类时序相关性是否是最近的信息近期优先旧信息衰减综合评分 语义 × 0.4 意图 × 0.4 时序 × 0.2。纯向量检索只用语义维度遗漏了意图和时序。2.3 提示注入排序记忆被注入到提示后模型按位置权重处理靠前的内容权重高系统提示靠后的权重低用户消息后的附加信息。个人偏好记忆如果排在系统提示后面会被系统提示的提供详细代码示例覆盖。正确排序用户偏好排在系统提示内部而不是末尾。格式系统提示 用户偏好区段 对话历史。三、生产级代码实现3.1 混合检索引擎// memory-retrieval.ts —— 多维度混合检索引擎 import { EmbeddingService } from ./embedding; import { VectorStore } from ./vector-store; export interface MemoryRecord { id: string; content: string; embedding: number[]; userId: string; type: preference | fact | instruction | correction; timestamp: number; // Unix 时间戳 confidence: number; // 记忆置信度 metadata: Recordstring, string; } export interface RetrievalResult { records: MemoryRecord[]; scores: Array{ semantic: number; // 语义相似度得分 intent: number; // 意图相关性得分 temporal: number; // 时序相关性得分 composite: number; // 综合得分 }; } export class MemoryRetriever { private vectorStore: VectorStore; private embeddingService: EmbeddingService; private intentClassifier: IntentClassifier; // 检索维度权重语义和意图同等重要时序辅助 private weights { semantic: 0.4, intent: 0.4, temporal: 0.2 }; constructor( vectorStore: VectorStore, embeddingService: EmbeddingService, intentClassifier: IntentClassifier, ) { this.vectorStore vectorStore; this.embeddingService embeddingService; this.intentClassifier intentClassifier; } /** 多维度混合检索综合语义、意图、时序三个维度 */ async retrieve( query: string, userId: string, topK: number 5, minCompositeScore: number 0.5, ): PromiseRetrievalResult { // Step 1: 向量相似度检索语义维度 const queryEmbedding await this.embeddingService.embed(query); const semanticResults await this.vectorStore.search( queryEmbedding, { filter: { userId }, topK: topK * 3 }, // 多取 3 倍候选后续筛选 ); // Step 2: 意图匹配意图维度 const queryIntent await this.intentClassifier.classify(query); // Step 3: 对每个候选记录计算多维度得分 const scoredResults: Array{ record: MemoryRecord; semanticScore: number; intentScore: number; temporalScore: number; compositeScore: number; } []; const now Date.now(); for (const result of semanticResults) { const record result.record as MemoryRecord; // 语义得分向量相似度0-1 const semanticScore result.score; // 意图得分当前查询意图与记忆类型的匹配度 const intentScore this.calculateIntentScore( queryIntent, record.type, record.content ); // 时序得分近期记忆得分高旧记忆衰减 // 衰减公式score exp(-λ × age_days) // λ 0.130 天前的记忆得分约 0.04基本遗忘 const ageDays (now - record.timestamp) / (24 * 3600 * 1000); const temporalScore Math.exp(-0.1 * ageDays); // 综合得分加权求和 const compositeScore semanticScore * this.weights.semantic intentScore * this.weights.intent temporalScore * this.weights.temporal; // 过滤综合得分低于阈值的不返回 if (compositeScore minCompositeScore) { scoredResults.push({ record, semanticScore, intentScore, temporalScore, compositeScore, }); } } // 按综合得分排序取 topK scoredResults.sort((a, b) b.compositeScore - a.compositeScore); const finalResults scoredResults.slice(0, topK); return { records: finalResults.map((r) r.record), scores: finalResults.map((r) ({ semantic: r.semanticScore, intent: r.intentScore, temporal: r.temporalScore, composite: r.compositeScore, })), }; } /** 计算意图得分当前查询意图与记忆类型的匹配度 */ private calculateIntentScore( queryIntent: string, memoryType: string, memoryContent: string, ): number { // 意图与记忆类型的对应关系 // code_query → preference/instruction 记忆相关 // explanation_query → fact 记忆相关 // preference_query → preference 记忆高度相关 const intentTypeMap: Recordstring, Recordstring, number { code_generation: { preference: 0.9, instruction: 0.8, fact: 0.3, correction: 0.5 }, explanation: { fact: 0.8, preference: 0.4, instruction: 0.5, correction: 0.7 }, preference_setting: { preference: 1.0, instruction: 0.6, fact: 0.2, correction: 0.3 }, fact_correction: { correction: 0.9, fact: 0.7, preference: 0.2, instruction: 0.3 }, }; const typeScores intentTypeMap[queryIntent] ?? {}; const baseScore typeScores[memoryType] ?? 0.3; // 内容关键词匹配加分记忆内容包含查询关键词 // 这一步解决了相似度≠相关度的问题 // 不喜欢代码示例包含代码关键词 // 代码类查询时意图得分高解释类查询时意图得分低 const queryKeywords query.toLowerCase().split(/\s/); const memoryKeywords memoryContent.toLowerCase().split(/\s/); const keywordOverlap queryKeywords.filter( (k) memoryKeywords.includes(k) k.length 2 ).length; const keywordBonus Math.min(keywordOverlap * 0.1, 0.3); return Math.min(baseScore keywordBonus, 1.0); } } /** 简化意图分类器 */ class IntentClassifier { async classify(query: string): Promisestring { const patterns: Recordstring, string[] { code_generation: [代码, 写, 实现, code, write, implement], explanation: [解释, 为什么, explain, why, 原理], preference_setting: [我喜欢, 我偏好, 以后, prefer, like], fact_correction: [不对, 错了, 纠正, wrong, correct], }; for (const [intent, keywords] of Object.entries(patterns)) { if (keywords.some((k) query.includes(k))) { return intent; } } return general; } }3.2 提示注入排序器// prompt-assembler.ts —— 记忆注入到提示的正确排序 export class PromptAssembler { /** 组装最终提示系统指令 用户偏好 对话历史 */ assemble( systemPrompt: string, userPreferences: MemoryRecord[], dialogHistory: Array{ role: string; content: string }, currentQuery: string, ): string { const sections: string[] []; // Section 1: 系统指令权重最高 sections.push(systemPrompt); // Section 2: 用户偏好区段权重仅次于系统指令 // 关键偏好不是放在提示末尾而是嵌入系统指令内部 // 这样偏好的权重与系统指令等同不会被覆盖 if (userPreferences.length 0) { const preferenceText this.formatPreferences(userPreferences); sections.push(## User Preferences (MUST follow these over general instructions)\n${preferenceText}); } // Section 3: 对话历史权重中等 for (const msg of dialogHistory.slice(-10)) { // 只取最近 10 条 sections.push(${msg.role}: ${msg.content}); } // Section 4: 当前查询权重高但需要结合偏好处理 sections.push(user: ${currentQuery}); return sections.join(\n\n); } /** 格式化偏好记忆明确标注优先级 */ private formatPreferences(preferences: MemoryRecord[]): string { // 每条偏好用明确的指令格式标注 // MUST 必须遵守覆盖系统指令中的冲突部分 // SHOULD 尽量遵守不覆盖系统指令 return preferences .map((p) { const priority p.confidence 0.9 ? MUST : SHOULD; return - [${priority}] ${p.content} (noted on ${new Date(p.timestamp).toLocaleDateString()}); }) .join(\n); } }3.3 语义校验器# semantic_validator.py —— 记忆语义校验防止歧义误用 import logging logger logging.getLogger(semantic-validator) class SemanticValidator: 校验检索到的记忆是否与当前查询语义一致 def validate_memory_application( self, query: str, memory: dict, application_plan: str, # Agent 计划如何使用这条记忆 ) - dict: 校验记忆的语义与应用计划是否一致 checks { negation_check: self._check_negation(query, memory), scope_check: self._check_scope(query, memory), conflict_check: self._check_conflict(memory, application_plan), } all_passed all(c[passed] for c in checks.values()) return { valid: all_passed, checks: checks, recommendation: self._generate_recommendation(checks) if not all_passed else None, } def _check_negation(self, query: str, memory: dict) - dict: 否定校验记忆是否包含否定含义不喜欢不要 content memory.get(content, ) negation_words [不喜欢, 不要, 别, 不需要, 不喜欢, don\t, not, no] has_negation any(w in content.lower() for w in negation_words) # 如果记忆包含否定Agent 的应用计划不能是肯定方向 # 例如不喜欢代码示例 → Agent 不能计划提供代码示例 if has_negation: # 检查 application_plan 是否与否定方向一致 # 如果 Agent 计划做记忆说不要的事这是误用 plan_words application_plan.lower() # 从否定内容中提取肯定方向 # 不喜欢代码 → 肯定方向是代码 # 如果 plan 包含代码且记忆说不喜欢代码 → 冲突 for neg_word in negation_words: if neg_word in content.lower(): # 提取否定对象 negated_object content.lower().replace(neg_word, ).strip() if negated_object and negated_object in plan_words: return { passed: False, reason: fMemory says {content} but plan includes {negated_object}, } return {passed: True, reason: No negation conflict detected} def _check_scope(self, query: str, memory: dict) - dict: 范围校验记忆的适用范围是否与当前查询匹配 # 偐好记忆的适用范围特定场景 vs 全局 # 不喜欢代码示例 → 适用范围所有对话全局偏好 # 在解释 API 时不需要代码 → 适用范围只 API 解释场景 content memory.get(content, ) scope_keywords { code_context: [代码, 实现, 编程, code, implement], explanation_context: [解释, 说明, explain, describe], all_context: [所有, 总是, 以后, always, every], } # 判断记忆的适用范围 memory_scope all # 默认全局 for scope, keywords in scope_keywords.items(): if any(k in content for k in keywords): memory_scope scope # 判断当前查询的上下文 query_context general for scope, keywords in scope_keywords.items(): if any(k in query for k in keywords) and scope ! all_context: query_context scope # 如果记忆是全局偏好任何查询都适用 → pass # 如果记忆是特定场景偏好只在匹配场景适用 → 检查 if memory_scope all or memory_scope query_context: return {passed: True, reason: fScope match: memory{memory_scope}, query{query_context}} else: return { passed: False, reason: fScope mismatch: memory applies to {memory_scope}, query is {query_context}, } def _check_conflict(self, memory: dict, application_plan: str) - dict: 冲突校验记忆与系统指令是否矛盾 # 检查记忆的置信度高置信度记忆覆盖系统指令 confidence memory.get(confidence, 0.5) if confidence 0.9: return {passed: True, reason: High-confidence memory overrides system instruction} else: return { passed: True, # 低置信度记忆不覆盖但不阻止使用 reason: Low-confidence memory, should not override system instruction, } def _generate_recommendation(self, checks: dict) - str: 生成校验失败时的修正建议 failed_checks [c for c in checks.values() if not c[passed]] return ; .join([c[reason] for c in failed_checks])四、边界分析与架构权衡4.1 多维度检索的计算成本向量检索语义维度需要计算嵌入和搜索向量空间延迟约 50-100ms。意图分类意图维度需要调用轻量模型延迟约 10-50ms。总检索延迟约 60-150ms在高频对话中可能成为瓶颈。对策对高频用户做记忆预加载——用户进入对话时提前检索并缓存偏好记忆后续对话直接使用缓存不需要每次重新检索。4.2 否定校验的局限性否定校验只能检测简单否定模式不喜欢 X不能处理复杂否定除非是 Python 代码否则不要提供代码示例。复杂否定的语义解析需要更强的 NLU 能力。4.3 适用边界与禁用场景适用有个人偏好的多轮对话 Agent、需要跨会话记忆的助手型 Agent、有用户纠正历史的知识型 Agent禁用单次问答 Agent不需要记忆、完全确定性 Agent不需要偏好、隐私敏感场景记忆存储合规问题五、结语Agent 记忆系统的核心问题是存了但没用上——三个环节都有断点检索不准纯向量相似度忽略了意图和时序、排序不对偏好记忆排在提示末尾被系统指令覆盖、理解错了否定语义被误用为肯定。解决方案多维度混合检索语义 0.4 意图 0.4 时序 0.2、偏好记忆嵌入系统指令内部权重等同于系统指令、语义校验器检查否定冲突和适用范围。时序衰减用 exp(-0.1 × age_days) 公式30 天前的记忆基本遗忘。否定校验检测不喜欢 X→ Agent 不能计划做 X。偏好记忆必须标注优先级MUST vs SHOULD高置信度偏好覆盖系统指令冲突。