Agent 每次“看到”什么——上下文组装管线全景

2026-10-06·上下文工程·组装管线
模型不是全知的——它只知道上下文窗口里有的东西。但窗口容量有限,而要塞进去的东西远不止对话历史:系统提示词、安全规则、工具 schema、记忆召回、TaskMemory 快照……这篇用一次完整的'组装过程'走查来揭示——模型每次 LLM 调用时'看到'什么,是怎么被决定的。
一个你可能从没想过的问题
你对 AI Agent 说"帮我重构这个模块的认证逻辑"。Agent 开始工作——读文件、改代码、跑测试。
在每一轮工具调用之前,引擎要做一个关键决策:这次发给 LLM 的 prompt 里放什么?
不是"对话历史"这么简单。一次 LLM 调用的上下文包含了至少十种不同来源的信息:
| 信息类型 | 来源 | 典型大小 |
|---|---|---|
| 引擎安全规则 | 硬编码 | ~2K token |
| Agent 人设 + 硬约束 | AgentConfig | ~1-3K |
| 技能指令 | 已启用的 Skills | ~2-5K |
| 工具 schema | ToolRegistry | ~12-17K |
| 上一 Run 的认知结算快照 | SessionState | ~1-2K |
| TaskMemory(Plan + Findings) | TaskStore | ~1-3K |
| 记忆召回(Eager + Lazy) | Memory Store | ~1-3K |
| 对话历史 | Session Messages | 变化极大(5K-150K) |
| 当前用户消息 | 本轮输入 | ~0.2-2K |
| prompt cache hint | 计算得出 | 0(元数据) |
十种来源、十种生命周期、十种优先级。 塞不下时先丢谁?顺序搞错了谁吃亏?哪些需要每轮更新、哪些可以缓存?
这不是一个"拼字符串"的问题——是一条组装管线的产物。

为什么顺序重要
上下文中信息的排列顺序至少影响三件事:
影响一:prompt cache 命中率
主流 LLM provider 支持前缀缓存——重复的消息前缀只计算一次。如果系统提示词在第 1 轮和第 2 轮完全一样(逐字节相同),第 2 轮只需要计算新增的部分。
这意味着"会变的内容"必须排在"不会变的内容"之后。 如果一段每轮都变化的内容排在第 5 位,它之后所有的内容(第 6、7、8……位)即使没变,cache 也全部失效。
一个真实的教训:一段约 300 token 的"任务进度"被放在系统提示词的第 9 位——它随着 Plan 推进每轮都变。结果排在它后面的"技能指令"(约 2.2K token)被永久挤出可缓存前缀。85 轮对话的 prompt cache 命中率钉在 4-7%。把"任务进度"移到动态区之后——cache 命中率回到 80%+。
影响二:窗口满时的裁剪顺序
当上下文逼近窗口限制时,引擎需要裁剪内容。裁剪不是"随机丢"——是按优先级来的。
但优先级不是一个简单的数字——它取决于信息类型:
- 安全规则:永不裁剪(写在 Zone A,最前面)
- 上一轮结论:Pin 保护(不参与裁剪)
- 旧对话历史:最先被裁剪(按年龄丢弃)
- 工具 schema:按 Domain 整域淘汰(不用的工具域先丢)
- 记忆召回:预算紧时截取 TopK 减半
"先塞进去的最后被丢"——这就是排列顺序的第二重意义。
影响三:模型注意力分布
学术研究已证实("Lost in the Middle",Liu et al.):LLM 对上下文中间部分的注意力显著低于开头和结尾。
把最重要的信息(安全规则、Agent 人设)放在开头、把最紧迫的信息(当前用户消息、工具调用结果)放在结尾——中间留给可以"不那么仔细读"的内容(历史对话、工具 schema)。
管线全景——一次完整的组装过程
以下用一个具体场景走一遍完整的组装管线。
场景:Agent 正在执行一个代码重构任务的第 50 轮。上一轮它修改了 auth.go,这一轮用户说"继续"。
Stage 1:消息预处理
输入:Session 中的原始消息历史(50 轮,约 200 条消息)
做三件事:
- 标记原子对:每对
tool_use+tool_result被标记为不可分割——压缩时要么一起保留、要么一起丢弃。拆开它们会让模型看到"调用了工具但没看到结果"或"有结果但不知道谁调的"。 - 消毒:清除不可见 unicode 字符(零宽空格、方向控制符等)——这些字符可能来自用户粘贴的文本或恶意注入。
- 初始 token 估算:粗算每条消息的 token 数,为后续预算判断做准备。
Stage 1.5:file_ref 展开
输入:当前轮的消息
file_ref 是一种轻量引用——"这里有一个文件,路径是 X"。Stage 1.5 把当前轮的 file_ref 展开为实际的文件内容(多模态 block)。
关键设计:只展开当前轮,不展开历史轮次的 file_ref。历史轮次的文件引用保留为引用形式——省 token。如果模型需要回看历史文件内容,可以用 read 工具重新读取。
Stage 2:系统提示组装——最复杂的一步
输入:AgentConfig + 各种 Overlay 回调 输出:一条大的系统消息
这一步把十多种信息源按固定的 section 顺序拼装成系统提示词。顺序分两个区域:
Zone A(可缓存前缀)——在同一 Session 的相邻 Turn 之间内容不变:
| 位置 | Section | 内容 | 典型大小 |
|---|---|---|---|
| 1 | 引擎宪法 | 安全规则 + 行为纪律 | ~2K |
| 2 | 应用身份 | "你是 编程工具 的编程助手" | ~0.5K |
| 3 | 工具使用指南 | 怎么正确调用工具 | ~1K |
| 4 | 模型族纪律 | Claude/GPT 特有的执行规则 | ~0.5K |
| 5 | Agent 人设 | "你擅长 Go 编程" | ~1K |
| 6 | 硬约束 | "不要修改 vendor/ 目录" | ~0.5K |
| 7 | 技能指令 | 已启用 skill 的具体指令文本 | ~2-5K |
Zone B(每轮变化)——内容随 Run 推进而变:
| 位置 | Section | 内容 | 典型大小 |
|---|---|---|---|
| 8 | 任务锚点 | 当前目标 + 关键决策 + 当前步骤 | ~0.5K |
| 9 | 运行时上下文 | 附件状态、渠道身份、resume 上下文 | ~0.3K |
| 10 | 工具能力 | 可用工具列表(domain 折叠后) | ~0.5K |
| 11 | 会话状态 | 上一 Run 认知结算快照 | ~1K |
| 12 | Agent 教训 | 最近 7 天同 Agent 的反思 | ~0.3K |
| 13 | 记忆区 | Eager 记忆 + 行为规则 + TaskMemory overlay | ~1-3K |
| 14 | 工作区 | 当前时间/OS/CWD | ~0.1K |
Zone A 在前、Zone B 在后——确保前缀缓存最大化。Zone A 的约 8-12K token 在整个 Session 内字节稳定;Zone B 的约 3-6K token 每轮可能变化。

