From 1b77a191f4cc291c9bc1f32a7f2fac0878dee6a2 Mon Sep 17 00:00:00 2001 From: riunyfir <144903038+riunyfir@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:46:11 +0800 Subject: [PATCH] feat: The response.incomplete event is not handled when using stream_mode=['messages'] (#33871) --- .../langchain_openai/chat_models/base.py | 2 +- .../test_incomplete_response.yaml.gz | Bin 0 -> 4435 bytes .../chat_models/test_responses_api.py | 20 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 libs/partners/openai/tests/cassettes/test_incomplete_response.yaml.gz diff --git a/libs/partners/openai/langchain_openai/chat_models/base.py b/libs/partners/openai/langchain_openai/chat_models/base.py index a694d68e35a..c343bf50f25 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -4351,7 +4351,7 @@ def _convert_responses_chunk_to_generation_chunk( elif chunk.type == "response.created": id = chunk.response.id response_metadata["id"] = chunk.response.id # Backwards compatibility - elif chunk.type == "response.completed": + elif chunk.type in ("response.completed", "response.incomplete"): msg = cast( AIMessage, ( diff --git a/libs/partners/openai/tests/cassettes/test_incomplete_response.yaml.gz b/libs/partners/openai/tests/cassettes/test_incomplete_response.yaml.gz new file mode 100644 index 0000000000000000000000000000000000000000..f9ac5b9da957f8f7682300cd02cad11de6ec8233 GIT binary patch literal 4435 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 None: assert response.response_metadata["service_tier"] # type: ignore[typeddict-item] +@pytest.mark.vcr +def test_incomplete_response() -> None: + model = ChatOpenAI( + model=MODEL_NAME, use_responses_api=True, max_completion_tokens=16 + ) + response = model.invoke("Tell me a 100 word story about a bear.") + assert response.response_metadata["incomplete_details"] + assert response.response_metadata["incomplete_details"]["reason"] + assert response.response_metadata["status"] == "incomplete" + + full: AIMessageChunk | None = None + for chunk in model.stream("Tell me a 100 word story about a bear."): + assert isinstance(chunk, AIMessageChunk) + full = chunk if full is None else full + chunk + assert isinstance(full, AIMessageChunk) + assert full.response_metadata["incomplete_details"] + assert full.response_metadata["incomplete_details"]["reason"] + assert full.response_metadata["status"] == "incomplete" + + @pytest.mark.default_cassette("test_web_search.yaml.gz") @pytest.mark.vcr @pytest.mark.parametrize("output_version", ["responses/v1", "v1"])