资讯动态

上下文工程实战:管好AI编码代理的记忆与MCP工具加载

发布时间:2026/9/28 20:13:31 来源:尧图企业网站定制
最近这半年我身边用 AI 写代码的人明显变多了。但大家遇到的情况出奇一致编码代理刚启动那几分钟确实猛让它看仓库、找函数、写单测效率堪比一个精力充沛的实习生可一旦任务超过二十分钟、对话超过几十轮它就开始犯迷糊——忘了最初的需求反复读同一个文件甚至刚查过的问题又去重查一遍。你要是追问它为什么这么做它会一本正经地道歉然后继续跑偏。我把这个现象归结为一句话不是模型变笨了而是上下文没管好。这半年我在实际项目里反复折腾 AI 编码代理的上下文管理从 ChatMemory 的滑动窗口设置到给 MCPModel Context Protocol做上下文模式的按需加载踩了无数坑也沉淀了一套能稳定复用的方案。这篇就当成一次项目实战复盘把我用过有效的那部分以及那些看起来高级但根本不实用的做法一起摊开说。先对齐一下概念编码代理不是简单的能聊天的智能助手而是一个会自己读文件、跑命令、调外部工具的自主系统。它每做一步动作都会把文件内容、命令输出、工具返回值甚至自己的思考过程追加到上下文里。上下文一旦失控再强的模型也会变成金鱼记忆。这篇文章适合三类人正在被编码代理半途而废逼疯的开发者想在设计稿、浏览器、代码仓库等多个工具之间连 MCP 但被 token 撑爆的玩家以及单纯想理解上下文工程到底在解决什么问题的技术爱好者。1. 上下文工程编码代理真正卡脖子的地方1.1 提示词工程和上下文工程根本是两回事很多同学听到上下文优化第一反应是把提示词写清楚不就行了。这个理解只对了十分之一。提示词工程解决的是怎么把需求表达准确而上下文工程解决的是模型在每一步执行中眼里看到的信息是否足够、是否精简、是否最新。编码代理是长时间运行、多步骤执行的系统用户最初那几句话只是它看到的冰山一角。我常用的一个类比是开会。提示词工程是开会前把任务交代清楚上下文工程则是保证会议中每个人桌上始终只有他需要的那几张纸而不是把整个档案室都搬进会议室。模型的工作记忆是宝贵的喂进去一张无用的日志截图可能就把紧急改动所需的那几条关键信息挤出了窗口。这个道理说起来简单但在真实项目里绝大多数人连什么东西占了我的上下文都不清楚。1.2 上下文爆炸是怎么发生的编码代理和普通聊天不一样它的上下文增长路径非常凶残。一次中等复杂度的重构任务下来典型的上下文数据流是这样的系统提示和任务描述先占一块代理开始扫描仓库目录树、文件内容、搜索命中结果不断累积接着它调用工具每个 MCP server 的工具定义 JSON Schema 全量注入每轮交互的输入输出又全部保留在会话历史里再加上各种命令输出、错误日志、临时生成的补丁内容很快就能撑爆 80K token 的窗口。这里有个反常识的地方更大的上下文窗口并不能真正救你。研究表明模型对超长上下文的注意力分布不是均匀的它对开头和结尾的内容印象最深刻中段内容明显会迷失这就是常说的 Lost in the middle 问题。窗口越大遗忘问题反而越隐蔽。所以上下文工程的核心思路不是多装一点而是少而准。宁可让模型只看 20 条高质量信息也别让它在 200 条噪音里大海捞针。2. ChatMemory 与滑动窗口记忆系统的两大支柱2.1 ChatMemory把记忆当资产而不是日志先解决失忆问题。最幼稚的做法是把所有对话历史都留着每个新会话都把以前的内容拼进系统提示。实测下来三小时的任务就撑不住了纯属给自己找麻烦。我采用的是两级记忆结构常驻记忆很小每轮都放在上下文最顶部档案记忆很大按需通过检索召回。这就像人的大脑工作记忆容量有限但可以随时从长期记忆里调取需要的片段。常驻记忆我推荐用 Markdown 文件而不是数据库。原因很实际编码代理本身就会读文件Markdown 对人类可读、能用 git 做版本管理、还能直接 diff 看变化根本不需要额外接一套检索服务。我在项目根目录下建了.agent/目录维护三个文件global.md记录项目全局约定比如技术栈、目录规范、命名习惯tasks.md记录当前任务进展每完成一步就更新decisions.md记录关键决策和理由防止代理隔天换个思路推倒重来。记忆不能只写不删。踩过的坑是代理把失败的尝试也当成了经验写进记忆结果后续会话里它反复走老路。后来我给记忆文件加了规范每条记录必须带状态标记done、failed、blocked。每次会话结束时清洗一次凡是 failed 的尝试只保留结论和原因不再保留完整过程避免记忆污染。2.2 滑动窗口裁剪之外还要会压缩滑动窗口是上下文管理的底座。最简单的实现是新消息追加到末尾窗口满了就丢掉最旧的消息。但直接丢弃是有代价的——早期对话里往往埋着用户的核心需求丢掉了模型就会忘本。实际生产里我采用的是窗口 摘要压缩的组合策略窗口分三个区域系统提示和常驻记忆永远不参与滚动最近 N 轮对话保留原文更早的对话先压缩成摘要再放入窗口。给你一个算得清的账。假设模型上下文预算 80K token系统提示占 8K常驻记忆占 4KMCP 工具定义优化后占 4K那对话和临时工作区一共能分到 64K。如果最近 30 轮对话平均每轮 1.2K token那就是 36K把这 30 轮滚动压缩成摘要后通常只剩 6K。压缩前后省出 30K相当于多出一整块临时工作区来放代码 diff 和搜索结果。这也是为什么滑动窗口的关键不在窗口本身而在压缩手段。2.3 一个能落地的窗口滚动与压缩策略我最终采用的滚动策略是这样的维护一个消息队列设置两个水位线。当滚动区 token 超过窗口预算的 70% 时触发压缩但不压缩最近 8 轮只对更旧的消息做摘要压缩完成后如果仍旧超过 50%再对再旧一批消息做第二级摘要。保留最近 8 轮的理由很直观模型正在参考的代码片段和最近几步操作都在这 8 轮里压掉了反而得不偿失。压缩时需要给模型一个专门的提示模板明确要求提取用户核心诉求、已完成的改动、被拒绝的方案、当前受阻点而不是让它写流水账。这和信号处理里的滑动窗口滤波其实是一个思想窗口滑过一段数据用某种聚合值来代替原始序列里的冗余信息只保留最有信号价值的部分。上下文摘要就是语言层面的滤波。用一段伪代码来表示触发逻辑大概长这样def maybe_compress(messages, budget): current sum(msg.tokens for msg in messages) if current budget * 0.7: return messages frozen messages[-8:] # 最近 8 轮不压缩 compressible messages[:-8] summary summarize(compressible) # 调模型生成摘要 return [summary_entry] frozen这套逻辑跑了一段时间后我最大的体感是代理的项目级连贯性明显变好尤其是跨会话恢复任务时它不再东张西望地重新摸索需求而是直接进入干活状态。但这只是记忆侧的问题还没碰到真正的成本大头——工具定义。3. Context-mode MCP从全量注入到按需加载3.1 MCP 给上下文带来的真实成本MCP 是 Anthropic 推出的开放协议定位相当于是 AI 应用的外设接口标准。它让编码代理能连接文件系统、git 仓库、浏览器、设计稿、数据库等外部工具架构上是 MCP client 连接多个 MCP serverserver 暴露三类原语tools工具调用、resources资源读取、prompts提示模板。听起来很清爽但代价藏在细节里。每个 MCP server 都会向模型暴露一连串工具定义也就是 JSON Schema。一个中等复杂度的工具比如打开浏览器页面或者搜索代码仓库它的 schema 动辄 600 到 1000 token。你要是给代理连了 12 个 server每个平均 1500 token光工具定义就吃掉 1.8 万 token。这不只是 token 成本问题更隐蔽的是选择成本工具多了模型在每一步都要从几十上百个候选中挑一个选错的概率随之上升。实测下来工具数量超过 25 个之后误调用率肉眼可见地增加。大家意识到这个问题的直接结果就是标题里那个 Context-mode MCP 上下文优化思路。3.2 三种轻量的 Context-mode 实现路径所谓 Context-mode我的理解是不让所有 server 的工具定义一股脑全塞进系统提示而是建立一个上下文感知的按需加载层。目前我试过三种做法由轻到重。第一种是工具分组加任务路由。把 MCP server 按行为域分组比如 git、测试、文档、设计稿、浏览器。编码代理的第一个动作不是干活而是先做一次轻量任务分类判断当前任务属于哪个域然后只把这个域的工具定义注入上下文。其他 server 保持连接但工具定义不加载等任务真正用到时再动态注册进来。第二种是动态系统上下文注入。打磨版的方案是在系统提示里只保留一个极小的工具索引每个工具一行名字加一句说明当模型决定要调用某个工具时再通过一次工具发现请求拿到完整 Schema。这种方式性价比最高适合大多数日常编码场景。第三种是工具调用拦截网关。适合需要对多 server 做统一审计的团队场景在 MCP client 和 server 之间加一个轻量聚合层所有工具调用先经过网关过滤、鉴权、参数校验再转发到对应 server。网关维护全套工具目录但只向模型暴露白名单子集保证模型视野始终干净。我实际生产环境跑的是第二种加第一种的混合体。伪代码大致长这样const domains { git: [list_branches, diff, commit], docs: [search_docs, read_doc], browser: [navigate, screenshot, extract], }; function buildContext(domain: string) { const schemas domains[domain] .map(toolName getSchema(toolName)) .join(\n); return 当前可用工具:\n${schemas}; }需要多说一句为什么我强调按行为域分组而不是按 MCP server 分组因为一个 server 往往同时提供多种能力比如一个文件工具 server 既能读文件又能写文件后者属于高风险操作。如果按 server 全量加载等于把一个低风险动作和一个高风险操作绑在一起模型很容易误触。行为域分组能天然隔离不同安全级别的操作。3.3 在 Claude Code / Cursor / Trae 类代理里配置 MCP编码代理配置 MCP server 的方式大同小异。以 Claude Code 为例项目级配置写在.mcp.json里全局配置则写在用户目录的配置文件中。我推荐用项目级配置因为 MCP server 选型高度依赖项目需要跟着仓库走能保证团队成员配置一致。一个典型的配置文件片段长这样{ mcpServers: { git: { command: npx, args: [-y, modelcontextprotocol/server-git] }, playwright: { command: npx, args: [-y, playwright/mcplatest] }, filesystem: { command: npx, args: [-y, modelcontextprotocol/server-filesystem, ./allowed-dir] } } }配置本身不难真正的难点在接入以后怎么管。我给自己的规矩有四条不允许一次性全量接入新 server先逐个验证工具列表是否正常周期性用/mcp或mcp list查看全部工具评估整体 schema 体量对只读类 server 尽量在配置里限定可访问路径避免它读到不该读的东西凡是支持 Context-mode 的代理优先使用按需加载模式而不是盲目贪多。很多人对 MCP 的印象是接得越多越强大这个理解在上下文工程视角下是错的。工具连接多不等于任务成功率就高。相反每多一个 server就多一分工具定义挤占上下文、多一分误选择风险。我现在的原则是为任务配上下文而不是为功能凑工具。4. 端到端实战给一个重构任务配上下文方案4.1 场景设定与目标拿我最近压测这套方案的一个真实任务举例在一个中等规模的前端仓库里让代理实现一个按标签聚合 TODO 统计的功能要求读取多个目录下的源码、搜索 TODO 标记、统计分类、再输出一份汇总报告全程限时半个小时内完成。这个任务看起来简单但它跨了文件检索、代码阅读、数据聚合、报告生成好几个环节是典型的上下文敏感型任务。如果用默认配置直接跑代理会经历三个阶段前五分钟非常顺畅地定位到相关文件十分钟左右开始反复搜索重复关键词十五分钟以后甚至出现忘记用户最初要求按标签聚合的细节。原因就出在它把自己见过的所有文件内容都囤在上下文里越到后面越分不清轻重。改成上下文优化方案后整体运行稳了很多耗时也压缩到了二十分钟左右。4.2 优化前 vs 优化后的上下文预算分配我把这个任务的上下文预算分配整理成一张对照表这样你更能看清差距。上下文项目优化前默认全量优化后Context-mode 滚动压缩系统提示与任务描述6K6KChatMemory 常驻记忆未启用3.5KMCP 工具定义7 个 server9.8K1.2K索引 3.4K按需加载会话历史全量保留无限膨胀最近 8 轮原文 早前摘要搜索与文件读取结果重复读取无淘汰按需读取过时内容滚动清理峰值上下文占用接近 85K超窗口频繁稳定在 58K 左右数字不是编的是我在日志里统计出来的。优化前最糟的情况是上下文在任务后半段已经逼近窗口上限模型开始丢弃早期文件读取结果导致它后续需要重新翻文件形成读文件—遗忘—再读文件的死循环。优化后由于会话历史被摘要化、工具定义按需出现上下文始终留有余量代理终于能把注意力放在代码本身上。4.3 落地三件套记忆初始化、路由加载、会话收尾这套方案落到仓库里具体只有三件事。第一件是在.agent/目录下初始化记忆文件先写入项目基础约定和本次任务的初始状态比如本仓库使用 React 18TODO 标记散落在 src 目录下这种能帮代理少走弯路的信息。第二件是启动会话时在系统提示里追加一段话明确要求代理先读取.agent/目录下的三个记忆文件然后根据任务类型只激活与文件检索相关的 MCP 工具分组。第三件是会话结束时更新tasks.md和decisions.md把完成情况、剩余事项、重要结论写清楚并清理掉已验证的无用方案。这个流程最妙的地方在于下一次会话无论隔多久代理都能快速回到知道自己在干什么的状态。记忆文件就是代理的交接班日志滑动窗口保证它当前状态下注意力集中MCP 按需加载则确保它伸手拿工具时不被无关选项干扰。三者合在一起才算是完整的上下文工程闭环。5. 常见问题与排查技巧实录5.1 五个高频问题速查实践过程中遇到的问题五花八门但绝大多数都能归到五类。整理成一个速查表方便你对照排查。症状根本原因解决方案任务中途模型忘掉最初需求上下文被无关信息挤占早期指令被挤出窗口启用 ChatMemory把核心需求写入常驻记忆提高系统提示优先级代理反复执行已被否定的方案记忆里保留了失败尝试的完整记录记忆条目加状态标记failed 记录只保留结论MCP server 连接不上stdio 传输依赖本地环境依赖未装或启动失败检查 server 日志先用mcp list验证工具列表是否可见模型经常调用错误工具工具定义过多过泛选择空间过大启用 Context-mode 按需加载按行为域分组注入正在修改的代码片段被窗口截断滚动压缩不小心把新鲜代码压掉了保留最近 8 轮原文不压缩代码 diff 单独放工作区每个问题的排查思路都不一样。比如 MCP 连接失败我要先判断是配置问题还是环境问题在终端里手动执行一遍 server 启动命令如果命令能起来说明配置路径或参数写错了如果命令也起不来那就是依赖缺失或版本不匹配。这种从外到内的排查习惯能帮你省下大量时间。5.2 我踩过的三个坑第一个坑是一开始贪多一口气连了十几个 MCP server光看工具列表就很爽结果代理每一步都在选工具效率反而下降。砍掉大部分 server 后任务成功率立竿见影地回升。现在我默认只留三个以内的核心 server其余全部按需加载。第二个坑是记忆文件被我写成了流水账。一开始tasks.md里什么都有包括临时想到的想法、没经证实的猜测、甚至一些 sql 查询草稿文件膨胀到十几 K。后来代理每次开会话前都要花一堆 token 读完它反而把真正重要的内容稀释了。现在记忆文件严格限制篇幅每条必须能让人在十秒内看懂。第三个坑是滑动窗口压掉了正在进行的修改上下文。事情发生在一次大重构中代理刚把某个函数的修改方案想清楚结果滚动压缩把它之前整理的 diff 摘要给压掉了后续几步它又推倒重来。从那以后我调整了压缩策略明确规定最近几轮以及所有包含代码 diff 的消息是冻结区永远不参与压缩。这个小小的改动让长时间任务的稳定性提升了一大截。结尾把上下文工程当成一项持续性投资做这轮优化之前我以为上下文工程无非是给编码代理加个记忆文件、调大窗口参数做完之后才理解它其实是在帮代理维持一种刚开工的团队状态——每个人都知道目标是什么、知道自己手头有哪些材料、知道哪些工具现在能用并且不会被旧账拖累。我个人在实际操作中的体会是上下文优化最值得投入的环节不是调模型而是建立可观测的反馈回路。就是每次任务结束后回头统计一下 token 都用在哪了、哪些部分是浪费的、哪类信息最容易把代理带偏然后把结论沉淀回记忆文件和工具配置里。这比任何一次性的参数调优都管用。最后再分享一个小经验别把上下文工程想成一次配置就永久生效的东西。项目会迭代、记忆会过时、MCP server 会升级这套机制需要像维护代码一样定期维护。你可以安排一个简单的周度巡检每次只要花十分钟看看记忆文件是否超过预定长度、MCP 工具列表有没有新增、最近的窗口压缩比例是否正常。把这十分钟花出去后面省下的是和失控代理斗智斗勇的几十倍时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