跳到主要内容

上下文的地基:每次调用都是无状态的

这一章讲三件事: 「上下文」在 API(程序之间相互调用的约定,行话也叫接口)层面到底长什么样;一个最小可用的 Agent 循环有多少行代码;以及那个让后面一切技术(缓存、压缩、Skills)都 站得住的结构事实——上下文分两半,前半不能动,后半一直在长。

1. 这一章讲什么

第 01 章说上下文是「Agent 的眼睛」。这一章把这双眼睛拆到 API 的字节(信息的最小存储单位)层面: 你发出去的每个请求里到底有什么,模型「看到」的顺序是什么,以及为什么 「上一轮说过什么」这件事,必须由外部代码一遍遍替模型重新送

先给这一章最重要的一句话:上下文工程(系统性地设计、组织 AI 完成任务所需的全部 背景,而不是往提示词里堆料)之所以成立,是因为模型侧有个硬约束——每次调用 都是无状态的:模型不「记住」上一次说了什么,所有它需要的信息,必须在这一次 请求里完整给到1

2. 顶层全景

你的代码 模型服务
──────── ────────
组装 messages 列表 ──完整历史──▶ 读列表 → 回一条
▲ │
│ 把回复追加进列表 │
└── 需要调工具?执行,把结果也追加 ──┘

列表长一圈,再发一遍(从头发)

图说:所谓「对话」,就是同一份列表被反复重发、每轮长一截。
模型是纯函数:同样的列表进去,才有可能同样的回答出来。

3. 核心原理

3.1 先想清楚一件事:决定上限的是上下文,不是模型

书里开篇讲了个扎心的现象:大模型标准测试成绩亮眼,一到真实业务就让人失望。 原因不神秘——你的产品架构、业务规则、内部约定,模型根本不知道2。 作者引了 OpenAI 研究员翁家翌的一句话:「人和模型一样,最重要的是 Context」, 他自己说,换一个人,只要有他在 OpenAI 积累的全部背景,也能干他的活3

再往前一步,是本书少见地离开技术的一笔:上下文工程首先是组织问题。 多数团队的关键知识是隐性的——架构决策只在老员工脑子里,业务规则靠口口相传。 要喂给 AI,先得把这些写下来。所以「构建 AI 原生团队」的第一步是一场文档化运动: AI Agent 是一个永远的新员工,你得像给新员工准备入职手册一样准备上下文4

3.2 消息的四种角色

API 层面,上下文就是一个消息列表,每条消息带一个角色:

角色谁在说内容
system开发者行为规则(「你是客服,退款前必须核实订单」)
user用户用户的输入
assistant模型模型的回复:文字、思考、或工具调用请求
tool框架工具执行的结果,用 tool_call_id 关联到是哪次调用5

这四种角色是全部后续章节的舞台:第 04 章讲的提示工程改的是 system, 状态栏借的是 user 的槽位(消息列表里留给某种角色的位置),压缩删的是 tool 与旧轮次。

3.3 主走查:两轮对话,列表怎么长

走查内容与消息结构来自原书的例子(问温哥华时间与天气);工具返回的具体 数值是原书演示 JSON(带名字标签的结构化数据格式)里的示例值。

第 1 轮。 用户问:「现在温哥华几点?」。代码发出的列表只有两条消息: 一条 system(「你是一个助手,可用工具如下:查时间、查天气……」),一条 user (「现在温哥华几点?」)。模型读完,回了一条 assistant 消息——注意, 这条消息里没有给用户的文字,只有一个工具调用请求: 「调 get_current_time,参数 timezone: America/Vancouver」。

框架接手。 代码解析(读出其中的指令)这条请求,真的去调了时间服务,拿到 「2026-08-27 14:32 PDT」。它做两件事:把模型刚才那条 assistant 消息 原样追加进列表,再把工具结果作为一条 tool 消息(带着 tool_call_id: call_abc123)追加进去。现在列表四条。

