From 900f8a3513c9b4cd5d5b739d9f573d2fb0663db5 Mon Sep 17 00:00:00 2001 From: ccurme Date: Sun, 22 Mar 2026 14:23:24 -0400 Subject: [PATCH] fix(openai): support phase parameter (#36161) --- .../langchain_openai/chat_models/base.py | 44 ++++++--- .../openai/tests/cassettes/test_phase.yaml.gz | Bin 0 -> 4104 bytes .../cassettes/test_phase_streaming.yaml.gz | Bin 0 -> 15178 bytes .../chat_models/test_responses_api.py | 92 ++++++++++++++++++ libs/partners/openai/uv.lock | 15 +-- 5 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 libs/partners/openai/tests/cassettes/test_phase.yaml.gz create mode 100644 libs/partners/openai/tests/cassettes/test_phase_streaming.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 e9d4a06d5a5..947c23236de 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -4278,6 +4278,7 @@ def _construct_responses_api_input(messages: Sequence[BaseMessage]) -> list: # Aggregate content blocks for a single message if block_type in ("text", "output_text", "refusal"): msg_id = block.get("id") + phase = block.get("phase") if block_type in ("text", "output_text"): # Defensive check: block may not have "text" key text = block.get("text") @@ -4303,17 +4304,20 @@ def _construct_responses_api_input(messages: Sequence[BaseMessage]) -> list: if "content" not in item: item["content"] = [] item["content"].append(new_block) + if phase is not None: + item["phase"] = phase break else: # If no block with this ID, create a new one - input_.append( - { - "type": "message", - "content": [new_block], - "role": "assistant", - "id": msg_id, - } - ) + new_item: dict = { + "type": "message", + "content": [new_block], + "role": "assistant", + "id": msg_id, + } + if phase is not None: + new_item["phase"] = phase + input_.append(new_item) elif block_type in ( "reasoning", "compaction", @@ -4467,6 +4471,7 @@ def _construct_lc_result_from_responses_api( additional_kwargs: dict = {} for output in response.output: if output.type == "message": + phase = getattr(output, "phase", None) for content in output.content: if content.type == "output_text": block = { @@ -4480,13 +4485,20 @@ def _construct_lc_result_from_responses_api( else [], "id": output.id, } + if phase is not None: + block["phase"] = phase content_blocks.append(block) if hasattr(content, "parsed"): additional_kwargs["parsed"] = content.parsed if content.type == "refusal": - content_blocks.append( - {"type": "refusal", "refusal": content.refusal, "id": output.id} - ) + refusal_block = { + "type": "refusal", + "refusal": content.refusal, + "id": output.id, + } + if phase is not None: + refusal_block["phase"] = phase + content_blocks.append(refusal_block) elif output.type == "function_call": content_blocks.append(output.model_dump(exclude_none=True, mode="json")) try: @@ -4707,6 +4719,16 @@ def _convert_responses_chunk_to_generation_chunk( elif chunk.type == "response.output_item.added" and chunk.item.type == "message": if output_version == "v0": id = chunk.item.id + elif phase := getattr(chunk.item, "phase", None): + _advance(chunk.output_index, 0) + content.append( + { + "type": "text", + "text": "", + "phase": phase, + "index": current_index, + } + ) else: pass elif ( diff --git a/libs/partners/openai/tests/cassettes/test_phase.yaml.gz b/libs/partners/openai/tests/cassettes/test_phase.yaml.gz new file mode 100644 index 0000000000000000000000000000000000000000..dc93a34fa19b61c4752230c3915e8aed74a07d79 GIT binary patch literal 4104 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 GHvj;8&e383 literal 0 HcmV?d00001 diff --git a/libs/partners/openai/tests/cassettes/test_phase_streaming.yaml.gz b/libs/partners/openai/tests/cassettes/test_phase_streaming.yaml.gz new file mode 100644 index 0000000000000000000000000000000000000000..4ea488fe752362bf38689b0d2c6f813309c0e9bd GIT binary patch literal 15178 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�WJ&m#WCLQVUqs1tVg$t0sMg@YnMvsO9dRa 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 RU!<=?P_>#p!Oj#E None: ) +@pytest.mark.default_cassette("test_phase.yaml.gz") +@pytest.mark.vcr +@pytest.mark.parametrize("output_version", ["responses/v1", "v1"]) +def test_phase(output_version: str) -> None: + def get_weather(location: str) -> str: + """Get the weather at a location.""" + return "It's sunny." + + model = ChatOpenAI( + model="gpt-5.4", + use_responses_api=True, + verbosity="high", + reasoning={"effort": "medium", "summary": "auto"}, + output_version=output_version, + ) + + agent = create_agent(model, tools=[get_weather]) + + input_message = { + "role": "user", + "content": ( + "What's the weather in the oldest major city in the US? State your answer " + "and then generate a tool call this turn." + ), + } + result = agent.invoke({"messages": [input_message]}) + first_response = result["messages"][1] + text_block = next( + block for block in first_response.content if block["type"] == "text" + ) + assert text_block["phase"] == "commentary" + + final_response = result["messages"][-1] + text_block = next( + block for block in final_response.content if block["type"] == "text" + ) + assert text_block["phase"] == "final_answer" + + +@pytest.mark.default_cassette("test_phase_streaming.yaml.gz") +@pytest.mark.vcr +@pytest.mark.parametrize("output_version", ["responses/v1", "v1"]) +def test_phase_streaming(output_version: str) -> None: + def get_weather(location: str) -> str: + """Get the weather at a location.""" + return "It's sunny." + + model = ChatOpenAI( + model="gpt-5.4", + use_responses_api=True, + verbosity="high", + reasoning={"effort": "medium", "summary": "auto"}, + streaming=True, + output_version=output_version, + ) + + agent = create_agent(model, tools=[get_weather]) + + input_message = { + "role": "user", + "content": ( + "What's the weather in the oldest major city in the US? State your answer " + "and then generate a tool call this turn." + ), + } + result = agent.invoke({"messages": [input_message]}) + first_response = result["messages"][1] + if output_version == "responses/v1": + assert [block["type"] for block in first_response.content] == [ + "reasoning", + "text", + "function_call", + ] + else: + assert [block["type"] for block in first_response.content] == [ + "reasoning", + "text", + "tool_call", + ] + text_block = next( + block for block in first_response.content if block["type"] == "text" + ) + assert text_block["phase"] == "commentary" + + final_response = result["messages"][-1] + assert [block["type"] for block in final_response.content] == ["text"] + text_block = next( + block for block in final_response.content if block["type"] == "text" + ) + assert text_block["phase"] == "final_answer" + + @pytest.mark.default_cassette("test_tool_search.yaml.gz") @pytest.mark.vcr @pytest.mark.parametrize("output_version", ["responses/v1", "v1"]) diff --git a/libs/partners/openai/uv.lock b/libs/partners/openai/uv.lock index 10a7d2109cb..3eb1ed90d23 100644 --- a/libs/partners/openai/uv.lock +++ b/libs/partners/openai/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10.0, <4.0.0" resolution-markers = [ "python_full_version >= '3.13' and platform_python_implementation == 'PyPy'", @@ -547,7 +547,7 @@ wheels = [ [[package]] name = "langchain" -version = "1.2.12" +version = "1.2.13" source = { editable = "../../langchain_v1" } dependencies = [ { name = "langchain-core" }, @@ -560,6 +560,7 @@ requires-dist = [ { name = "langchain-anthropic", marker = "extra == 'anthropic'", editable = "../anthropic" }, { name = "langchain-aws", marker = "extra == 'aws'" }, { name = "langchain-azure-ai", marker = "extra == 'azure-ai'" }, + { name = "langchain-baseten", marker = "extra == 'baseten'", specifier = ">=0.2.0" }, { name = "langchain-community", marker = "extra == 'community'" }, { name = "langchain-core", editable = "../../core" }, { name = "langchain-deepseek", marker = "extra == 'deepseek'" }, @@ -577,7 +578,7 @@ requires-dist = [ { name = "langgraph", specifier = ">=1.1.1,<1.2.0" }, { name = "pydantic", specifier = ">=2.7.4,<3.0.0" }, ] -provides-extras = ["community", "anthropic", "openai", "azure-ai", "google-vertexai", "google-genai", "fireworks", "ollama", "together", "mistralai", "huggingface", "groq", "aws", "deepseek", "xai", "perplexity"] +provides-extras = ["community", "anthropic", "openai", "azure-ai", "google-vertexai", "google-genai", "fireworks", "ollama", "together", "mistralai", "huggingface", "groq", "aws", "baseten", "deepseek", "xai", "perplexity"] [package.metadata.requires-dev] lint = [{ name = "ruff", specifier = ">=0.15.0,<0.16.0" }] @@ -610,7 +611,7 @@ typing = [ [[package]] name = "langchain-core" -version = "1.2.19" +version = "1.2.20" source = { editable = "../../core" } dependencies = [ { name = "jsonpatch" }, @@ -1105,7 +1106,7 @@ wheels = [ [[package]] name = "openai" -version = "2.26.0" +version = "2.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1117,9 +1118,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/91/2a06c4e9597c338cac1e5e5a8dd6f29e1836fc229c4c523529dca387fda8/openai-2.26.0.tar.gz", hash = "sha256:b41f37c140ae0034a6e92b0c509376d907f3a66109935fba2c1b471a7c05a8fb", size = 666702, upload-time = "2026-03-05T23:17:35.874Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/15/203d537e58986b5673e7f232453a2a2f110f22757b15921cbdeea392e520/openai-2.29.0.tar.gz", hash = "sha256:32d09eb2f661b38d3edd7d7e1a2943d1633f572596febe64c0cd370c86d52bec", size = 671128, upload-time = "2026-03-17T17:53:49.599Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/2e/3f73e8ca53718952222cacd0cf7eecc9db439d020f0c1fe7ae717e4e199a/openai-2.26.0-py3-none-any.whl", hash = "sha256:6151bf8f83802f036117f06cc8a57b3a4da60da9926826cc96747888b57f394f", size = 1136409, upload-time = "2026-03-05T23:17:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b1/35b6f9c8cf9318e3dbb7146cc82dab4cf61182a8d5406fc9b50864362895/openai-2.29.0-py3-none-any.whl", hash = "sha256:b7c5de513c3286d17c5e29b92c4c98ceaf0d775244ac8159aeb1bddf840eb42a", size = 1141533, upload-time = "2026-03-17T17:53:47.348Z" }, ] [[package]]