[控场AI]
· 10 分钟阅读· 5,252 字

Claude思维块绑定机制:Agent历史为何变成只读账本

Claude思维块绑定机制:Agent历史为何变成只读账本

Anthropic 的 preserved thinking 要求思维块前缀不可变,Agent 框架常见改写历史的做法会导致 400 错误。

Anthropic 为防止模型蒸馏推出 preserved thinking 机制,要求回传的 thinking 块之前的内容(system 提示、tools 数组、前置消息)必须与生成时完全一致,否则签名校验失败并返回 400 错误。该检查对新账号默认强制执行,老账号需通过 beta 头或特定参数触发。重新渲染系统提示、中途编辑工具集、裁剪中间轮次等六种常见 Agent 框架做法现在都会破坏请求。开发者可借助 `thinking-binding-controls-2026-08-01` beta 头与 `input_transformations` 输出提前定位问题,或用 `drop_block` 作为临时权宜之计,但代价是输出 token 显著增加。根本修复方向是持久化并回放实际发送与接收的内容。

如果你的 Claude 集成会重写自己的对话历史,API 可能返回一个以 messages.1.content.0: Invalid \signature` in `thinking` block` 开头的 400 错误——提示该思维块被绑定到了另一段对话。开头的路径会随失败的块而变化,所以排查时要盯住路径之后的那段描述。

Claude思维块绑定机制解析

这段措辞出自 Anthropic 的 preserved thinking 文档。Anthropic 在一篇关于蒸馏防御的研究文章中解释了这条规则的来由:preserved thinking 是"防止新建 API 账号编辑 Claude 此前上下文"的一项措施。它是三层防御体系之一,目的是阻止其他实验室用 Claude 的推理过程训练自家模型。Anthropic 在文章中给出一个合并结论:

早期证据表明,这些方法使蒸馏者采集的数据量减少了约 90% 到 99%。

Anthropic 称其为"早期证据",并未说明如何测量。而你这一侧的成本更容易确定:同一篇文章承认,preserved thinking "要求部分开发者更新其集成"。

三层防御,其中一层在你的请求链路上

Anthropic 列出的三层防御分别是:

  • Preserved thinking:随 Claude Fable 5.1 和 Claude Opus 5.5 一同发布。在 Claude Sonnet 5.5 上,推理记录只对发起请求的组织可见。
  • 两阶段分类器:运行在 Claude Sonnet 5.5、Opus 5 和 5.5、Fable 5 和 5.1 之上,Anthropic 称其误报率低于 0.1%。
  • 强制执行:持续尝试提取推理的账号会被施加更严格的限制乃至封禁。Anthropic 已恢复对在 Claude 响应前就被安全机制拦截的请求收费,前提是这些安全机制在蒸馏等领域的误报率低于 0.1%。

有媒体评价这一取舍"可以辩护",但同时指出,它把摩擦转移到了付费客户身上。分类器和计费规则主要在你"运气不好"时才会碰到,而 preserved thinking 影响每一个多轮集成,因为它改变了"合法请求"的定义。

API 对每个思维块检查什么

对你回传的每一个 thinking 或 redacted_thinking 块,文档描述了两项检查。第一,模型必须能够读取该块,否则 API 会静默丢弃它而不报错。第二:"思维块之前的内容没有任何变化"。

这里的"之前"指顶层 system 提示、tools 数组,以及该块之前的每一条消息。只要其中任何一项与生成该块时的状态不同,该块及其之后的所有思维块都会失效。

强制执行取决于账号创建时间:在此之后创建的账号默认启用该检查并收到 400 错误;较老的账号上检查仍会运行,但只有设置了特定参数的请求才会真正强制执行,否则失败的块会被放行至模型。前缀检查覆盖多款模型。

追加消息始终有效。对 effort、max_tokens、tool_choice、metadata 和 cache_control 标记的修改同样有效,服务端压缩也不会触发问题,因为检查"比较的是你发送的内容,而不是服务端编辑后的副本"。

什么是 thinking 块与 signature

在 Anthropic 的扩展思考(extended thinking)机制中,模型在给出最终答案前会先输出一段推理过程。启用 thinking 后,API 响应里会包含 thinking 类型的 content block,其结构大致为 {"type": "thinking", "thinking": "...", "signature": "..."}。其中 signature 是一个与生成该块的完整上下文绑定的加密校验值,由 Anthropic 服务端生成,用于证明这段推理确实由对应模型在该特定前缀下产生。客户端在后续轮次回传历史时,必须原样携带这些块,服务端会重新校验签名是否与当前请求的 system、tools 及前置消息一致。redacted_thinking 是当推理内容因安全策略被遮蔽时返回的变体,结构类似但 thinking 字段被替换为不可读密文,签名同样承载绑定信息。正因如此,只要前缀哪怕发生一个字符的变化,签名校验就会失败,API 便以 400 错误拒绝整个请求。

这些习惯现在会打断 Agent 循环

大多数 Agent 框架都会在某个地方改动历史。文档列出了会失败的几种模式及替代做法:

  1. 重新渲染系统提示。注入当天日期、或把更新后的 CLAUDE.md/AGENTS.md 重新加载进 system,都会改变前缀。应当只在开始时加载一次指令文件,后续版本作为对话中途的系统消息追加,或不信任文件内容时放进下一个 user 轮次。
  2. 会话中途编辑 tools。应在开始时声明完整工具集,用 defer_loading: true 标记后加入的工具,并通过 tool_addition 和 tool_removal 块来启用或撤回。
  3. 通过移除工具来关闭工具使用。应改用 tool_choice: {"type": "none"}。
  4. 从中间裁掉轮次。这会让裁切点之后的每个思维块失效。应使用服务端上下文编辑来修剪旧的工具结果。
  5. 自己写 keep-tail 摘要。被保留的轮次携带的是针对已被替换的历史产生的思维。应让 API 通过按需压缩来生成摘要,或从被保留的轮次中剥离思维。
  6. 跳过"空"块的序列化器。文档警告 thinking 字段默认为空,推理内容由 signature 承载。丢掉这个块就等于丢掉推理。