第 2 次调用。 代码把整个四条列表重新发给模型。模型这次不再要工具, 直接吐出文字:「温哥华现在是下午 2 点 32 分」。列表五条,任务结束。

紧接着用户追问:「那温哥华天气如何?」 关键时刻来了——这次请求必须包含 前一轮的全部历史:system、第一条 user、第一条 assistant(那次工具调用)、 那条 tool 结果、模型的文字回复,再加上新的 user 提问。模型看到旧历史,发起 第二次工具调用(call_def456,查天气),框架执行、追加,第三次调用拿到 天气后给出最终回答6

书里特意圈出三个最容易做错的细节7:

  1. 第二次请求包含第一次的全部历史——因为无状态,少一条模型就「失忆」;
  2. assistant 消息要原样放回——让模型能「看见」自己上一轮做了什么决策;
  3. tool 消息靠 tool_call_id 对号入座——两个工具调用并行时,模型靠这个 字段(数据里带名字的格子)知道哪个结果对应哪次调用(第 01 章那次三路并行换汇,靠的就是它)。

3.4 二十行的循环

把上面的动作写成代码,核心就是这么点(书里给了带错误处理的完整版,骨架如下):

messages = [{"role": "user", "content": 任务}]

while True:
resp = 调模型(messages)
messages.append(resp) # 模型的回复进列表
if 没有工具调用请求: break # 模型认为信息够了
for call in resp.tool_calls:
result = 执行(call) # 真的去干
messages.append(工具结果消息) # 结果进列表
# 回到循环顶,带着变长的列表再调一次模型

整个 Agent 框架的核心逻辑,就是这一个 while 循环加一个判断:有工具调用就执行、 继续;没有就输出、退出。messages 列表每轮都在长——「框架」这个听起来庞大的 东西,它最核心的工作不过是管理这份列表8

但书里在代码注释里钉了一颗钉子:生产代码必须在这里加轮数上限。注释原文说得 直白:Agent 会陷入重复调用同一个工具的死循环,没有上限就会一直烧钱9。 「循环必须有熔断」这件事,第 09 章讲故障恢复时会展开成完整的分级体系。

3.5 上下文的两半:全书最重要的结构事实

盯住 messages 列表看它的增长方式,会发现两半的成长速度完全不同10:

┌─────────────────────────────────┐
│ system 消息 + 工具定义 │ ← 静态前缀:整个对话期间一个字不变
├─────────────────────────────────┤
│ user / assistant / tool 消息 │ ← 轨迹:每轮都变长,只增(平时)不减
└─────────────────────────────────┘

图说:「前面不能动、后面可以压缩」——第 03 章的缓存、
第 05 章的压缩,全部建立在这条分界线上。

静态前缀(系统提示词与工具定义,对话期间保持不变的前半段)与轨迹(随着 交互不断增长的动态消息历史,第 01 章已定义)——这一刀切下去,后续所有技术的 用武之地就定了:想让模型快和省,去压轨迹;想让模型变强,去经营前缀和轨迹的 内容。书里在本章末尾预告了这条线的全部站点:缓存、提示工程、注入防御、 Skills、状态栏、压缩——本组拆解的 02 至 05 章就是按这张地图走的11

3.6 实验:6 亿参数的小模型也能可靠转起来

书里的实验把这套 API 跑在本地一个 0.6B(约 6 亿参数——作为对照,OpenAI 早期的代表模型就叫 GPT,GPT-3 一代有 1750 亿参数,0.6B 是其约三百分之一)的小模型上。

苹果 M2 芯片,生成速度超过每秒 100 个 token(token,即模型处理文本的基本单位: 一个中文字约对应 1-2 个 token,一个英文单词约 1-3 个)——这个词从这里开始全书都要用12

实验还披露了输出的固定顺序:模型先在 <think> 标签里思考,再输出给用户的文字, 最后才是工具调用请求。由此有个实用的工程技巧:工具调用的参数一旦生成完整、 通过校验,就可以立即开始执行,不必等模型把后面的话说完13

