diff --git a/libs/partners/openai/langchain_openai/chat_models/base.py b/libs/partners/openai/langchain_openai/chat_models/base.py index 35c21dda9a4..af5f49abfd0 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -4162,6 +4162,9 @@ def _construct_responses_api_payload( payload["max_output_tokens"] = payload.pop(legacy_token_param) if "reasoning_effort" in payload and "reasoning" not in payload: payload["reasoning"] = {"effort": payload.pop("reasoning_effort")} + # Responses API has no `stop` parameter (Chat Completions does); drop it to + # avoid request rejection. + payload.pop("stop", None) # Remove temperature parameter for models that don't support it in responses API # gpt-5-chat supports temperature, and gpt-5 models with reasoning.effort='none' diff --git a/libs/partners/openai/tests/cassettes/TestChatOpenAICodexStandard.test_stop_sequence.yaml.gz b/libs/partners/openai/tests/cassettes/TestChatOpenAICodexStandard.test_stop_sequence.yaml.gz new file mode 100644 index 00000000000..9e5ff8202e1 Binary files /dev/null and b/libs/partners/openai/tests/cassettes/TestChatOpenAICodexStandard.test_stop_sequence.yaml.gz differ diff --git a/libs/partners/openai/tests/unit_tests/chat_models/test_base.py b/libs/partners/openai/tests/unit_tests/chat_models/test_base.py index 13283009e90..92af400e982 100644 --- a/libs/partners/openai/tests/unit_tests/chat_models/test_base.py +++ b/libs/partners/openai/tests/unit_tests/chat_models/test_base.py @@ -1382,6 +1382,39 @@ def test_minimal_reasoning_effort_payload( assert payload["max_completion_tokens"] == 100 +@pytest.mark.parametrize("via_invoke", [False, True]) +def test_responses_api_payload_excludes_stop(via_invoke: bool) -> None: + """The Responses API rejects `stop`, so it must be dropped from the payload. + + Covers `stop` supplied both at construction time and at invoke time, since + they reach the payload through different code paths. + """ + if via_invoke: + llm = ChatOpenAI(model=OPENAI_TEST_MODEL, use_responses_api=True) + payload = llm._get_request_payload( + [HumanMessage(content="Hello")], stop=["END"] + ) + else: + llm = ChatOpenAI( # type: ignore[call-arg] + model=OPENAI_TEST_MODEL, stop=["END"], use_responses_api=True + ) + payload = llm._get_request_payload([HumanMessage(content="Hello")]) + + assert "stop" not in payload + + +def test_chat_completions_payload_includes_stop() -> None: + """`stop` must be preserved for the Chat Completions API, which supports it. + + Guards against an over-broad change dropping `stop` outside the Responses API. + """ + llm = ChatOpenAI(model=OPENAI_TEST_MODEL, stop=["END"]) # type: ignore[call-arg] + + payload = llm._get_request_payload([HumanMessage(content="Hello")]) + + assert payload["stop"] == ["END"] + + def test_output_version_compat() -> None: llm = ChatOpenAI(model="gpt-5", output_version="responses/v1") assert llm._use_responses_api({}) is True