From d27fb0c432afa93f7738ec7a7d76b10400f268f6 Mon Sep 17 00:00:00 2001 From: Towseef Altaf <31309254+towseef41@users.noreply.github.com> Date: Thu, 11 Dec 2025 02:05:23 +0530 Subject: [PATCH] feat(langchain,openai): add strict flag to ProviderStrategy structured output (#34149) --- .../langchain/agents/structured_output.py | 25 +++++++++++++------ .../unit_tests/agents/test_response_format.py | 12 +++++++++ .../langchain_openai/chat_models/base.py | 3 ++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/libs/langchain_v1/langchain/agents/structured_output.py b/libs/langchain_v1/langchain/agents/structured_output.py index 75038675807..e8e6aea20d4 100644 --- a/libs/langchain_v1/langchain/agents/structured_output.py +++ b/libs/langchain_v1/langchain/agents/structured_output.py @@ -255,21 +255,32 @@ class ProviderStrategy(Generic[SchemaT]): def __init__( self, schema: type[SchemaT], + *, + strict: bool = False, ) -> None: - """Initialize ProviderStrategy with schema.""" + """Initialize ProviderStrategy with schema. + + Args: + schema: Schema to enforce via the provider's native structured output. + strict: Whether to request strict provider-side schema enforcement. + """ self.schema = schema - self.schema_spec = _SchemaSpec(schema) + self.schema_spec = _SchemaSpec(schema, strict=strict) def to_model_kwargs(self) -> dict[str, Any]: """Convert to kwargs to bind to a model to force structured output.""" # OpenAI: # - see https://platform.openai.com/docs/guides/structured-outputs - response_format = { + json_schema: dict[str, Any] = { + "name": self.schema_spec.name, + "schema": self.schema_spec.json_schema, + } + if self.schema_spec.strict: + json_schema["strict"] = True + + response_format: dict[str, Any] = { "type": "json_schema", - "json_schema": { - "name": self.schema_spec.name, - "schema": self.schema_spec.json_schema, - }, + "json_schema": json_schema, } return {"response_format": response_format} diff --git a/libs/langchain_v1/tests/unit_tests/agents/test_response_format.py b/libs/langchain_v1/tests/unit_tests/agents/test_response_format.py index 0fad139c7f8..0290cbffa98 100644 --- a/libs/langchain_v1/tests/unit_tests/agents/test_response_format.py +++ b/libs/langchain_v1/tests/unit_tests/agents/test_response_format.py @@ -737,6 +737,18 @@ class TestResponseFormatAsProviderStrategy: assert response["structured_response"] == EXPECTED_WEATHER_DICT assert len(response["messages"]) == 4 + def test_provider_strategy_strict_flag(self) -> None: + """ProviderStrategy should pass through strict flag for provider schemas.""" + # Default should not set strict + strategy_default = ProviderStrategy(WeatherBaseModel) + kwargs_default = strategy_default.to_model_kwargs() + assert "strict" not in kwargs_default["response_format"]["json_schema"] + + # Explicit strict True should include the flag + strategy_strict = ProviderStrategy(WeatherBaseModel, strict=True) + kwargs_strict = strategy_strict.to_model_kwargs() + assert kwargs_strict["response_format"]["json_schema"]["strict"] is True + class TestDynamicModelWithResponseFormat: """Test response_format with middleware that modifies the model.""" diff --git a/libs/partners/openai/langchain_openai/chat_models/base.py b/libs/partners/openai/langchain_openai/chat_models/base.py index 8428c03e402..71853997810 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -1886,9 +1886,10 @@ class BaseChatOpenAI(BaseChatModel): ): # compat with langchain.agents.create_agent response_format, which is # an approximation of OpenAI format + strict = response_format["json_schema"].get("strict", None) response_format = cast(dict, response_format["json_schema"]["schema"]) kwargs["response_format"] = _convert_to_openai_response_format( - response_format + response_format, strict=strict ) return super().bind(tools=formatted_tools, **kwargs)