AI编程上下文工程实战:三层架构让模型视野精准对焦

发布时间:2026/9/10 10:19:23
AI编程上下文工程实战:三层架构让模型视野精准对焦 最近这半个月我一直在折腾一个叫context-mode的思路。起因很普通我把日常工作里大部分“问代码”的活交给了 AI 编程助手结果它在一个 8 万行左右的项目里给我表演了十几次一本正经的胡说八道——引用不存在的接口、把旧实现当成新实现、修一个 bug 又带出两个新 bug。起初我也以为是模型不够聪明后来把对话记录翻出来逐条排查才发现问题根本不在于模型理解能力而在于我喂给它的上下文是乱的。所以我真正想聊的不是“怎么调教大模型”而是把它背后的逻辑抽成一套可以复用的模式如何让 AI 在正确的时间、看到正确范围的代码。context-mode 这个名字就是这么来的它解决的问题也很具体——你能不能让 AI 的“视野”像相机一样自动对焦到当前问题而不是整个模糊背景。这篇文章适合正在用 AI 写代码但总觉得“效果不稳定”的人也适合想自己写 IDE 插件、CLI 工具想认真搞一搞上下文工程的朋友。1. 一个让我忍无可忍的场景AI 在正确的代码里找不到正确的上下文1.1 一次典型的翻车现场当时我在改一个多人协作的后端服务。有个需求是排查“用户登录 token 在哪些地方做了二次校验”我随手在 IDE 里打开了某个 Controller 文件然后把问题丢给 AI。它很快给出一段看起来非常完整的回答说校验逻辑在AuthController.refresh里还附了代码片段。可实际上真正的校验早就在AuthService的实现类里做完了Controller 只是层壳。AI 为什么答错因为它能看到的“当前上下文”就是我当时打开的那个 Controller 文件而真正承载业务逻辑的 service 文件压根不在它的视野里。它不是在推理而是在拿着半张地图画路线图。我后来复盘时发现这个场景太常见了。现在的 AI 编程工具普遍会收集 IDE 里“当前打开文件”“最近编辑文件”这类信息但你打开哪个文件往往取决于你手指的操作习惯而不是当前问题的答案藏在哪里。上下文收集和问题意图之间出现了错位输出质量立刻崩盘。1.2 上下文给错比模型不够强更致命要理解为什么“上下文错了”会这么致命得先知道大模型的工作方式。模型一次能处理的内容是有限的专业说法叫上下文窗口比如 128K tokens 或 200K tokens。听起来很多可一个中大型代码仓库动辄几十万行根本塞不完。就算塞得下也存在两个被反复验证过的现象大海捞针效应当一段关键信息被埋在一大堆无关文本中间时模型很可能直接漏看。信息存在但它“没注意到”。位置偏好模型对上下文开头和结尾的内容记得更牢中间部分容易稀里糊涂。我习惯用一个生活类比来解释你叫了一个刚入职的新人帮你排查生产事故然后丢给他一个几万文件的代码仓库也没告诉他该看哪几个文件。他会怎么做大概率是打开最近改过的几个文件、项目入口文件然后开始猜。AI 也一样它手里只有几块拼图自然会脑补出完整图画。所以问题的关键不是“往窗口里塞了多少信息”而是“塞进去的信息跟当前问题是否相关”。而相关性这件事靠自动收集很难做对因为它本质上是意图判断。1.3 大多数人的两种做法和我选择的不同思路我观察过身边同事使用 AI 编程助手的习惯基本可以分成两类做法典型表现结果依赖插件自动抓取当前文件“我就打开那个文件问那个问题”跨文件、跨模块的问题频繁翻车一股脑把整个仓库塞进去“你不是窗口大吗都给你看”token 消耗飙升模型注意力被噪声稀释回答开始“东拉西扯”手动复制粘贴关键代码每次问之前先翻代码复制准确率最高但效率太低多轮对话下来体力活太重这三种做法的问题要么是上下文太窄要么是太宽要么是太费人工。我想要的是一种显式的上下文模式——在开始一次 AI 会话之前先明确告诉系统“这一轮你要看哪一层代码”就像手机里的勿扰模式、飞行模式一样不同场景切换不同配置。context-mode的核心就是给 AI 编程引入“模式切换”的概念全局模式、会话模式、聚焦模式。后面我会把每一层拆开讲。2. 三层上下文架构全局、会话、聚焦2.1 全局模式Global让 AI 先懂项目底细第一层叫全局模式解决的是“AI 不懂这个项目的基本盘”的问题。很多 AI 回答不靠谱不是因为它不理解语法而是因为它完全不了解这个项目的技术约束和业务约定。比如这个项目实际上用的是 TestNG 而不是 JUnit 5所有对外接口的返回结构必须包一层ResultT数据库连接不能直接裸用必须走代理层。这些信息如果 AI 不知道它写出来的代码再“正确”也是废的。所以在我的方案里每个项目根目录都维护一份CONTEXT.md相当于这个项目的“体检报告”。里面写五类东西技术栈和关键依赖版本以及哪些依赖被禁用了目录结构说明哪里放 controller、service、dao、工具类业务领域名词表比如“订单”在这个系统里特指什么状态机编码规约比如异常处理方式、日志规范、命名习惯踩坑记录比如“这个模块的缓存曾经出现过脏数据改这里必须清缓存”有人可能会问为什么不自动生成这份文档我尝试过自动生成的CONTEXT.md内容很快会失真它会混入一堆废弃模块的描述甚至把已经删掉的老架构写进去。我后来选择人工维护每次改架构时顺手更新几行代价很小但收益极高——AI 每次回答都基于一份稳定的项目事实而不是靠猜。2.2 会话模式Session让 AI 看到“眼前正在发生什么”第二层是会话模式解决的是“AI 看不到你当前正在做的事”的问题。这一层负责收集和当前这一次开发会话直接相关的动态信息比如当前 git 分支、本次改动涉及的文件列表git diff --name-only当前打开的文件、光标停留的位置最近一次构建报错或测试失败的输出和当前改动相关的接口签名或函数调用链适用场景很明确开发一个新接口、修复一个 bug、做一轮代码评审。我实测下来git diff加git status的输出是所有动态信息里性价比最高的因为它精确地反映了“你自己刚改了什么”AI 结合这些信息能快速理解你的意图。但这里有个坑自动收集不能无脑收。我最初的做法是把所有打开过的文件都放进上下文结果 AI 老是看到一堆无关的测试代码和构建脚本反而把它们当成主逻辑。后来我加了两条规则一是文件必须被激活超过 1 秒、或者被编辑过才进入候选池二是只向依赖关系的上游扩展一层。宁可让它少看也不要让它看错。2.3 聚焦模式Focus手动 pin 才是真正的杀手锏第三层叫聚焦模式也是我认为最核心的一层解决的是“AI 永远猜不准你到底想让它看什么”的问题。自动收集做得多好都取代不了“人工指定”。原因很简单AI 没有读心术它不知道你现在真正纠结的是哪一行。比如你正在排查一个诡异的跨服务调用问题真正有价值的上下文不是整个 service 文件而是某一个方法里的那几行远程调用代码外加一个报错堆栈。聚焦模式的操作方式非常直接选中一段代码按快捷键把它 pin 到上下文池或者直接把报错堆栈喂进去也可以用 语法显式引用某个函数名让系统去源码里找到这个函数并拉进来。被 pin 的内容拥有最高的优先级不会被后面的自动收集内容挤掉。一个让我印象特别深的例子有次排查一个空指针没开聚焦模式之前AI 一直围绕当前方法里的判空逻辑打转建议我加各种 if 判断。我把堆栈里第 55 行和调用链上两个方法都 pin 进去之后它立刻反应过来——空值根本不是当前方法产生的而是上游某个接口在特定条件下返回了 null。问题瞬间定位。这个案例让我确信焦点信息手动 pin 一次胜过自动收集一整天。2.4 三种模式怎么组合这三种模式不是三选一而是分层组合。我把常见场景和推荐配置整理成了下面这张表场景组合方式典型上下文内容新人熟悉项目整体架构全局CONTEXT.md、目录树、核心业务流程图开发一个独立功能全局 会话项目约定 当前分支改动 相关接口定义排查疑难 bug全局 会话 聚焦项目约定 git diff 报错堆栈 手动 pin 的代码多轮重构全局 聚焦项目约定 待重构模块的关键方法在我实际使用的过程中百分之八十的任务都只需要“全局 会话”就够只有遇到真正棘手的问题才手动打开聚焦模式。这样既不会让操作变得繁琐又保留了一个随时可以深度干预的入口。3. 把上下文塞进窗口之前我先学会了做取舍3.1 token 预算是一道必答题构建 context-mode 时我绕不开一个现实问题模型的上下文窗口是有限的而代码对 token 的消耗比自然语言快得多。我实测下来的经验公式大致是Java、TypeScript 这类强类型语言平均每一行代码会消耗 6 到 8 个 tokens中文自然语言大约每 100 个字符消耗 50 到 90 个 tokens英文则要便宜一些。算一笔账一个 1000 行的 Java 文件光源码就要 6000 到 8000 tokens。如果一次会话里既要贴 5 个这样的文件又要保留几轮对话历史还要给模型的输出预留空间128K 的窗口看着很宽裕实际可用空间可能只有 30K 到 60K。一旦超过这个安全线回答质量就会明显下降——不是报错而是开始遗忘。所以我在 context-mode 里对所有进入上下文的条目做了一层结构化封装类似这样{ path: src/main/java/com/example/OrderService.java, startLine: 128, endLine: 156, priority: high, source: focus-pin, timestamp: 1717000000 }每条上下文都带上文件路径、起止行、优先级和来源。这样既方便压缩模块做决策也让 AI 能分辨“哪段是用户手动指定的重点哪段只是自动收集的辅助材料”。3.2 压缩的三个层次在 token 不够用的时候我按下面三个层次做裁剪顺序不能乱先裁剪无信息量内容注释、import 块、空行、没有改动过的样板代码这些对解决问题几乎没有帮助直接去掉。再做摘要化对于确实相关、但整体太长的方法保留函数签名、关键判断分支和返回值逻辑其余部分压缩成两三句话的功能说明。比如一个 80 行的OrderService.submitOrder方法压缩后可能只剩签名加核心状态流转的几行。最后淘汰低价值条目维护一个“优先级 FIFO”的队列低优先级且长时间没被引用的条目不直接扔进回收站而是降级成一行路径加摘要。万一后面用到可以按路径快速找回。这里的关键原则是宁可让 AI 看到一小块“局部的完整”也不要让它看到一大片“模糊的全部”。局部完整能推理模糊全部只会让它自由发挥。3.3 自动收集时的噪声治理自动收集最怕的是“无意识噪声”。在开发 context-mode 的过程中我花了不少时间清理两类噪声。第一类是无关文件混入。自动扫描当前工作区时测试文件、构建脚本、自动生成的 DTO、package-lock.json 这类东西都会进来。它们看起来“都在项目里”但跟当前问题关系可能为零。我的解决办法是维护一个文件白名单只收集 controller、service、repository 这类真正的业务代码层同时给自动收集器增加一个“引入理由”字段——它必须说明“为什么这个文件与当前问题相关”。理由牵强的自动条目权重会大幅降低。第二类是旧的会话信息残留。多轮对话之后前面几轮的内容可能已经过时了比如 AI 上一轮让你改了 A 文件这一轮你要改的其实是 B 文件。如果没有清理机制A 文件的内容会一直占着上下文窗口。我的方案是超过 N 轮未被再次引用的自动收集条目自动触发一次“压缩总结”把它的摘要保留原始内容释放掉。4. 实测对比同一批 issue开与不开 context-mode 差多远4.1 我不想只凭感觉说话光说“效果变好了”没有说服力所以我专门做了一组对比测试。测试项目就是我自己维护的那个 8 万行左右的后端服务模型用的是同一个 APIprompt 也保持一致性。我从真实任务池里挑了 5 个问题包括新增一个分页查询接口、修复一个空指针异常、重构一个工具函数、排查一个事务不生效的问题、给旧的订单模块补单元测试。对照组使用默认模式也就是只让 AI 看到“当前打开文件 所有对话历史”实验组采用 context-mode 的推荐配置默认开全局 会话遇到卡壳再手动加聚焦。4.2 数据结果指标对照组实验组一次通过的可用率32%71%平均往返次数解决一个问题4.2 次2.1 次单任务平均 token 消耗118K96K人工纠错时间每次超过 20 分钟基本只要复核一次有两个数据很有意思。第一实验组多花了“收集上下文”的功夫但总 token 消耗反而下降了 18% 左右。原因是无效往返少了模型不再反复问“你这个文件在哪”“能贴一下那个方法吗”省下来的 token 远大于收集成本。第二一次通过率从 32% 涨到 71%剩下的 29% 里有一半还是因为需求本身表述模糊不完全怪上下文。说明只要把视野喂对了AI 编程的可靠性就能从“偶尔翻车”提升到“基本可用”的级别。4.3 一个值得仔细看的 case这里我拆解一下那个空指针异常的 case因为它是体现聚焦模式价值的最佳例子。现象是某个接口在特定参数下会抛出空指针堆栈指向OrderService.getOrderDetail方法的第 55 行。对照组里AI 反复围绕第 55 行做文章——检查order对象是不是 null、要不要加判空条件、是不是并发问题。我按它说的改了两次问题依然存在。切换到 context-mode 之后我做了一个关键动作把堆栈完整 pin 进聚焦池再把调用链上OrderApi和OrderQueryHandler两个方法也 pin 进去。AI 看到完整调用链之后只用了一轮就定位到真正的原因OrderQueryHandler在某些条件下返回了一个空对象而这个空对象是从外部接口透传进来的OrderService里怎么判空都没用因为问题根本不在这一层。这个场景让我意识到context-mode本质上不是在“帮模型变聪明”而是在“帮模型节省排除干扰的时间”。手动 pin 一个调用链等于告诉它“别猜了问题就在这条路上”。5. 落地过程中踩过的三个大坑5.1 全量塞入窗口没爆效果先崩了我最早犯的错误是把“上下文窗口大”误当成“可以什么都往里塞”。有一段时间我为了省事干脆让脚本把整个 src 目录下所有文件都转成上下文丢给长窗口模型处理。结果非常惨。AI 开始大量引用无关内容最典型的一次是它在回答业务逻辑问题时引用了测试类里 mock 出来的假数据还一本正经地标注了出处。我冷静下来分析原因不是长窗口模型不能处理长文本而是当大量低相关度文本混入时模型的注意力会被稀释真正关键的信息反而沉底了。这和让一个新人一下子读 50 个文件他大概率什么都记不住是一样的道理。后来我把策略改成了“先给目录树再按需深入”初始只给文件结构概览AI 可以根据目录结构主动要求查看具体文件或者由收集器根据问题关键词做一次检索。效果立刻恢复。5.2 自动收集有时聪明得过了头自动收集还有一个让人哭笑不得的问题就是它会把“表面相关”的文件当成“深度相关”的文件收集进来。举个例子我的 Python 项目里有两个同名但不同用途的config.py一个在业务模块一个在测试工具模块。自动收集器通过关键词匹配把测试工具里的ConfigParser当成了业务配置推荐给 AI。结果 AI 给出的方案里全是从测试配置模块里读取参数直接跑不起来。这个问题的根源是同名文件和模糊路径带来的歧义。解决方式是我在收集器里强制使用模块全限定名比如module.core.events而不是events同时要求收集器给出“为什么收集这个文件”的理由并展示在界面上。用户一眼就能看出哪个收集不合理可以一键移除。这时候我又想起那句话自动化的前提是保留人工可监督的通道否则就是盲飞。5.3 模式切换把用户 pin 的数据给弄丢了这是我踩过最难受的坑因为它属于工程状态管理的低级失误。当时我实现了“全局 / 会话 / 聚焦”三种模式的互斥切换从聚焦模式切回会话模式的时候我写了一段重置逻辑把整个上下文池清空了。结果用户手动 pin 过的代码片段、前几轮费劲贴进去的堆栈信息全部消失。当时在项目里跑了一上午的排查所有手动焦点全部丢失只能重新来一遍。后来我把架构改成了“模式只影响自动收集策略不影响用户静态数据”。具体做法是把上下文池拆成两个独立的区域动态区负责会话和自动收集静态区负责用户手动 pin 的内容两者互不干扰。切换模式时只改动态区的收集规则静态区原封不动。这个改动之后再也没有出现过焦点丢失的情况。6. 从零复刻一套 context-mode最少只需要半小时6.1 不写代码的极简版很多人一听到“上下文工程”就觉得很高级其实最低成本的方案只需要三样东西一个文件、一条命令、一个习惯。第一步在项目根目录创建一个CONTEXT.md花十分钟写上十条项目事实技术栈、目录结构、接口返回格式、数据库访问方式、常见坑位。这相当于给 AI 一份“项目入职培训手册”。第二步每次开始 AI 会话时在第一条消息里让 AI 读取这个文件。第三步在涉及具体改动时运行一次git status和git diff把输出贴进对话。这里有一个可以直接抄走的 Prompt 模板先阅读项目根目录的 CONTEXT.md理解项目背景和约定。 然后结合我提供的 git diff 内容分析问题。 除非 AI 明确需要否则不要使用我没提到的其他文件。别小看这三步它已经覆盖了全局模式 会话模式的核心能力。我身边很多同事在没引入任何插件的情况下靠这个模板就把 AI 回答的可用率拉高了一大截。6.2 用脚本自动化一半如果你不想每一次都手动贴git diff可以写一个不到 50 行的小脚本自动组装上下文块。大致思路是收集当前分支名、最近 5 条 commit message收集git diff --stat和具体git diff内容收集当前光标所在文件的函数名可以用 LSP 的textDocument/documentSymbol接口拿把以上内容拼接成一段纯文本复制到剪贴板比如这样的一个片段#!/bin/bash echo ## Branch: $(git branch --show-current) echo ## Recent commits: git log --oneline -5 echo ## Changed files: git diff --name-only echo ## Diff content: git diff --stat这段脚本跑完复制输出到对话里AI 就已经掌握当前会话最关键的动态信息了。纯手工版和半自动化版之间的差距主要是省掉了每次敲 git 命令的体力活。再往后如果你想更进一步就可以考虑在 IDE 里写一个插件监听文件切换事件和光标移动事件把收集逻辑做成常驻服务。这时候 LSP 的能力就可以补进来了跳转到定义、查找引用、列出某个函数的所有调用方这些符号级别的信息比简单的字符串匹配准确得多。6.3 下一步可以怎么扩展做到这里基础版 context-mode 已经能为你所用。如果你想让它在更多场景下发挥作用可以从三个方向继续扩展。第一个方向是语义检索。本地起一个 Embedding 模型把项目源码切成代码块并向量化每次遇到问题时先用当前错误信息做语义召回把相关的代码块自动拉进聚焦池。这个思路能弥补关键词匹配的不足但它需要额外维护一套向量索引适合项目规模更大、模块关系更复杂的场景。第二个方向是跨会话沉淀。把每次排查过程中 AI 发现的“坑位”自动追加到CONTEXT.md里。比如这次发现某个模块不能用 MyBatis 的二级缓存下次直接写进项目事实避免后续再踩。这样时间越长CONTEXT.md越值钱AI 回答的本地化程度也会越来越高。第三个方向是适配不同模型。不同模型的上下文窗口差异很大有些只有 32K有些到了 1M。对于窗口大的模型可以适当放宽自动收集的阈值对于窗口小的模型则要更激进地做压缩和摘要。把上下文组装策略和模型能力解耦会让这套模式更耐打。最后说一点个人体会。context-mode这个项目做下来我对 AI 辅助开发的认知改变挺大的过去我总觉得“效果不好就换更强的新模型”现在我会先检查“我给的上下文是不是配得上这个模型的水平”。真正好用的 AI 编程不是靠玄学调提示词碰运气而是把“它每天能看到什么”当成工程来设计和维护。一个小技巧是每次切换模式时我心里会默念一遍“我现在只让它看这一层”多花十秒想清楚边界AI 给出的答案质量就会有肉眼可见的差异。如果你也在折腾 AI 编程助手或者想做一个自己的上下文管理工具希望这篇文章能给你提供一条值得试探的路。