API 支持:用法因产品而异。
- Messages API:始终开启。保持请求仅追加:将先前的轮次按返回的原样发回,且不要更改
system、tools或先前的消息。 - Managed Agents:始终开启。由 Managed Agents 构建请求。
“Preserved thinking”(保留思考)是较新 Claude 模型的一项特性,用于防范 “distillation”(蒸馏)。它决定模型能否使用您从早期轮次发回的 “thinking block”(思考块)。从 Claude Fable 5.1 开始,当请求中带回 thinking 或 redacted_thinking 块时,API 会检查该块的 signature 是否满足以下两点:
- 模型能够读取该块。 每个模型都能读取自己的思考块以及一组固定的其他模型的思考块。Claude Fable 5.1 可以读取来自 Claude Opus 5 的块,并且在 Claude API 上还可以读取来自 Claude Opus 5.5 的块;Claude Opus 5 和 Claude Opus 5.5 都不能读取来自 Claude Fable 5.1 的块。如果当前模型无法读取某个块,API 会在该请求中丢弃它,且不会报错。请参阅在对话中途切换模型。
- 思考块之前的内容均未更改。 该块之前的顶层
system提示、tools和messages构成它的 “prefix”(前缀)。如果前缀与生成该块时您发送的内容不同,则该块及其后的所有思考块都将无法通过前缀检查。请参阅保持前缀不变。
来自 Claude Sonnet 5.5 和 Claude Haiku 5.5 的思考块还与生成它们的账户绑定。有关此规则的适用范围,请参阅思考块仅限于生成它们的账户。
让您的集成保持仅追加(append-only)模式,以便您发回的思考块能够通过前缀检查。
前缀检查影响哪些用户
前缀检查在 Claude 5.1 及更高版本的模型上运行。其他模型不会运行此检查。如果您的请求由 Claude Code、claude.ai、Claude Managed Agents 或 Claude Agent SDK 构建,或者您的代码在一个会话中保持 system 和 tools 不变并且只向 messages 追加内容,则您无需执行任何操作。如果您从不将思考块(thinking block)发回,则该检查没有任何可检验的内容,模型也不会获得其先前的任何推理。
如果您的集成在同一对话的两次请求之间执行了以下任何操作,请进行检查。每一项都链接到相应的替代做法:
- 重建
system提示:日期、模式标志、重新读取的项目说明,或在第一轮之后才连接的插件或 MCP 服务器 - 重新渲染第一条用户消息中的上下文
- 清除或缩短旧的工具结果,或重新编码旧图像
- 在客户端总结或丢弃旧轮次,同时保留最近轮次及其思考
- 在
tools中添加、删除或编辑条目 - 向用户轮次添加提醒,之后又将其删除或改写
- 丢弃部分
thinking块而保留之后的块,或删除它们后又恢复这些块 - 从模板重建已保存的会话,而不是重放实际发送过的内容
使用您自己的 API 密钥运行时没有出现错误,并不能证明您的代码保持了前缀完整。当请求未设置 prefix_mismatch_behavior 时,在 未设置 prefix_mismatch_behavior 时 中的表格显示不会报错的所有情况下,未通过检查的块都会在不报错的情况下到达模型。使用自己的 API 密钥运行您工具的用户可能会收到 400 错误,即使您没有收到。要从您的账户中看到该错误,请在测试请求中将 prefix_mismatch_behavior 设置为 "error",如 检查您的代码是否编辑了前缀 所示。
如果您的集成将一个账户中的 Claude Sonnet 5.5 或 Claude Haiku 5.5 思考块放入另一个账户发出的请求中,也请进行检查。请参阅思考块仅限于生成它们的账户。
在对话中途切换模型
Claude Fable 5.1 和 Claude Mythos 5.1 可以读取彼此以及较早 Claude 模型生成的思考块。没有其他模型能读取来自 Claude Fable 5.1 或 Claude Mythos 5.1 的思考块。
Claude Opus 5.5 可以读取来自 Claude Opus 5、更早的 Opus、Sonnet 和 Haiku 模型的思考块,在 Claude API 和 Claude Models on Google Cloud (Google managed) 上还可以读取来自 Claude Sonnet 5.5 和 Claude Haiku 5.5 的思考块,但无法读取来自 Claude Fable 或 Claude Mythos 模型的思考块。在 Claude API 上,Claude Fable 5.1 和 Claude Mythos 5.1 可以读取来自 Claude Opus 5.5 的思考块;其他模型都不能。因此,从 Claude Opus 5 切换到 Claude Opus 5.5 的对话会保留其推理,在 Claude API 上从 Claude Opus 5.5 升级到 Claude Fable 5.1 或 Claude Mythos 5.1 的对话也是如此。而从 Claude Fable 5.1 或 Claude Mythos 5.1 切换到 Claude Opus 5.5,或从 Claude Opus 5.5 切换到这两个模型以外的任何模型的对话,在切换后的轮次中将不带有前一个模型的推理。如本节后文所述,这些块会被丢弃,而不是被拒绝。
Claude Sonnet 5.5 可以读取来自 Claude Sonnet 5、Claude Opus 4.8、Claude Haiku 4.5 及更早模型的思考块,在 Claude API 和 Claude Models on Google Cloud (Google managed) 上还可以读取来自 Claude Haiku 5.5 的思考块,但无法读取来自 Claude Opus 5、Claude Opus 5.5 或任何 Claude Fable 或 Claude Mythos 模型的思考块。在这两个平台上,Claude Opus 5.5 可以读取来自 Claude Sonnet 5.5 的思考块;其他模型都不能。因此,从 Claude Sonnet 5 切换到 Claude Sonnet 5.5 的对话会保留其推理,在这些平台上从 Claude Sonnet 5.5 升级到 Claude Opus 5.5 的对话也是如此。而从 Claude Opus 5、Claude Opus 5.5 或某个 Claude Fable 或 Claude Mythos 模型切换到 Claude Sonnet 5.5 的对话,在切换后的轮次中将不带有前一个模型的推理。从 Claude Sonnet 5.5 切换到其他模型的任何情况也是如此,例如服务器端回退到 Claude Sonnet 5。
Claude Haiku 5.5 可以读取来自 Claude Sonnet 5、Claude Opus 4.8、Claude Haiku 4.5 及更早模型的思考块,但不能读取来自 Claude Opus 5、Claude Opus 5.5、Claude Sonnet 5.5 或任何 Claude Fable 或 Claude Mythos 模型的思考块。在 Claude API 和 Claude Models on Google Cloud (Google managed) 上,Claude Opus 5.5 和 Claude Sonnet 5.5 可以读取来自 Claude Haiku 5.5 的思考块;其他模型都不能。因此,从 Claude Haiku 4.5 切换到 Claude Haiku 5.5 的对话会保留其推理,在 Claude API 和 Claude Models on Google Cloud (Google managed) 上从 Claude Haiku 5.5 升级到 Claude Opus 5.5 或 Claude Sonnet 5.5 的对话也是如此。从 Claude Opus 5、Claude Opus 5.5、Claude Sonnet 5.5 或某个 Claude Fable 或 Claude Mythos 模型切换到 Claude Haiku 5.5 的对话,在切换后的轮次中将不带有前一个模型的推理,从 Claude Haiku 5.5 切换出去的任何其他情况也是如此。
- 从较早模型切换到 Claude Fable 5.1,或在 Claude API 上从 Claude Opus 5.5 切换到 Claude Fable 5.1 的对话会保留其推理。 较早模型的思考块仍然可读,因此模型从切换后的第一轮起就会照常思考。
- 从 Claude Fable 5.1 切换到 Claude Mythos 5.1 以外任何模型的对话,会在该请求中丢失 Claude Fable 5.1 的推理。 这种情况发生在路由器将某一轮发送给更便宜的模型时、分类器拒绝回退之后,或服务器端回退期间。API 会在提示到达模型之前删除不可读的块。这些块不计费,也不计入
input_tokens。
请在每个请求中持续发送完整历史记录(包括思考块),让 API 丢弃当前模型无法读取的内容。API 从不编辑您的 messages 数组,因此被丢弃的块仍保留在您的历史记录中。当相同的历史记录重新发送给 Claude Fable 5.1 时,它的块会再次可读。只有当您的客户端自行删除这些块时,推理才会永久丢失,例如在切换模型时剥离思考的工具框架(harness),或根据每个模型所用内容重建历史记录的工具框架。
使用 thinking-binding-controls-2026-08-01 beta 标头时,响应会在顶层 input_transformations 数组中列出每个被丢弃的块,并带有 reason: "model_binding_mismatch":
{
"input_transformations": [
{
"type": "thinking_dropped",
"path": "messages.3.content.0",
"reason": "model_binding_mismatch"
}
]
}
如果不使用该标头,丢弃会静默进行。此条目并不表示您的集成存在错误,prefix_mismatch_behavior 对其也没有影响:当前模型无法读取的块总是会被丢弃。
思考块仅限于生成它们的账户
Claude Sonnet 5.5 或 Claude Haiku 5.5 生成的思考块只能在生成它们的账户或与之关联的账户中使用。当其他账户发送这些块之一时,API 会在模型看到之前丢弃该块,请求仍会成功。模型在回答时不会带有被丢弃块中的推理。来自更早模型的块不受影响。
在 Claude API 和 Claude Models on Google Cloud (Google managed) 上,使用 thinking-binding-controls-2026-08-01 beta 标头时,响应会在 input_transformations 中以 thinking_dropped 条目列出每个被丢弃的块,并带有 reason: "organization_binding_mismatch"。不使用该标头时,丢弃是静默的。
如果生成某个思考块的另一个账户信誉已不再良好,API 也可能会省略该思考块。这适用于发往 Claude Fable 和 Claude Mythos 模型、Claude Opus 5、Claude Opus 5.5、Claude Sonnet 5.5 和 Claude Haiku 5.5 的请求。请求仍会成功,模型会在没有该块推理的情况下作答。
保持前缀不变
在 Claude 5.1 及更高版本的模型上,只有当您在思考块之前发送的所有内容在后续请求中都保持不变时,该思考块才保持有效。被检查的前缀包含三个部分:
- 顶层
system提示 tools集合- 该块之前的每条
message
使用服务器端压缩时,被检查的前缀从最近的压缩块开始。
这三个字段之外的请求参数,例如 effort、max_tokens、output_config、tool_choice 和 metadata,不属于前缀检查的范围,cache_control 标记也不属于。哪些算作编辑中有完整列表。
更早的思考块不在前缀中,但每个思考块都会跨轮次记录在它之前的是哪个思考块。您可以删除最旧的思考块(从历史记录的开头)、最新的思考块(从末尾),或将其全部删除。会导致失败的是中间出现空缺:您保留的思考块必须是原始序列中连续不断的一段,因此从中间删除一个块会使其后的思考块失效。一旦删除某个块,就不要再放回去。将其放回会使在它缺失期间生成的思考块失效。
在会话期间保持 system 和 tools 固定,并将 messages 视为仅追加。同样的做法也能为提示缓存保持前缀稳定:大多数会使思考失效的编辑也会让缓存重新开始。
API 如何处理无效的块
在这些模型上,“prefix check”(前缀检查)始终会运行。设置 thinking.block_binding.prefix_mismatch_behavior 来选择未通过检查的块会如何处理。该字段和 input_transformations 数组需要 thinking-binding-controls-2026-08-01 beta 标头。设置不匹配行为并读取 input_transformations 展示了每种 SDK 中的请求。
"error": API 以 400invalid_request_error拒绝请求,并指出第一个未通过检查的块。"drop_block": API 会丢弃每个未通过检查的块及其之后的所有思考块,请求会成功。被丢弃的块不计费。模型在该轮次中作答时不会使用被丢弃块中的推理,并且“prompt cache”(提示缓存)会从编辑处重新开始。响应会在input_transformations中(流式传输时位于message_start事件上)列出每个被丢弃的块,并带有reason: "prefix_binding_mismatch"。这是一种临时措施,会带来输出令牌成本。
未设置 prefix_mismatch_behavior 时
未设置该字段的请求会得到下表中的结果:
| 模型 | 2026 年 8 月 31 日或之后创建的账户 | 2026 年 8 月 31 日之前创建的账户 |
|---|---|---|
| Claude Fable 5.1 | 400 错误 | 无错误(未通过检查的块会传递给模型) |
| Claude Mythos 5.1 | 400 错误 | 400 错误 |
| Claude Opus 5.5 | 400 错误 | 无错误(未通过检查的块会传递给模型) |
| Claude Sonnet 5.5 | 400 错误 | 无错误(未通过检查的块会传递给模型) |
| Claude Haiku 5.5 | 400 错误 | 无错误(未通过检查的块会传递给模型) |
截止时间为 2026 年 8 月 31 日 00:00 UTC,在 Claude API 和云平台上相同。Claude Mythos 5.1 这一行适用于 Claude API、Claude Models on Google Cloud (Google managed) 和 Microsoft Foundry。此表仅涵盖前缀检查:对于任一列中的账户,模型无法读取的块都会被丢弃。
传递给模型的未通过检查的块会像任何其他输入一样被读取和计费。使用 beta 标头时,响应会在 input_transformations 中将每个此类块列为 thinking_mismatch_allowed。
在 Message Batches API 中,未设置该字段的条目不会失败:在表中显示 400 错误的情况下,API 会改为丢弃未通过检查的块。将该字段设置为 "error" 并发送了未通过检查的块的条目会解析为 errored。
要确定您的账户属于哪一列,请取一个包含思考块的 Claude Fable 5.1 对话,并更改该块之前的某些内容。在不带 beta 标头或 block_binding 字段的情况下将其发送给 Claude Fable 5.1。如果返回指出该标头的 400 错误,则表示您的请求会得到“2026 年 8 月 31 日或之后”列中的结果。如果返回 200 响应,则表示它们会得到“2026 年 8 月 31 日之前”列中的结果。要确认 200 结果,请带上 beta 标头但不带 block_binding 再次发送相同的请求:响应会在 input_transformations 中将您编辑处之后的每个思考块列为 thinking_mismatch_allowed。
400 错误
消息的开头为:
messages.1.content.0: Invalid `signature` in `thinking` block. The block is bound to a different conversation. Remove the block, or set `thinking.block_binding.prefix_mismatch_behavior` to "drop_block".
如果请求未发送 beta 标头,消息会继续:
That setting requires the `thinking-binding-controls-2026-08-01` value in the `anthropic-beta` header.
它通常以一句说明更改内容的句子结尾,例如 system 提示或 tools 列表与创建该块时不同。有关该句子可能指出的内容,请参阅“思考故障排除”中的 400 错误提示思考块签名无效。
被篡改或无法解密的签名是另一种失败。它始终返回 400 错误(Invalid `signature` in `thinking` block,不带有关对话的句子),并且 prefix_mismatch_behavior 不适用于它。
"drop_block" 的输出令牌成本
警告
"drop_block"能让请求持续成功,但它不会修复导致不匹配的编辑,而且它有成本。丢弃块可能会显著增加输出令牌,尤其是在每个轮次都丢弃块时;在后续轮次基于先前推理的任务上,输出质量可能会发生变化。请将"drop_block"作为修复编辑期间的临时措施,而不是长期设置。
当 Claude 无法读取较早轮次中的推理时,它会重新生成该推理,因此丢弃思考块的请求会生成更多输出令牌,并且可能需要更长时间才能响应。有意进行的一次性丢弃(例如在客户端压缩边界处)成本很小。每个轮次都丢弃的成本更高,具体高多少取决于您的 harness:它编辑前缀的频率,以及每个轮次在多大程度上基于先前的推理。
在依赖 "drop_block" 之前,请在您自己的工作负载上运行评估以衡量增加的幅度。如果增幅很小,"drop_block" 可以在您修复编辑期间让请求持续成功。无论哪种情况,长期的解决办法都是找到并替换该编辑:统计每个会话中 input_transformations 含有 prefix_binding_mismatch 条目的响应,针对它们设置警报,并用在不编辑前缀的情况下进行更改中对应的模式替换每个编辑。
在代码中处理错误
当请求收到 400 错误时,不要重新发送相同的请求体:它每次都会以同样的方式失败。作为临时措施,请带上 beta 标头和 prefix_mismatch_behavior: "drop_block" 重试一次("drop_block" 可能会增加输出令牌),并将该选择与会话一起存储,以便之后的每个请求(包括重启之后)也发送它。如果您无法发送 beta 标头或 block_binding(在 Claude Sonnet 5.5 和 Claude Haiku 5.5 上,block_binding 仅适用于 thinking: {"type": "adaptive"}),请移除错误中指出的块以及之后所有的 thinking 和 redacted_thinking 块,将它们排除在外,然后继续。之后再修复导致不匹配的编辑。
设置不匹配行为并读取 input_transformations
thinking-binding-controls-2026-08-01 beta 标头会添加:
- 每个响应上的顶层
input_transformations数组 thinking配置上的block_binding对象,其唯一字段为prefix_mismatch_behavior
block_binding 可与 thinking.type: "adaptive" 和 thinking.type: "enabled" 一起使用。在 Claude Sonnet 5.5 和 Claude Haiku 5.5 上,block_binding 仅适用于 thinking: {"type": "adaptive"}。在 Claude Sonnet 5.5 上将其与 thinking: {"type": "between_tools"} 一起发送,或在 Claude Haiku 5.5 上将其与 thinking: {"type": "disabled"} 一起发送,都会返回 400 错误。在没有 beta 标头的情况下发送它会返回 400 错误,其消息以 block_binding: Extra inputs are not permitted 结尾。不运行前缀检查的模型会接受该对象,并且只报告因模型无法读取而被丢弃的块,因此同一个请求体可以跨模型使用。API 参考将前缀检查称为对话检查。
以下请求选择丢弃而不是拒绝。在第一轮中没有需要重放的内容,因此 input_transformations 返回为空:
cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: thinking-binding-controls-2026-08-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-fable-5-1",
"max_tokens": 16000,
"thinking": {
"type": "adaptive",
"block_binding": {
"prefix_mismatch_behavior": "drop_block"
}
},
"messages": [
{
"role": "user",
"content": "What is the greatest common divisor of 1071 and 462?"
}
]
}'
CLI
ant beta:messages create --beta thinking-binding-controls-2026-08-01 \
--format yaml <<'YAML'
model: claude-fable-5-1
max_tokens: 16000
thinking:
type: adaptive
block_binding:
prefix_mismatch_behavior: drop_block
messages:
- role: user
content: What is the greatest common divisor of 1071 and 462?
YAML
Python
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-fable-5-1",
max_tokens=16000,
thinking={
"type": "adaptive",
"block_binding": {"prefix_mismatch_behavior": "drop_block"},
},
messages=[
{
"role": "user",
"content": "What is the greatest common divisor of 1071 and 462?",
}
],
betas=["thinking-binding-controls-2026-08-01"],
)
for block in response.content:
if block.type == "text":
print(block.text)
print(f"Input transformations: {len(response.input_transformations or [])}")
TypeScript
const client = new Anthropic();
const response = await client.beta.messages.create({
model: "claude-fable-5-1",
max_tokens: 16000,
thinking: {
type: "adaptive",
block_binding: { prefix_mismatch_behavior: "drop_block" }
},
messages: [
{ role: "user", content: "What is the greatest common divisor of 1071 and 462?" }
],
betas: ["thinking-binding-controls-2026-08-01"]
});
for (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}
console.log(`Input transformations: ${response.input_transformations?.length ?? 0}`);
C#
AnthropicClient client = new();
var response = await client.Beta.Messages.Create(
new()
{
Model = Model.ClaudeFable5_1,
MaxTokens = 16000,
Thinking = new BetaThinkingConfigAdaptive
{
BlockBinding = new()
{
PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.DropBlock,
},
},
Messages =
[
new BetaUserMessageParam
{
Content = "What is the greatest common divisor of 1071 and 462?",
},
],
Betas = [AnthropicBeta.ThinkingBindingControls2026_08_01],
}
);
foreach (var block in response.Content)
{
if (block.TryPickText(out var textBlock))
{
Console.WriteLine(textBlock.Text);
}
}
Console.WriteLine($"Input transformations: {response.InputTransformations?.Count ?? 0}");
Go
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeFable5_1,
MaxTokens: 16000,
Thinking: anthropic.BetaThinkingConfigParamUnion{
OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{
BlockBinding: anthropic.BetaThinkingBlockBindingParam{
PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorDropBlock,
},
},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What is the greatest common divisor of 1071 and 462?")),
},
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaThinkingBindingControls2026_08_01},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
fmt.Println(textBlock.Text)
}
}
fmt.Printf("Input transformations: %d\n", len(response.InputTransformations))
Java
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaThinkingBlockBinding;
import com.anthropic.models.beta.messages.BetaThinkingConfigAdaptive;
import com.anthropic.models.beta.messages.BetaThinkingPrefixMismatchBehavior;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_FABLE_5_1)
.maxTokens(16000L)
.addBeta(AnthropicBeta.THINKING_BINDING_CONTROLS_2026_08_01)
.thinking(BetaThinkingConfigAdaptive.builder()
.blockBinding(BetaThinkingBlockBinding.builder()
.prefixMismatchBehavior(BetaThinkingPrefixMismatchBehavior.DROP_BLOCK)
.build())
.build())
.addUserMessage("What is the greatest common divisor of 1071 and 462?")
.build();
BetaMessage response = client.beta().messages().create(params);
response.content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> IO.println(textBlock.text()));
IO.println("Input transformations: "
+ response.inputTransformations().map(List::size).orElse(0));
}
PHP
use Anthropic\Beta\AnthropicBeta;
use Anthropic\Beta\Messages\BetaThinkingBlockBinding;
use Anthropic\Beta\Messages\BetaThinkingConfigAdaptive;
use Anthropic\Beta\Messages\BetaThinkingPrefixMismatchBehavior;
use Anthropic\Client;
$client = new Client();
$response = $client->beta->messages->create(
model: 'claude-fable-5-1',
maxTokens: 16000,
thinking: BetaThinkingConfigAdaptive::with(
blockBinding: BetaThinkingBlockBinding::with(
prefixMismatchBehavior: BetaThinkingPrefixMismatchBehavior::DROP_BLOCK,
),
),
messages: [
['role' => 'user', 'content' => 'What is the greatest common divisor of 1071 and 462?'],
],
betas: [AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01],
);
foreach ($response->content as $block) {
if ($block->type === 'text') {
echo $block->text, PHP_EOL;
}
}
echo 'Input transformations: ', count($response->inputTransformations ?? []), PHP_EOL;
Ruby
client = Anthropic::Client.new
response = client.beta.messages.create(
model: Anthropic::Model::CLAUDE_FABLE_5_1,
max_tokens: 16_000,
thinking: {
type: "adaptive",
block_binding: {prefix_mismatch_behavior: "drop_block"}
},
messages: [
{role: "user", content: "What is the greatest common divisor of 1071 and 462?"}
],
betas: [Anthropic::AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01]
)
response.content.each do |block|
puts block.text if block.type == :text
end
puts "Input transformations: #{response.input_transformations&.length || 0}"
Output
The greatest common divisor of 1071 and 462 is 21.
Input transformations: 0
在 beta 标头下,支持思考的模型返回的每个响应都带有 input_transformations。每个条目通过其 path(例如 messages.1.content.0)指明一个思考块,并给出 reason。条目有两种类型:
thinking_dropped: API 会在模型读取该块之前将其丢弃,且该块不计费。reason为prefix_binding_mismatch或model_binding_mismatch(请参阅在对话中途切换模型)。reason也可以是organization_binding_mismatch,表示该块来自另一个账户(请参阅思考块始终归属于生成它们的账户)。thinking_mismatch_allowed: 该块未通过前缀检查,但 API 允许其在本次请求中通过,因此该块会原样传递给模型并计费。reason始终为prefix_binding_mismatch。此条目仅出现在未设置prefix_mismatch_behavior且未设置prefix_mismatch_behavior时中的表格显示不会报错的请求中。设置了该字段的请求永远不会收到此条目。
当没有块被丢弃且没有块未通过前缀检查时,该数组为空。请忽略您无法识别其 type 或 reason 的条目,因为后续的检查会添加新的值。
在进行“streaming”(流式传输)时,该数组会出现在 message_start 事件的 message 对象上(参见流式传输)。在流式传输过程中发生服务器端回退后,最终的 message_delta 事件会再次携带该数组,其中包含实际提供服务的模型的条目。对于消息批处理,请参阅未设置 prefix_mismatch_behavior 时。令牌计数端点会运行相同的前缀检查,并可能返回相同的 400 错误。
哪些算作编辑
每一行都将一个请求与其前一个请求进行比较。“后续思考块”是指历史记录中已存在的、位于已更改内容之后的思考块:
| 请求之间的更改 | 后续思考块 |
|---|---|
| 在末尾追加消息 | 有效 |
添加一个带有 defer_loading: true 且尚未被任何内容引用的工具 |
有效 |
从历史记录的开头或末尾删除 thinking 块,或全部删除 |
有效(模型会失去该推理) |
更改 model、system、tools 和 messages 之外的任何请求参数(effort、max_tokens、output_config、tool_choice、metadata、thinking.display 等) |
有效 |
更改 model |
对新模型可以读取的块有效;API 会丢弃其他块且不报错(请参阅在对话中途切换模型) |
添加、移动或删除 cache_control 标记 |
有效 |
| 返回相同字节的轮换签名 URL | 有效 |
| 服务器端压缩或上下文编辑删除或替换内容 | 有效(检查比较的是您发送的内容,而不是服务器编辑后的副本) |
| 保留在原位的已清除轮次范围系统消息 | 有效 |
编辑、重新排序或删除任何更早的 user、assistant 或 system 消息 |
无效,除非在保留思考的有效条件下,由按需压缩生成的签名块替换其所汇总的消息 |
| 使用更改后的值重新渲染您放在第一条用户消息中的上下文 | 所有思考块均无效 |
清除或缩短更早的 tool_result、重新编码更早的图像,或更改更早的 tool_use 输入 |
之后的所有思考块均无效 |
| 向更早的用户轮次添加文本块,或删除您上次添加的文本块 | 无效 |
更改顶层 system 字符串或块 |
无效 |
在 tools 中添加、删除、重命名或编辑工具 |
无效 |
从历史记录中间删除 thinking 块并保留后面的块 |
之后的所有思考块均无效 |
放回您在更早请求中删除的 thinking 块 |
在它缺失期间生成的思考块无效 |
| 在下一次请求时返回不同字节的图像或文档 URL | 无效 |
| 同一条轮次范围消息在后续请求中被删除或改写 | 无效 |
检查您的代码是否编辑了前缀
让 Claude Code 为您检查代码
/claude-api preserved-thinking-migration
如需手动检查,首先比较您发送的内容。捕获您的集成在几个正常轮次中发送的请求体,其中包括一次压缩或一次工具更改。对于每一对连续请求,比较 system、tools 以及它们共有的 messages。除了新追加的轮次之外,它们应当完全相同。
然后,通过 API 进行确认。添加 thinking-binding-controls-2026-08-01 beta 标头,将 prefix_mismatch_behavior 设置为 "drop_block",并在 claude-fable-5-1 上通过您的集成运行一个正常的多轮会话。以下示例按照您的集成应有的方式运行两轮:messages 只增不减,每个助手轮次都完全按照 API 返回的样子(包括 thinking 块)发回,并且每个请求都设置了 block_binding。每轮结束后,它会打印响应中 thinking 块的数量以及被丢弃块的数量:
cURL
此工作流需要的逻辑超出了单个 shell 命令所能实现的范围。请使用 Claude SDK 运行它。
CLI
此工作流需要的逻辑超出了单个 shell 命令所能实现的范围。请使用 Claude SDK 运行它。
Python
client = anthropic.Anthropic()
user_turns = [
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?",
]
# messages 随轮次增长:每个 assistant 轮次均按返回时的原样传回
messages = []
for user_turn in user_turns:
messages.append({"role": "user", "content": user_turn})
response = client.beta.messages.create(
model="claude-fable-5-1",
max_tokens=16000,
thinking={
"type": "adaptive",
"block_binding": {"prefix_mismatch_behavior": "drop_block"},
},
messages=messages,
betas=["thinking-binding-controls-2026-08-01"],
)
messages.append({"role": "assistant", "content": response.content})
thinking_blocks = sum(block.type == "thinking" for block in response.content)
dropped = len(response.input_transformations or [])
print(f"thinking blocks: {thinking_blocks}, dropped: {dropped}")
TypeScript
const client = new Anthropic();
const userTurns = [
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?"
];
// messages 随轮次增长:每个 assistant 轮次均按返回时的原样传回
const messages: Anthropic.Beta.BetaMessageParam[] = [];
for (const userTurn of userTurns) {
messages.push({ role: "user", content: userTurn });
const response = await client.beta.messages.create({
model: "claude-fable-5-1",
max_tokens: 16000,
thinking: {
type: "adaptive",
block_binding: { prefix_mismatch_behavior: "drop_block" }
},
messages,
betas: ["thinking-binding-controls-2026-08-01"]
});
messages.push({ role: "assistant", content: response.content });
const thinkingBlocks = response.content.filter((block) => block.type === "thinking");
const dropped = response.input_transformations ?? [];
console.log(`thinking blocks: ${thinkingBlocks.length}, dropped: ${dropped.length}`);
}
C#
AnthropicClient client = new();
string[] userTurns =
[
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?",
];
// messages 随轮次增长:每个 assistant 轮次都按返回时的原样回传
List<BetaMessageParam> messages = [];
foreach (var userTurn in userTurns)
{
messages.Add(new BetaUserMessageParam { Content = userTurn });
var response = await client.Beta.Messages.Create(
new()
{
Model = Model.ClaudeFable5_1,
MaxTokens = 16000,
Thinking = new BetaThinkingConfigAdaptive
{
BlockBinding = new()
{
PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.DropBlock,
},
},
Messages = messages,
Betas = [AnthropicBeta.ThinkingBindingControls2026_08_01],
}
);
messages.Add(new BetaAssistantMessageParam
{
Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
});
var thinkingBlocks = response.Content.Count(block => block.TryPickThinking(out _));
var dropped = response.InputTransformations?.Count ?? 0;
Console.WriteLine($"thinking blocks: {thinkingBlocks}, dropped: {dropped}");
}
Go
client := anthropic.NewClient()
userTurns := []string{
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?",
}
// messages 随轮次增长:每个 assistant 轮次均按返回时的原样传回
messages := []anthropic.BetaMessageParam{}
for _, userTurn := range userTurns {
messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(userTurn)))
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeFable5_1,
MaxTokens: 16000,
Thinking: anthropic.BetaThinkingConfigParamUnion{
OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{
BlockBinding: anthropic.BetaThinkingBlockBindingParam{
PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorDropBlock,
},
},
},
Messages: messages,
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaThinkingBindingControls2026_08_01},
})
if err != nil {
log.Fatal(err)
}
messages = append(messages, response.ToParam())
thinkingBlocks := 0
for _, block := range response.Content {
if block.Type == "thinking" {
thinkingBlocks++
}
}
fmt.Printf("thinking blocks: %d, dropped: %d\n", thinkingBlocks, len(response.InputTransformations))
}
Java
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.beta.messages.BetaContentBlock;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaThinkingBlockBinding;
import com.anthropic.models.beta.messages.BetaThinkingConfigAdaptive;
import com.anthropic.models.beta.messages.BetaThinkingPrefixMismatchBehavior;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
List<String> userTurns = List.of(
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?");
// 构建器的消息列表随轮次增长:每个 assistant 轮次均按返回时的原样回传
MessageCreateParams.Builder conversation = MessageCreateParams.builder()
.model(Model.CLAUDE_FABLE_5_1)
.maxTokens(16000L)
.thinking(BetaThinkingConfigAdaptive.builder()
.blockBinding(BetaThinkingBlockBinding.builder()
.prefixMismatchBehavior(BetaThinkingPrefixMismatchBehavior.DROP_BLOCK)
.build())
.build())
.addBeta(AnthropicBeta.THINKING_BINDING_CONTROLS_2026_08_01);
for (String userTurn : userTurns) {
conversation.addUserMessage(userTurn);
BetaMessage response = client.beta().messages().create(conversation.build());
conversation.addMessage(response);
long thinkingBlocks = response.content().stream()
.filter(BetaContentBlock::isThinking)
.count();
int dropped = response.inputTransformations().map(List::size).orElse(0);
IO.println("thinking blocks: " + thinkingBlocks + ", dropped: " + dropped);
}
}
PHP
use Anthropic\Beta\AnthropicBeta;
use Anthropic\Beta\Messages\BetaThinkingBlockBinding;
use Anthropic\Beta\Messages\BetaThinkingConfigAdaptive;
use Anthropic\Beta\Messages\BetaThinkingPrefixMismatchBehavior;
use Anthropic\Client;
$client = new Client();
$userTurns = [
'How many positive integers below 500 have exactly 6 positive divisors?',
'How many of those are odd?',
];
// $messages 随轮次增长:每个 assistant 轮次都按返回时的原样传回
$messages = [];
foreach ($userTurns as $userTurn) {
$messages[] = ['role' => 'user', 'content' => $userTurn];
$response = $client->beta->messages->create(
model: 'claude-fable-5-1',
maxTokens: 16000,
thinking: BetaThinkingConfigAdaptive::with(
blockBinding: BetaThinkingBlockBinding::with(
prefixMismatchBehavior: BetaThinkingPrefixMismatchBehavior::DROP_BLOCK,
),
),
messages: $messages,
betas: [AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01],
);
$messages[] = ['role' => 'assistant', 'content' => $response->content];
$thinkingBlocks = array_filter($response->content, fn ($block) => $block->type === 'thinking');
$dropped = $response->inputTransformations ?? [];
echo 'thinking blocks: ', count($thinkingBlocks), ', dropped: ', count($dropped), PHP_EOL;
}
Ruby
client = Anthropic::Client.new
user_turns = [
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?"
]
# messages 随轮次增长:每个 assistant 轮次均按返回时的原样传回
messages = []
user_turns.each do |user_turn|
messages << {role: "user", content: user_turn}
response = client.beta.messages.create(
model: Anthropic::Model::CLAUDE_FABLE_5_1,
max_tokens: 16_000,
thinking: {
type: "adaptive",
block_binding: {prefix_mismatch_behavior: "drop_block"}
},
messages: messages,
betas: [Anthropic::AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01]
)
messages << {role: "assistant", content: response.content}
thinking_blocks = response.content.count { |block| block.type == :thinking }
dropped = (response.input_transformations || []).length
puts "thinking blocks: #{thinking_blocks}, dropped: #{dropped}"
end
Output
thinking blocks: 1, dropped: 0
thinking blocks: 1, dropped: 0
两轮都没有丢弃任何块,因为之前的内容没有任何更改。请检查第一个响应是否包含 thinking 块。使用自适应思考时,有些响应不包含思考块。如果会话中没有任何响应包含思考块,就没有可检查的内容,无论您更改什么,丢弃计数都为 0,因此请重新运行该示例。
请在您自己集成的每一轮中记录 input_transformations。当 API 丢弃某个块时,条目如下所示:
{
"input_transformations": [
{
"type": "thinking_dropped",
"path": "messages.1.content.0",
"reason": "prefix_binding_mismatch"
}
]
}
- 在包含
thinking块的会话中每一轮都为空: 您的集成保持了前缀完整。 reason: "prefix_binding_mismatch": 自path处的块生成以来,该块之前的某些内容发生了更改。请比较截至该轮的system、tools和messages以找出它,或使用"error"重新发送请求:400 错误通常以一句说明更改内容的话结尾。然后在在不编辑前缀的情况下进行更改中找到对应的替代做法。reason: "model_binding_mismatch": 对话切换到了一个无法读取先前模型思考块的模型。这不是前缀编辑。请参阅在对话中途切换模型。reason: "organization_binding_mismatch": 该块来自另一个账户。请参阅思考块仅限于生成它们的账户。
要有意观察失败情况,请在前面的示例中发送第三轮,并仅在该请求中添加一个 system 提示,使其与前两次(没有 system 提示的)请求不同。使用 "drop_block" 时,丢弃计数不再为 0:响应会为历史记录中的每个思考块提供一个条目,每个条目都带有 reason: "prefix_binding_mismatch"。使用 "error" 时,请求会返回API 如何处理失效的块中所述的 400 错误,其最后一句会指出 system 提示。如果计数仍为 0,说明没有可检查的内容:请确认模型是 claude-fable-5-1、请求设置了 block_binding、您发送的历史记录包含 thinking 块,并且前两次请求没有 system 提示。
两个普通轮次很少能暴露问题。请针对以下每种情况运行一个会话,并设置 "error",以便回归问题会导致您的 CI 失败:
- 第一次客户端压缩或裁剪
- 在第一轮之后才连接的工具、插件或 MCP 服务器
- 模式或指令的更改
- 较长的工具循环(如果您会添加提醒或缩短旧的工具结果)
- 切换到另一个模型再切换回来
- 保存、重启,并在之后的某个日期恢复
在未设置 prefix_mismatch_behavior 时中的表格显示不会出错的情况下,您也可以观察生产流量:发送 beta 标头,省略 block_binding,并记录 input_transformations。仅发送该标头不会改变模型接收到的内容。位于您所编辑内容之后的每个思考块都会未通过检查,并各自获得一个 thinking_mismatch_allowed 条目,其 path 和 reason 字段与 thinking_dropped 条目相同。位于编辑之前的块会通过检查。对 system 或 tools 的编辑位于所有块之前,因此请求中的每个思考块都会未通过检查。一个条目如下所示:
{
"input_transformations": [
{
"type": "thinking_mismatch_allowed",
"path": "messages.1.content.0",
"reason": "prefix_binding_mismatch"
}
]
}
请从在 2026 年 8 月 31 日之前创建的账户运行以下示例,因为对于在该日期当天或之后创建的账户,API 会以 400 错误拒绝其第三个请求。它发送标头但不带 block_binding,并通过第三个请求扩展前面的会话,该请求添加了一个系统提示,有意更改前缀。每轮结束后,它会打印 thinking 块的数量以及被标记块的数量:
cURL
此工作流需要的逻辑超出了单个 shell 命令所能实现的范围。请使用 Claude SDK 运行它。
CLI
此工作流需要的逻辑超出了单个 shell 命令所能实现的范围。请使用 Claude SDK 运行它。
Python
client = anthropic.Anthropic()
user_turns = [
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?",
"And how many of the odd ones are below 100?",
]
# messages 随轮次增长:每个 assistant 轮次都按返回时的原样传回
messages = []
for turn, user_turn in enumerate(user_turns, start=1):
messages.append({"role": "user", "content": user_turn})
response = client.beta.messages.create(
model="claude-fable-5-1",
max_tokens=16000,
thinking={"type": "adaptive"},
# 仅最后一个请求添加系统提示,有意改变前缀
system="Answer briefly." if turn == len(user_turns) else anthropic.omit,
messages=messages,
betas=["thinking-binding-controls-2026-08-01"],
)
messages.append({"role": "assistant", "content": response.content})
thinking_blocks = sum(block.type == "thinking" for block in response.content)
flagged = sum(
transformation.type == "thinking_mismatch_allowed"
for transformation in response.input_transformations or []
)
print(f"thinking blocks: {thinking_blocks}, flagged: {flagged}")
TypeScript
const client = new Anthropic();
const userTurns = [
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?",
"And how many of the odd ones are below 100?"
];
// messages 随轮次增长:每个 assistant 轮次都按返回时的原样传回
const messages: Anthropic.Beta.BetaMessageParam[] = [];
for (const [turnIndex, userTurn] of userTurns.entries()) {
messages.push({ role: "user", content: userTurn });
const response = await client.beta.messages.create({
model: "claude-fable-5-1",
max_tokens: 16000,
thinking: { type: "adaptive" },
// 仅最后一个请求添加系统提示,有意改变前缀
system: turnIndex === userTurns.length - 1 ? "Answer briefly." : undefined,
messages,
betas: ["thinking-binding-controls-2026-08-01"]
});
messages.push({ role: "assistant", content: response.content });
const thinkingBlocks = response.content.filter((block) => block.type === "thinking");
const flagged = (response.input_transformations ?? []).filter(
(transformation) => transformation.type === "thinking_mismatch_allowed"
);
console.log(`thinking blocks: ${thinkingBlocks.length}, flagged: ${flagged.length}`);
}
C#
AnthropicClient client = new();
string[] userTurns =
[
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?",
"And how many of the odd ones are below 100?",
];
// messages 随轮次增长:每个 assistant 轮次均按返回时的原样回传
List<BetaMessageParam> messages = [];
for (var turnIndex = 0; turnIndex < userTurns.Length; turnIndex++)
{
messages.Add(new BetaUserMessageParam { Content = userTurns[turnIndex] });
var response = await client.Beta.Messages.Create(
new()
{
Model = Model.ClaudeFable5_1,
MaxTokens = 16000,
Thinking = new BetaThinkingConfigAdaptive(),
// 仅最后一个请求添加系统提示,有意改变前缀
System = turnIndex == userTurns.Length - 1 ? new("Answer briefly.") : null,
Messages = messages,
Betas = [AnthropicBeta.ThinkingBindingControls2026_08_01],
}
);
messages.Add(new BetaAssistantMessageParam
{
Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
});
var thinkingBlocks = response.Content.Count(block => block.TryPickThinking(out _));
var flagged = response.InputTransformations?.Count(transformation =>
transformation.TryPickThinkingMismatchAllowed(out _)
) ?? 0;
Console.WriteLine($"thinking blocks: {thinkingBlocks}, flagged: {flagged}");
}
Go
client := anthropic.NewClient()
userTurns := []string{
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?",
"And how many of the odd ones are below 100?",
}
// messages 随轮次增长:每个 assistant 轮次均按返回时的原样回传
messages := []anthropic.BetaMessageParam{}
for i, userTurn := range userTurns {
messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(userTurn)))
// 仅最后一个请求添加系统提示,有意改变前缀
var system []anthropic.BetaTextBlockParam
if i == len(userTurns)-1 {
system = []anthropic.BetaTextBlockParam{{Text: "Answer briefly."}}
}
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeFable5_1,
MaxTokens: 16000,
Thinking: anthropic.BetaThinkingConfigParamUnion{
OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{},
},
System: system,
Messages: messages,
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaThinkingBindingControls2026_08_01},
})
if err != nil {
log.Fatal(err)
}
messages = append(messages, response.ToParam())
thinkingBlocks := 0
for _, block := range response.Content {
if block.Type == "thinking" {
thinkingBlocks++
}
}
flagged := 0
for _, transformation := range response.InputTransformations {
if transformation.Type == "thinking_mismatch_allowed" {
flagged++
}
}
fmt.Printf("thinking blocks: %d, flagged: %d\n", thinkingBlocks, flagged)
}
Java
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.beta.messages.BetaContentBlock;
import com.anthropic.models.beta.messages.BetaInputTransformation;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaThinkingConfigAdaptive;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
List<String> userTurns = List.of(
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?",
"And how many of the odd ones are below 100?");
// 构建器的消息列表随轮次增长:每个 assistant 轮次都按返回时的原样回传
MessageCreateParams.Builder conversation = MessageCreateParams.builder()
.model(Model.CLAUDE_FABLE_5_1)
.maxTokens(16000L)
.thinking(BetaThinkingConfigAdaptive.builder().build())
.addBeta(AnthropicBeta.THINKING_BINDING_CONTROLS_2026_08_01);
for (int turnIndex = 0; turnIndex < userTurns.size(); turnIndex++) {
if (turnIndex == userTurns.size() - 1) {
// 仅最后一个请求添加了系统提示,这是有意改变前缀
conversation.system("Answer briefly.");
}
conversation.addUserMessage(userTurns.get(turnIndex));
BetaMessage response = client.beta().messages().create(conversation.build());
conversation.addMessage(response);
long thinkingBlocks = response.content().stream()
.filter(BetaContentBlock::isThinking)
.count();
long flagged = response.inputTransformations().stream()
.flatMap(List::stream)
.filter(BetaInputTransformation::isThinkingMismatchAllowed)
.count();
IO.println("thinking blocks: " + thinkingBlocks + ", flagged: " + flagged);
}
}
PHP
use Anthropic\Beta\AnthropicBeta;
use Anthropic\Beta\Messages\BetaThinkingConfigAdaptive;
use Anthropic\Beta\Messages\BetaThinkingMismatchAllowedInputTransformation;
use Anthropic\Client;
$client = new Client();
$userTurns = [
'How many positive integers below 500 have exactly 6 positive divisors?',
'How many of those are odd?',
'And how many of the odd ones are below 100?',
];
// $messages 随轮次增长:每个 assistant 轮次都按返回时的原样传回
$messages = [];
foreach ($userTurns as $turnIndex => $userTurn) {
$messages[] = ['role' => 'user', 'content' => $userTurn];
$response = $client->beta->messages->create(
model: 'claude-fable-5-1',
maxTokens: 16000,
thinking: BetaThinkingConfigAdaptive::with(),
// 只有最后一个请求添加了系统提示,这会有意改变前缀
system: $turnIndex === array_key_last($userTurns) ? 'Answer briefly.' : null,
messages: $messages,
betas: [AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01],
);
$messages[] = ['role' => 'assistant', 'content' => $response->content];
$thinkingBlocks = array_filter($response->content, fn ($block) => $block->type === 'thinking');
$flagged = array_filter(
$response->inputTransformations ?? [],
fn ($transformation) => $transformation instanceof BetaThinkingMismatchAllowedInputTransformation,
);
echo 'thinking blocks: ', count($thinkingBlocks), ', flagged: ', count($flagged), PHP_EOL;
}
Ruby
client = Anthropic::Client.new
user_turns = [
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many of those are odd?",
"And how many of the odd ones are below 100?"
]
# messages 随轮次增长:每个 assistant 轮次都按返回时的原样回传
messages = []
user_turns.each_with_index do |user_turn, turn_index|
messages << {role: "user", content: user_turn}
# 仅最后一个请求添加系统提示,这会有意改变前缀
system_param = (turn_index == user_turns.length - 1) ? {system_: "Answer briefly."} : {}
response = client.beta.messages.create(
model: Anthropic::Model::CLAUDE_FABLE_5_1,
max_tokens: 16_000,
thinking: {type: "adaptive"},
messages: messages,
betas: [Anthropic::AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01],
**system_param
)
messages << {role: "assistant", content: response.content}
thinking_blocks = response.content.count { |block| block.type == :thinking }
flagged = (response.input_transformations || []).count do |transformation|
transformation.type == :thinking_mismatch_allowed
end
puts "thinking blocks: #{thinking_blocks}, flagged: #{flagged}"
end
Output
thinking blocks: 1, flagged: 0
thinking blocks: 1, flagged: 0
thinking blocks: 1, flagged: 2
第三个响应会标记来自早期轮次的每个思考块(本次运行中每轮一个),因为新的系统提示位于所有这些块之前。模型仍然读取了它们。
请像处理 prefix_binding_mismatch 丢弃一样处理这些条目。编辑位于所列第一个块之前:将 system、tools 以及直到该块 path 的 messages 与上一个请求进行比对以找到编辑,然后使用在不编辑前缀的情况下进行更改中对应的模式替换它。如果请求设置了 prefix_mismatch_behavior,或者当 prefix_mismatch_behavior 未设置时中的表格显示您的模型和账户会返回 400 错误,则 API 会改为拒绝请求或丢弃未通过检查的块。这些条目只是 "drop_block" 会移除内容的下限。使用 "drop_block" 时,一个未通过检查的块还会连带移除该轮次中其余的思考块,而移除一个块也可能导致下一个块同样未通过检查。当 API 允许未通过检查的块通过时,它会单独评估每个块,并且只在该块本身未通过时才将其列出。
在不编辑前缀的情况下进行更改
每种常见的前缀编辑都有一种替代做法,既能向模型提供相同的信息,又能保持之前的字节不变,从而使之后的思考保持有效。请在第一列中找到您的代码目前所做的编辑:
| 替代以下做法 | 改用 | Beta 标头 |
|---|---|---|
重建顶层 system 提示 |
对话中途的系统消息 | 无 |
| 在每次请求时重新渲染第一条用户消息中的上下文(环境、日期、记忆、项目说明) | 只渲染一次并原样重新发送。当内容发生变化时,将新版本放在最新的轮次中 | 无 |
就地清除或缩短旧的 tool_result 内容,或重新编码旧图像 |
在第一次发送之前(而不是之后)缩短工具结果或缩小图像。如需稍后清除旧结果,请使用 clear_tool_uses_20250919 在服务器端裁剪上下文 |
context-management-2025-06-27 |
| 注入提醒并在下一次请求时将其删除 | 轮次范围系统消息(clear_at: "next_user_message") |
mid-conversation-system-clear-at-2026-08-21 |
在 tools 中添加或删除条目 |
tool_addition 和 tool_removal 块 |
inline-tools-2026-09-15(当工具来自通过 MCP 连接器连接的 MCP 服务器时,还需添加 mcp-client-2026-09-15),或较旧的 mid-conversation-tool-changes-2026-07-01,后者适用于 Claude API、Amazon Bedrock 和 Claude Models on Google Cloud (Google managed) |
更改顶层 output_config.effort(会使缓存重新开始,不影响思考) |
按消息设置的 output_config |
mid-conversation-output-config-2026-07-01 |
| 在客户端丢弃或总结旧轮次 | 使用按需压缩保留最近轮次及其思考、其他服务器端压缩或上下文编辑,或不保留过时思考的客户端压缩 | compact-2026-09-04 |
| 字节在请求之间发生变化的图像或文档 URL | 来自 Files API 的 file_id,或 base64 |
无 |
以上所有做法都假定您完全按照返回的样子发回助手轮次。对话中途的系统消息、轮次范围系统消息和工具更改并非在所有模型上都可用:对话中途的系统消息与工具变更列出了支持它们的模型。如果您的代码服务于多个模型,对于不支持它们的模型,请继续编辑顶层 system 提示。
要在 Claude API 上的一个请求中使用多个 beta,请将它们的名称合并到一个 anthropic-beta 标头中:
anthropic-beta: thinking-binding-controls-2026-08-01,mid-conversation-system-clear-at-2026-08-21,inline-tools-2026-09-15
提供这些 beta 的其他平台使用相同的名称。要在这些平台上发送它们,请参阅其他平台上的 Beta 功能。
完全按照返回的样子发回助手轮次
存储每个响应中的 content 数组,并将其原样作为助手轮次发回:包括所有块类型,按接收顺序排列,也包括 thinking 字段为空的 thinking 块。丢弃未知块类型、丢弃空字段或重新排列块顺序的序列化器,会为之后的每一轮编辑前缀。
在执行前缀检查的模型上,thinking 字段默认为空,推理由 signature 承载,因此跳过空块的序列化程序会移除思考内容。如果它移除了所有思考块,则不会有任何检查失败,但模型在每个轮次都会丢失其先前的推理。如果您自行解析流,即使没有收到任何思考文本,也要保留该块:它会开启,在 signature_delta 事件中接收其 signature,然后关闭。以空 signature 回传的块会未通过检查。
使用对话中途系统消息添加指令
有些运行框架会在每次请求时重建顶层 system 提示,以携带当前时间、令牌预算、模式标志或新发现的项目上下文。这会使对话中的所有思考块失效。相反,请在会话开始时固定 system。当有内容更改时,在 messages 中该更改生效的位置追加一条 role: "system" 消息:
{
"role": "system",
"content": "The user switched the workspace to read-only mode. Do not write files until told otherwise."
}
模型会以系统提示的权威性对待这条消息,而它之前的所有内容保持不变。在工具循环中,请将该消息放在 tool_result 用户消息之后,切勿放在助手的 tool_use 与其 tool_result 之间(请参阅限制)。一旦发送,该消息就成为后续思考前缀的一部分:在后续请求中请将其保留在原位。
将变化的上下文放在最新轮次中
有些运行框架会在第一条用户消息中放入环境块(工作目录、分支、日期、记忆、项目说明),并在每次请求时重新渲染。当任何值发生变化时,messages[0] 就会改变,对话中的所有思考块都会失效。请只渲染一次该块,并按原样重新发送。当某个值发生变化时,请在最新轮次中说明:向您即将发送的用户消息添加一个文本块,或者如果更改来自作为运营方的您,则追加一条对话中途系统消息。
{
"role": "user",
"content": [
{
"type": "text",
"text": "Environment update: the current branch is now release-2."
},
{ "type": "text", "text": "Run the tests again." }
]
}
一旦发送,该文本块就成为后续思考前缀的一部分:在后续请求中请将其保留在原位。
将每轮提醒作为轮次作用域系统消息发送
一种常见的前缀编辑是每轮提示:例如“将独立的读取请求放在一起”或“您已经有一段时间没有向用户更新进展了”之类的语句,由您的代码在每批工具结果之后追加。为了避免提醒不断堆积,请将每条提示作为带有 clear_at: "next_user_message" 的对话中途的系统消息发送,并放在 tool_result 用户消息之后。clear_at 需要 beta 标头 mid-conversation-system-clear-at-2026-08-21。以下 messages 数组是两次工具调用及其结果之后的请求。messages[3] 是上一个请求的提示,保留在原位,而 messages[6] 是本次请求的副本:
[
{ "role": "user", "content": "Fix the failing test." },
{
"role": "assistant",
"content": [
{ "type": "thinking", "thinking": "", "signature": "..." },
{
"type": "tool_use",
"id": "toolu_01",
"name": "read_file",
"input": { "path": "tests/test_auth.py" }
}
]
},
{
"role": "user",
"content": [{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "..." }]
},
{
"role": "system",
"clear_at": "next_user_message",
"content": "Request every independent read in one turn."
},
{
"role": "assistant",
"content": [
{ "type": "thinking", "thinking": "", "signature": "..." },
{
"type": "tool_use",
"id": "toolu_02",
"name": "read_file",
"input": { "path": "src/auth.py" }
}
]
},
{
"role": "user",
"content": [{ "type": "tool_result", "tool_use_id": "toolu_02", "content": "..." }]
},
{
"role": "system",
"clear_at": "next_user_message",
"content": "Request every independent read in one turn."
}
]
仅包含 tool_result 块的用户消息也算作“下一条用户消息”,因此 messages[3] 已被清除。它不会为模型接收的内容增加任何东西,也不消耗输入令牌,但由于它仍在数组中,messages[4] 中的思考保持有效。messages[6] 是模型在本轮接收的副本。在之后的请求中,请将两者都保留在原位,并在下一条 tool_result 消息之后追加一个新的副本。
使用 tool_addition 和 tool_removal 添加或删除工具
在会话中途编辑 tools 数组会使保留的思考块失效。请保持该数组与您首次发送时一致,并通过追加一条携带 tool_addition 或 tool_removal 块的 role: "system" 消息,来更改模型可以使用的工具。这些属于对话中途的工具变更,需要 beta 标头 inline-tools-2026-09-15,该标头在 Claude API 上可用。较旧的 mid-conversation-tool-changes-2026-07-01 标头仍适用于通过引用指定工具的变更,可在 Claude API、Amazon Bedrock 和 Claude Models on Google Cloud (Google managed) 上使用。
您可以通过两种方式使用这些块:
- 预先声明所有工具。 在第一个请求中将会话可能需要的所有工具放入
tools,对于暂时不想提供给模型的工具设置defer_loading: true。然后使用命名这些工具的tool_addition和tool_removal块来启用和停用它们。 - 从快照开始,逐步添加工具。 在第一个请求中将您已知的工具放入
tools。当您了解到另一个工具时,在tool_addition块中定义它,而不是编辑tools。
无论哪种方式,tools 都不会改变,因此之前的思考保持有效,提示缓存也仍然命中,唯一的例外将在本节后面说明。
例如,在模式切换后撤回一个危险工具:
{
"role": "system",
"content": [
{ "type": "tool_removal", "tool": { "type": "tool_reference", "name": "delete_branch" } },
{ "type": "text", "text": "Branch deletion is disabled for the rest of this session." }
]
}
要启用您使用 defer_loading: true 声明的工具,请追加一个指定该工具的 tool_addition 块:
{
"role": "system",
"content": [
{ "type": "tool_addition", "tool": { "type": "tool_reference", "name": "deploy" } },
{ "type": "text", "text": "Authentication succeeded. Deployment is now available." }
]
}
有时您无法预先声明某个工具,因为您还不知道它的 schema:例如您的应用程序在运行时发现的工具,或在第一轮之后才连接的 MCP 服务器。请在 tool_addition 块中定义它,而不是修改 tools。使用 inline-tools-2026-09-15 时,该块的 tool 可以是 {"type": "tool_definition", "definition": {...}},其中携带的条目与您原本会放入 tools 的条目相同:
{
"role": "system",
"content": [
{
"type": "tool_addition",
"tool": {
"type": "tool_definition",
"definition": {
"name": "db_query",
"description": "Run a read-only SQL query against the analytics database.",
"input_schema": {
"type": "object",
"properties": { "sql": { "type": "string" } },
"required": ["sql"]
}
}
}
}
]
}
新工具通过 messages 到达,tools 从不改变,之前的思考保持有效。请在 tools 中至少保留一个未设置 defer_loading: true 的工具:如果其中所有工具都是延迟加载的,那么您以这种方式定义的第一个工具会导致一次完整的提示缓存未命中。
如果 API 通过 MCP 连接器为您连接 MCP 服务器,还需发送 mcp-client-2026-09-15。它涵盖了 mcp-client-2025-11-20 的所有功能,因此请发送它来代替后者。此时该块的 definition 可以是针对 mcp_servers 中所列服务器的 mcp_toolset。当 API 需要获取某个服务器的工具列表时,响应会以该服务器的 mcp_tool_listing 块开头。请将其与助手轮次的其余部分一起原样发回,并在之后每个携带该块的请求中持续发送 mcp-client-2026-09-15。该块会将工具集固定到该列表,因此 API 不会为此再次联系服务器。这些 MCP 连接器功能在 Claude API 上可用。
如果只有较早的标头,您仍然可以将会话中途得知的工具以 defer_loading: true 追加到 tools,然后通过 tool_addition 块提供它。这样做是安全的,因为在 tool_addition 块引用延迟工具之前,前缀检查会忽略它。添加未设置 defer_loading: true 的工具会改变前缀,并使之前的思考失效。
携带这些块的 role: "system" 消息会成为后续思考前缀的一部分。在后续请求中请将其保留在原位。
使用按消息设置的 output_config 更改 effort
在请求之间更改顶层 output_config.effort 不会使思考失效,因为 effort 不是前缀的一部分。但更改顶层 effort 确实会重新开始提示缓存。在模型和平台支持按消息设置 effort 的情况下,请改用该方式:追加一条 content 为空并带有新级别的 role: "system" 消息。它需要 beta 标头 mid-conversation-output-config-2026-07-01。在 Claude Haiku 5.5 上,按消息设置 effort 在 Claude API 和 Claude Models on Google Cloud (Google managed) 上可用,并且需要自适应思考。
{ "role": "system", "content": [], "output_config": { "effort": "low" } }
新级别何时生效取决于您放置该消息的位置。如果紧跟在带有新输入的 user 消息之后,它从 Claude 对该消息的回复开始生效。在其他任何位置,例如在 assistant 消息之后,或在仅包含 tool_result 块的 user 消息之后,它从下一条带有新输入的 user 消息开始生效,因此已在进行中的工具循环会保持当前级别直到那时。一旦发送,该消息就成为 messages 的一部分,因此也是后续思考的前缀的一部分:在后续请求中请将其保留在原位,如需再次更改 effort,请追加另一条消息。
在服务器端裁剪上下文
另一种常见的前缀编辑是客户端裁剪:丢弃或汇总最旧的轮次,并逐字保留近期轮次。被保留轮次的思考块是在被删除的历史记录仍存在时生成的,因此它们无法通过检查。服务器端的等效操作不算作编辑,因为检查比较的是您发送的对话:
- 按需压缩(beta)通过单独的请求返回摘要,该请求可以在后台运行,您发送返回的块来替换它所总结的消息。检查接受这种替换,因此在保留思考的条件下,您保留的轮次及其思考可以保持有效。请求摘要展示了该请求,并列出了所需的 beta 标头。
- 按令牌阈值压缩会在上下文接近您设置的阈值时将较旧的轮次总结为一个压缩块,被检查的前缀从该块重新开始。它的
instructions参数接受您自己的摘要提示,例如“保留每个股票代码、持仓规模和明确的假设”。 - 上下文编辑按规则清除旧的工具结果或旧的思考块,从最早的开始。相应策略为
clear_tool_uses_20250919和clear_thinking_20251015。
在客户端进行压缩
您可以在客户端进行 “compaction”(压缩)。如果您自己编写摘要,请不要发回在您编写摘要之前生成的思考块。如果由 API 通过按需压缩编写摘要,保留的思考保持有效的条件列出了保留的思考何时保持有效。
简单压缩(推荐)
当对话变得过长时,将整个会话总结为一条用户消息,并只发送该消息加上下一条指令。之前的内容都不会被重放,因此不会留下任何可能无法通过检查的思考,模型会根据摘要重新推理。
[
{
"role": "user",
"content": "<summary of the session so far>\n\n<the next instruction>"
}
]
Claude 模型在长周期任务上使用此方案进行过训练,对于大多数工作负载,它的表现都很好。
保留尾部压缩
保留尾部压缩会总结较旧的轮次并逐字保留最近的轮次,因此模型仍然可以逐字接收最后几次交互。如果您自己编写摘要,保留轮次中的思考将无法通过前缀检查:保留的助手轮次携带的思考块是在原始轮次(而非摘要)位于其之前时生成的。
要保留这些思考,请让 API 通过按需压缩编写摘要。保留最近轮次的压缩展示了具体做法,保留的思考保持有效的条件列出了保留的思考何时保持有效。
本节其余部分介绍您自己编写摘要的情况。
解决方法:保持这些轮次原样不变,并发送 prefix_mismatch_behavior: "drop_block"。API 会丢弃过时的思考块,模型读取被保留轮次的 text 和 tool_use 块,请求成功。
将压缩后的历史作为 messages 传入,并在 thinking 配置上设置 block_binding。在以下示例中,compacted_messages 是您的压缩步骤生成的数组:摘要消息,后面跟着与 API 返回时完全一致的被保留轮次,包括 thinking 块:
cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: thinking-binding-controls-2026-08-01" \
-d '{
"model": "claude-fable-5-1",
"max_tokens": 16000,
"thinking": {
"type": "adaptive",
"block_binding": { "prefix_mismatch_behavior": "drop_block" }
},
"messages": '"$COMPACTED_MESSAGES"'
}'
CLI
ant beta:messages create --beta thinking-binding-controls-2026-08-01 <<YAML
model: claude-fable-5-1
max_tokens: 16000
thinking:
type: adaptive
block_binding:
prefix_mismatch_behavior: drop_block
messages: $COMPACTED_MESSAGES
YAML
Python
client = anthropic.Anthropic()
# compacted_messages:先是摘要消息,然后是按返回顺序保留的轮次
response = client.beta.messages.create(
model="claude-fable-5-1",
max_tokens=16000,
thinking={
"type": "adaptive",
"block_binding": {"prefix_mismatch_behavior": "drop_block"},
},
messages=compacted_messages,
betas=["thinking-binding-controls-2026-08-01"],
)
print(response.input_transformations)
TypeScript
const client = new Anthropic();
// compactedMessages:先是摘要消息,然后是按返回顺序保留的轮次
const response = await client.beta.messages.create({
model: "claude-fable-5-1",
max_tokens: 16000,
thinking: {
type: "adaptive",
block_binding: { prefix_mismatch_behavior: "drop_block" }
},
messages: compactedMessages,
betas: ["thinking-binding-controls-2026-08-01"]
});
console.log(response.input_transformations);
C#
AnthropicClient client = new();
// compactedMessages:先是摘要消息,然后是按返回顺序保留的轮次
var response = await client.Beta.Messages.Create(
new()
{
Model = Model.ClaudeFable5_1,
MaxTokens = 16000,
Thinking = new BetaThinkingConfigAdaptive
{
BlockBinding = new()
{
PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.DropBlock,
},
},
Messages = compactedMessages,
Betas = [AnthropicBeta.ThinkingBindingControls2026_08_01],
}
);
Console.WriteLine(response.InputTransformations?.Count ?? 0);
Go
client := anthropic.NewClient()
// compactedMessages:先是摘要消息,然后是按原样返回的保留轮次
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeFable5_1,
MaxTokens: 16000,
Thinking: anthropic.BetaThinkingConfigParamUnion{
OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{
BlockBinding: anthropic.BetaThinkingBlockBindingParam{
PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorDropBlock,
},
},
},
Messages: compactedMessages,
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaThinkingBindingControls2026_08_01},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(len(response.InputTransformations))
Java
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaThinkingBlockBinding;
import com.anthropic.models.beta.messages.BetaThinkingConfigAdaptive;
import com.anthropic.models.beta.messages.BetaThinkingPrefixMismatchBehavior;
import com.anthropic.models.beta.messages.MessageCreateParams;
import com.anthropic.models.messages.Model;
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// compactedMessages:先是摘要消息,然后是按返回顺序保留的轮次
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_FABLE_5_1)
.maxTokens(16000L)
.thinking(BetaThinkingConfigAdaptive.builder()
.blockBinding(BetaThinkingBlockBinding.builder()
.prefixMismatchBehavior(BetaThinkingPrefixMismatchBehavior.DROP_BLOCK)
.build())
.build())
.messages(compactedMessages)
.addBeta(AnthropicBeta.THINKING_BINDING_CONTROLS_2026_08_01)
.build();
BetaMessage response = client.beta().messages().create(params);
IO.println(response.inputTransformations());
}
PHP
use Anthropic\Beta\AnthropicBeta;
use Anthropic\Beta\Messages\BetaThinkingBlockBinding;
use Anthropic\Beta\Messages\BetaThinkingConfigAdaptive;
use Anthropic\Beta\Messages\BetaThinkingPrefixMismatchBehavior;
use Anthropic\Client;
$client = new Client();
// $compactedMessages:先是摘要消息,然后是按返回结果保留的轮次
$response = $client->beta->messages->create(
model: 'claude-fable-5-1',
maxTokens: 16000,
thinking: BetaThinkingConfigAdaptive::with(
blockBinding: BetaThinkingBlockBinding::with(
prefixMismatchBehavior: BetaThinkingPrefixMismatchBehavior::DROP_BLOCK,
),
),
messages: $compactedMessages,
betas: [AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01],
);
var_dump($response->inputTransformations);
Ruby
client = Anthropic::Client.new
# compacted_messages:先是摘要消息,然后是按返回原样保留的轮次
response = client.beta.messages.create(
model: "claude-fable-5-1",
max_tokens: 16_000,
thinking: {
type: "adaptive",
block_binding: {prefix_mismatch_behavior: "drop_block"}
},
messages: compacted_messages,
betas: [Anthropic::AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01]
)
puts response.input_transformations
响应照常携带新的助手轮次,外加每个被丢弃块对应的一个 input_transformations 条目。对于图中的历史,即助手轮次 3 和 4 上的思考:
{
"input_transformations": [
{
"type": "thinking_dropped",
"path": "messages.2.content.0",
"reason": "prefix_binding_mismatch"
},
{
"type": "thinking_dropped",
"path": "messages.4.content.0",
"reason": "prefix_binding_mismatch"
}
]
}
只要这两个轮次仍在历史中,就请在后续请求中继续发送 "drop_block"。模型从此请求开始生成的思考位于摘要之后,并保持有效。如果您不想依赖 beta 标头,替代方法是在构建压缩历史时,自行从被保留的助手轮次中移除 thinking 和 redacted_thinking 块。
后台(异步)压缩
后台压缩在对话继续进行的同时,在关键路径之外构建摘要,然后在几个请求之后将其换入。要保留在此期间生成的思考,请让 API 通过按需压缩编写摘要:后台压缩提供了具体步骤,这些思考在与保留的最近轮次相同的保留思考的条件下保持有效。
如果您自行构建摘要,保留的思考会以与自行编写的 keep-tail 摘要之后相同的方式失效。在构建摘要期间生成的每个助手轮次都带有早于替换操作的思考,一旦摘要被替换进来,所有这些思考都会失效。请像对待 keep-tail 一样对待替换操作:从替换操作开始发送 "drop_block",或者同步进行压缩。
不适用于保留思考的模式
- 从中间删除轮次。 删除单个轮次会使其后的所有思考块失效,任何压缩方案都无法避免这一点。如果您删除某个轮次是为了更改指令,请改为追加一条对话中途系统消息。要有选择地移除旧的工具结果或旧的思考,请使用服务器端上下文编辑。
- 在工具轮次中途压缩。 不要在助手轮次的
tool_use与回应它的tool_result之间进行压缩。请将该助手轮次连同其完整的思考一起发回,以便模型带着其推理完成该轮次。请参阅保留思考块。
通过 ID 引用文件,而不是通过内容会变化的 URL
对于带有 url 来源的 image 或 document 块,检查针对的是获取到的字节,而不是 URL 字符串。内容会变化的 URL 会使后续的思考失效:例如“最新截图”端点,或有人在轮次之间编辑的文档。指向同一文件的轮换签名 URL 则不会。对于您在多个轮次中引用的内容,请使用 Files API 上传一次并使用 file_id,或者发送 base64。
库、代理和网关
库、代理或网关位于他人的历史记录与 API 之间,因此它自身所做的重写都算作编辑,而其用户既看不到也无法修复这些编辑。
- 原样传递您无法识别的内容。 原样转发调用方的
anthropic-beta值和thinking.block_binding,并将input_transformations返回给调用方。拒绝未知键的选项模式会阻止您的用户选择"drop_block"。 - 将
role: "system"消息保留在调用方放置的位置。 将其移入顶层system字段会更改该请求的system,并使对话中的所有思考块失效。 - 要为某个请求关闭工具使用,请发送
tool_choice: {"type": "none"}。 不要移除tools。 - 不要隐藏 400 错误。 如果您的代码捕获了该错误、移除了思考并代表调用方重试,请记录这一操作:调用方的历史记录仍然被编辑过,并且模型在之后的每个请求中都会丢失其先前的推理。
常见问题
我可以在 2026 年 8 月 31 日之前创建的账户上测试保留思考吗?
可以。发送 thinking-binding-controls-2026-08-01 beta 请求头并设置 thinking.block_binding.prefix_mismatch_behavior。该设置适用于任何账户上的该请求:"error" 会以 400 错误拒绝经过编辑的历史记录,而 "drop_block" 则允许请求通过,并在 input_transformations 中列出被丢弃的内容。请参阅检查您的代码是否编辑了前缀。
如果思考块之前的任何内容发生变化,哪怕只是一个工具描述,对话就无法使用了吗?
不是。失败的是历史中位于您更改点之后已有的思考,而您可以选择如何处理它。使用 prefix_mismatch_behavior: "drop_block" 时,API 会丢弃这些块,请求成功:模型在没有该推理的情况下回答该轮次,提示缓存从编辑处重新开始。使用 "error" 时,API 会以 400 错误拒绝请求,直到您撤销编辑或使用 "drop_block" 重新发送。请参阅API 如何处理失效的块。哪些算作编辑列出了哪些更改会产生影响。
在请求之间更改 effort 或其他思考设置会使之前的思考失效吗?
不会。output_config.effort、max_tokens 和 thinking 配置不是被检查前缀的一部分,该前缀只涵盖 system、tools 和 messages。顶层 effort 更改会使大部分提示缓存失效。在模型和平台支持按消息设置 effort 的情况下,按消息更改 effort 可以保留提示缓存,并在再次更改之前一直作为新的 effort 级别使用。
我的工具列表在会话中途会变化。如何避免使对话失效?
不要编辑 tools。在会话开始时声明完整的工具集,将尚不可用的工具标记为 defer_loading: true,并使用 tool_addition 和 tool_removal 块提供或撤回它们。如果您只在会话中途才得知某个工具的 schema,请在 tool_addition 块中定义它(使用 inline-tools-2026-09-15,对于 API 的 MCP 连接器所连接的服务器,还需加上 mcp-client-2026-09-15),并保持 tools 不变。携带这些块的 role: "system" 消息会成为后续思考的前缀的一部分,因此之后不要移动、改写或删除它们。请参阅使用 tool_addition 和 tool_removal 添加或删除工具。
我通过总结较早的轮次并逐字保留最近的轮次来进行压缩。这样还可行吗?
可行,前提是由 API 编写摘要。按需压缩(beta 标头 compact-2026-09-04)会将较早的轮次总结为一个签名块,您发送该块来替代这些轮次。在满足保留思考的条件时,最近的轮次会保留其思考。
如果您自己编写摘要,被保留轮次的思考将无法通过检查,因为这些块是基于您替换掉的历史生成的。请从您保留的轮次中移除 thinking 和 redacted_thinking 块,并保留其 text 和 tool_use 块,或者发送 prefix_mismatch_behavior: "drop_block" 让 API 丢弃它们。简单压缩不会留下任何可能失败的思考,是推荐的方法:一条摘要消息加上下一个用户轮次,不重放任何之前的轮次。服务器端压缩和上下文编辑不算作编辑。请参阅在客户端进行压缩。
如何处理在会话中途发生变化的指令文件,例如 AGENTS.md 或 CLAUDE.md?
在会话开始时加载它们一次,并保持顶层 system 提示和 tools 固定。当文件发生变化时,在 messages 中的该位置追加新版本,而不是编辑原始内容。对于来自作为运营方的您的指令,请使用对话中途的系统消息。对于您视为不受信任、不应具有系统提示权威性的文件文本,请改为将内容放入下一个 user 轮次。请参阅使用对话中途的系统消息添加指令和限制。
我可以在重启后或第二天恢复已保存的会话吗?
可以。恢复的会话就是一个普通的后续请求:system、tools 和之前的 messages 必须与您上次发送的内容相同。JSON 格式和键的顺序无关紧要,值才重要。请完整保存您发送和接收的内容,并重放这些内容:渲染后的系统提示、工具定义,以及按返回原样的每个助手轮次。不要根据此后可能已变化的输入重新渲染,例如日期、更新后的指令文件或新的工具版本。任何新内容都应放在追加的消息中。请参阅完全按照返回的样子发回助手轮次。
已保存的会话在每次请求时都失败。如何让它重新正常工作?
存储的历史记录中包含一处编辑,因此按原样发回时每次都会失败,即使您修复了进行该编辑的代码也是如此。要继续该会话,请在之后的每个请求中发送 beta 标头并设置 prefix_mismatch_behavior: "drop_block"。或者,删除错误中指出的块以及之后的每个 thinking 和 redacted_thinking 块,然后重试一次。如果您无法发送 block_binding(在 Claude Sonnet 5.5 和 Claude Haiku 5.5 上,它仅在使用 thinking: {"type": "adaptive"} 时有效),请删除这些块。只要之前的内容不再发生更改,模型此后生成的思考就会保持有效。然后修复进行该编辑的代码,使新会话不会再遇到此问题。请参阅在代码中处理该错误。
我的框架可以将某个轮次路由到非 Claude 模型。这些轮次会使 Claude 之前的思考失效吗?
不会,前提是它们追加在现有历史之后,且之前的内容没有任何变化:不含思考块的助手消息与其他追加的消息一样。请将另一个模型的输出作为 text 和 tool_use 内容发送。
我可以将一个对话的推理带入新的对话吗?
不能带入另一个对话。思考块只有在紧跟其生成时所基于的完全相同的 system、tools 和 messages 时才可用。一个在分叉点之前原样重放该历史的分支会保留其思考。从其他任何内容开始的对话都无法使用它,因此请像简单压缩那样,从任务状态的摘要开始该对话:目标、已做出的决定、目前为止的文件和结果,以及下一步。Claude Sonnet 5.5 和 Claude Haiku 5.5 的思考块也不能在其他账户中重用(请参阅思考块仅限于生成它们的账户)。
后续步骤
-
思考故障排除:诊断并修复最常见的思考故障:配置 400 错误、思考块为空或缺失、
max_tokens停止以及缓存未命中。 -
对话中途系统消息和工具变更:在对话进行到一半时更改系统指令或工具可用性,而不会使其之前的已缓存前缀失效。
-
压缩:服务器端上下文压缩,用于管理接近上下文窗口限制的长对话。
-
提示缓存:使用
cache_control缓存提示前缀以降低成本和延迟,可使用自动缓存或带有 5 分钟或 1 小时 TTL 的显式断点。
可用性
保留思考在部分模型上可用。
API 支持
Messages API
- 可用性:始终开启
- 用法:保持请求仅追加:将先前的轮次按返回的原样发回,且不要更改
system、tools或先前的消息。
Managed Agents
- 可用性:始终开启
- 用法:由 Managed Agents 构建请求。
| 状态 | 云平台 | 模型 |
|---|---|---|
| 可用 | Claude API, Claude Platform on AWS, Claude Models on Google Cloud (Anthropic managed) | Claude Fable 5.1, Claude Mythos 5.1, Claude Opus 5.5, Claude Sonnet 5.5, Claude Haiku 5.5 |