第 7 章 上下文最小化:别让脏东西留在脑子里
装得下还不够,还得干净
上一章讲"怎么装得下"。这一章讲一个更容易被忽略、但同样致命的问题:装进去的东西,干不干净?
想象你让 Agent 处理用户输入。用户可能是普通人,也可能是心怀恶意的攻击者——他们会在输入里藏指令,试图操纵 Agent(这就是 prompt injection,提示词注入)。
更隐蔽的问题是:就算输入一开始是干净的,它在上下文里待得太久也会变脏。Agent 处理完某段用户文本,本该把它忘掉,但它还留在上下文里。下一轮做无关任务时,这段旧文本里的恶意指令(或只是噪音)仍然在"暗中影响"模型的推理。
这一章讲三个模式,核心都是"让上下文保持干净、精简":
- Context Minimization(上下文最小化):用过的不可信内容,立刻从上下文里清掉。
- Dynamic Context Injection(动态上下文注入):需要时才注入,而不是常驻。
- Session-Scoped Context Runtime(会话级上下文运行时):用一个运行时层管理上下文,读写走缓存,避免重复粘贴。
模式一:上下文最小化(Context Minimization)
问题
在长会话里,原始的用户文本和工具输出,经常在它们早就用完之后还留在上下文里。如果这些 token 里藏着对抗性指令,它们会在几轮之后"悄悄激活"——即使当前这一步和它们完全无关。这就造成了延迟的提示词注入风险,外加不必要的上下文膨胀。
方案
用过即焚:一旦某段不可信内容完成了它的使命,就把它们从上下文里清除或脱敏。
典型的流程:
- 把不可信的输入转成一个安全的中间形式(比如把用户自然语言转成一个结构化的查询、一个对象)。
- 把原始 prompt 从上下文里删掉。
- 之后的推理只看到可信数据,潜伏的注入指令就失去了作用。
更激进的做法是:连中间产生的 LLM 输出也一起清掉(如果它们可能被污染过)。
把上下文当成一条分阶段的流水线:摄入不可信文本 → 转换 → 激进地丢弃原始污染材料 → 只留下已经"签字确认"的结构化产物给下游使用。
sql = LLM("把这句话转成SQL", 用户提示词)
清除(用户提示词) # 污染token 消失
行 = 数据库.查询(sql)
答案 = LLM("总结这些行", 行)证据
- 来自学术论文(Luca Beurer-Kellner 等人,2025),和 Plan-Then-Execute 同源。
- 这是对抗 prompt injection 的核心策略之一:让攻击面越小越好。
怎么用
- 凡是"用户输入或工具输出会进入上下文"的 Agent,都该考虑。
- 转换动作要发生在摄入之后、使用之前,转换完立刻清原。
- 判断哪些内容"可能被污染"要有标准——宁可多清,不可留着。
取舍
- 好处:消除延迟注入风险;上下文更精简。
- 代价:需要设计"安全的中间形式";如果转换丢了信息,后续就没法用了。
模式二:动态上下文注入(Dynamic Context Injection)
问题
有些 Agent 把上下文全放在"分层配置文件"里(第 5 章讲过),但真实工作流里,Agent 常常临时需要某个特定信息:某个文件的内容、某个脚本的输出、某段常用的复杂指令。如果每次都去改配置文件、或者往 prompt 里手动粘一大段文本,又慢又笨。
方案
给用户提供在会话中动态注入上下文的机制。两个最经典的实现:
1. 文件/文件夹 @ 提及
用户输入一个特殊字符(比如 @)加上文件或文件夹路径,Agent 就把该文件内容(或该文件夹的摘要)读进来,注入当前上下文。
用户: 帮我改一下这个按钮的样式 @src/components/Button.tsx
Agent: (读取 Button.tsx,注入上下文)好的,我看到这个按钮组件了...2. 自定义斜杠命令
用户可以把常用的复杂指令或上下文片段存成单独文件,用斜杠命令一键加载。比如 ~/.claude/commands/deploy.md 里存了部署检查清单,输入 /deploy 就把它注入上下文。
这样,上下文是按需、精准加载的,而不是一上来就全塞进去。这也是 Claude Code 这类工具的标配交互方式。
证据
- 来自 Boris Cherny(Claude Code 相关实践),状态
established(成熟)。 - 几乎所有主流编码 Agent 工具(Claude Code、Cursor 等)都实现了 @ 提及或斜杠命令。
怎么用
- 给 Agent 工具设计交互时,加上 @ 提及和斜杠命令。
- 常用复杂指令固化成命令文件,团队共享(配合第 9 章的技能库)。
- 和上一章的"精选窗口"配合:动态注入是"用户主动触发"的精选。
取舍
- 好处:上下文按需加载,精简高效;用户体验流畅。
- 代价:需要实现注入机制;依赖用户知道"该 @ 什么"。
模式三:会话级上下文运行时(Session-Scoped Context Runtime)
问题
编码 Agent 有个常见的浪费:同一个文件、同一个命令输出,一个会话里要被读很多次。每一次读取,都把完整文本粘进模型上下文——哪怕那个文件根本没变过。成本和延迟就随着"重复 + 啰嗦"一起涨。
方案
在 Agent 旁边放一个上下文运行时(context runtime,通常是一个 MCP server),专门负责"工作区的状态如何进入模型":
会话级缓存:对读操作做缓存,用便宜的校验方式(比如文件修改时间)确认没变过。相同的内容,第二次读就命中缓存,不再发完整内容。
结构化读取模式:提供多种"瘦身"读取方式——依赖图谱、函数签名、diff、按任务过滤的摘录。Agent 请求恰好支持下一步决策的最小表示,而不是整个文件。
归一化的工具通道:shell 和搜索结果经过压缩适配器(对稳定的格式,比如常见的
git、包管理器输出,做模式化压缩)。
运行时夹在 IDE/宿主和模型之间:工具先调它,它返回紧凑的、带类型的上下文,并记录"这个 session 里哪些内容已经在缓存里了"。
on_读取(路径, 模式):
if 缓存有效(路径): 返回 缓存项(路径, 模式)
解析结果 = 加载并解析(路径)
投影 = 投影(解析结果, 模式) # 依赖图谱 | 签名 | diff | ...
存缓存(路径, 模式, 投影)
返回 投影证据
- 来自 Lean-ctx 等项目实践,基于 Anthropic 的 MCP 规范。
- 状态偏
emerging,但思路(缓存 + 结构化读取)在系统设计里是成熟套路。
怎么用
- 编码类 Agent、或任何"反复读同一批文件/命令输出"的场景。
- 把读、搜、shell、目录树操作都统一走运行时,让路由一致。
- 缓存校验要便宜(mtime 就够),别让"检查缓存"本身变成开销。
取舍
- 好处:重复内容不再重复花钱;上下文紧凑;延迟下降。
- 代价:多一层基础设施(一个运行时/MCP server);投影逻辑要维护。
三个模式怎么选
| 场景 | 推荐模式 |
|---|---|
| 用户/工具输入可能带注入,且会滞留 | 上下文最小化(必做) |
| 需要交互式、按需给 Agent 喂信息 | 动态上下文注入 |
| 同一个文件/输出反复读 | 会话级上下文运行时 |
三个模式角度不同:最小化管"干净",动态注入管"按需",运行时管"去重"。它们和上一章的"压缩与精选"一起,构成完整的上下文管理体系。
实践清单
- 不可信输入转成安全中间形式后,立刻清除原始内容
- 判断"可能被污染"的标准要明确,宁可多清
- 工具交互加上 @ 提及和斜杠命令,按需注入
- 常用复杂指令固化成命令文件,团队共享
- 反复读的文件/输出,加会话级缓存(用 mtime 校验)
- 提供结构化读取模式(签名、diff、摘要),别让 Agent 读全文
- 自问:Agent 上下文里,还有多少内容是"早该删掉"的?
本章小结
- 上下文最小化:用过即焚,把污染材料和噪音从上下文里清掉。
- 动态上下文注入:需要时才注入,@ 提及 + 斜杠命令。
- 会话级上下文运行时:缓存读写结果,结构化读取,去重去冗余。
- 装得下(第 6 章) + 装干净(本章)= 健康的上下文。
下一章,我们讲记忆体系——让 Agent 不只是"记得住这个会话",而是"记得住过去很多会话"。