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 <mason@langchain.dev>
This commit is contained in:
Christophe Bornet
2026-01-10 03:48:33 +01:00
committed by GitHub
parent bb5bd1181f
commit bfe0a26547
3 changed files with 20 additions and 18 deletions

View File

@@ -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)

View File

@@ -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(

View File

@@ -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
)