
去年做客服Agent项目时我碰到过一次很抓狂的线上事故用户在第30轮对话后说“我刚才不是说过我房子的面积了吗”Agent回了一段语义含混的话然后自顾自地推荐起装修套餐。拉日志排查才发现模型没有抽风而是上下文窗口被撑满后最早几轮对话被粗暴丢弃用户之前明确说过的核心信息跟着一起丢了。这种“大模型上下文窗口用完了”的体验凡是做过Agent的人应该都熟悉官方文档把窗口写得很大100k、200k看着唬人可真跑起Agent来系统提示词、工具定义、工具返回结果、模型思考过程全在抢那点空间真正留给用户真实对话的往往不到一半。所以我现在做Agent规划时总会先把三个词想清楚Agent的记忆怎么做、工具调用怎么管、要不要上MCPModel Context Protocol。这篇文章就把我这一路的思考和实操完整写出来适合已经跑通Agent demo、正在为上下文膨胀和工具接入头疼的朋友参考。1. 上下文窗口为什么总是不够用一次Agent调用的token账本1.1 别被100k窗口骗了可用上下文是这样被瓜分的先打一个比方上下文窗口相当于一张工作台面。普通Chat产品里用户问一句模型答一句台面上只需要放最近几轮对话。但Agent完全不同它会往台面上摆一堆东西系统提示词、当前所有工具的JSON Schema定义、多轮工具调用返回的完整数据、模型自己的推理草稿。我拆过一个实际在跑的Agent把每次完整请求的日志抓出来逐一数token一次“用户提问 → Agent调两次工具 → 给出最终回复”的闭环光工具定义和返回结果就占了5000多个token。如果再叠加几轮历史对话一次请求轻松破1万token。有朋友会问模型上下文不是有200k吗注意上下文窗口是“全部占用”的预算池不是“可用对话长度”。我之前测算过一个典型请求的token分布组成部分示例内容大概token占用系统提示词角色设定、业务规则、输出约束800 ~ 3000工具定义全部工具的JSON Schema2000 ~ 20000工具返回结果数据库查询、API响应明细每轮800 ~ 8000对话历史用户消息、模型回复、推理过程随轮次线性增长模型输出最终回答200 ~ 1000这就能解释为什么窗口明明很大跑几个工具密集型任务就报警。Agent每次调用都要把工具定义重新注入连续三轮对话下来光工具定义和返回结果就反复占了几千token。真正留给“用户说了什么”的窗口空间远比你想象中少。1.2 无脑裁历史的代价上下文满了怎么办最常见的做法是谁占地方删谁。早期我项目里也这么干过直接取最后N轮对话塞进prompt结果就是开头那场事故。更隐蔽的问题是模型感知不到“历史被裁过”裁掉的信息恰好是某个工具调用的前置条件时后续推理就会建立在错误假设上。举个例子用户在第5轮说了“收货地址改成公司”Agent在第一次工具调用时正确使用了新地址。到了第18轮历史被裁剪后Agent再次查快递时拿到的是旧地址。这种错误非常难排查因为从模型视角看“旧地址”这个信息并不存在矛盾它根本不知道自己漏掉了什么。所以我后来把“如何管理上下文”拆成三个独立问题记忆怎么存、工具怎么接、每轮往窗口里放什么。下面先讲记忆系统。2. Agent记忆系统把“忘记”变成可控策略2.1 给记忆分三格工作记忆、语义记忆、情景记忆做Agent记忆第一件事是分类。我参考认知科学的思路把记忆分成三格工作记忆当前任务中必须保持在上下文里的临时状态比如本轮生成的报表参数、已经选好的文件路径。特点是生命周期短、必须实时可见。语义记忆用户偏好、项目背景、领域知识比如“用户偏好用顺丰发货”“库存低于10的SKU需要预警”。特点是生命周期长、不需要每轮都放在窗口里按需取用。情景记忆发生过的事件比如“昨天上午查过订单A”。特点是按时间线组织偶尔需要回溯。很多人做Agent记忆时只做了一种“用向量库存所有历史”这等于把工作台上的便签、仓库里的文件、日记本全混进一个抽屉。检索时相关度排序没法区分优先级经常把一周前的闲聊捞出来当权威信息还占了大量token。2.2 窗口加摘要最省token的短期记忆方案短期记忆处理的核心是“该退出上下文的先变成摘要再退出”。我给每个会话维护一个运行中的摘要当对话历史超过阈值比如3000 token时触发一次摘要更新。摘要不是简单一句“总结一下上面的对话”而是固定结构## 会话摘要 - 用户目标... - 已确认信息... - 已完成事项... - 未决事项/下一步... - 关键身份/偏好如已知...这样每次注入摘要只需要80到150个token相比保留3000 token的原始历史压缩比超过20倍。实测下来摘要结构化的价值远大于文笔优美。只要保证四个关键字段不丢细节丢一点完全不影响后续任务。2.3 长期记忆的正确姿势先提炼再入库长期记忆直接拿原始对话分块、embedding、做向量检索是很多教程的标准做法但实际效果一般。原始对话噪声大而且没有时效性概念。我现在的方法是在Agent完成关键任务节点后让模型通过一个小工具调用把值得记的内容提取出来按统一格式写入。记录字段包括实体用户/客户/项目名事实一句话或一个小JSON时间戳记录发生时间置信度和来源人工确认还是模型推断失效条件例如“该偏好仅在用户提到新地址后失效”检索时也不只算向量相似度还要做轻量规则过滤失效条件是否命中、是否距今超过30天降权、是否与当前实体冲突。这几个过滤规则能有效减少“旧信息当前提”的错误。2.4 记忆写入不要太勤快记忆写入是有成本的。如果每轮对话结束都调一次“记忆更新”工具token消耗是其次更麻烦的是会产生大量重复条目。我后来把写入策略改成两种触发方式结合节点触发用户主动修正信息、完成关键步骤、明确表达偏好。阈值触发每隔5到10轮检查会话摘要里是否有值得沉淀的新事实再写入。这个节奏能让记忆库保持干净也避免给Agent主流程增加太多额外调用。3. 工具接入是上下文囤积的第二源头3.1 工具定义写得越详细烧得越快原生Function Calling里工具定义就是一段JSON Schema模型每轮都要把所有工具定义读一遍。很多同学为了让模型理解得更准确把description写得像FAQ一样长结果工具一多光定义就超过了系统提示词。我见过一个极端案例40个工具定义加起来接近2万token。每次请求还没开始干活预算已经烧完模型反而因为选择过多频繁调错。合理的工具定义应该遵守“API文档思维”description只写边界、触发条件、关键限制不要写完整教程。parameters只写必填字段和少数高频选填字段。能用enum限制的选择尽量用enum不要开放自由字符串。一个10个工具的域工具定义总token尽量控制在2500以内。我在部分项目里还做过“短描述长说明”的双层设计短描述用于常驻上下文长说明通过MCP resource按需获取效果非常好。3.2 返回结果截断、摘要、分页三板斧工具返回结果是大头。一个“查库存”接口返回200行明细是常有的事直接把全部返回塞给模型一次调用就是5000 token左右。我总结了三板斧字段投影返回前只留下模型真正需要的字段把status描述、备注这类大字段去掉。结果分页列表接口返回前limit到20条并在返回里注明“共120条当前展示前20条如需更多可调用第2页”。自然语言摘要当模型只需要结论时在工具端先做一次摘要只返回“库存42件低于警戒线的有5个SKU分别是A01、A02……”。这些工程手段的本质是让工具结果从“原始数据”变成“决策材料”。做完以后我发现任务完成质量不降反升模型对噪声更少的数据更容易做出可靠判断上下文占用也大幅减少。3.3 动态工具选择别把所有工具都塞进窗口工具数量超过20个以后可以考虑给Agent加一层“工具路由”。具体做法是系统提示词里只放一个工具索引每行一句话比如“order域可查订单/改订单/开发票stock域可查库存/设置预警”。Agent每次调用前先用一个轻量分类函数根据任务关键词选出3到5个候选工具只把这几个工具的完整schema注入上下文。这个路由可以是一个规则函数也可以用一个小模型做分类。我实际用了正则加关键实体匹配已经能覆盖90%的场景。这一招在工具多的时候省掉的token肉眼可见而且因为模型不需要大海捞针工具选错率也下降。3.4 失败响应也要瘦身工具调用失败时直接把原始异常堆栈抛给模型模型通常会在错误信息里绕圈子白白消耗好几轮上下文。我有个习惯所有工具统一返回结构化错误包包含错误码、一句话原因、建议动作。例如{ code: ORDER_NOT_FOUND, message: 订单A001不存在, suggestion: 请检查订单号是否输入正确或调用list_orders查看有效订单 }配合最多两次重试策略上下文开销比满屏的原始error要小得多排查日志也更清晰。4. MCP把工具和记忆移出上下文4.1 没有MCP时工具接入有多乱MCP普及之前Agent接工具基本是“一家一个标准”OpenAI有function callingLangChain有tool装饰器自研框架还得造一套HTTP回调。换一个Agent框架所有工具实现都要推倒重写。更麻烦的是不管用什么框架工具定义最终都要渲染进系统提示词工具越多上下文越贵。这个问题一直没被真正解决。MCP是Anthropic提出的开放协议思路是把工具、资源、提示词都变成Agent外部的一个个“服务能力”通过标准接口按需调用而不是一股脑灌进上下文。现在Claude、OpenAI以及主流Agent框架基本都支持了MCP它逐步成了Agent生态里事实意义上的“标准插槽”。4.2 MCP的核心能力Tools、Resources、Prompts一个MCP Server对外暴露三类能力工具可执行的动作类似函数调用。Agent可以动态发现有哪些工具、每个工具的参数schema是什么再按需执行。资源可读取的数据比如用户手册、团队知识库、历史订单。Agent按需把resource内容拉取到上下文。提示词模板预置的“一键流程”比如“客户投诉处理流程”把标准步骤注入Agent。我常用的比喻是没有MCP时每个工具都是一台独立家电每台家电都要自己拉一根专用电源线有了MCP家电统一做成标准插头往标准插座上一插就能用。对Agent来说插座就是MCP Client它负责发现工具、传递参数、取回结果。4.3 一个最小可用的MCP Server直接给一段可以跑起来的Python示例用FastMCP最省事from mcp.server.fastmcp import FastMCP mcp FastMCP(order-server) mcp.tool() def query_order(order_id: str) - str: 按订单号查询订单状态和金额。参数order_id字符串例如A001。 # 这里接你的数据库或接口 return f订单{order_id}状态为已发货金额4200元。 if __name__ __main__: mcp.run(transportstdio)安装依赖并启动pip install mcp python order_server.pyAgent侧连上同一个MCP client后就能自动发现query_order并调用它。从上下文压力角度看最大的变化是Agent不再需要把order-server的全部工具定义提前写进系统提示词它先发一个list_tools协议请求拿到工具名和schema再按需调用。工具多到几百个时这种按需发现的优势非常明显。4.4 MCP真正缓解了哪部分上下文压力MCP解决的其实不是“token总量”问题而是“常驻上下文”问题。工具定义不再常驻系统提示词工具资源按需拉取工具返回结果该截断还是得自己截断。MCP相当于替你把“工具百科”搬到了台下Agent在台上只需要演好当前剧本需要道具时再让场务传上来。维度传统Function CallingMCP工具定义位置常驻在系统提示词协议动态发现工具范围通常是本地函数本地/远程服务多Agent复用需要重复实现一次部署多端接入资源/提示词模板自己写死resource/prompts标准能力跨框架兼容差好这一点很关键记忆系统同样可以做成MCP Server。比如把用户偏好、团队知识库作为resource暴露给AgentAgent查询长期记忆和调用订单工具用的是同一种协议。原来“记忆接入”也是各做各的现在统一成标准通道了。4.5 生态现状MCP已经不只是聊天工具的玩具现在MCP生态已经蔓延到各个方向远比很多人想象的广Cherry Studio这类客户端可以通过MCP工具把流式输出写到本地文件。Dify平台里有人做浏览器MCP让Agent能读取页面信息。有IDE插件通过MCP连接Oracle数据库做查询分析。逆向分析圈有人给IDA写MCP插件让大模型辅助分析二进制。设计工具Figma、蓝湖也有MCP通道编码Agent可以直接拉取设计稿信息。游戏引擎Unreal、工控软件TIA、Altium Designer这类专业软件也陆续出现了MCP接入。这些案例说明MCP的价值正在从“聊天工具接小工具”上升到“软件通过统一协议向Agent开放能力”。这也是为什么我把这篇文章的终点定在MCP它把Agent、记忆、工具之间那层糊在一起的边界重新划清了。5. 工程化落地从上下文管理到MCP的演进路径5.1 先记账给上下文做预算无论用不用MCP我建议先给上下文做预算。拿一个实际Agent任务举例组成预算上限系统提示词500 token会话摘要800 token动态工具定义3000 token工具返回结果2000 token模型推理与回复输出1500 token合计7800 token预算设计好后如果模型上下文是32k你就有大约24k token的空间留给多轮对话和长任务而不是一开始就被工具定义和历史吃光。预算表同时也是性能监控的基础每次请求把各部分token数量打进日志超过红线就报警并触发压缩策略。5.2 演进路线先记忆后工具再MCP我把演进路径总结成一句话先让记忆有序再让工具苗条最后让工具和记忆外置。第一步上下文记账和摘要机制。不管接不接MCP先把“往窗口里放什么”从拍脑袋变成预算管理。第二步动态工具选择、返回结果截断、分组schema。工具数量在20个以内时这些改造收益非常直接。第三步接入MCP。当工具跨系统复用、同一套工具要服务多个Agent时MCP的协议收益开始超过改造成本。我自己的判断标准是单机demo阶段用原生function calling完全够。一旦出现“同一个工具被多个Agent复用”“工具要在不同项目里反复对接”这两类诉求再上MCP不迟。5.3 实践里踩过的坑第一不要把100个工具全部堆进同一个MCP Server。Server适合按领域聚合订单域一个server、知识库一个server。一个server里挂太多工具会回到schema膨胀和选择过载的老路。第二注意MCP Server的部署形态。stdio方式适合本地和开发环境要处理好子进程的生命周期远程HTTP server要加认证与限流否则等于把内部工具暴露给整个网络环境。第三MCP调用是有延迟的。序列化、网络往返都要时间高频且极简单的工具比如字符串处理、日期转换用本地闭包函数更合适MCP更适合跨系统、跨语言、需要复用的工具。第四MCP的resource没有实时推送。数据源频繁更新时要在上游做变更事件标记或者让Agent按需轮询不要指望有push消息主动通知。5.4 一个可参考的轻量架构最后分享我目前比较顺手的参考架构用户输入 → 记忆管理层 → 工具路由 → Agent核心 → MCP Client → 各领域MCP Server记忆管理层负责在每次请求前准备好“会话摘要相关长期记忆”工具路由决定本次用哪一组工具定义Agent核心只负责推理和决策MCP Client统一执行工具调用并处理返回截断各领域Server各自守护具体业务逻辑。这套架构跑下来上下文窗口才真正变成了“为当前任务准备的工作台面”而不是装下所有东西的仓库。我觉得这就是从上下文窗口到MCP这条路上最核心的工程思维转变。最后再分享一点个人体会。踩过上下文爆掉的坑之后我越来越觉得Agent工程本质上是在做结构化给记忆分级、给工具分组、给上下文做预算、用MCP把工具和资源移出上下文。不要指望换一个更大窗口的模型就能解决所有问题——窗口再大如果什么东西都往里塞它最终只会变成一锅粥。先把该外置的外置该压缩的压缩然后再谈模型选型。按这个顺序来Agent在长任务和工具密集型场景下会稳得多。希望这篇实践笔记能帮你少走几条弯路。