这个实验最值得带走的结论是作者的原话:0.6B 的小模型,在合理的提示词设计下, 也能可靠完成工具调用——模型大小重要,但不是唯一决定因素。高端手机已经 跑得动这个量级,端侧(设备本地,不联网)Agent 的时代比多数人预期的近14

4. 作者的判断与证据

书里给了证据的: 0.6B 模型的完整实验(部署、速度、顺序、工具调用可靠性), 以及消息列表逐轮变化的完整 JSON 跟踪——这些是能自己重跑的。

是作者的判断: 「上下文工程首先是组织问题」「AI Agent 是永远的新员工」—— 这部分没有实验支撑,是从创业实践中归纳的主张;但它与「知识锁在老员工脑子里」 的行业常识一致,我们采纳为合理的工程判断,不是被证明的结论。 如果错,会错在: 若某些团队的隐性知识天然无法文档化(全靠师徒传承), 「文档化运动」就不是 AI 原生团队的前置条件。

一个术语必须现在交代(全书都在用,现在不说清后面会乱):作者把 reasoning 统一译作「思考」(模型展开中间推导的过程),把 inference 统译作「推理」(模型的 前向计算与部署运行)。两个英文词直译都是「推理」,不分开会歧义15

5. 边界与局限

  • 书里的示例绑在特定厂商(OpenAI 风格)的调用格式上,换一家厂商字段名要重新 对照——但「四种角色+无状态」这个抽象是通用的;
  • 本章的循环是串行的:一次只处理一批(同轮发出的全部)工具调用。并行执行、事件打断、 后台任务,都留给第 08 章;
  • 「框架的核心就是管理 messages 列表」描述的是最小内核;真实的框架还包着 重试、权限、观测(第 08、09 章),别拿这二十行低估也别高估框架;
  • 温哥华例子里的工具返回值是演示值,别把「14:32」当真实数据。

6. 可带走的

  1. 每次调用无状态:模型不记得任何事,完整历史每轮重发——一切缓存与压缩技术的出发点;
  2. 消息四角色:system 定规则、user 是输入、assistant 是回复、tool 是结果, tool_call_id 负责对号入座;
  3. assistant 消息必须原样放回,模型才能看见自己上轮干了什么;
  4. Agent 内核 = while 循环 + 「有工具调用就执行」——不到二十行;
  5. 生产循环必须加轮数上限,因为 Agent 会死循环调同一个工具;
  6. 上下文 = 静态前缀(不变)+ 轨迹(一直长):前面不能动,后面可以压;
  7. 输出顺序固定:思考 → 文字 → 工具调用;参数完整即可先执行,不必等全文;
  8. 上下文工程先是组织问题:给永远的「新员工」写入职手册;
  9. 小模型+好上下文,强过空有参数量——决定上限的是每个决策点能看到多少、多准的信息。

7. 原文地图

