# 17 · 扩展思考的处理：签名、模型绑定，和那些能调一整天的坑

> 扩展思考能显著提升 Agent 解难题的能力，却带来一组独特的坑：思考块必须整段保留、带着不可篡改的签名、而且签名和具体模型绑定。讲清思考块的三条铁律、它和流式/历史/模型回退的相互作用，以及为什么它是最容易让你调一整天的特性。

- 作者：David（道雾轩）
- 专栏：从 LLM 到 Coding Agent（https://daiw.org/manual/llm-to-agent.md）
- 最后更新：2026-08-03
- 原文：https://daiw.org/manual/llm-to-agent/thinking
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 17 · 扩展思考的处理：签名、模型绑定，和那些坑

第六阶段收尾。现代模型有一种特殊能力——**扩展思考**(extended thinking)：在给出答案前，先进行一段可见的、结构化的推理。它能显著提升 Agent 解决难题的能力，但也带来一组**独特到反直觉**的工程坑。

## 一、背景：多了一种你必须原样保管的内容块

开启思考后，模型的响应里会多出一类内容块：**思考块**(`thinking`)。它长这样：

```ts title="响应里的思考块"
{
  role: 'assistant',
  content: [
    {
      type: 'thinking',
      thinking: '让我先分析一下这个 bug……报错在 auth.ts:42,可能是……',
      signature: 'EqoBCkgI...(一长串加密签名)',   // ← 注意这个
    },
    { type: 'text', text: '我找到问题了,是……' },
  ],
}
```

注意那个 `signature`——它是模型对这段思考的**加密签名**，用来证明“这段思考确实是我在这个上下文里生成的、没被篡改过”。**正是这个签名，是所有思考相关的坑的根源。**

## 二、思考块的三条铁律

处理思考块，有三条不容违反的规则。违反任何一条，轻则报错，重则让你对着一个诡异的 400 调一整天。

<Callout type="warn">
  **① 思考块必须整段、原样保留在历史里——包括那串签名，一个字节都不能改。** 你不能“顺手把思考内容精简一下”再放回历史(签名会对不上，API 直接拒)；也不能只留 `text`、把 `thinking` 块丢掉(在一个包含工具调用的思考轨迹里，丢了思考块会破坏轨迹完整性)。**思考块对你是只读的、不透明的——你只负责原样搬运，不许加工。**
</Callout>

