
1. 从上下文窗口说起Agent 记忆问题的根源在哪1.1 上下文窗口到底是个什么东西先把概念说清楚。大模型的上下文窗口本质上就是模型一次推理能“看到”的 token 总量上限。你可以把它想象成一张办公桌的桌面面积——桌面就这么大你摊开的文件越多能同时看到的就越多但一旦超过桌面面积旧文件就得被推到地上。这个“桌面面积”在早期模型里只有 4K token后来涨到 32K、128K现在头部模型已经能做到 200K 甚至 1M。数字看着很唬人但实际用起来你会发现一个残酷的事实窗口大不等于记得住。我实测过不少场景当对话轮次超过 30 轮、工具调用返回结果累计超过 50K token 之后模型对早期关键信息的召回率会明显下降。这不是模型“笨”而是注意力机制在长序列上的固有衰减。所以当有人问“大模型上下文窗口用完了怎么办”问题的本质不是“窗口不够大”而是我们一直在用错误的方式使用窗口。把所有历史对话、所有工具返回、所有中间推理步骤一股脑塞进上下文这本身就是一种偷懒的做法。1.2 Agent 场景下记忆压力的三个来源普通聊天机器人的记忆压力其实不大用户问一句答一句上下文增长是线性的。但 Agent 不一样Agent 的记忆压力来自三个方向第一工具调用的返回膨胀。一个 Agent 调用搜索工具返回 10 条结果每条 500 字一次就是 5000 字。如果 Agent 在一个任务里调用了 8 次工具光工具返回就 4 万字。这些内容大部分是一次性的用完就该扔但很多框架默认全留在上下文里。第二多轮推理的中间状态。Agent 做规划、反思、重试的时候会产生大量“思考草稿”。这些草稿对当前决策有用但对后续轮次往往是噪音。我见过一个 ReAct 风格的 Agent单次任务跑了 40 多轮上下文里塞满了“Thought: 我需要先...”“Observation: 上一步返回了...”真正有用的结论被淹没在中间过程里。第三跨会话的长期记忆需求。用户昨天让 Agent 处理过一个项目今天继续Agent 需要记得昨天的关键决策。这部分记忆不能靠上下文窗口扛必须外置。理解了这三个来源你就能明白为什么单纯“换个更大窗口的模型”解决不了问题。窗口再大也架不住无节制的堆积而且窗口越大推理成本和延迟越高这是硬约束。1.3 记忆分层把“记什么”和“记多久”分开设计我的做法是把 Agent 记忆分成三层对应不同的存储介质和生命周期记忆层级存储位置生命周期典型内容工作记忆上下文窗口单轮任务当前推理链、最近工具返回会话记忆外部缓存/摘要单次会话对话摘要、关键决策点长期记忆向量库/结构化存储跨会话用户偏好、项目知识、历史结论这个分层不是拍脑袋定的而是对应了三种不同的访问频率和精度要求。工作记忆要求毫秒级访问、高精度会话记忆要求秒级访问、中等精度长期记忆可以接受百毫秒级检索、低精度但高召回。注意很多团队一上来就搞向量数据库做长期记忆结果发现 Agent 连当前任务都跑不明白。顺序反了。先把工作记忆管好再谈长期记忆。2. 上下文窗口的精细化管理别让 token 白白烧掉2.1 工具返回结果的裁剪策略工具返回是上下文膨胀的头号元凶。我的处理原则是工具返回进上下文之前必须先过一道“摘要闸门”。具体怎么做以搜索工具为例原始返回可能是 10 条结果、每条包含标题、URL、摘要、正文片段。直接塞进去就是几千 token。我的做法是在工具层就做一次过滤只保留与当前查询最相关的前 3 条每条结果只保留标题和 200 字以内的摘要正文片段截断如果 Agent 后续需要详情再单独发起一次“获取详情”的工具调用。这样一次搜索的上下文占用从 5000 token 降到 800 token 左右压缩比超过 80%。代价是 Agent 可能需要多调用一次工具但换来的是上下文能撑更多轮次。代码层面这个逻辑应该封装在工具适配器里而不是让 Agent 自己去裁剪。我见过有人在 prompt 里写“请只保留最重要的信息”这种靠模型自觉的做法极不稳定。能代码做的别交给模型。def truncate_tool_result(raw_result, max_items3, max_chars200): items raw_result.get(items, [])[:max_items] truncated [] for item in items: truncated.append({ title: item.get(title, ), snippet: item.get(snippet, )[:max_chars] }) return truncated2.2 对话历史的滚动摘要机制多轮对话里历史消息不能无限保留。我的方案是“滑动窗口 滚动摘要”保留最近 N 轮完整对话N 一般取 5 到 8更早的对话压缩成一段摘要摘要随新对话不断更新摘要里只保留三类信息用户明确表达的偏好、已达成的结论、未完成的待办。这里有个坑摘要本身也会膨胀。如果不控制摘要写到最后比原文还长。我的做法是给摘要设一个硬上限比如 500 字每次更新时如果超过上限就让模型做二次压缩只保留最核心的几条。实测下来这套机制能让一个 50 轮的对话上下文占用稳定在 8K token 以内而关键信息召回率保持在 90% 以上。2.3 中间推理过程的“用完即弃”Agent 的思考草稿Thought和工具观察Observation是典型的“过程性内容”。它们在当前决策步有用但决策完成后就是垃圾。我的处理方式是在每一轮推理结束后判断这一步的结论是否已经沉淀到最终答案或摘要里。如果是就把这一步的详细过程从上下文中移除只留一句“已完成 X 操作结论是 Y”。这个操作听起来激进但效果很好。一个原本 40 轮、上下文 60K token 的任务经过过程清理后上下文能压到 15K 以内而且模型后续决策质量没有下降——因为它本来就不需要看那些已经完成的中间步骤。实操心得清理过程性内容时一定要保留“失败重试”的记录。模型需要知道哪些路走不通避免重复踩坑。成功路径可以精简失败路径要留痕。3. MCP 登场工具接入的标准化为什么重要3.1 MCP 到底是什么解决什么问题MCP 全称 Model Context Protocol是一套让模型和外部工具、数据源之间标准化通信的协议。你可以把它理解成“AI 世界的 USB-C 接口”——以前每个工具都要写一套适配代码现在只要工具实现了 MCP 服务端任何支持 MCP 的 Agent 都能直接接入。在 MCP 出现之前Agent 接工具是这样的搜索工具写一个适配器数据库写一个适配器文件系统写一个适配器每个适配器的参数格式、返回格式、错误处理都不一样。Agent 框架里堆满了胶水代码换个模型或换个框架全部重写。MCP 把这些统一了。它定义了三种核心能力Tools工具模型可以调用的函数有明确的输入 schema 和输出格式Resources资源模型可以读取的数据类似文件或数据库记录Prompts提示模板预定义的提示模板方便复用。这个设计的好处是工具提供方只需要实现一次 MCP 服务端就能被所有 MCP 客户端也就是各种 Agent 框架使用。对 Agent 开发者来说接入新工具从“写适配器”变成“配置一个 MCP 服务地址”。3.2 MCP 与上下文窗口的协同关系很多人把 MCP 单纯当成“工具接入协议”忽略了它和上下文管理的深层联系。实际上MCP 的设计里有一个很关键的点工具的描述信息schema和调用结果是分开管理的。传统做法是把所有工具的 schema 都塞进 system prompt工具一多光 schema 就占几千 token。MCP 支持按需加载工具描述——Agent 先看到工具列表的简要信息决定要用某个工具时再拉取完整的参数 schema。这就省下了大量上下文空间。另外MCP 的 Resources 机制天然适合做记忆外置。你可以把长期记忆做成一个 MCP ResourceAgent 需要时通过标准接口读取而不是把所有记忆都塞进上下文。这就把“记忆”和“上下文”解耦了。我实测过一个场景一个接入了 12 个 MCP 工具的 Agent如果全量加载 schemasystem prompt 要 6000 token改成按需加载后降到 1500 token 左右。省下的 4500 token够多跑好几轮对话。3.3 自己动手搭一个最小 MCP 服务光说概念没意思直接上代码。下面是一个最小的 MCP 服务端示例提供一个“查询天气”的工具from mcp.server import Server from mcp.types import Tool, TextContent server Server(weather-server) server.list_tools() async def list_tools(): return [ Tool( nameget_weather, description查询指定城市的天气, inputSchema{ type: object, properties: { city: {type: string, description: 城市名} }, required: [city] } ) ] server.call_tool() async def call_tool(name: str, arguments: dict): if name get_weather: city arguments[city] # 实际项目中这里调用真实天气 API result f{city}今天晴气温 22-28 度 return [TextContent(typetext, textresult)]这个服务端跑起来之后任何 MCP 客户端都能通过标准协议调用get_weather。你不需要为每个 Agent 框架写适配代码这就是标准化的价值。注意MCP 服务端的工具描述要写得精准。描述模糊会导致模型选错工具或者传错参数。我一般会在 description 里写清楚“什么时候用这个工具”和“参数格式要求”这两点比功能描述本身更重要。4. 记忆与工具的配合让 Agent 真正“记得住、用得上”4.1 记忆写入的时机判断Agent 什么时候该把信息写入长期记忆这个问题没有标准答案但有几条经验规则用户明确表达偏好时“我习惯用 Python”“以后都用中文回复”立即写入任务产生可复用结论时“这个项目的 API 地址是 X”“数据库表结构是 Y”任务结束时写入用户纠正 Agent 错误时“不对应该是 Z”立即写入并标记为高优先级。反过来以下内容不要写入长期记忆一次性的工具返回、中间推理草稿、临时变量。这些写进去只会污染记忆库降低检索精度。我踩过的一个坑是早期把所有对话都存进向量库结果检索时经常召回无关内容Agent 被误导。后来改成“只存结论和偏好”检索准确率大幅提升。4.2 记忆检索的触发策略长期记忆不能每轮都检索那样太慢也太吵。我的做法是设置触发条件用户消息里出现“之前”“上次”“继续”等指代词时触发检索当前任务涉及的项目/实体在记忆库里有记录时触发检索每隔 N 轮N 取 5 左右做一次主动检索防止遗漏。检索回来的记忆也不是全塞进上下文而是先做相关性排序只取 top 3 条每条压缩到 100 字以内。这样一次记忆检索的上下文开销控制在 300 token 左右。4.3 工具调用与记忆的联动MCP 工具和记忆系统可以联动。比如一个“保存笔记”的 MCP 工具Agent 调用它时工具内部不仅保存内容还自动把内容索引到记忆库。下次 Agent 需要相关信息时通过记忆检索就能找到而不需要重新调用工具。这种联动的好处是Agent 不需要显式地区分“这是工具调用”还是“这是记忆操作”。对它来说都是通过 MCP 接口完成的一次操作。底层的存储和检索逻辑由工具实现方负责。我目前的做法是把记忆库本身也封装成一个 MCP 服务提供save_memory、search_memory、update_memory三个工具。这样 Agent 框架不需要内置记忆模块只要接入这个 MCP 服务就行。架构上更干净也更容易替换不同的记忆后端。5. 实战中踩过的坑与排查清单5.1 上下文突然爆掉的排查思路Agent 跑着跑着突然报“context length exceeded”这是最常见的问题。排查顺序如下先看工具返回。90% 的情况是某个工具返回了超大结果比如读取了一个大文件、搜索返回了完整网页。检查工具适配器有没有做截断。再看对话历史。如果对话轮次很多检查滚动摘要是否正常工作有没有摘要本身膨胀的情况。最后看 system prompt。工具 schema 是不是全量加载了有没有可以按需加载的部分我整理了一个速查表症状可能原因解决方向单轮任务就爆工具返回过大工具层截断多轮后爆历史未压缩滚动摘要接入新工具后爆schema 全量加载按需加载记忆检索后爆召回条数过多限制 top-k5.2 MCP 工具调用失败的常见原因MCP 工具调不通一般不是协议问题而是配置问题。我遇到过的原因包括服务端没启动、端口被占用、工具 schema 格式不合法、参数类型不匹配。排查时先用 MCP 客户端自带的调试工具手动调一次确认服务端正常。如果手动能调通但 Agent 调不通那就是 Agent 侧的配置问题检查工具是否注册成功、schema 是否正确解析。实操心得MCP 服务端的日志一定要开详细模式。工具调用失败时错误信息往往在服务端日志里客户端只显示一个笼统的失败。我习惯在开发阶段把服务端日志级别调到 DEBUG上线后再调回 INFO。5.3 记忆污染的处理记忆库用久了会出现“污染”过时的信息、错误的信息、重复的信息混在一起检索时召回质量下降。我的处理方式是定期做记忆清理每月跑一次脚本把超过 90 天未被检索过的记忆标记为“冷记忆”冷记忆不参与默认检索但保留可手动查询。同时做去重相似度超过 0.95 的记忆合并。另外记忆要支持“失效标记”。当用户说“之前那个方案不用了”Agent 应该把相关记忆标记为失效而不是删除。删除会丢失历史失效标记保留了可追溯性。6. 关于 Agent 记忆与工具的一些个人体会这套东西我断断续续折腾了大半年最大的体会是Agent 的记忆问题本质上是信息管理问题不是模型能力问题。很多人指望换个更大窗口的模型就解决了结果发现窗口大了垃圾信息也多了模型反而更容易被带偏。真正有效的做法是在信息进入上下文之前就做好筛选和压缩。工具返回要截断对话历史要摘要中间过程要清理长期记忆要外置。这四件事做好了一个 32K 窗口的模型也能跑出很稳的 Agent。MCP 的价值在于它把工具接入和记忆管理都标准化了。以前每个项目都要重新造轮子现在可以复用。我现在的做法是把常用的工具和记忆服务都做成 MCP 服务新项目直接接入开发效率提升很明显。最后分享一个小技巧调试 Agent 记忆问题时把每一轮的上下文 token 数打出来画成曲线。你会很直观地看到 token 是在哪一步膨胀的比看日志快得多。这个习惯帮我定位过好几次隐蔽的上下文泄漏问题。