主题原书章原文位置
上下文工程的定义2 上下文工程text/04-ch02.txt:5(搜「上下文工程」)
能力通用但缺背景、组织问题2 上下文工程text/04-ch02.txt:11(搜「标准测试中成绩亮眼」) · text/04-ch02.txt:25(搜「组织问题」)
翁家翌引言2 上下文工程text/04-ch02.txt:31(搜「最重要的是 Context」)
消息四角色与 tool_call_id2 上下文工程text/04-ch02.txt:39(搜「四种角色」) · text/04-ch02.txt:49(搜「tool_call_id」)
单轮调用与无状态2 上下文工程text/04-ch02.txt:84(搜「无状态」)
两轮交互与三个关键细节2 上下文工程text/04-ch02.txt:208(搜「三个关键细节」) · text/04-ch02.txt:210(搜「全部对话历史」) · text/04-ch02.txt:212(搜「原样放回」) · text/04-ch02.txt:214(搜「模型据此知道」)
ReAct 循环的 API 实现2 上下文工程text/04-ch02.txt:228(搜「API 层面的具体实现」)
二十行循环与轮数上限2 上下文工程text/04-ch02.txt:310(搜「while 循环和一个判断」) · text/04-ch02.txt:285(搜「repeating the same tool calls」)
messages 逐轮变化跟踪2 上下文工程text/04-ch02.txt:312(搜「每一轮的变化」)
静态前缀 + 轨迹2 上下文工程text/04-ch02.txt:347(搜「静态前缀」)
实验 2-1:0.6B、token 换算2 上下文工程text/04-ch02.txt:351(搜「本地 LLM 服务部署」) · text/04-ch02.txt:363(搜「100 个 token」)
输出顺序与提前执行2 上下文工程text/04-ch02.txt:377(搜「固定顺序」)
端侧结论2 上下文工程text/04-ch02.txt:385(搜「端侧 Agent 的时代」)
术语约定引言text/02-fm.txt:83(搜「术语约定」)

Footnotes

  1. 出处:「2 上下文工程」第 84 段(text/04-ch02.txt:84,搜「无状态」)。原文:「每次调用都是无状态的,所有模型需要的信息必须在请求的消息列表中完整提供。」

  2. 出处:「2 上下文工程」第 11 段(text/04-ch02.txt:11,搜「标准测试中成绩亮眼」)。

  3. 出处:「2 上下文工程」第 31 段(text/04-ch02.txt:31,搜「最重要的是 Context」)。

  4. 出处:「2 上下文工程」第 25 段(text/04-ch02.txt:25,搜「组织问题」)。「永远的新员工」的类比出自本节前后的论述。

  5. 出处:「2 上下文工程」第 39 段(text/04-ch02.txt:39,搜「四种角色」)与第 49 段(text/04-ch02.txt:49,搜「tool_call_id」)。

  6. 出处:「2 上下文工程」第 86-228 段的完整 JSON 走查(text/04-ch02.txt:86,搜「带工具调用的多轮交互」);循环机制的总结见第 228 段(text/04-ch02.txt:228,搜「API 层面的具体实现」)。

  7. 出处:「2 上下文工程」第 208-214 段(text/04-ch02.txt:208,搜「三个关键细节」;text/04-ch02.txt:210,搜「全部对话历史」;text/04-ch02.txt:212,搜「原样放回」;text/04-ch02.txt:214,搜「关联——模型据此」)。

  8. 出处:「2 上下文工程」第 310 段(text/04-ch02.txt:310,搜「while 循环和一个判断」)。

  9. 出处:「2 上下文工程」第 284-285 段的代码注释(text/04-ch02.txt:285,搜「repeating the same tool calls」)。注释原文:「Production code needs a max_iterations cap here … Agents can get stuck repeating the same tool calls forever」。

  10. 出处:「2 上下文工程」第 341-350 段(text/04-ch02.txt:343,搜「完整构成」;text/04-ch02.txt:347,搜「静态前缀」)。原文:「系统提示词和工具定义构成静态前缀,用户消息、模型回复和工具执行结果构成动态增长的消息历史」。

  11. 出处:「2 上下文工程」第 350 段末(text/04-ch02.txt:349,搜「提示工程」)。原文一口气预告了 KV Cache、提示工程、注入防御、Skills、状态栏与压缩。

  12. 出处:「2 上下文工程」第 351 段(text/04-ch02.txt:351,搜「本地 LLM 服务部署」)与第 363 段(text/04-ch02.txt:363,搜「100 个 token」)。

  13. 出处:「2 上下文工程」第 377 段(text/04-ch02.txt:377,搜「固定顺序」)。

  14. 出处:「2 上下文工程」第 385 段(text/04-ch02.txt:385,搜「端侧 Agent 的时代」)。

  15. 出处:「引言」第 83 段(text/02-fm.txt:83,搜「术语约定」)。