🛡️ ARK 智能体诊断报告

langchain-ai/langchain#39100 · ChatAnthropic system 块 id 原样透传导致 400 · 2026-07-28 · W32 诊断 5/5

📋

问题摘要

🔴

LangChain v1 标准内容块 API create_text_block() 会给每个块铸造一个 id(形如 lc_<uuid4>)。 langchain_anthropic._format_messages() 对 human/assistant 文本块做了字段收窄(只保留 Anthropic 接受的键), 但 system 分支把块原样透传——lc_ id 直达 Anthropic API, 换回 400 "system.0.id: Extra inputs are not permitted"。 同一路径也被 get_num_tokens_from_messages() 复用,token 计数同样失败。

框架自铸元数据泄漏到 wire 协议 同一函数内双重标准(role 收窄 / system 透传) ARK 实机离线复现 ✅(无需 API key)

影响范围

langchain-anthropic 1.5.2 · 所有用 v1 content_blocks 构造 SystemMessage 的用户

框架现状

⚠️ open · 2 位贡献者认领 · 修复方向共识(system 块套用同一收窄逻辑)

ARK 方案

✅ OutputValidator 出站 payload 契约 + OTel 字段级留痕

🔍

根因定位(P2 Probe)

缺陷是「同一函数内的不对称清洗」——出站边界只对一半的消息角色执行契约:

  1. create_text_block()(langchain-core v1 标准内容块)默认给块铸造 id="lc_<uuid4>"——这是 LangChain 内部的溯源元数据,不属于任何 provider 的 wire 协议
  2. _format_messages()langchain_anthropic/chat_models.py)对 human/assistant 的 dict 文本块收窄到 Anthropic 接受的字段(type/text/cache_control/citations)
  3. system 分支缺失同样的收窄:dict 块原样进入 payload 的 system 字段
  4. Anthropic API 对 system 块执行严格 schema 校验(Extra inputs are not permitted)→ 400;get_num_tokens_from_messages() 复用同一 formatted payload → token 计数同样 400

结构性观察: 这是「框架自铸元数据 → 泄漏到出站协议」的典型样本。v1 内容块把 id 设计为一等公民(用于溯源/去重), 意味着每个 provider 适配器的每个消息角色分支都必须显式过滤,漏掉任何一个分支就是 400 或静默行为差异。 越是「推荐的新 API」(v1 content_blocks + create_text_block 正是官方迁移方向),越先踩中——与 #39047 的「官方指引即陷阱」同构。

🧪

ARK 实机验证(2026-07-28 · langchain-anthropic 1.5.2 / langchain-core 1.5.1 / anthropic 0.120.0 / Python 3.11)

ARK 采用离线确定性验证:不打真实 API,直接检视 _format_messages() 产出的出站 payload,同批消息同时覆盖 system / human / assistant 三个角色,证明不对称清洗:

create_text_block() minted id: 'lc_cd83ecad-14c9-472b-8437-e198d59bfade' formatted SYSTEM payload(原样直达 Anthropic): [{"type": "text", "text": "...", "id": "lc_cd83ecad-..."}] ← id 仍在 formatted human/assistant messages: [{"type": "text", "text": "Say hi."}] ← id 已被剥离 [{"type": "text", "text": "hi"}] ← id 已被剥离 system text block still carries `id` : True human/assistant text block carries `id` : False VERDICT: REPRODUCED — system 块透传 lc_ id,role 块收窄; Anthropic 严格校验 system 块 → 400 "system.0.id: Extra inputs are not permitted"

验证强度说明:离线检视出站 payload 比打真实 API 更强——它把根因钉死在 langchain-anthropic 的格式化层(而非网络/账号/模型版本),且完全确定性、可在 CI 中回归。复现脚本:scripts/repros/repro_39100_system_block_id_leak.py

🛡️

ARK Trust Layer 如何拦截(P3 Prescribe)

① OutputValidator · 出站 payload 契约(发送前)
对每个 provider 声明 wire schema 白名单(Anthropic system 块只接受 type/text/cache_control/citations)。 ARK 在 HTTP 请求发出前对最终 payload 做 schema 对账,lc_ id 在出站边界被拦截并指明来源块, 而非让用户面对一个看似「Anthropic 侧」的 400。

② InputGuard · 内部元数据标记
框架自铸字段(lc_ 前缀 id、内部溯源键)在进入适配层前统一打上 internal 标记; 任何 internal 字段出现在出站 payload 即为契约违规——把「每个适配器每个分支都要记得过滤」的 N×M 人工纪律, 收敛为单点自动强制

③ OTel 留痕
请求 span 记录 block_source(create_text_block) → formatter(system 分支) → rejected_field(id) 链路。 「为什么 SystemMessage 会 400 而 HumanMessage 不会」从翻源码对比分支,变成 trace 上一眼可见的字段级差异。

📚

缺陷模式索引归档(P4 Publish)

新设 F9 · 出站契约不对称 / 内部元数据泄漏到 wire 协议(首例)。 与 F2(内部状态泄漏到后续请求)互为镜像:F2 泄漏的是时间维度(本次参数污染下次调用), F9 泄漏的是层级维度(框架内部元数据穿透到 provider 协议层)。 共性根因都是「边界上缺一道强制清洗」,且都随分支/适配器数量线性放大遗漏概率。 与 #39047 共享「官方推荐路径先崩」的传播叙事:v1 content_blocks 正是 LangChain 力推的标准化方向。

ARK — Agent Reliability Kit · github.com/wzg0911/ark

诊断方法论:4P Framework(Pinpoint → Probe → Prescribe → Publish)· 所有结论基于实机验证,复现与未复现均如实披露

生成时间:2026-07-28 15:38 CST · W32 诊断报告 5/5(本周诊断目标达成)