<Callout type="warn">
  **② 思考块的签名和“生成它的那个模型”绑定。** 这是最阴险的一条。签名是**特定模型**签发的。如果你把一段带签名的思考块，replay 给**另一个**模型(比如[上一篇](https://daiw.org/manual/llm-to-agent/error-recovery)的模型回退触发了、切到了备用模型)，那个模型不认这个签名，请求当场 400。所以——**一旦发生模型回退，必须先把历史里的思考签名块清理/剥离掉，再喂给新模型。** 这两个特性([模型回退](https://daiw.org/manual/llm-to-agent/error-recovery)和思考)单独看都对，组合起来却会打架，而且症状(一个莫名其妙的 400)完全指不到根因上。
</Callout>

<Callout type="warn">
  **③ 一段“思考轨迹”必须完整保留，直到它彻底结束。** 一次带工具调用的思考(思考 → 发起工具调用 → 拿到结果 → 继续)，这**整条轨迹**里的思考块都得留着，不能中途删。你不能刚思考完、工具还没跑完，就把思考块清掉——那会破坏这条尚未闭合的轨迹。
</Callout>

<Callout type="info">
  这三条规则合起来，可以浓缩成一句话：**思考块是模型“working memory”的一次带签名的快照；你的唯一职责是完整、原样、在正确的生命周期内保管它，别碰它的任何一个字节。** 想清楚这一点，大部分思考相关的坑就都能绕开了。(还有一种 `redacted_thinking` 块——出于安全被加密隐藏的思考，同样只需原样保管即可。)
</Callout>

## 三、和前面几个系统的相互作用

思考不是一个孤立特性，它和我们前面搭的好几个系统都有微妙的相互作用，值得逐一点一下：

- **和[流式](https://daiw.org/manual/llm-to-agent/streaming)**：思考内容也是流式到达的(有自己的 `thinking_delta`)。你可以实时把它展示给用户(让用户看到模型在“想什么”)，但要注意签名是在思考块**结束时**才给的——和[工具参数那个“逐字到达、末尾才完整”](https://daiw.org/manual/llm-to-agent/streaming)的模式如出一辙。
- **和[历史 / 上下文压缩](https://daiw.org/manual/llm-to-agent/context-compaction)**：压缩历史时，思考块要么完整保留、要么整段移除(连同它所在的轨迹)，**绝不能“部分保留”**。半段带签名的思考块是历史里的一颗雷。
- **和[模型回退](https://daiw.org/manual/llm-to-agent/error-recovery)**：如前所述，回退前必须剥离思考签名。这是两个正确特性组合出的一个必须专门处理的交叉点。
- **和 `max_tokens`**：思考也消耗输出 token，而且往往消耗不少。开了思考，你的 `max_tokens` 预算要相应放大，否则容易[被截断](https://daiw.org/manual/llm-to-agent/error-recovery)。

## 四、实现细节：enable、保管、别加工

用起来，开启思考只是请求里加一个配置：

```ts title="开启扩展思考"
const stream = client.messages.stream({
  model, max_tokens: 16000,
  thinking: { type: 'enabled', budget_tokens: 8000 },   // ← 给思考留 8000 token 预算
  messages, tools,
})
```

难的从来不是“开启”，而是**之后对思考块的保管纪律**：

```ts title="保管思考块:原样搬运,不加工"
// ✅ 对的:把完整的 content(含 thinking 块及其签名)原样放回历史
messages.push({ role: 'assistant', content: res.content })

// ❌ 错的:过滤掉 thinking 块,只留 text —— 破坏轨迹,后续可能 400
messages.push({ role: 'assistant', content: res.content.filter(b => b.type !== 'thinking') })

// ❌ 错的:精简 thinking 内容 —— 签名对不上,直接被拒
```

<Callout type="info">
  你会发现，思考的处理再次呼应了[缓存那篇](https://daiw.org/manual/llm-to-agent/prompt-caching)“别原地改要复用的消息”的纪律——**凡是要回流给 API 的东西，保持字节原样**。思考块把这条纪律推到了极致：它不仅要字节原样，还带着一个会“验真”的签名，和一个“认签名的”特定模型。**把“原样搬运、按生命周期保管、别碰字节”当成处理这类内容的默认心法，你就能躲开绝大多数坑。**
</Callout>

## 五、第六阶段总结

**健壮性阶段到此完整。** 这一章的三篇，让 Agent 具备了在真实世界里稳定运行的韧性：

| 篇章 | 韧性来自 |
| --- | --- |
| [15 错误恢复](https://daiw.org/manual/llm-to-agent/error-recovery) | 把出错当常态：退避、回退、恢复、防螺旋 |
| [16 成本追踪](https://daiw.org/manual/llm-to-agent/cost-tracking) | 把花销看清：四类计价、覆盖所有出口、预算硬闸 |
| **17 思考处理** | 把特殊内容保管好：签名、模型绑定、生命周期 |

我们的 Agent 现在自主、快速、可控、可扩展、上下文管理精细、还扛得住真实世界的抖动。**单个 Agent 能做的，它基本都会了。**

最后一个阶段，我们把视野从“一个 Agent”扩展到“一个系统”：当一个 Agent 打不过时，就上一群([子 Agent](https://daiw.org/manual/llm-to-agent/subagents))；当内置工具不够时，就接入外部工具生态([MCP](https://daiw.org/manual/llm-to-agent))；以及，怎么把这一切**持久化**下来，支持关掉再回来接着干([会话历史](https://daiw.org/manual/llm-to-agent))。进入[第七阶段](https://daiw.org/manual/llm-to-agent/subagents)。

---

**本篇对应代码**：配套仓库为 `runAgent` 加入可选的扩展思考配置，并演示思考块的原样保管(`src/agent.ts`)。`git checkout article-17`。
