🛡️ ARK 智能体诊断报告

langchain-ai/langchain#39039 · Responses API 流式静默丢弃 response.failed / error 事件——失败流与成功流无法区分 · 2026-07-24

📋

问题摘要

🔴

ChatOpenAI(use_responses_api=True) 流式调用时, OpenAI Responses API 的 4 种终止事件中,转换器只处理了 response.completedresponse.incomplete, 而 response.failed 与 error 事件直接掉进 else 分支被丢弃—— 包括 OpenAI 返回的 error code/message 全部丢失。 更糟的是:连接中途死掉(无任何终止事件)也被当作正常完成返回。 调用方拿到一个"看起来正常"的 AIMessage,实际是半截损坏输出

严重 · 静默失败 失败流 = 成功流,不可区分 生产事故实录:排查 2 天

影响范围

所有 Responses API 流式调用

框架现状

⚠️ open · bug/openai 标签

ARK 方案

✅ OutputValidator + CircuitBreaker

🔍

根因定位

🧬 现象链(issue 作者生产实录)

LangGraph Agent 流式生成中途 → OpenAI 后端杀掉了流 → ainvoke 返回一条"正常"的 AIMessage(只含半截 reasoning、没有正文)→ graph 把它当最终答案 → 用户收到静默 → 任何一层都没有 raise、没有 log,排查耗时 2 天

⚙️ 根因

langchain_openai/chat_models/base.py_convert_responses_chunk_to_generation_chunk: Responses API 有 4 种终止事件(completed / incomplete / failed / error), 转换器只对前两种 stamp status+usage 到 response_metadata, 后两种落入最终 else 被静默丢弃(连 OpenAI 附带的 error payload 一起丢)。 无终止事件的"死流"同样按正常结束处理。本质是终止状态机不完备 + 缺失"输出必须携带终态"的不变式校验
💡 本质:这是典型的 "边界处无健康校验"问题——框架层状态机漏了分支,而应用层又完全信任框架输出。 只要在 Agent 与框架的边界放一层结构化输出校验(终态必须存在、失败必须可见), 这类事故就会从"2 天排查的静默事故"变成"1 秒可见的校验失败"。 这正是 ARK OutputValidator + CircuitBreaker 的守备范围。
📊

关键证据

📄 复现(issue 提供 MockTransport 零依赖复现,节选)

# B) 流以 response.failed 结束(附带 error payload)
run("B: stream ends with response.failed", base_frames() + [FAILED_EVENT])

# 实际输出:
#   raised: nothing                      ← 没有异常
#   text: 'The an'                       ← 半截输出被当正文返回
#   'status' in response_metadata: False ← 终态丢失
#   error surfaced anywhere: False       ← error payload 完全丢弃 ❌

📄 根因代码路径(issue 定位)

# _convert_responses_chunk_to_generation_chunk
if event.type == "response.completed":   # ✅ 处理,stamp status/usage
elif event.type == "response.incomplete": # ✅ 处理
...
else:                                     # ❌ response.failed / error 掉进这里
    pass                                  #    连 error.code/message 一起丢弃

失败可见性

静默(0 报错 0 日志)

生产排查耗时

2 天

复现版本

1.1.12 ~ 1.4.0

🔧

ARK 一键修复

✅ 方案A(推荐):OutputValidator 守住"终态不变式"

from ark import OutputValidator

# 不变式:流式输出必须携带明确终态,且 failed 必须可见
schema = {
    "type": "object",
    "fields": {
        "text":   {"type": "string", "min_length": 1},
        "status": {"type": "string", "enum": ["completed", "incomplete"]},
    },
    "required": ["text", "status"],
}
validator = OutputValidator(schema)

msg = await llm.ainvoke(prompt)
validated = validator.validate({
    "text": msg.text,
    "status": msg.response_metadata.get("status"),  # 缺失 → 立即 ValidationError
})
# 半截输出/丢失终态在边界被拦下,而不是流向 graph 下游

💡 issue 作者花 2 天才定位的问题,本质是 status 键缺失——一条 schema 规则即可在 1 秒内暴露,并伴随 ark.validation.fail 事件进入观测栈。

✅ 方案B:CircuitBreaker 隔离抖动的上游流

from ark import CircuitBreaker

breaker = CircuitBreaker(failure_threshold=3, timeout_seconds=30)

def guarded_stream(prompt):
    def _call():
        msg = invoke_and_validate(prompt)   # 方案A 的校验包在里面
        return msg
    return breaker.call(_call)

# OpenAI 后端抖动导致连续死流时:
# 熔断打开 → 快速失败 + 告警,而非持续把损坏输出喂给用户

💡 校验失败计入熔断统计,把"上游偶发抖动"与"业务持续损坏"解耦,恢复后半开态自动探测。

✅ 方案C:auto_init 全链路装配 + 可观测

import ark
ark.auto_init()   # 探测 LangChain/LangGraph,自动装配 校验+熔断+追踪

# 所有 ark.validation.fail / ark.circuit.open 事件
# → OTLP/JSON → Langfuse / Jaeger / Tempo
# "静默失败"从此在 Dashboard 上有名有姓、可审计

💡 与方案A/B 组合:OutputValidator 防静默、CircuitBreaker 防级联、OTel Bridge 让每次失败都留痕。

📈

健康得分

45

45/100 · 正常路径完好,但所有失败路径均不可见

稳定性60
效率70
正确性(安全性)30

💡 健康得分 45/100。正确性仅 30——失败流与成功流完全不可区分, 意味着下游没有任何机会自救:半截输出会直接流入 RAG、决策、支付等链路。接入 ARK OutputValidator(终态不变式)+ CircuitBreaker(抖动隔离)后, 失败在边界 1 秒可见,正确性分可回到 90+

本报告由 ARK 生成 · 智能体健康感知系统
langchain-ai/langchain#39039 · 锚定自真实 Issue(open · bug/openai · 附零依赖 MockTransport 复现 · 生产事故实录)