AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

ChatOpenAI Responses API Streamed Chunks Use Inconsistent IDs (Resp_ Vs Lc_run--)

When using ChatOpenAI with use_responses_api=True and output_version='v1', only the first chunk (response.created) carries the provider ID resp_...; subsequent content chunks get id=None and are filled with lc_run--<run_id>. The final merged AIMessage prefers the provider ID, causing duplicate messages in LangGraph stream_mode='messages' and frontends that reconcile by ID.

mediumConfidence 92%Langchain-OpenaiAffected V1.6.6

Origin Analysis

In langchain_openai.chat_models._convert_responses_chunk_to_generation_chunk, the provider response ID is assigned only to the response.created chunk. Later chunks have message.id=None, so BaseChatModel stamps them with lc_run--<run_id>. When merging, add_ai_message_chunks prefers the non-None provider ID, yielding a mismatch.
Run the provided snippet with langchain-openai 1.6.6, ChatOpenAI(model='gpt-4.1-mini', use_responses_api=True, output_version='v1'). Stream 'Say hello' and collect chunk IDs. The printed IDs include both resp_... and lc_run--...; the final merged AIMessage.id is resp_...; assertions fail.

Fixing Code Block

def _stream(self, *args, **kwargs): response_id = None for chunk in super()._stream(*args, **kwargs): message = getattr(chunk, 'message', None) if message is not None: if message.id is not None: response_id = message.id elif response_id is not None: message.id = response_id yield chunk async def _astream(self, *args, **kwargs): response_id = None async for chunk in super()._astream(*args, **kwargs): message = getattr(chunk, 'message', None) if message is not None: if message.id is not None: response_id = message.id elif response_id is not None: message.id = response_id yield chunk
This adds state-carrying in _stream and _astream: the provider ID from response.created is saved in a local variable and stamped onto any later chunk whose message.id is None. This keeps one ID per response and aligns streamed chunks with the final merged message.

Edge Case Audit

The local variable is per-invocation, so concurrent streams do not interfere. If OpenAI ever sends multiple response.created events in one stream, the last ID would overwrite earlier chunks; such behavior is not currently expected. Rollback: revert these methods and pin langchain-openai to a version before the fix. Test both sync and async streaming and LangGraph stream_mode='messages'.

Ecosystem Topology