上下文:Agent 的视野
本章摘要:
每次 API 请求的上下文由三大块组成:system(系统提示词)、messages(消息历史)、tools(工具定义)。为了缓存的稳定性,上下文只可增,不可改。
每次与 Agent 对话,表面是一问一答。但背后,每次 API 调用都在向大模型发送一个庞大的上下文——不止你的提问,还包含系统规则、对话历史、工具定义、环境信息、文件内容、记忆片段等。
上下文是 Agent 的"记忆与视野":它决定了 Agent 此刻能看到什么、知道什么、能做什么。
开始之前:使用缓存来降低模型成本
在进入上下文的细节之前,先讲一个贯穿全文的底层机制——提示缓存(prompt caching)。
为什么要缓存
一次典型的 Agent 会话往往包含几十次甚至上百次 API 调用,每次调用都会把整个上下文重新发给模型,上下文不断累加,成本会快速失控,延迟也会越拖越长。
但这个过程有个特点,绝大部分内容在两次调用之间是不变的,变化的只是末尾新增的那几条。模型就不需要重头从第一个字开始重新构建它的整个推理流程,只要从缓存中去读取已完成的推理流程的中间参数。
这引出一个问题:到底请求哪一部分内容是已经在缓存中了——也就是前缀匹配。
前缀匹配:为什么"顺序"很重要
缓存是前缀匹配——系统从前往后检查,遇到第一个不一致的字节就断开,断开之后的内容全部失效。
请求 A:[工具定义] [系统提示词] [消息1] [消息2]
请求 B:[工具定义] [系统提示词] [消息1] [消息3]
└── 完全相同,整段命中缓存
└── 只有 [消息3] 需要新处理
请求 C:[工具定义] [系统提示词改了] [消息1] [消息2]
└── 这里不一致,从这里开始全部失效
└── 后面所有内容都要重新写入这也是为什么后面会反复强调:
- 静态内容放前面(系统提示词的静态区域、工具定义)
- 动态内容放后面(环境信息、当前任务、消息历史)
- 已有的内容只追加不修改(一旦改动历史消息,整段缓存全废)
一句话总结:上下文的结构设计,本质上是为了让缓存尽可能多地命中,越是静态不变的内容越放在前部。
上下文全景:一次 API 调用发送了什么
每次 API 请求的上下文由三大块组成:
┌────────────────────────────────────────────────────┐
│ API 请求 │
├────────────────────────────────────────────────────┤
│ system │
│ ├─ 静态区块(全局缓存) │
│ │ ├─ 身份与安全 │
│ │ ├─ 任务执行原则 │
│ │ ├─ 工具使用指导 │
│ │ └─ 语调与风格 │
│ └─ 动态区块(会话缓存 + 按需刷新) │
│ ├─ 环境信息(当前目录、操作系统、模型等) │
│ ├─ 语言偏好(语言、输出风格等) │
│ ├─ 会话特定指导(工具、技能、MCP 指令) │
│ └─ 自动记忆(记忆机制、预算、清理提醒) │
├────────────────────────────────────────────────────┤
│ messages │
│ ├─ [0] <available-deferred-tools>(延迟工具列表) │
│ ├─ [1] <system-reminder> AGENTS.md + 记忆 + 日期 │
│ ├─ [2] user: "帮我修复这个 bug" │
│ ├─ [3] assistant: 文本回复 + 工具调用 │
│ ├─ [4] user: tool_result(工具执行结果) │
│ ├─ [5] assistant: 文本回复 + 工具调用 │
│ ├─ ... │
│ └─ [n] attachment: 动态注入的附件(如:记忆检索) │
├────────────────────────────────────────────────────┤
│ tools │
│ ├─ BashTool: name, description, input_schema │
│ ├─ FileReadTool: name, description, ... │
│ └─ ...(所有可用工具的定义) │
└────────────────────────────────────────────────────┘三大块各司其职:
| 块 | 作用 | 体积特征 |
|---|---|---|
| system | 定义 Agent 是谁、怎么做事 | 相对固定,几 KB 到十几 KB |
| messages | 对话历史和动态注入内容 | 持续增长,是上下文的主体 |
| tools | 声明可用的工具列表 | 固定,几 KB 到十几 KB |
三大块各司其职——system 是"出厂设置"、messages 是"工作记录"、tools 是"能力清单"。
发送 API 请求后,模型侧的内部处理顺序其实是:
system → tools → messages最稳定的内容置于最前,提示缓存的前缀匹配窗口尽可能大。系统提示词和工具定义往往跨会话完全不变(因为系统版本一致时,内置工具不会发生变化,延迟工具会放在 messages 中),messages 持续变化。
系统提示词:Agent 的世界观
大语言模型每次调用都是无状态的——它不记得"上次说过什么",完全靠你传给它的内容来理解当前任务。系统提示词在每次调用时定义 Agent "是谁"、"怎么做事"、"用什么工具"、"怎么说话"——是整个行为的底层蓝图。
系统提示词的质量直接决定 Agent 的行为质量。
静态与动态的分离
系统提示词由多个段落拼装而成,分为两大区域,中间有一条明确的缓存边界:
| 区域 | 内容性质 | 缓存策略 |
|---|---|---|
| 静态区域 | 通用行为准则,对所有用户一致 | 全局缓存,跨会话复用 |
| 动态区域 | 用户/会话/环境特定的信息 | 会话缓存,关键事件按需刷新 |
静态区域是 Agent 的"出厂设置"——不关心当前项目,只规定 Agent 作为某种角色(比如编程助手)的基本行为准则。
- 你是谁:你是帮助用户完成任务的交互式 Agent,不是闲聊机器人。
- 基础系统规则:定义了如何处理输出、权限、外部输入等——确保行为安全、可控且符合用户预期。
- 怎么做任务:先读文件再动手,做最小必要修改,不顺手做其他事,不假装验证通过。
- 什么事要谨慎:本地可逆操作可以直接做;删除文件、覆写文件这类高风险操作要先确认。
- 怎么用工具:有专用工具就优先用专用工具,多个独立工具调用可以并行。
- 怎么说话:简洁、直接,不用 emoji;引用其他文件时给出路径和行号。
动态区域描述本次运行的环境、用户偏好和可用能力。它不是一整段上下文,而是一组独立片段,每个片段负责一个具体维度(环境信息、用户偏好、能力指令等):
| 片段名称 | 内容 |
|---|---|
| 环境信息 | 工作目录、平台、Shell、操作系统、当前模型名称和 ID等 |
| 语言偏好 | 用户设定的语言偏好指令 |
| 输出风格 | 输出风格配置(如简洁程度、表格偏好等) |
| 会话指导 | 工具的使用指导——如 Agent 工具说明、技能调用说明等 |
| 记忆系统指令 | 记忆系统的行为指令(如何读写记忆,并非实际的记忆内容) |
| 临时文件 | 会话临时文件目录(如 /tmp/)的使用说明(Agent 可写入临时产出) |
| MCP 指令 | 已连接 MCP 服务器的使用说明 |
| Token 预算 | Token 预算目标的指导(仅在启用时注入) |
静态与动态分离的核心价值是缓存效率——静态内容对所有用户相同,缓存一次全网复用;动态内容大多数时间可以跨会话共享,但用户间不共享。
消息历史:上下文的主体
系统提示词定义 Agent 的"出厂设置",但真正让它理解当前任务进展的,是消息历史——占据上下文绝大部分 token 预算。接下去依次介绍其组成成分。
延迟工具列表
<available-deferred-tools>
mcp__browser_click
mcp__browser_navigate
...
</available-deferred-tools>这条消息列出所有延迟工具的名称(详见:工具系统:Agent 的双手),不含任何 Schema。模型必须先通过 ToolSearch "发现"延迟工具再执行调用。
AGENTS.md
紧接着,系统自动插入一条 <system-reminder> 消息(AGENTS.md 章节和记忆系统章节已介绍):
<system-reminder>
[AGENTS.md 及其引用文件的内容 + 自动记忆]
# currentDate
Today's date is 2026/05/29.
</system-reminder>旧事重提:为什么 AGENTS.md 不放在系统提示词中? 核心原因是权限隔离——模型后训练时,会提升系统提示词对模型的影响权重,如果把 AGENTS.md 放入系统提示词,容易遭受恶意 AGENTS.md 的安全攻击。
消息角色与流转
消息历史按时间顺序排列,每条消息带有"角色"和"内容"。三种角色循环往复:
user: "帮我整理一下这份旅游攻略"
↓
assistant: "我先看一下你提供的资料" + [工具调用:读取文件]
↓
user: [工具结果:资料内容...]
↓
assistant: "找到了,开始整理" + [工具调用:写入新文件]
↓
user: [工具结果:写入成功]
↓
assistant: "整理完成。结构是这样..."用户消息主要包括用户输入的文本,或工具执行结果(如果模型/助理上次返回了一次工具调用的消息)
助理回复是消息历史的核心,包含两种内容:
- 文本内容:用户可见的回复——解释、分析、总结、提问
- 工具调用声明:模型表达"我要用这个工具、传这些参数",系统随后将执行结果以 user 消息拼回
attachment 指动态注入的附件,主要为了告知模型随着时间变化,某些内容
模型就是这样"边想边做":看文件内容 → 发现问题 → 调用编辑工具 → 看到结果 → 确认正确 → 给出文字回复。
附件注入
每轮工具循环结束后,系统向消息列表注入各种附件(attachment),共 40 多种类型,是动态上下文的核心来源。按用途可以归纳为几类:
| 类别 | 典型触发条件 | 注入内容举例 |
|---|---|---|
| 用户引用 | 用户输入包含 @path | 被引用文件的完整内容 |
| 模式状态 | 处于计划/自动模式 | 提醒保持当前模式、自主推进任务 |
| 任务状态 | 后台任务变化、长期未操作 | 任务进度、当前未完成的任务列表 |
| 能力清单增量 | 可用的 Agent / 技能 / MCP 工具发生变化 | 新增或移除的能力名称与描述 |
| 记忆变化 | 自动记忆被新增/更新 | 触发记忆写入的提示与上下文 |
| 运行环境监控 | 每轮自动生成、文件被修改、出错或警告 | Token 用量、已修改文件列表、错误与警告 |
设计要点:附件是按需注入的——只有触发了相应条件,对应附件才会出现在消息列表里。这一机制让上下文既能携带丰富的动态信息,又不会无差别地膨胀。
技能描述的注入有严格的 token 预算——通常不超过上下文窗口的 1%(约 8000 字符),每条技能描述被截断到 250 字符以内。内置技能不受此限制,始终展示完整描述;用户自定义和项目级技能则可能被截断。
附件注入时机
Agent Loop 的每一轮只追加消息、不改历史,附件注入同样遵循此规则。但注入时机并非简单的"每轮一次",而是分两个窗口。
窗口一:用户提交输入时。即 Agent Loop 的第一轮——用户输入刚刚到达,模型尚未开始推理。此时注入的附件均源自用户行为:@path 引用的文件、@agent 引用的 Agent、MCP 资源等。模型在首次回复前即拥有这些上下文。
窗口二:每轮工具执行结束后。即 Agent Loop 的后续轮次——模型调用工具,系统执行完毕并返回结果。此时注入的附件反映工具执行的副作用:已修改文件列表、诊断信息更新、任务状态变化、Token 消耗量等。模型在下一轮推理前即感知到刚才的操作对环境的影响。
工具定义:能力的声明
每次 API 请求还包含所有立即可用的工具的名称、描述和参数定义,需要注意三点:
tools字段只存放立即可用的工具,而非延迟工具(详见工具系统章节);tools字段全程不变,因为在模型侧提示词拼接时,它会被放在系统提示词后(system -> tools -> messages);
Subagent 的系统提示词
Subagent 走另一条独立路径——它的系统提示词只包含 Agent 自己的提示词 + 少量固定附加内容(环境信息、行为备注),不使用默认提示词中的任何静态或动态段落。
这种"独立路径"的设计哲学是角色隔离——不同 Agent 有各自的"出厂设置",避免无关上下文污染子任务。
本章要点
- 上下文由 system / messages / tools 三大块组成
- system 定义 Agent 的出厂设置——身份、行为准则、工具使用规范
- messages 是上下文的主体——对话历史、工具调用和结果
- tools 是能力声明——既影响 token 消耗,也影响行为边界
- 静态与动态分离是为了缓存效率——稳定内容可跨会话复用
- Subagent 有独立的系统提示词路径,不共享主 Agent 的静态/动态段落