ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LLM Agent记忆系统实战:基于MCP与Docker的长期记忆与经验回溯

LLM Agent记忆系统实战:基于MCP与Docker的长期记忆与经验回溯 1. 从“hindsight”说起为什么Agent Memory值得单独拎出来做“hindsight”这个词本身挺有意思字面意思是“事后的洞察力”也就是我们常说的“后见之明”。放在LLM Agent的语境里它指向一个非常具体且要命的问题Agent能不能记住之前发生过什么并且在后续决策中真正用上这些记忆我接触过不少做Agent项目的团队大家一开始都把精力砸在工具调用、Prompt工程、工作流编排上等到系统跑了一段时间才发现真正让Agent表现拉开差距的往往不是模型本身有多强而是它有没有一套靠谱的记忆机制。一个没有记忆的Agent每次对话都是“初次见面”用户上一轮说过的偏好、约束、上下文下一轮就全丢了。这种体验放在任何实际业务场景里都是灾难。“hindsight”这个项目标题我理解它要解决的核心就是Agent的长期记忆与经验回溯。它不是一个简单的对话历史缓存而是一套让Agent能够“回头看”、从过往交互中提取有效信息、并在未来任务中主动调用的机制。结合热搜词里出现的agent memory、LLM、MCP、Docker这几个关键词可以基本判断这是一个围绕LLM Agent记忆系统展开的工程实践项目大概率涉及记忆的存储、检索、注入、更新这几个核心环节并且很可能通过MCP协议与外部工具链打通用Docker做环境隔离和部署。这篇文章适合谁看如果你正在做Agent相关的产品或者研究尤其是被“Agent记不住东西”这个问题困扰过那这篇内容应该能给你一些可以直接抄作业的思路。如果你刚接触LLM应用开发对Agent Memory还没有具体概念也没关系我会从最基础的设计逻辑讲起把每个环节的“为什么”说清楚。提示本文涉及的所有代码和配置均为示意性示例实际使用时需要根据你的模型服务、存储方案和网络环境做调整。2. Agent Memory的核心设计思路拆解2.1 为什么“记住”比“聪明”更难很多人有一个误区觉得只要模型够大、上下文窗口够长记忆问题就自然解决了。实际做过项目的人都知道这完全是两码事。上下文窗口再长你也不可能把几百轮对话全部塞进去token成本先不说模型对长上下文的注意力衰减是客观存在的。更关键的是原始对话历史不等于有效记忆。用户说了十句话可能只有一句是真正需要长期保留的偏好信息其余都是噪音。所以Agent Memory的第一个设计难点就是什么该记什么该忘什么该压缩什么该原样保留。这其实是一个信息生命周期管理的问题。我在实际项目中的做法是把记忆分成几个层次来处理工作记忆Working Memory当前任务执行过程中的临时状态比如正在处理的文件路径、中间计算结果。生命周期短任务结束就可以丢弃。情景记忆Episodic Memory具体某次交互的完整记录包括用户说了什么、Agent做了什么、结果如何。需要保留但可以压缩。语义记忆Semantic Memory从多次交互中提炼出来的通用知识或用户偏好比如“这个用户喜欢简洁的回答风格”。这是最高价值的记忆需要长期保留并持续更新。“hindsight”这个项目从标题的语义来看重点应该放在情景记忆的回溯与语义记忆的提炼上。也就是说它不只是存下来还要能“回头看”从过往经历中提取出对未来有用的东西。2.2 记忆的写入、检索与注入三个必须打通的环节一套完整的Agent Memory系统不管具体实现用什么技术栈本质上都要解决三个问题写入Write什么时候把什么信息写进记忆库是每轮对话都写还是任务结束后批量写写入的时候要不要做摘要和结构化我的经验是不要每轮都写那样会产生大量冗余。更好的做法是在任务边界或者关键决策点触发写入同时用LLM做一次信息抽取把非结构化的对话转成结构化的记忆条目。检索Retrieve当Agent需要回忆的时候怎么找到最相关的记忆最简单的做法是关键词匹配但效果很有限。现在主流方案是向量检索把记忆条目编码成向量用语义相似度来召回。但纯向量检索也有问题它容易忽略时间维度和重要性维度。所以实际系统中通常会做混合检索向量相似度 时间衰减 重要性权重三者加权排序。注入Inject检索到的记忆怎么塞回给LLM是直接拼在System Prompt里还是作为独立的上下文消息这里有个坑如果检索回来的记忆太多太杂反而会干扰模型对当前任务的注意力。我的做法是控制注入量每次最多注入3到5条最相关的记忆并且用明确的标记把它们和当前对话区分开。“hindsight”这个项目名暗示的“事后回溯”很可能就是在检索和注入这两个环节做了特别的优化。比如它可能不是简单地按相似度召回而是会结合当前任务的状态去回溯历史上类似任务的处理方式把“当时是怎么做的、结果怎么样”作为参考信息注入给Agent。2.3 为什么选MCP和Docker工程化的必然选择热搜词里出现了MCP和Docker这两个技术选型其实很能说明问题。MCPModel Context Protocol本质上是一套让LLM应用与外部工具、数据源标准化对接的协议。在没有MCP之前每接一个工具就要写一套适配代码维护成本极高。MCP把这件事标准化了工具提供方实现一个MCP ServerAgent侧实现一个MCP Client双方通过统一的协议通信。对于Agent Memory系统来说MCP的价值在于把记忆存储和检索能力做成一个独立的服务任何支持MCP的Agent都可以接入不用关心底层用的是向量数据库还是图数据库。Docker的引入则解决了环境一致性和部署隔离的问题。Agent Memory系统通常依赖多个组件向量数据库、嵌入模型服务、可能还有图数据库或者关系型数据库。这些组件版本不一、依赖复杂直接在宿主机上装很容易出现“在我机器上能跑”的问题。用Docker Compose把整个记忆系统打包成一组容器一键启动环境隔离迁移也方便。注意如果你在Windows上跑Docker Desktop可能会遇到“Virtualization support not detected”的报错。这不是Docker本身的问题而是BIOS里的虚拟化支持没打开。进BIOS找到Intel VT-x或者AMD-V选项启用之后重启即可。另外Windows家庭版需要额外配置WSL2后端这个后面会细说。3. 核心细节解析与实操要点3.1 记忆条目的结构化设计别把原始对话直接往里塞我见过不少项目记忆库里面存的就是原始对话的JSON检索的时候把整段对话拿出来做相似度匹配。这种做法在Demo阶段能跑通但到了真实场景就会暴露两个问题一是检索精度差因为一段对话里可能只有一两句话是真正相关的二是注入效率低把整段对话塞给LLMtoken浪费严重。正确的做法是把记忆条目结构化。一个设计良好的记忆条目至少应该包含以下字段字段名类型说明memory_idstring唯一标识建议用UUIDcontentstring记忆的核心内容经过摘要和结构化处理memory_typeenum工作记忆/情景记忆/语义记忆embeddingvector内容的向量表示用于语义检索importancefloat重要性评分0到1之间created_attimestamp创建时间last_accessedtimestamp最后访问时间用于时间衰减access_countint访问次数高频访问的记忆权重更高source_contextstring来源上下文比如哪次任务、哪个用户tagslist标签用于分类和过滤这个结构看起来简单但每个字段都有它的用处。importance字段可以在写入时由LLM打分也可以在后续根据访问频率动态调整。last_accessed和access_count结合起来可以实现记忆的遗忘曲线长时间不被访问的记忆检索时的权重会逐渐降低模拟人类记忆的自然衰减。“hindsight”这个项目如果要在记忆回溯上做文章source_context和tags这两个字段就特别关键。因为回溯的前提是你能按“当时是什么场景”来筛选记忆而不是只靠语义相似度。3.2 写入策略什么时候写、写什么、怎么写写入策略直接决定了记忆库的质量。我的经验是采用事件驱动 定期整理的混合模式。事件驱动的写入触发点包括任务完成或失败时写入一条情景记忆记录任务目标、执行过程摘要、最终结果用户明确表达偏好或约束时写入一条语义记忆Agent做出关键决策时写入一条工作记忆用于当前任务的后续步骤定期整理则是每天或每周跑一次批处理任务做三件事合并重复记忆、提升高频访问记忆的importance、归档或删除低价值记忆。写入时的内容处理也很关键。不要直接把原始对话扔进去而是用LLM做一次信息抽取和摘要。比如一段用户和Agent关于“帮我订机票”的对话抽取出来的记忆条目应该是{ content: 用户偏好靠窗座位不接受红眼航班常飞航线为北京到上海, memory_type: semantic, importance: 0.85, tags: [用户偏好, 出行, 机票], source_context: 2024-01-15 机票预订任务 }而不是把整段对话原封不动存进去。这个抽取过程可以用一个专门的Prompt来完成让LLM输出结构化的JSON。实操心得写入时的摘要Prompt里一定要加一条约束——“只保留对未来交互有参考价值的信息”。我试过不加这条约束结果记忆库里塞满了“用户说了你好”“Agent回复了你好”这种毫无价值的条目检索时噪音极大。3.3 检索策略混合排序才是王道纯向量检索的问题在于它只考虑语义相似度不考虑其他维度。但在实际场景中一条记忆的价值不仅仅取决于它和当前query的语义相似度还取决于它有多新、有多重要、被访问过多少次。我常用的混合排序公式是这样的final_score w1 * semantic_similarity w2 * importance w3 * recency_decay w4 * access_frequency其中recency_decay可以用指数衰减函数计算recency_decay exp(-lambda * (now - last_accessed))lambda是衰减系数控制记忆“变旧”的速度。对于工作记忆lambda可以设大一点比如0.1让它快速衰减对于语义记忆lambda设小一点比如0.01让它长期保留。权重的设置需要根据具体场景调。我的经验值是语义相似度占0.5重要性占0.2时间衰减占0.2访问频率占0.1。但这个不是固定的比如在客服场景中用户偏好的重要性可能更高可以把importance的权重提到0.3。检索的时候还有一个技巧先过滤再排序。先用tags或者source_context做粗筛把候选集缩小到一定范围再做向量检索和混合排序。这样既能提高精度又能降低计算开销。3.4 注入策略少即是多标记要清晰检索回来的记忆怎么注入给LLM这件事比很多人想象的要微妙。我踩过的坑是一开始觉得检索回来的记忆越多越好结果注入了十几条记忆模型反而被干扰了回答质量下降。后来我总结的原则是每次注入不超过5条按final_score排序取Top-K并且用明确的标记把记忆和当前对话区分开。比如[相关记忆] 1. 用户偏好靠窗座位不接受红眼航班重要性高来源2024-01-15 2. 用户常飞航线为北京到上海重要性中来源2024-01-10 [/相关记忆] [当前对话] 用户帮我看看下周去上海的航班这种标记方式让模型清楚地知道哪些是历史记忆、哪些是当前输入避免混淆。另外注入的记忆内容要尽量简洁如果原始记忆条目太长可以在注入前再做一次压缩。注意注入位置也很重要。我的经验是把记忆放在System Prompt之后、当前对话之前效果最好。放在System Prompt里面容易和系统指令混淆放在对话后面模型又容易忽略。4. 实操过程与核心环节实现4.1 环境准备Docker Compose一键拉起记忆服务假设我们要搭建一个最小可用的Agent Memory系统核心组件包括向量数据库用Qdrant轻量且API友好嵌入模型服务用本地部署的BGE-M3也可以用API记忆管理服务自己写的FastAPI应用实现写入、检索、注入逻辑MCP Server把记忆管理服务包装成MCP工具用Docker Compose编排docker-compose.yml大概长这样version: 3.8 services: qdrant: image: qdrant/qdrant:latest ports: - 6333:6333 volumes: - ./qdrant_data:/qdrant/storage restart: unless-stopped embedding: image: ghcr.io/huggingface/text-embeddings-inference:latest ports: - 8080:80 volumes: - ./model_cache:/data command: --model-id BAAI/bge-m3 --port 80 restart: unless-stopped memory-service: build: ./memory-service ports: - 8000:8000 environment: - QDRANT_HOSTqdrant - QDRANT_PORT6333 - EMBEDDING_URLhttp://embedding:80 depends_on: - qdrant - embedding restart: unless-stopped mcp-server: build: ./mcp-server ports: - 8001:8001 environment: - MEMORY_SERVICE_URLhttp://memory-service:8000 depends_on: - memory-service restart: unless-stopped这个编排文件里每个服务的职责很清晰Qdrant负责向量存储和检索embedding服务负责把文本转成向量memory-service是核心业务逻辑mcp-server是对外暴露的标准化接口。启动命令很简单docker compose up -d第一次启动会拉取镜像和下载模型需要等几分钟。启动完成后可以用docker compose ps检查各容器状态。实操心得如果你在国内网络环境下拉取镜像比较慢可以配置Docker镜像加速器。另外BGE-M3模型文件大概2GB左右第一次下载会比较久建议提前把模型缓存目录挂载出来避免每次重建容器都重新下载。4.2 记忆写入的代码实现memory-service的核心写入逻辑用Python写大概是这样import uuid from datetime import datetime from qdrant_client import QdrantClient from qdrant_client.models import PointStruct, VectorParams, Distance import httpx class MemoryWriter: def __init__(self, qdrant_host, qdrant_port, embedding_url): self.client QdrantClient(hostqdrant_host, portqdrant_port) self.embedding_url embedding_url self._ensure_collection() def _ensure_collection(self): collections self.client.get_collections().collections if not any(c.name agent_memory for c in collections): self.client.create_collection( collection_nameagent_memory, vectors_configVectorParams(size1024, distanceDistance.COSINE) ) def _get_embedding(self, text): resp httpx.post( f{self.embedding_url}/embed, json{inputs: text} ) return resp.json()[0] def write(self, content, memory_type, importance, tags, source_context): memory_id str(uuid.uuid4()) embedding self._get_embedding(content) now datetime.utcnow().isoformat() point PointStruct( idmemory_id, vectorembedding, payload{ content: content, memory_type: memory_type, importance: importance, tags: tags, source_context: source_context, created_at: now, last_accessed: now, access_count: 0 } ) self.client.upsert( collection_nameagent_memory, points[point] ) return memory_id这段代码的关键点在于写入时同步生成embedding把结构化字段和向量一起存进Qdrant。size1024是因为BGE-M3的输出维度是1024如果你用其他嵌入模型这个值要相应调整。4.3 混合检索的实现细节检索部分比写入要复杂一些因为要做混合排序。核心代码如下import math from datetime import datetime class MemoryRetriever: def __init__(self, qdrant_client, embedding_url): self.client qdrant_client self.embedding_url embedding_url def retrieve(self, query, top_k5, memory_typeNone, tagsNone): query_vector self._get_embedding(query) # 构建过滤条件 filter_conditions {} if memory_type: filter_conditions[memory_type] memory_type if tags: filter_conditions[tags] tags # 向量检索多召回一些候选 candidates self.client.search( collection_nameagent_memory, query_vectorquery_vector, limittop_k * 4, query_filterfilter_conditions if filter_conditions else None ) # 混合排序 now datetime.utcnow() scored [] for hit in candidates: payload hit.payload semantic_sim hit.score importance payload.get(importance, 0.5) last_accessed datetime.fromisoformat(payload[last_accessed]) days_since (now - last_accessed).days recency math.exp(-0.05 * days_since) access_count payload.get(access_count, 0) frequency min(access_count / 10.0, 1.0) final_score ( 0.5 * semantic_sim 0.2 * importance 0.2 * recency 0.1 * frequency ) scored.append((final_score, hit)) scored.sort(keylambda x: x[0], reverseTrue) return [hit for _, hit in scored[:top_k]]这里有几个细节值得展开说。第一向量检索时limit设成top_k * 4是为了给后面的混合排序留出足够的候选集。如果只召回top_k条混合排序就没什么发挥空间了。第二recency的计算用的是天数差衰减系数0.05意味着大约14天衰减一半这个值可以根据你的场景调整。第三frequency做了归一化访问10次以上就按满分算避免高频记忆过度主导排序。4.4 MCP Server的封装把记忆服务包装成MCP Server核心是定义好工具描述和参数schema。用Python的MCP SDK大概是这样from mcp.server import Server, NotificationOptions from mcp.server.models import InitializationOptions import mcp.server.stdio import mcp.types as types server Server(agent-memory) server.list_tools() async def handle_list_tools(): return [ types.Tool( namewrite_memory, description写入一条Agent记忆, inputSchema{ type: object, properties: { content: {type: string, description: 记忆内容}, memory_type: {type: string, enum: [working, episodic, semantic]}, importance: {type: number, minimum: 0, maximum: 1}, tags: {type: array, items: {type: string}}, source_context: {type: string} }, required: [content, memory_type] } ), types.Tool( nameretrieve_memory, description检索相关记忆, inputSchema{ type: object, properties: { query: {type: string, description: 检索查询}, top_k: {type: integer, default: 5}, memory_type: {type: string}, tags: {type: array, items: {type: string}} }, required: [query] } ) ] server.call_tool() async def handle_call_tool(name, arguments): if name write_memory: memory_id writer.write(**arguments) return [types.TextContent(typetext, textf记忆已写入ID: {memory_id})] elif name retrieve_memory: results retriever.retrieve(**arguments) formatted format_memories(results) return [types.TextContent(typetext, textformatted)]这样封装之后任何支持MCP的Agent框架都可以通过标准协议调用记忆的写入和检索不需要关心底层实现。这就是MCP的价值所在把能力标准化让集成成本降到最低。5. 常见问题与排查技巧实录5.1 记忆检索不准确怎么办这是最常见的问题。表现是明明记忆库里有相关信息但检索的时候就是召不回来或者召回来的都是不相关的。排查思路按优先级来第一步检查embedding质量。把query和记忆条目的embedding分别算出来看余弦相似度。如果相关条目的相似度低于0.6说明embedding模型可能不适合你的领域。BGE-M3在通用中文场景下表现不错但如果你是垂直领域比如医疗、法律可能需要微调或者换用领域适配的模型。第二步检查记忆条目的内容质量。如果写入时摘要做得不好记忆条目本身信息量不足检索自然不准。我遇到过一种情况写入时把“用户说不喜欢红色”摘要成了“用户有偏好”这种摘要丢失了关键信息检索时根本匹配不上“颜色偏好”这样的query。第三步调整混合排序的权重。如果语义相似度没问题但排序靠后可能是importance或者recency的权重太高把相关但较旧的记忆压下去了。可以试着把semantic_similarity的权重从0.5提到0.7。第四步检查过滤条件。如果检索时带了memory_type或者tags过滤可能把相关记忆过滤掉了。可以先去掉过滤条件看召回结果是否改善。5.2 Docker环境下的网络问题Docker Compose编排多个服务时网络配置是最容易出问题的地方。常见症状是memory-service启动时报错“Connection refused”连不上Qdrant或者embedding服务。根本原因通常是服务间的网络通信没有走Docker内部网络。在Compose文件里各服务默认在同一个bridge网络中可以用服务名作为hostname互相访问。比如memory-service里配置的QDRANT_HOSTqdrant这个qdrant就是Compose文件里定义的服务名Docker会自动做DNS解析。如果你在memory-service的代码里写了localhost:6333那就错了因为localhost在容器内部指向的是容器自己不是宿主机也不是其他容器。正确的做法就是用服务名。另一个常见问题是端口映射冲突。比如宿主机上已经有一个服务占了6333端口Qdrant容器启动时就会报端口绑定失败。解决办法是改宿主机的映射端口比如6334:6333然后外部访问用6334容器内部通信还是用6333。实操心得排查Docker网络问题时docker compose logs service_name看日志是最快的。另外可以用docker exec -it container_id sh进容器内部用ping或者curl测试网络连通性。如果容器里没有这些工具可以临时装一个或者用docker compose exec直接在服务里执行命令。5.3 记忆膨胀导致检索变慢系统跑了一段时间之后记忆库越来越大检索延迟从几十毫秒涨到几百毫秒甚至几秒。这个问题不处理的话用户体验会急剧下降。解决思路有三个层面层面一控制写入量。不是所有对话都值得写入记忆。我通常会在写入前加一个判断如果这条记忆的importance低于0.3直接丢弃。这个阈值可以根据实际情况调。层面二定期归档和清理。每周跑一次批处理把超过一定时间比如90天且access_count为0的工作记忆删除把低价值的的情景记忆压缩成一条摘要。语义记忆一般不删但可以合并重复项。层面三优化索引结构。Qdrant支持HNSW索引默认参数下召回率和速度的平衡还不错。如果数据量特别大百万级以上可以调整m和ef_construct参数。另外给tags字段建payload索引可以加速过滤查询。问题现象可能原因排查方法解决方案检索召回不相关embedding质量差计算query和记忆的余弦相似度换模型或微调检索召回不全过滤条件过严去掉过滤条件重试放宽tags或memory_type排序不合理权重配置不当打印各维度得分调整混合排序权重检索延迟高记忆库过大查看collection大小清理归档优化索引写入失败向量维度不匹配检查embedding输出维度调整collection配置5.4 MCP连接失败的排查MCP Server启动后Agent侧连不上或者调用工具时报schema错误。这类问题通常出在协议版本或者参数格式上。首先确认MCP Server的传输方式。如果是stdio方式Agent侧需要通过子进程启动Server确保启动命令和路径正确。如果是SSE或者WebSocket方式检查端口和URL是否正确。其次检查工具定义的inputSchema。MCP对schema的格式要求比较严格必须是合法的JSON Schema。常见错误包括required字段里的属性名和properties里定义的不一致、类型定义用了MCP不支持的格式、枚举值拼写错误等。如果报错信息是“provider rejected the request schema or tool payload”大概率是schema格式问题。可以用在线的JSON Schema验证工具先验证一遍确保格式合法。注意不同Agent框架对MCP的支持程度不一样。有些框架只支持stdio有些只支持SSE。在选型之前先确认你的Agent框架支持哪种传输方式避免白忙活。6. 记忆系统的持续优化与扩展方向6.1 从“被动检索”到“主动回忆”目前大多数Agent Memory系统都是被动检索Agent需要的时候才去查记忆。但“hindsight”这个词暗示的是一种更主动的机制Agent应该能够在合适的时机主动回忆起相关的过往经验而不是等外部触发。实现主动回忆的一个思路是在任务规划阶段就做记忆预取。当Agent接收到一个新任务时先用任务描述去检索相关记忆把结果作为规划阶段的参考信息。这样Agent在制定执行计划时就已经考虑了历史经验而不是执行到一半才想起来去查。另一个思路是基于上下文的自动提醒。当Agent的当前对话内容与某条记忆的tags高度匹配时系统自动把这条记忆推送到Agent的上下文中不需要Agent显式调用检索工具。这需要在对话流中加一个轻量的匹配逻辑可以用规则也可以用小型分类模型。6.2 记忆的冲突处理与版本管理当用户偏好发生变化时新旧记忆可能冲突。比如用户之前说“喜欢简洁回答”后来又说“希望详细解释”这两条记忆同时存在检索时可能都召回来让Agent无所适从。处理冲突的核心是时间优先 显式覆盖。新记忆写入时如果检测到与旧记忆冲突可以把旧记忆标记为“已过期”或者降低其importance。更优雅的做法是引入版本管理每条记忆有一个version字段新版本写入时旧版本自动失效但保留历史记录以备回溯。检测冲突可以用LLM来做把新记忆和检索到的相似旧记忆一起给LLM让它判断是否冲突、是否需要更新。这个判断可以异步做不阻塞主流程。6.3 多Agent场景下的记忆共享单个Agent的记忆系统相对简单但多Agent协作时记忆的共享和隔离就变成一个复杂问题。哪些记忆应该共享哪些应该隔离共享的记忆如何保证一致性我的经验是采用分层记忆池的设计每个Agent有自己私有的工作记忆和情景记忆同时有一个共享的语义记忆池。私有记忆只有Agent自己能访问共享记忆所有Agent都可以读写。写入共享记忆时需要加锁或者用乐观并发控制避免冲突。共享记忆的检索需要考虑Agent的身份和权限。比如Agent A不应该看到Agent B的私有情景记忆但可以看到从B的经验中提炼出来的语义记忆前提是B允许共享。这套机制在“hindsight”这类项目中应该是一个重要的扩展方向。随着Agent系统越来越复杂单Agent记忆会逐渐向多Agent记忆网络演进记忆的共享、隔离、权限、一致性都会成为核心问题。6.4 评估记忆系统的效果最后说一个容易被忽略的问题怎么知道你的记忆系统好不好用不能只靠感觉需要有一套评估指标。我常用的指标包括检索命中率在测试集中相关记忆被召回的比例检索精确率召回的记忆中真正相关的比例注入有效率注入的记忆中被Agent实际使用的比例可以通过观察Agent的回答是否引用了记忆内容来判断任务成功率提升有记忆系统 vs 无记忆系统任务成功率的差异token开销记忆检索和注入带来的额外token消耗这些指标不需要每次都全跑但在系统迭代时跑一遍能帮你判断改动是正向还是负向的。我自己的经验是检索命中率提升10%任务成功率大概能提升5%到8%这个投入产出比是值得的。实操心得评估集的建设很关键。我建议从真实业务日志中采样一批任务人工标注每个任务需要哪些历史记忆然后用这批数据来测检索效果。标注100条左右就能看出趋势了不需要一开始就搞几千条。这套记忆系统的搭建和调优我前前后后花了大概两个月时间中间踩了不少坑也推翻过好几版设计。最大的体会是不要追求一步到位先跑通最小闭环再逐步优化。一开始用最简单的向量检索 固定权重排序就能跑起来等业务量上来了再考虑混合排序、记忆衰减、冲突处理这些高级特性。先把写入和检索这两个环节做扎实后面的扩展都是水到渠成的事。
返回列表