news 2026/9/28 20:13:15

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
上下文工程实战:管好AI编码代理的记忆与MCP工具加载

最近这半年,我身边用 AI 写代码的人明显变多了。但大家遇到的情况出奇一致:编码代理刚启动那几分钟确实猛,让它看仓库、找函数、写单测,效率堪比一个精力充沛的实习生;可一旦任务超过二十分钟、对话超过几十轮,它就开始犯迷糊——忘了最初的需求,反复读同一个文件,甚至刚查过的问题又去重查一遍。你要是追问它为什么这么做,它会一本正经地道歉,然后继续跑偏。

我把这个现象归结为一句话:不是模型变笨了,而是上下文没管好。这半年我在实际项目里反复折腾 AI 编码代理的上下文管理,从 ChatMemory 的滑动窗口设置,到给 MCP(Model 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,常驻记忆占 4K,MCP 工具定义优化后占 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 server,server 暴露三类原语: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/mcp@latest"] }, "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 + 滚动压缩)
系统提示与任务描述6K6K
ChatMemory 常驻记忆未启用3.5K
MCP 工具定义(7 个 server)9.8K1.2K(索引)+ 3.4K(按需加载)
会话历史全量保留,无限膨胀最近 8 轮原文 + 早前摘要
搜索与文件读取结果重复读取,无淘汰按需读取,过时内容滚动清理
峰值上下文占用接近 85K,超窗口频繁稳定在 58K 左右

数字不是编的,是我在日志里统计出来的。优化前最糟的情况是上下文在任务后半段已经逼近窗口上限,模型开始丢弃早期文件读取结果,导致它后续需要重新翻文件,形成"读文件—遗忘—再读文件"的死循环。优化后由于会话历史被摘要化、工具定义按需出现,上下文始终留有余量,代理终于能把注意力放在代码本身上。

4.3 落地三件套:记忆初始化、路由加载、会话收尾

这套方案落到仓库里,具体只有三件事。第一件是在.agent/目录下初始化记忆文件,先写入项目基础约定和本次任务的初始状态,比如"本仓库使用 React 18,TODO 标记散落在 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 工具列表有没有新增、最近的窗口压缩比例是否正常。把这十分钟花出去,后面省下的是和失控代理斗智斗勇的几十倍时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 20:12:33

多村庄数据隔离怎么做?服务端上下文切换的一次工程复盘

一、切了三次村庄&#xff0c;有一次列表是别人的系统里一共管着二十三个村&#xff0c;账号体系里有十一个账号同时属于两个以上村庄&#xff0c;主要是乡镇干部、驻村干部和几个兼任邻村职务的两委成员。上线两周之后&#xff0c;一位驻村干部反馈说他在村庄切换器里选到第三…

作者头像 李华
网站建设 2026/9/28 20:11:48

仓库巡检机器人怎么配?库位、消防与叉车动线三处

仓库巡检常被当成“顺带做掉”的事&#xff1a;没有连续生产线要盯&#xff0c;也没有固定值守岗位&#xff0c;出问题的往往不是设备而是货和通道——货架被撞变形、货物堆超高超宽、消防卷帘门下堆了托盘、叉车转弯区人车混行。这些隐患有一个共同点&#xff1a;靠人去翻、去…

作者头像 李华
网站建设 2026/9/28 20:11:45

第279篇_眼镜验光配镜价格与门店采集

【Python爬虫实战】第279篇:配一副眼镜水有多深——眼镜验光配镜价格与门店采集实战 所属专栏:【Python爬虫实战】从零到企业级爬虫工程师(CSDN 付费专栏) 本篇篇目:第 279 篇(本地消费价格采集专题) 难度等级:中级,需要掌握 requests 与 BeautifulSoup 基础 阅读时长…

作者头像 李华
网站建设 2026/9/28 20:11:05

选股扫描如何秒出结果?tick-stock-panel 选股缓存与矩阵预热机制完整指南

选股扫描如何秒出结果&#xff1f;tick-stock-panel 选股缓存与矩阵预热机制完整指南 【免费下载链接】tick-stock-panel TSP自托管、零运维的 A 股「选股 监控 回测」量化工作台 | LLM能力驱使策略定制个股分析复盘 | 自由接入第三方数据源与个性化扩展数据 | 个人开源 项…

作者头像 李华
网站建设 2026/9/28 20:10:30

五款视频翻译配音工具横评

UTranDub vs VideoLingo vs pyVideoTrans vs KrillinAI vs MemoAI&#xff1a;五款视频翻译配音工具横评&#xff08;2026&#xff09;一句话结论&#xff1a;这五款工具都能完成「视频 → 字幕翻译 → AI 配音」&#xff0c;差别在于你愿意付出多少配置成本。开源三杰&#xf…

作者头像 李华