From bfe0a26547496b4e8a88c4337a63f99d02aedd7e Mon Sep 17 00:00:00 2001 From: Christophe Bornet Date: Sat, 10 Jan 2026 03:48:33 +0100 Subject: [PATCH] chore(langchain): remove generic from `FakeToolCallingModel` (#34572) * Making `FakeToolCallingModel` generic on its `structured_response` doesn't help anywhere in typing. * There are more than 120 references of `FakeToolCallingModel` in the code where you get ` error: Need type annotation for "model" [var-annotated]` because mypy can't resolve the generic type (we don't see them atm because they are in files temporarily excluded from mypy checking). We would need to explicitly type them to `FakeToolCallingModel[Any]` Co-authored-by: Mason Daugherty --- .../tests/unit_tests/agents/model.py | 22 ++++++++++--------- .../unit_tests/agents/test_react_agent.py | 2 +- .../unit_tests/agents/test_response_format.py | 14 ++++++------ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/libs/langchain_v1/tests/unit_tests/agents/model.py b/libs/langchain_v1/tests/unit_tests/agents/model.py index 16e4ca52cf0..917673c5a9a 100644 --- a/libs/langchain_v1/tests/unit_tests/agents/model.py +++ b/libs/langchain_v1/tests/unit_tests/agents/model.py @@ -3,9 +3,7 @@ from collections.abc import Callable, Sequence from dataclasses import asdict, is_dataclass from typing import ( Any, - Generic, Literal, - TypeVar, ) from langchain_core.callbacks import CallbackManagerForLLMRun @@ -19,13 +17,12 @@ from langchain_core.outputs import ChatGeneration, ChatResult from langchain_core.runnables import Runnable from langchain_core.tools import BaseTool from pydantic import BaseModel - -StructuredResponseT = TypeVar("StructuredResponseT") +from typing_extensions import override -class FakeToolCallingModel(BaseChatModel, Generic[StructuredResponseT]): - tool_calls: list[list[ToolCall]] | list[list[dict]] | None = None - structured_response: StructuredResponseT | None = None +class FakeToolCallingModel(BaseChatModel): + tool_calls: list[list[ToolCall]] | list[list[dict[str, Any]]] | None = None + structured_response: Any | None = None index: int = 0 tool_style: Literal["openai", "anthropic"] = "openai" @@ -52,7 +49,9 @@ class FakeToolCallingModel(BaseChatModel, Generic[StructuredResponseT]): if is_native and not tool_calls: if isinstance(self.structured_response, BaseModel): content_obj = self.structured_response.model_dump() - elif is_dataclass(self.structured_response): + elif is_dataclass(self.structured_response) and not isinstance( + self.structured_response, type + ): content_obj = asdict(self.structured_response) elif isinstance(self.structured_response, dict): content_obj = self.structured_response @@ -71,11 +70,14 @@ class FakeToolCallingModel(BaseChatModel, Generic[StructuredResponseT]): def _llm_type(self) -> str: return "fake-tool-call-model" + @override def bind_tools( self, - tools: Sequence[dict[str, Any] | type[BaseModel] | Callable | BaseTool], + tools: Sequence[dict[str, Any] | type | Callable[..., Any] | BaseTool], + *, + tool_choice: str | None = None, **kwargs: Any, - ) -> Runnable[LanguageModelInput, BaseMessage]: + ) -> Runnable[LanguageModelInput, AIMessage]: if len(tools) == 0: msg = "Must provide at least one tool" raise ValueError(msg) diff --git a/libs/langchain_v1/tests/unit_tests/agents/test_react_agent.py b/libs/langchain_v1/tests/unit_tests/agents/test_react_agent.py index be561179cfd..bfcc87987f3 100644 --- a/libs/langchain_v1/tests/unit_tests/agents/test_react_agent.py +++ b/libs/langchain_v1/tests/unit_tests/agents/test_react_agent.py @@ -362,7 +362,7 @@ # return "The weather is sunny and 75°F." # expected_structured_response = WeatherResponse(temperature=75) -# model = FakeToolCallingModel[WeatherResponse]( +# model = FakeToolCallingModel( # tool_calls=tool_calls, structured_response=expected_structured_response # ) # agent = create_agent( 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 44573309069..b19a0c58852 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 @@ -343,7 +343,7 @@ class TestResponseFormatAsToolStrategy: ], ] - model = FakeToolCallingModel[WeatherBaseModel | LocationResponse](tool_calls=tool_calls) + model = FakeToolCallingModel(tool_calls=tool_calls) agent = create_agent( model, @@ -655,7 +655,7 @@ class TestResponseFormatAsProviderStrategy: [{"args": {}, "id": "1", "name": "get_weather"}], ] - model = FakeToolCallingModel[WeatherBaseModel]( + model = FakeToolCallingModel( tool_calls=tool_calls, structured_response=EXPECTED_WEATHER_PYDANTIC ) @@ -678,7 +678,7 @@ class TestResponseFormatAsProviderStrategy: ] # But we're using WeatherBaseModel which has different field requirements - model = FakeToolCallingModel[dict]( + model = FakeToolCallingModel( tool_calls=tool_calls, structured_response={"invalid": "data"}, # Wrong structure ) @@ -699,7 +699,7 @@ class TestResponseFormatAsProviderStrategy: [{"args": {}, "id": "1", "name": "get_weather"}], ] - model = FakeToolCallingModel[WeatherDataclass]( + model = FakeToolCallingModel( tool_calls=tool_calls, structured_response=EXPECTED_WEATHER_DATACLASS ) @@ -719,7 +719,7 @@ class TestResponseFormatAsProviderStrategy: [{"args": {}, "id": "1", "name": "get_weather"}], ] - model = FakeToolCallingModel[WeatherTypedDict]( + model = FakeToolCallingModel( tool_calls=tool_calls, structured_response=EXPECTED_WEATHER_DICT ) @@ -737,7 +737,7 @@ class TestResponseFormatAsProviderStrategy: [{"args": {}, "id": "1", "name": "get_weather"}], ] - model = FakeToolCallingModel[dict]( + model = FakeToolCallingModel( tool_calls=tool_calls, structured_response=EXPECTED_WEATHER_DICT ) @@ -858,7 +858,7 @@ def test_union_of_types() -> None: ], ] - model = FakeToolCallingModel[WeatherBaseModel | LocationResponse]( + model = FakeToolCallingModel( tool_calls=tool_calls, structured_response=EXPECTED_WEATHER_PYDANTIC )