ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Agent记忆架构实战:基于MCP与Docker的三层记忆系统设计

Agent记忆架构实战:基于MCP与Docker的三层记忆系统设计 1. 从“hindsight”说起为什么我们需要给 Agent 装上“后视镜”“hindsight”这个词本身很有意思字面意思是“事后的洞察力”也就是我们常说的“后见之明”。放在 AI Agent 和 LLM 的语境里它指向一个非常具体且要命的问题Agent 的记忆到底该怎么存、怎么取、怎么用我接触过不少做 Agent 落地的团队大家一开始都特别乐观觉得只要把 LLM 接上工具、挂上 MCPAgent 就能自己干活了。结果一上生产环境就发现Agent 像个失忆症患者——上一轮对话里用户明确说过的偏好下一轮就忘得一干二净昨天已经排查过的报错今天遇到一模一样的问题还是从头再来一遍。这不是模型不够聪明而是记忆架构没设计好。“hindsight”这个项目标题我理解它要解决的核心就是让 Agent 具备“回头看”的能力。不是简单地存聊天记录而是把历史交互、工具调用结果、环境状态这些信息经过结构化处理之后变成 Agent 可以高效检索和复用的“经验”。这背后涉及几个关键技术点agent memory 的分层设计、LLM 的上下文管理、MCP 协议下的工具编排、以及 Docker 化部署带来的可复现性。这篇文章适合谁看如果你正在做 Agent 应用开发或者你已经在用 Dify、Coze 这类平台搭工作流但发现记忆模块总是差点意思那这篇内容应该能给你一些可以直接抄作业的思路。如果你只是刚听说 MCP 和 agent memory 这些词也没关系我会从最基础的概念讲起用生活化的类比把原理说清楚再一步步拆解实操细节。我自己的经验是Agent 记忆这件事设计阶段多花一天想清楚后面能省一周的调试时间。下面我就把“hindsight”这个项目涉及的核心思路、技术选型、实操步骤和踩坑记录完整地梳理一遍。2. 核心思路拆解Agent Memory 到底该怎么分层2.1 为什么“把聊天记录全塞进上下文”是最蠢的做法很多人第一次做 Agent 记忆直觉反应就是把历史对话全部拼接到 prompt 里不就行了我试过短对话还行一旦超过二三十轮token 消耗直接爆炸而且模型对超长上下文的注意力会严重衰减。你塞进去 8000 token 的历史记录模型真正“注意到”的可能只有最后 1000 token。更麻烦的是无差别地塞历史记录会引入大量噪声。用户三年前问过的一个无关问题和当前任务毫无关系但它占着上下文窗口挤掉了真正有用的信息。这就像你找一个同事帮忙他非要把过去三年所有工作邮件都打印出来带在身上然后每次问你问题都要翻一遍——效率极低而且关键信息很容易被淹没。所以“hindsight”的核心设计思路一定是分层存储 按需检索。不是把所有东西都放在一个篮子里而是根据信息的时效性、重要性、使用频率分到不同的存储层里用的时候再精准取出来。2.2 三层记忆架构working memory、episodic memory、semantic memory基于常见实践我倾向于把 Agent 记忆分成三层这个分法在认知科学里也有对应概念落地到工程上非常自然。第一层Working Memory工作记忆。这是最活跃的一层存放当前对话轮次、最近几轮交互、当前任务的状态变量。它的特点是容量小、读写快、生命周期短。你可以把它理解成 Agent 的“桌面”——正在处理的文件摊在桌面上随手就能拿到。技术上通常用内存缓存或者 Redis 来实现TTL 设置得比较短比如 30 分钟到 2 小时。第二层Episodic Memory情景记忆。这一层存放的是“发生过什么”的记录包括每次对话的摘要、工具调用的输入输出、任务执行的结果。它不是原始日志而是经过压缩和结构化的版本。比如一次完整的工具调用原始记录可能有几千 token但情景记忆里只保留“什么时间、调了什么工具、关键参数是什么、结果成功还是失败、失败原因是什么”这几个字段。这一层通常用关系型数据库或者文档数据库来存支持按时间范围、任务 ID、工具名称等维度检索。第三层Semantic Memory语义记忆。这是最高层存放的是从大量交互中提炼出来的“知识”和“偏好”。比如“这个用户喜欢用简洁的回复风格”、“这个项目的代码规范要求用 4 空格缩进”、“这类报错通常是因为权限配置问题”。语义记忆不依赖于具体某次对话而是跨会话、跨任务的通用知识。这一层通常用向量数据库来存支持语义相似度检索。提示三层记忆不是互斥的而是协同工作的。Agent 在处理一个任务时会先从 working memory 拿当前状态再从 episodic memory 检索相似历史案例最后从 semantic memory 获取通用规则和偏好三者融合之后才形成最终的上下文。2.3 为什么选 MCP 作为工具编排协议MCPModel Context Protocol最近热度很高但很多人对它的理解还停留在“又一个协议”的层面。我一开始也疑惑为什么不用现成的 REST API 或者函数调用后来实际用下来发现MCP 解决的是一个很具体的问题让 LLM 以统一的方式发现和调用外部工具。在没有 MCP 之前每接一个工具你都要写一套适配代码定义函数签名、写参数校验、处理返回格式、做错误映射。工具一多维护成本直线上升。MCP 的思路是把工具的描述和调用标准化LLM 通过 MCP 协议就能知道“有哪些工具可用、每个工具需要什么参数、返回什么格式”不需要为每个工具单独写适配层。这和硬件协议的概念有点像——USB 接口标准化之后你不需要为每个外设单独设计一个接口插上就能用。MCP 就是 AI 工具调用领域的“USB 标准”。在“hindsight”这个项目里MCP 主要用来做两件事一是把记忆存储和检索能力暴露成标准工具让 Agent 可以主动调用二是把外部数据源比如数据库、文件系统、API接入进来作为记忆的补充来源。2.4 Docker 化部署为什么这是必选项而不是可选项Agent 记忆系统涉及多个组件LLM 服务、向量数据库、关系型数据库、缓存、MCP 服务端。如果每个组件都手动安装配置光是环境问题就能耗掉一整天。Docker Compose 的价值在于把整个系统的依赖关系和环境配置固化下来换一台机器只需要docker compose up就能跑起来。我踩过的一个坑是在本地开发环境跑得好好的向量检索部署到服务器上就报错排查半天发现是两边的向量数据库版本不一致索引格式不兼容。用 Docker 之后镜像版本锁定这种问题基本不会再出现。而且 Docker 的网络配置让各个服务之间的通信变得很清晰不需要去折腾宿主机的端口和防火墙规则。3. 核心细节解析记忆的写入、检索与更新机制3.1 记忆写入什么时候存、存什么、怎么存记忆写入不是越多越好写入策略直接决定了检索质量。我见过一些实现把每一轮对话的原始文本全部存进去结果检索的时候返回一堆无关内容。正确的做法是在写入前做一次摘要和结构化。具体来说一次完整的记忆写入流程包括这几个步骤触发判断不是每轮对话都需要写入长期记忆。我的做法是设置几个触发条件——用户明确表达了偏好或纠正、工具调用产生了重要结果、任务状态发生了关键变化、对话轮次达到一定数量比如每 5 轮做一次摘要写入。内容摘要用 LLM 对当前对话片段做摘要提取关键信息。这里的关键是 prompt 设计要让模型输出结构化的 JSON而不是自由文本。比如要求输出{ event_type: preference_update, summary: ..., entities: [...], importance: 0.8 }这样的格式。分层路由根据摘要结果决定写入哪一层。偏好类信息写入 semantic memory任务执行记录写入 episodic memory当前状态更新写入 working memory。向量化与索引对于需要语义检索的内容调用 embedding 模型生成向量写入向量数据库。同时把结构化字段写入关系型数据库方便做精确查询。# 记忆写入的伪代码示例 def write_memory(dialogue_segment, context): # 第一步判断是否触发写入 if not should_trigger_write(dialogue_segment, context): return # 第二步LLM 摘要与结构化 summary llm_summarize(dialogue_segment, output_formatjson) # 第三步分层路由 if summary[event_type] preference: store_to_semantic_memory(summary) elif summary[event_type] task_execution: store_to_episodic_memory(summary) # 第四步向量化 embedding embed_model.encode(summary[summary]) vector_db.upsert(embedding, metadatasummary)注意摘要 prompt 里一定要明确要求模型保留“实体”信息比如人名、项目名、工具名、参数值。这些实体是后续检索的关键锚点丢了就很难精准召回。3.2 记忆检索怎么在正确的时间拿到正确的记忆检索是记忆系统里最考验设计功力的环节。检索的目标不是“找到所有相关记忆”而是“找到当前任务真正需要的那几条”。返回太多记忆会挤占上下文窗口还会引入噪声返回太少又可能漏掉关键信息。我的做法是多路召回 重排序第一路向量相似度召回。用当前 query 的 embedding 去向量数据库里找最相似的 top-K 条记忆。K 一般设 10 到 20不要太大。第二路结构化条件召回。根据当前任务的元数据比如任务 ID、用户 ID、工具名称从关系型数据库里精确查询相关记录。第三路时间衰减召回。最近发生的记忆权重更高用时间衰减函数给每条记忆算一个分数越新的记忆分数越高。三路召回的结果合并之后用一个轻量级的重排序模型或者直接用 LLM 做相关性打分做二次排序最终选出 top-3 到 top-5 条注入上下文。这里有个经验重排序这一步非常关键但很多人会忽略。向量相似度高不代表真的相关。我遇到过 query 是“怎么配置数据库连接”向量检索返回了一条“数据库连接池满了怎么排查”的记忆字面上很相似但实际场景完全不同。重排序模型能根据更细粒度的语义匹配把真正相关的那条排到前面。3.3 记忆更新与遗忘不是所有记忆都值得永久保留记忆系统如果只写不删很快就会变成一个垃圾场。遗忘机制和写入机制同样重要。我的策略是Working memory 自动过期设置 TTL比如 2 小时没有访问就自动清除。Episodic memory 定期压缩每周做一次批量处理把相似的情景记忆合并成一条更抽象的记录。比如 10 次“调用天气 API 成功”的记录压缩成一条“天气 API 调用成功率高平均响应时间 200ms”。Semantic memory 冲突检测当新写入的偏好和已有偏好冲突时不是简单覆盖而是记录冲突并标记需要人工确认。比如用户之前说“喜欢详细回复”现在说“太啰嗦了”系统应该记录这个变化而不是直接删掉旧偏好。重要性衰减每条记忆有一个 importance 分数随着时间推移和访问频率降低分数会衰减。低于阈值的记忆会被归档到冷存储不再参与常规检索但保留可恢复性。3.4 MCP 工具层的具体设计在“hindsight”项目里MCP 工具层主要暴露这几个能力工具名称功能输入参数输出memory_write写入记忆content, memory_type, metadatamemory_idmemory_search检索记忆query, top_k, filterslist of memoriesmemory_update更新记忆memory_id, new_contentsuccess/failmemory_forget删除或归档记忆memory_id, soft_deletesuccess/failmemory_stats获取记忆统计user_id, time_rangestats object这些工具通过 MCP 协议注册之后Agent 可以在需要的时候主动调用。比如 Agent 在回答用户问题之前可以先调memory_search看看有没有相关历史在对话结束之后调memory_write把关键信息存下来。MCP 的另一个好处是工具发现是动态的。如果后续要加新的记忆存储后端比如从 Redis 换成别的只需要在 MCP 服务端做调整Agent 侧不需要改代码。4. 实操过程从零搭建一套可运行的 Agent Memory 系统4.1 环境准备与 Docker Compose 编排先把基础环境搭起来。我假设你用的是 Windows 11 或者 macOSLinux 更简单步骤类似。第一步安装 Docker Desktop。Windows 用户去官网下载安装包安装过程中如果提示 “Virtualization support not detected”需要进 BIOS 开启虚拟化支持Intel VT-x 或 AMD-V。安装完成后在设置里确认 WSL 2 后端已启用。第二步创建项目目录结构。我的习惯是每个服务一个目录配置文件和代码分开hindsight/ ├── docker-compose.yml ├── .env ├── mcp-server/ │ ├── Dockerfile │ └── src/ ├── memory-service/ │ ├── Dockerfile │ └── src/ └── config/ └── init.sql第三步编写 docker-compose.yml。核心服务包括PostgreSQL存结构化记忆、Redis存 working memory、Qdrant 或 Milvus存向量、MCP 服务端、记忆服务端。version: 3.8 services: postgres: image: postgres:16-alpine environment: POSTGRES_DB: hindsight POSTGRES_USER: agent POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - pg_data:/var/lib/postgresql/data - ./config/init.sql:/docker-entrypoint-initdb.d/init.sql ports: - 5432:5432 healthcheck: test: [CMD-SHELL, pg_isready -U agent] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru ports: - 6379:6379 volumes: - redis_data:/data qdrant: image: qdrant/qdrant:latest ports: - 6333:6333 volumes: - qdrant_data:/qdrant/storage memory-service: build: ./memory-service depends_on: postgres: condition: service_healthy redis: condition: service_started qdrant: condition: service_started environment: DB_URL: postgresql://agent:${DB_PASSWORD}postgres:5432/hindsight REDIS_URL: redis://redis:6379 QDRANT_URL: http://qdrant:6333 ports: - 8000:8000 mcp-server: build: ./mcp-server depends_on: - memory-service environment: MEMORY_SERVICE_URL: http://memory-service:8000 ports: - 8080:8080 volumes: pg_data: redis_data: qdrant_data:注意.env文件里不要硬编码密码用环境变量注入。生产环境还要考虑网络隔离把数据库端口只暴露给内部网络。4.2 数据库表结构设计PostgreSQL 里主要建三张表episodic_memory、semantic_memory、memory_relations。CREATE TABLE episodic_memory ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id VARCHAR(64) NOT NULL, session_id VARCHAR(64), event_type VARCHAR(32) NOT NULL, summary TEXT NOT NULL, entities JSONB DEFAULT [], tool_calls JSONB DEFAULT [], importance FLOAT DEFAULT 0.5, created_at TIMESTAMP DEFAULT NOW(), last_accessed_at TIMESTAMP DEFAULT NOW(), access_count INT DEFAULT 0 ); CREATE INDEX idx_episodic_user_time ON episodic_memory(user_id, created_at DESC); CREATE INDEX idx_episodic_importance ON episodic_memory(importance DESC); CREATE TABLE semantic_memory ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id VARCHAR(64) NOT NULL, category VARCHAR(32) NOT NULL, content TEXT NOT NULL, confidence FLOAT DEFAULT 0.8, source_memory_ids UUID[], created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE memory_relations ( source_id UUID NOT NULL, target_id UUID NOT NULL, relation_type VARCHAR(32) NOT NULL, weight FLOAT DEFAULT 1.0, PRIMARY KEY (source_id, target_id, relation_type) );entities字段用 JSONB 存方便后续做 GIN 索引和精确查询。importance和access_count用来做检索排序的加权因子。4.3 记忆写入的完整实现写入流程我拆成三个函数should_write、summarize、route_and_store。import json from datetime import datetime, timedelta def should_write(dialogue, context): 判断是否触发记忆写入 # 条件1对话轮次达到阈值 if context.get(turn_count, 0) % 5 0: return True # 条件2检测到偏好表达 preference_keywords [我喜欢, 我习惯, 以后都, 不要再, 记住] if any(kw in dialogue for kw in preference_keywords): return True # 条件3工具调用失败 if context.get(last_tool_status) error: return True return False def summarize(dialogue, llm_client): 用 LLM 做摘要和结构化 prompt f请对以下对话片段做摘要输出 JSON 格式 {{ event_type: preference|task_execution|error_resolution|general, summary: 一句话摘要不超过50字, entities: [实体1, 实体2], importance: 0.0-1.0, tool_calls: [{{tool: 名称, status: success|fail}}] }} 对话内容 {dialogue} response llm_client.chat(prompt) return json.loads(response) def route_and_store(summary, user_id, session_id, db, vector_db): 分层路由并存储 if summary[event_type] preference: # 写入 semantic memory db.execute( INSERT INTO semantic_memory (user_id, category, content, confidence) VALUES (%s, %s, %s, %s) , (user_id, preference, summary[summary], summary[importance])) else: # 写入 episodic memory memory_id db.execute( INSERT INTO episodic_memory (user_id, session_id, event_type, summary, entities, tool_calls, importance) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id , (user_id, session_id, summary[event_type], summary[summary], json.dumps(summary[entities]), json.dumps(summary[tool_calls]), summary[importance])) # 向量化存储 embedding embed_model.encode(summary[summary]) vector_db.upsert( collection_nameagent_memory, points[{ id: str(memory_id), vector: embedding.tolist(), payload: { user_id: user_id, event_type: summary[event_type], importance: summary[importance], created_at: datetime.now().isoformat() } }] )4.4 检索流程的代码实现检索的核心是多路召回 重排序。我写了一个retrieve_memories函数把三路结果合并后做重排。def retrieve_memories(query, user_id, top_k5, dbNone, vector_dbNone): 多路召回 重排序 # 第一路向量相似度召回 query_embedding embed_model.encode(query) vector_results vector_db.search( collection_nameagent_memory, query_vectorquery_embedding.tolist(), query_filter{user_id: user_id}, limit20 ) # 第二路结构化条件召回最近7天高重要性记忆 structured_results db.fetchall( SELECT id, summary, importance, created_at FROM episodic_memory WHERE user_id %s AND created_at NOW() - INTERVAL 7 days AND importance 0.6 ORDER BY importance DESC LIMIT 10 , (user_id,)) # 第三路时间衰减召回最近24小时所有记忆 recent_results db.fetchall( SELECT id, summary, importance, created_at FROM episodic_memory WHERE user_id %s AND created_at NOW() - INTERVAL 24 hours ORDER BY created_at DESC LIMIT 10 , (user_id,)) # 合并去重 all_candidates merge_and_dedup(vector_results, structured_results, recent_results) # 重排序用 LLM 做相关性打分 reranked rerank_with_llm(query, all_candidates) return reranked[:top_k] def rerank_with_llm(query, candidates): 用 LLM 对候选记忆做相关性打分 prompt f当前查询{query} 请对以下候选记忆按相关性从高到低排序只输出 ID 列表 {json.dumps([{id: c[id], summary: c[summary]} for c in candidates], ensure_asciiFalse)} response llm_client.chat(prompt) ranked_ids json.loads(response) id_to_memory {c[id]: c for c in candidates} return [id_to_memory[mid] for mid in ranked_ids if mid in id_to_memory]提示重排序用 LLM 虽然效果好但会增加延迟。如果对响应时间敏感可以用一个小的 cross-encoder 模型替代速度更快效果也能接受。4.5 MCP 服务端的注册与调用MCP 服务端用 Python 的mcp库来实现核心是定义工具描述和处理函数。from mcp.server import Server, NotificationOptions from mcp.server.models import InitializationOptions import mcp.server.stdio import mcp.types as types server Server(hindsight-memory) server.list_tools() async def handle_list_tools(): return [ types.Tool( namememory_search, description检索 Agent 历史记忆返回与查询最相关的记忆条目, inputSchema{ type: object, properties: { query: {type: string, description: 检索查询}, top_k: {type: integer, default: 5}, user_id: {type: string} }, required: [query, user_id] } ), types.Tool( namememory_write, description写入一条新的 Agent 记忆, inputSchema{ type: object, properties: { content: {type: string}, memory_type: {type: string, enum: [episodic, semantic]}, user_id: {type: string}, metadata: {type: object} }, required: [content, memory_type, user_id] } ) ] server.call_tool() async def handle_call_tool(name: str, arguments: dict): if name memory_search: results retrieve_memories( queryarguments[query], user_idarguments[user_id], top_karguments.get(top_k, 5) ) return [types.TextContent(typetext, textjson.dumps(results, ensure_asciiFalse))] elif name memory_write: memory_id write_memory( contentarguments[content], memory_typearguments[memory_type], user_idarguments[user_id], metadataarguments.get(metadata, {}) ) return [types.TextContent(typetext, textfMemory written: {memory_id})]启动 MCP 服务端之后在支持 MCP 的客户端比如 Claude Desktop、Cursor、或者你自己写的 Agent 框架里配置好服务地址Agent 就能自动发现这些工具并调用了。5. 常见问题与排查技巧实录5.1 记忆检索返回不相关结果怎么办这是最常见的问题。我排查下来原因通常出在三个地方第一embedding 模型选得不对。不同 embedding 模型对中文、英文、代码的语义理解能力差异很大。如果你的 Agent 主要处理中文对话用英文为主的 embedding 模型效果会打折扣。建议先用 MTEB 榜单上的中文模型做对比测试选一个在你的数据上表现最好的。第二摘要质量太差。如果写入时的摘要就是一堆废话检索时自然找不到有用信息。检查你的摘要 prompt确保它要求模型输出具体、可检索的内容而不是“用户进行了对话”这种空泛描述。第三重排序没做好。向量相似度高不等于语义相关。加一个重排序步骤用 LLM 或者 cross-encoder 做二次筛选效果提升非常明显。5.2 Docker 网络不通导致服务间调用失败Docker Compose 默认会创建一个内部网络服务之间用服务名作为主机名通信。但有几个坑服务启动顺序用depends_on只能保证启动顺序不能保证服务就绪。要用healthcheckcondition: service_healthy来确保依赖服务真正可用之后再启动。端口映射 vs 内部通信ports映射是给宿主机访问用的容器之间通信直接用服务名和容器内部端口不要走宿主机端口。DNS 解析如果服务名解析失败检查是否在同一个 network 里。默认情况下 Compose 会创建一个 network所有服务都在里面但如果你手动指定了 network要确保所有服务都加入了。5.3 向量数据库性能瓶颈Qdrant 在数据量超过 100 万条之后检索延迟会明显上升。我的优化经验分 collection按用户 ID 或者按时间分 collection避免单 collection 过大。量化压缩开启 scalar quantization把 float32 向量压缩成 int8内存占用减少 75%检索速度提升 2 到 3 倍精度损失很小。索引参数调优hnsw_ef参数控制检索精度和速度的平衡默认值 128 可以调到 64 来提速如果精度不够再往上加。5.4 常见问题速查表问题现象可能原因排查方法解决方案检索结果不相关embedding 模型不匹配人工评估 top-10 结果换用中文优化模型检索结果不相关摘要质量差检查写入的 summary 字段优化摘要 prompt检索结果不相关缺少重排序对比有无重排序的效果加 LLM 或 cross-encoder 重排服务间调用超时依赖服务未就绪查看容器日志加 healthcheck 和重试向量检索慢数据量过大查看 collection 大小分 collection 量化记忆写入丢失触发条件太严格检查 should_write 逻辑放宽触发条件上下文超长检索返回太多统计注入的 token 数减少 top_k加摘要压缩Docker 启动失败端口冲突docker compose logs改端口或停掉占用进程5.5 几个我踩过的坑坑一忘记给向量数据库做持久化。第一次部署的时候没挂 volume容器一重启所有向量数据全没了。后来在 docker-compose 里加了qdrant_datavolume问题解决。坑二摘要 prompt 里没要求输出 JSON。早期版本让 LLM 自由输出摘要结果格式五花八门解析经常失败。后来强制要求 JSON 格式并在 prompt 里给了示例解析成功率从 70% 提升到 99%。坑三working memory 的 TTL 设得太短。一开始设了 10 分钟结果用户稍微思考一下回来继续对话时上下文就丢了。后来改成 2 小时体验好很多。这个值要根据实际场景调没有标准答案。坑四忽略了对记忆写入的去重。同一件事被反复写入检索时返回一堆重复内容。后来在写入前加了一个相似度检查如果和已有记忆的余弦相似度超过 0.95就跳过写入只更新访问时间。6. 记忆系统的扩展方向与个人体会这套架构跑通之后可以扩展的方向其实很多。比如跨 Agent 的记忆共享——多个 Agent 共用一套记忆系统A Agent 学到的经验 B Agent 也能用。再比如记忆的可视化——做一个面板让用户能看到 Agent 记住了什么、忘了什么方便调试和信任建立。还有记忆的版本控制——像 Git 一样管理记忆的变更历史出问题可以回滚。我个人的体会是Agent 记忆这件事工程实现只占三成七成在于对业务场景的理解。你得清楚你的 Agent 在什么场景下需要记住什么、什么时候需要想起来、什么时候该忘掉。这些判断没有通用答案只能在实际使用中不断调整。最后分享一个小技巧在开发阶段把每次检索的 query、返回的记忆、以及最终注入上下文的 token 数都打到日志里。上线之后定期 review 这些日志你会发现很多优化点——哪些记忆从来没被检索到、哪些 query 总是返回不相关结果、哪些记忆占着空间但毫无价值。这些观察比任何理论都管用。
返回列表