Stage 3:窗口修剪 + 结论保护
输入:系统提示 + 50 轮消息历史 输出:截取后的消息序列
两个关键操作:
结论 Pin 保护:上一轮的最终回复("我已经修改了 auth.go 的 login 函数,接下来要改 middleware")被标记为 Pinned=true——无论后续怎么裁剪和压缩,这条消息始终保留。
为什么保护上一轮结论?因为它包含了 Agent 对当前状态的最新理解——下一轮的推理从这里开始。如果它被裁剪掉,Agent 在新一轮会不知道自己上次做了什么。
窗口裁剪:如果 50 轮消息超出窗口容量——从最旧的消息开始丢弃(保留 Pinned 消息),并插入一条占位符:"[这里省略了 N 条较早的消息]"。这让模型知道历史不完整——避免它假设对话是从头开始的。
Stage 3.4 / 3.5:压缩干预
如果消息量已经占用了 70% 以上的窗口——触发压缩干预。
Stage 3.4 认知交班(70% 压力线):让主对话模型对完整历史产出一个"工作记忆快照"——类似于"写一份当前工作状态的笔记"。如果交班成功——用 Pin 消息替代大量历史,释放空间。如果交班失败——降级到下一层。
Stage 3.5 Safety-net 预压缩(85% 压力线):用机械策略(按年龄丢弃 + 丢最旧的非 Pin 消息)把占用率打回 85% 以下。
Stage 4:压缩事务
如果上面的预压缩不够——触发完整的压缩管线:
- 工具结果去重:同一个文件被读了 3 次,只保留最新的那次结果
- 文件卡片折叠:旧的
read/grep结果从完整内容折叠为元数据卡片("auth.go, 350 行, 函数: login/logout/verify") - 按年龄丢弃:最旧的非 Pin 消息被丢弃(丢弃前提取工具证据作为 Pin 保底)
- 内联截断:超长消息内容被截断
所有压缩策略都尊重 Pinned=true 标记——上一轮结论和手动 Pin 的消息不会被任何压缩策略碰到。
Circuit Breaker:如果压缩连续多次无效(压完还是超限),断路器跳闸——避免无限循环。
Stage 5:Turn Overlay——记忆和技能的注入点
这一步把当前轮特有的信息注入上下文:
5a Lazy 记忆召回:以当前用户消息为查询向量,在记忆库中搜索相关的长期记忆。搜索结果按混合相关度(BM25 文本匹配 + 向量语义相似 + 时间衰减)排序,截取 TopK 条注入。
关键设计:Lazy 召回的结果作为用户消息的副本注入——不修改原始用户消息、不写回存储。这是一种"只读注入"——模型看到了记忆内容,但记忆的状态不受影响。
5b 技能 Turn Overlay:根据当前轮激活的技能列表,注入技能的具体指令。技能指令作为当前 Turn 的 Overlay 注入——不修改系统提示词。
5c Profile 记忆:注入账号级偏好("用户喜欢 Go 风格的错误处理")。有 2000 字节上限——超出时按行边界截断,并追加截断标记。
Stage 6:工具 Schema 过滤
输入:ToolRegistry 的全量工具 + 预算 输出:过滤后的 tool schema 列表
工具按"域"(domain)分组——编程工具域、搜索工具域、浏览器工具域等。活跃域展开完整 schema,休眠域折叠为元数据。
超出工具 token 上限时按 LRU(最近最少使用)序整域淘汰——不是丢单个工具,是丢整个域。核心工具域和粘性域永不淘汰。
Turn 1+ 描述降级:工具 schema 中 "What"(是什么)部分在首次发送后降级为 "How-Only"(怎么用)——因为前缀缓存已建立,模型已知道工具是什么,后续只需知道参数格式。
Stage 7:预算信号 + 缓存
输出:最终的 AssembleResult
- 计算最终 BudgetLevel(Comfortable / Warning / Pressure / Critical / Blocked)
- 填充 ContextBreakdown(系统提示词占多少、对话占多少、工具占多少——可观测数据)
- 生成 CacheHints(前缀可缓存到哪个字节偏移——给 provider 做前缀缓存)
- 记录 CompactionBoundary(如果发生了压缩——标记压缩边界位置,SSE 重放用)

