fix(openai): expose inferred Responses API routing at initialization (#40715)

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
ccurmeandopen-swe[bot] authored and GitHub committed 2026-09-21 10:52:15 -04:00
1 parent 8caf77085b
commit 17d3d892cd
2 files changed
+72 -1

No files matched your search

@@ -1206,7 +1206,7 @@ class BaseChatOpenAI(BaseChatModel):
use_responses_api: bool | None = None
"""Whether to use the Responses API instead of the Chat API.
If not specified then will be inferred based on invocation params.
If not specified, set to `True` when instance settings require the Responses API,
!!! version-added "Added in `langchain-openai` 0.3.9"
"""
@@ -1325,6 +1325,13 @@ class BaseChatOpenAI(BaseChatModel):
self._add_version("langchain-openai", __version__)
return self
@model_validator(mode="after")
def _infer_use_responses_api(self) -> Self:
"""Expose unconditional instance-level Responses API routing."""
if self.use_responses_api is None and self._use_responses_api({}):
self.use_responses_api = True
return self
@model_validator(mode="after")
def validate_environment(self) -> Self:
"""Validate that api key and python package exists in environment."""
@@ -4593,6 +4593,70 @@ def test_gpt_5_temperature_case_insensitive(
assert payload["temperature"] == 0.7
@pytest.mark.parametrize(
"kwargs",
[
{"output_version": "responses/v1"},
{"context_management": []},
{"include": []},
{"reasoning": {}},
{"truncation": "auto"},
{"use_previous_response_id": True},
{"model": "gpt-5-pro"},
{"model": "gpt-5.3-codex"},
],
)
@pytest.mark.parametrize("explicit", [None, True, False])
def test_infer_use_responses_api(kwargs: dict, explicit: bool | None) -> None:
llm = ChatOpenAI(**kwargs, use_responses_api=explicit)
expected = explicit if explicit is not None else True
assert llm.use_responses_api is expected
assert llm._use_responses_api({}) is expected
@pytest.mark.parametrize(
"kwargs",
[
{},
{"output_version": "v1"},
{"reasoning_effort": "low"},
{"model": "gpt-6-astra"},
{"model_kwargs": {"text": {}}},
{"model_kwargs": {"tools": [{"type": "web_search"}]}},
],
)
def test_infer_use_responses_api_remains_dynamic(kwargs: dict) -> None:
llm = ChatOpenAI(**kwargs)
assert llm.use_responses_api is None
assert llm._use_responses_api({"tools": [{"type": "web_search"}]})
assert llm._use_responses_api({"text": {}})
assert not llm._use_responses_api({})
def test_infer_use_responses_api_from_output_version_env(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("LC_OUTPUT_VERSION", "responses/v1")
assert ChatOpenAI().use_responses_api is True
def test_inferred_responses_api_bind_tools_strict() -> None:
llm = ChatOpenAI(reasoning={})
tool = {
"type": "function",
"function": {
"name": "get_weather",
"parameters": {"type": "object", "properties": {}},
},
}
bound = llm.bind_tools(
[tool],
response_format={"title": "Weather", "type": "object", "properties": {}},
)
assert isinstance(bound, RunnableBinding)
assert "strict" not in bound.kwargs["tools"][0]["function"]
def test_gpt_6_tools_use_responses_api() -> None:
llm = ChatOpenAI(model="gpt-6-astra")
tools = [