还有两个更隐蔽的问题:网关把 role: "system" 消息移入顶层 system 字段,会使对话中每个思维块失效;以及通过 URL 引用的图片或文档在内容变化后,会破坏后续思维。跨轮次引用任何内容时,应使用 Files API 和 file_id。

defer_loading 与 tool_addition / tool_removal

Anthropic 为工具集动态变化提供的替代方案涉及几个工具栏字段。defer_loading: true 标记的工具在声明时不会立即把完整定义塞进上下文,而是把定义推迟到真正被调用时再加载,从而让前缀保持稳定。tool_addition 块用于在对话中途正式激活某个此前被延迟声明的工具,tool_removal 则相反,用于撤回一个工具。这两个块本身被设计为可追加的增量,因此不会改变此前思维块所依赖的前缀。与之相对,直接增删 tools 数组元素会让前缀与生成思维块时不一致,进而触发 messages.1.content.0 之类的签名错误。这一设计实际上把“工具集可变”从数组编辑问题转化成了消息流内的显式事件问题。

在变成 400 错误之前找到那些改动

你不需要新账号就能测试。文档 FAQ 说明,较老的账号可以发送 thinking-binding-controls-2026-08-01 beta 头,并且不设置 prefix_mismatch_behavior。失败的块仍会到达模型,而响应会在 input_transformations 中以 thinking_mismatch_allowed 把它们列出:

import logging
import anthropic

client = anthropic.Anthropic()

resp = client.beta.messages.create(
    model="claude-fable-5-1",
    max_tokens=16000,
    thinking={"type": "adaptive"},
    system=system_prompt,
    tools=tools,
    messages=history,
    betas=["thinking-binding-controls-2026-08-01"],
)

for t in resp.input_transformations or []:
    reason = getattr(t, "reason", None)
    logging.warning("%s %s %s", t.type, t.path, reason)

每条记录带有一个 path,例如 messages.1.content.0。第一条记录指向你的框架最早使其失效的块。

如果你暂时无法修复这个编辑,临时的权宜之计是 drop_block:

thinking={
    "type": "adaptive",
    "block_binding": {
        "prefix_mismatch_behavior": "drop_block",
    },
}

这样请求会成功,API 会丢弃每个失败的块及其之后的所有块,并以 reason: "prefix_binding_mismatch" 列出它们。文档警告这可能"显著增加生成的输出 token 数量",在一项多轮编码基准测试中,增幅范围为 2.5% 到 67.1%,取决于 effort 和块被丢弃的频率。模型也会在没有先前推理的情况下作答,所以应当计划移除触发这一行为的编辑。

input_transformations 与 beta 头

thinking-binding-controls-2026-08-01 是一个 Anthropic beta 头,启用它后可以调整思维块前缀校验的触发行为。其配套参数 prefix_mismatch_behavior 决定校验失败时如何处理:默认(新账号)为直接返回 400,设置为 drop_block 时则放行请求但丢弃失效块。老账号在未设置该参数时会把这批失败块原样传给模型,并在响应的 input_transformations 数组中逐条列出,每条包含 type、path 与 reason 字段。path 采用 JSON 指针风格,例如 messages.1.content.0 表示第 2 条消息的第 1 个内容块,据此可定位框架最早改坏历史的位置。这套观测能力让你无需新账号即可复现和排查前缀不匹配问题。

近期可以改什么

以下建议均来自文档:

  • 精确持久化你发送和接收的内容:渲染后的系统提示和工具定义,以及每一次返回的助手轮次。恢复时回放这些内容,而不是从可能已经变化的输入重新渲染。
  • 如果你运行代理或网关,要透传 anthropic-beta 值和 thinking.block_binding,把 input_transformations 返回给调用方,不要在 400 时静默剥离思维并重试。
  • 如果你使用多个账号,注意 Sonnet 5.5 和 Haiku 5.5 的思维块无法从另一个未关联的账号中复用。

我的看法是:Anthropic 正在用你的工程时间来支付它的蒸馏防御成本。好的一面是,一个只追加、可回放的消息记录,也是调试起来最容易的一类 Agent 运行记录。

Files API 与 file_id

原文提到通过 URL 引用的图片或文档在内容变化后会破坏后续思维块,解决方案是使用 Files API。Files API 允许你先将文件上传到 Anthropic 侧,获得一个稳定的 file_id,随后在消息中改用 file_id 引用而非直接嵌入 URL 或文件内容。由于 file_id 本身不随后续源文件变化而改变,签名校验时的前缀保持一致,思维块得以继续有效。若改用 URL,服务端会在每次请求时按 URL 重新拉取内容,源文件一改,前缀即变,之前所有携带签名的思维块随之失效。对于需要跨多轮引用同一张图或同一份文档的 Agent 场景,Files API 基本是唯一稳妥做法。

#Claude#preserved thinking#思维块绑定#distillation defense#AI Agent#Anthropic API#上下文管理
分享:

相关推荐