diff --git a/libs/langchain_v1/langchain/agents/factory.py b/libs/langchain_v1/langchain/agents/factory.py index 886436a6911..deb118815b9 100644 --- a/libs/langchain_v1/langchain/agents/factory.py +++ b/libs/langchain_v1/langchain/agents/factory.py @@ -495,9 +495,14 @@ def _supports_provider_strategy( if ( model_profile is not None and model_profile.get("structured_output") - # We make an exception for Gemini models, which currently do not support - # simultaneous tool use with structured output - and not (tools and isinstance(model_name, str) and "gemini" in model_name.lower()) + # We make an exception for Gemini < 3-series models, which currently do not support + # simultaneous tool use with structured output; 3-series can. + and not ( + tools + and isinstance(model_name, str) + and "gemini" in model_name.lower() + and "gemini-3" not in model_name.lower() + ) ): return True 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 e75684143dc..ad793ae8942 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 @@ -16,6 +16,7 @@ from pydantic import BaseModel, Field from typing_extensions import TypedDict from langchain.agents import create_agent +from langchain.agents.factory import _supports_provider_strategy from langchain.agents.middleware.types import ( AgentMiddleware, ModelCallResult, @@ -897,3 +898,44 @@ def test_union_of_types() -> None: assert response["structured_response"] == EXPECTED_WEATHER_PYDANTIC assert len(response["messages"]) == 5 + + +class TestSupportsProviderStrategy: + """Unit tests for `_supports_provider_strategy`.""" + + @staticmethod + def _make_structured_model(model_name: str): + class GeminiTestChatModel(GenericFakeChatModel): + model_name: str + + return GeminiTestChatModel( + messages=iter( + [ + AIMessage(content="test-response"), + ] + ), + profile={"structured_output": True}, + model_name=model_name, + ) + + def test_blocks_gemini_v2_with_tools(self) -> None: + """Gemini 2 series models cannot use provider strategy with tools.""" + model = self._make_structured_model("gemini-2.5-flash") + assert not _supports_provider_strategy(model, tools=[get_weather]) + + def test_allows_gemini_v3_with_tools(self) -> None: + """Gemini 3 series models support structured output alongside tools.""" + model = self._make_structured_model("gemini-3-pro-preview") + assert _supports_provider_strategy(model, tools=[get_weather]) + + @pytest.mark.parametrize( + "alias", + [ + "gemini-flash-latest", + "gemini-flash-lite-latest", + ], + ) + def test_blocks_gemini_latest_aliases(self, alias: str) -> None: + """Latest aliases stay blocked until they point to Gemini 3.""" + model = self._make_structured_model(alias) + assert not _supports_provider_strategy(model, tools=[get_weather])