行业对比:其他产品怎么组装上下文
Claude Code:三个独立机制
Claude Code 有三个和上下文管理相关的机制,但它们不是一条管线:
- Server-side compaction(
compact_20260112):token 阈值触发的自动压缩。开发者设 trigger(默认 150K),超过时自动摘要。 - Memory tool(
memory_20250818):模型可以在/memories目录写文件,Session 开始时加载。 - Context editing:清除已消费的工具结果(
tool-result clearing)。
三者各自独立——没有统一的预算管理、没有 Zone 分区、没有 Pin 保护。协调由开发者通过 hooks 实现。
Anthropic Cookbook 的推荐
Anthropic 的 Context Engineering Cookbook 推荐了一种分层策略:
| 策略 | 适用场景 |
|---|---|
| 工具结果清除 | 已消费的工具结果 |
| 压缩 | 长对话的历史摘要 |
| 动态工具选择 | 工具 schema 占用过多 |
| 便签笔记 | 关键发现的结构化记录 |
| 文件系统 | 超长任务的持久化状态 |
这些策略和我们管线中的 Stage 对应:工具结果清除 ≈ Stage 4 去重、压缩 ≈ Stage 3.4/3.5/4、动态工具选择 ≈ Stage 6 Domain Index、便签笔记 ≈ Stage 5 TaskMemory Overlay。
区别在于:Cookbook 描述的是"你可以用这些策略"(开发者自行组合),我们描述的是"这些策略按这个顺序自动执行"(引擎内建管线)。
LangGraph:开发者手搓
LangGraph 没有内置的上下文组装管线。开发者在每个 StateGraph 节点中自行构造 prompt——从 State 对象读取信息、拼装成消息列表、调用 LLM。
优势是完全灵活——开发者对每一步有完全控制。劣势是重复劳动——每个 Agent 都要自己实现预算管理、压缩触发、cache hint 生成。
你现在能做什么
1. 系统提示词分两区:稳定前缀 + 动态尾部。 把不会变的内容(安全规则、Agent 人设、工具指南)放在前面,会变的内容(任务进度、运行时上下文、记忆)放在后面。这一个改动就能显著提升 prompt cache 命中率。
2. 上一轮结论 Pin 保护。 无论怎么裁剪上下文——保留上一轮 Agent 的最终回复。这是下一轮推理的起点。丢掉它等于让 Agent "失忆一轮"。
3. 工具结果去重。 同一个文件被读了 3 次——只保留最新的那次。旧的读取结果在当前轮没有信息增益,只消耗 token。
4. 知道"固定开销"有多大。 系统提示词 + 工具 schema 的固定开销通常在 30K-37K token。128K 的窗口实际只有 ~80K 可用。打开你的 Agent 日志,看看实际的固定开销是多少。
5. 窗口裁剪时插入占位符。 不要静默地丢掉旧消息——插入一条"[省略了 N 条较早的消息]"。让模型知道历史不完整,避免它做出基于不完整信息的错误判断。
引用来源
- Anthropic Claude Cookbook, "Context engineering: memory, compaction, and tool clearing" — Context engineering: memory, compaction, and tool clearing | Claude Cookbook
- Liu et al., "Lost in the Middle: How Language Models Use Long Contexts" — 中间注意力衰减
- Claude Code 官方文档, "Permission modes" — compaction + memory tool
- IBM Granite Agent Cookbook, "Context Management" — 分层策略推荐
- LangGraph documentation — StateGraph 无内置上下文管理
- Cursor Blog, "Training Composer for longer horizons" (Mar 2026) — lossy compression
更多推荐


所有评论(0)