Skip to content

langchain-openai: Responses API streaming silently drops response.failed/error events. A failed stream is indistinguishable from a successful completion. #39039

Description

@GoodWorkRic

Submission checklist

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Package (Required)

  • langchain
  • langchain-openai
  • langchain-anthropic
  • langchain-classic
  • langchain-core
  • langchain-model-profiles
  • langchain-tests
  • langchain-text-splitters
  • langchain-chroma
  • langchain-deepseek
  • langchain-exa
  • langchain-fireworks
  • langchain-groq
  • langchain-huggingface
  • langchain-mistralai
  • langchain-nomic
  • langchain-ollama
  • langchain-openrouter
  • langchain-perplexity
  • langchain-qdrant
  • langchain-xai
  • Other / not sure / general

Related Issues / PRs

No response

Reproduction Steps / Example Code (Python)

"""
Repro: langchain-openai silently swallows Responses API failure events.

    pip install langchain-openai httpx


No API key or network needed (httpx.MockTransport). Reproduces on
langchain-openai 1.1.12 and 1.4.0.

The Responses API has four terminal stream events. The streaming converter
handles `response.completed` and `response.incomplete` (stamping `status`
into response_metadata), but `response.failed` and `error` fall through the
converter's `else` branch and are silently discarded — and a stream that
ends with NO terminal event at all is likewise returned as if successful.
"""

import json

import httpx
from langchain_openai import ChatOpenAI

_seq = 0


def sse(event_type: str, data: dict) -> bytes:
    global _seq
    data["sequence_number"] = _seq
    _seq += 1
    return f"event: {event_type}\ndata: {json.dumps(data)}\n\n".encode()


def base_frames() -> list[bytes]:
    """response.created, a message item, one text delta — no terminal event."""
    envelope = {
        "id": "resp_fake", "object": "response", "created_at": 1753246000,
        "model": "gpt-4.1", "status": "in_progress", "output": [],
        "parallel_tool_calls": False, "tool_choice": "auto", "tools": [],
    }
    return [
        sse("response.created", {"type": "response.created", "response": envelope}),
        sse("response.output_item.added", {
            "type": "response.output_item.added", "output_index": 0,
            "item": {"type": "message", "id": "msg_1", "role": "assistant",
                     "status": "in_progress", "content": []},
        }),
        sse("response.output_text.delta", {
            "type": "response.output_text.delta", "item_id": "msg_1",
            "output_index": 0, "content_index": 0, "delta": "The answer", "logprobs": [],
        }),
    ]


FAILED_EVENT = sse("response.failed", {
    "type": "response.failed",
    "response": {
        "id": "resp_fake", "object": "response", "created_at": 1753246000,
        "model": "gpt-4.1", "status": "failed", "output": [],
        "parallel_tool_calls": False, "tool_choice": "auto", "tools": [],
        "error": {"code": "server_error",
                  "message": "The model failed to generate a response."},
    },
})


def run(label: str, frames: list[bytes]) -> None:
    def handler(request: httpx.Request) -> httpx.Response:
        return httpx.Response(
            200, headers={"content-type": "text/event-stream"},
            content=b"".join(frames),
        )

    llm = ChatOpenAI(
        model="gpt-4.1",
        use_responses_api=True,
        api_key="sk-fake",
        http_client=httpx.Client(transport=httpx.MockTransport(handler)),
    )
    full = None
    for chunk in llm.stream("what is 2+2?"):
        full = chunk if full is None else full + chunk

    print(f"--- {label}")
    print(f"    raised: nothing")
    print(f"    text: {full.text!r}")
    print(f"    'status' in response_metadata: {'status' in full.response_metadata}"
          f"  (value: {full.response_metadata.get('status')!r})")
    print(f"    error surfaced anywhere: "
          f"{'error' in full.response_metadata or 'error' in full.additional_kwargs}")


# A) stream dies with no terminal event -> looks like a successful reply
run("A: stream ends with NO terminal event", base_frames())

# B) stream ends with response.failed (error payload included) -> identical:
#    event silently dropped, error payload lost, no exception
run("B: stream ends with response.failed", base_frames() + [FAILED_EVENT])

# C) control: response.completed IS handled -> status stamped, proving the
#    metadata channel exists for terminal events
completed = sse("response.completed", {
    "type": "response.completed",
    "response": {
        "id": "resp_fake", "object": "response", "created_at": 1753246000,
        "model": "gpt-4.1", "status": "completed",
        "parallel_tool_calls": False, "tool_choice": "auto", "tools": [],
        "error": None, "incomplete_details": None,
        "output": [{"type": "message", "id": "msg_1", "role": "assistant",
                    "status": "completed",
                    "content": [{"type": "output_text", "text": "The answer",
                                 "annotations": []}]}],
        "usage": {"input_tokens": 10, "output_tokens": 3, "total_tokens": 13,
                  "input_tokens_details": {"cached_tokens": 0},
                  "output_tokens_details": {"reasoning_tokens": 0}},
    },
})

Error Message and Stack Trace (if applicable)

Description

We hit this in production with a LangGraph agent on ChatOpenAI (use_responses_api=True). Mid-generation, OpenAI's backend killed the stream. Instead of an error, ainvoke returned a normal-looking AIMessage containing only the partial output (reasoning blocks, no text). Our graph treated it as a final answer and the user got
silence. It took us two days to trace, because nothing raised and nothing was logged at any layer.

The cause (we think) is in _convert_responses_chunk_to_generation_chunk in langchain_openai/chat_models/base.py. The Responses API has four terminal stream events. The converter handles response.completed and response.incomplete (that branch stamps status and usage into response_metadata), but response.failed and error fall through the final else and are dropped, including the error payload OpenAI sent (response.error with code and message). A stream that ends with no terminal event at all (connection died) is likewise returned as if it finished normally. In all these cases the returned message is indistinguishable from a deliberate short answer unless you know to check for a missing status key.

We expected an exception, or at a minimum a status: "failed" plus the error payload stamped into response_metadata so callers can detect it.

What actually happened was just an AI message

The repro runs as-is with no API key (mock transport). Scenario A is the no-terminal-event case, B is response.failed, C is the response.completed controlshowing the metadata channel works when the event is handled. Reproduced on langchain-openai 1.1.12 and 1.4.0 (langchain-core 1.5.0), Python 3.12.

System Info

System Information

OS: Windows
OS Version: 10.0.26200
Python Version: 3.12.0 (tags/v3.12.0:0fb18b0, Oct 2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)]

Package Information

langchain_core: 1.5.0
langsmith: 0.10.10
langchain_openai: 1.4.0
langchain_protocol: 0.0.18

Optional packages not installed

deepagents
deepagents-cli

Other Dependencies

anyio: 4.14.2
distro: 1.9.0
httpx: 0.28.1
jsonpatch: 1.33
openai: 2.47.0
orjson: 3.11.9
packaging: 26.2
pydantic: 2.13.4
pyyaml: 6.0.3
requests: 2.34.2
requests-toolbelt: 1.0.0
sniffio: 1.3.1
tenacity: 9.1.4
tiktoken: 0.13.0
typing-extensions: 4.16.0
uuid-utils: 0.17.0
websockets: 16.1.1
xxhash: 3.8.1
zstandard: 0.25.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugRelated to a bug, vulnerability, unexpected error with an existing featureexternalopenai`langchain-openai` package issues & PRs

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions