🛡️ ARK 智能体诊断报告

langchain-ai/langchain#39113 · ChatOpenAI (Responses API) 合成 AIMessage 的 lc_ id 泄漏导致 400 · 2026-07-29 · W32 诊断 7/5(超额)

📋

问题摘要

🔴

用户用官方推荐的 create_text_block() 构造合成对话历史(压缩/摘要长对话的常见做法)时,工厂方法会给每个块铸造 lc_<uuid4> 形式的 id。 langchain_openai._construct_responses_api_input()assistant 分支把该 id 原样写入出站 input[].id, 而 OpenAI Responses API 只接受服务端签发的 msg_* id—— 换回 400 "Invalid 'input[2].id': 'lc_...'. Expected an ID that begins with 'msg'."。 同样的对话发给 Anthropic / Google 均正常,唯独 OpenAI 崩溃。

框架自铸元数据泄漏到 wire 协议(F9 第2例) 与 #39100 跨 provider 镜像(Anthropic system / OpenAI assistant) ARK 实机离线复现 ✅(无需 API key)

影响范围

langchain-openai 1.4.1 · Responses API + v1 content_blocks 构造合成 AIMessage 的所有用户

框架现状

⚠️ open(2026-07-28 报告)· 无人认领 · 关联 #39100(同族已修复)

ARK 方案

✅ OutputValidator 出站 payload 契约 + InputGuard internal 字段标记

🔍

根因定位(P2 Probe)

缺陷是「无形状校验的 id 透传」——出站边界把「有 id」当成了「id 合法」:

  1. create_text_block() 对无 id 的块自动铸造 lc_<uuid4>——v1 内容块把 id 设计为一等公民;
  2. _construct_responses_api_input() 的 assistant 分支读取 msg_id = block.get("id"),只要 store is not False 就写入 new_item["id"] = msg_id——没有任何前缀/形状检查
  3. 该 id 的设计意图是回传服务端签发msg_* item id(stored-replay 场景合法且必要),但框架自铸的 lc_* id 走同一条路径无差别透传;
  4. 对比:human/system 消息块的 id 在同一函数中已被丢弃(用户实测「system 和 human 的 id 已被 pop」),唯独 assistant 分支缺这道清洗——与 #39100(Anthropic 唯独 system 分支缺清洗)构成精确镜像。
# langchain_openai/chat_models/base.py · _construct_responses_api_input() elif msg["role"] == "assistant": ... msg_id = block.get("id") # ← lc_ 或 msg_ 一视同仁 ... else: new_item: dict = {"type": "message", "content": [new_block], "role": "assistant"} if store is not False: new_item["id"] = msg_id # ← 无形状校验,lc_ id 直达 wire input_.append(new_item)

ARK 离线确定性复现(无需 API key): 通过 ChatOpenAI._get_request_payload() 直接检视出站 payload—— 默认 store=None 时 assistant item 携带 "id": "lc_..."(必 400); store=False 时 id 被整体省略(合法); 服务端 msg_* id 正常透传(合法)。三态对照锁死根因。 脚本:scripts/repros/repro_39113_responses_assistant_lc_id_leak.py

💥

后果分析

💊

ARK 处方(P3 Prescribe)

组件机制
上游修复建议 _construct_responses_api_input() assistant 分支写 id 前加形状校验:仅 msg_id.startswith("msg_")(或非 lc_ 前缀)时保留,否则省略——与 store=False 路径行为对齐
ARK 防线 1 OutputValidator 对每个 provider 声明 wire schema 白名单(OpenAI Responses:input[].id 必须 ^msg_),发送前对账,违规即拦截+告警
ARK 防线 2 InputGuard 框架自铸字段(lc_ 前缀)统一打 internal 标记,任何 internal 字段出现在出站 payload 即契约违规——单点强制取代 N×M 人工纪律
ARK 防线 3 OTel 留痕 ark.validation.fail 事件记录被拦截字段与来源块,替代「400 后从堆栈反推」的排障路径
🧬

缺陷族谱 · F9 出站契约不对称(第 2 例)

Issue适配器泄漏分支状态
#39100langchain-anthropicsystem(human/assistant 已收窄)✅ 当日修复(PR #39101)
#39113langchain-openai (Responses)assistant(human/system 已 pop)⚠️ open

两例互为镜像:同一个自铸 id、同一类出站边界,泄漏点却在不同 provider 的互补角色分支——精确演示了「每个适配器的每个分支都必须记得过滤」这一人工纪律的不可持续性。#39100 的修复(PR #39101)只堵了 Anthropic system 一处,OpenAI assistant 分支原样存活至今,正是 ARK「单点自动强制」主张的最强证据。

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

4P Framework: Pinpoint → Probe → Prescribe → Publish · 生成于 2026-07-29 05:34 CST