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

2026-10-06·上下文工程·组装管线

模型不是全知的——它只知道上下文窗口里有的东西。但窗口容量有限,而要塞进去的东西远不止对话历史:系统提示词、安全规则、工具 schema、记忆召回、TaskMemory 快照……这篇用一次完整的'组装过程'走查来揭示——模型每次 LLM 调用时'看到'什么,是怎么被决定的。

一个你可能从没想过的问题

你对 AI Agent 说"帮我重构这个模块的认证逻辑"。Agent 开始工作——读文件、改代码、跑测试。

在每一轮工具调用之前,引擎要做一个关键决策:这次发给 LLM 的 prompt 里放什么?

不是"对话历史"这么简单。一次 LLM 调用的上下文包含了至少十种不同来源的信息:

信息类型来源典型大小
引擎安全规则硬编码~2K token
Agent 人设 + 硬约束AgentConfig~1-3K
技能指令已启用的 Skills~2-5K
工具 schemaToolRegistry~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 条消息)

做三件事:

  1. 标记原子对:每对 tool_use + tool_result 被标记为不可分割——压缩时要么一起保留、要么一起丢弃。拆开它们会让模型看到"调用了工具但没看到结果"或"有结果但不知道谁调的"。
  2. 消毒:清除不可见 unicode 字符(零宽空格、方向控制符等)——这些字符可能来自用户粘贴的文本或恶意注入。
  3. 初始 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
5Agent 人设"你擅长 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
12Agent 教训最近 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 每轮可能变化。

系统提示词的 Zone A / Zone B 分区——冻结仓库 vs 动态车间

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:压缩事务

如果上面的预压缩不够——触发完整的压缩管线:

  1. 工具结果去重:同一个文件被读了 3 次,只保留最新的那次结果
  2. 文件卡片折叠:旧的 read / grep 结果从完整内容折叠为元数据卡片("auth.go, 350 行, 函数: login/logout/verify")
  3. 按年龄丢弃:最旧的非 Pin 消息被丢弃(丢弃前提取工具证据作为 Pin 保底)
  4. 内联截断:超长消息内容被截断

所有压缩策略都尊重 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

  1. 计算最终 BudgetLevel(Comfortable / Warning / Pressure / Critical / Blocked)
  2. 填充 ContextBreakdown(系统提示词占多少、对话占多少、工具占多少——可观测数据)
  3. 生成 CacheHints(前缀可缓存到哪个字节偏移——给 provider 做前缀缓存)
  4. 记录 CompactionBoundary(如果发生了压缩——标记压缩边界位置,SSE 重放用)

七步组装管线——从 200 条原始消息到一个精心裁剪的上下文包

行业对比:其他产品怎么组装上下文

Claude Code:三个独立机制

Claude Code 有三个和上下文管理相关的机制,但它们不是一条管线:

  1. Server-side compaction(compact_20260112):token 阈值触发的自动压缩。开发者设 trigger(默认 150K),超过时自动摘要。
  2. Memory tool(memory_20250818):模型可以在 /memories 目录写文件,Session 开始时加载。
  3. 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
Logo

更多推荐