chore(prebuilt): remove support for models that used bind_X (#5958)

Remove support for models w/ `.bind` used to streamline public API +
recommendations
Also cleaning up `typing.py` file as requested :)
This commit is contained in:
Sydney Runkle
2025-08-19 13:56:16 -04:00
committed by GitHub
parent b58a7fb2fe
commit a5aa9ce27d
3 changed files with 11 additions and 122 deletions
@@ -2,26 +2,9 @@ from __future__ import annotations
from typing import Awaitable, Callable, TypeVar, Union
from langgraph._internal._typing import StateLike
try:
from typing import ParamSpec # 3.10+
except ImportError:
from typing_extensions import ParamSpec # type: ignore
from langchain_core.language_models import LanguageModelInput
from langchain_core.messages import BaseMessage
from langchain_core.runnables import Runnable
from typing_extensions import ParamSpec
P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")
MaybeAwaitable = Union[T, Awaitable[T]]
SyncOrAsync = Callable[P, MaybeAwaitable[R]]
# PreConfiguredChatModel is used to support chat models that have been pre-configured
# using .bind().
# For example, chat_model.bind(api_key="...") will return a PreConfiguredChatModel
PreConfiguredChatModel = Runnable[LanguageModelInput, BaseMessage]
ContextT = TypeVar("ContextT", bound=StateLike)
SyncOrAsync = Callable[P, Union[R, Awaitable[R]]]
@@ -30,9 +30,7 @@ from langchain_core.messages import (
)
from langchain_core.runnables import (
Runnable,
RunnableBinding,
RunnableConfig,
RunnableSequence,
)
from langchain_core.tools import BaseTool
from pydantic import BaseModel
@@ -46,8 +44,6 @@ from langgraph.graph.message import add_messages
from langgraph.graph.state import CompiledStateGraph
from langgraph.managed import RemainingSteps
from langgraph.prebuilt._internal._typing import (
ContextT,
PreConfiguredChatModel,
SyncOrAsync,
)
from langgraph.prebuilt.responses import (
@@ -60,6 +56,7 @@ from langgraph.prebuilt.tool_node import ToolNode
from langgraph.runtime import Runtime
from langgraph.store.base import BaseStore
from langgraph.types import Checkpointer, Command, Send
from langgraph.typing import ContextT
from langgraph.warnings import LangGraphDeprecatedSinceV10
StructuredResponse = Union[dict, BaseModel]
@@ -158,29 +155,6 @@ def _get_prompt_runnable(prompt: Optional[Prompt]) -> Runnable:
return prompt_runnable
def _get_model(model: LanguageModelLike) -> BaseChatModel:
"""Get the underlying model from a RunnableBinding or return the model itself."""
if isinstance(model, RunnableSequence):
model = next(
(
step
for step in model.steps
if isinstance(step, (RunnableBinding, BaseChatModel))
),
model,
)
if isinstance(model, RunnableBinding):
model = model.bound
if not isinstance(model, BaseChatModel):
raise TypeError(
f"Expected `model` to be a ChatModel or RunnableBinding (e.g. model.bind_tools(...)), got {type(model)}"
)
return model
def _validate_chat_history(
messages: Sequence[BaseMessage],
) -> None:
@@ -220,12 +194,7 @@ class _AgentBuilder:
model: Union[
str,
BaseChatModel,
PreConfiguredChatModel,
SyncOrAsync[[StateSchema, Runtime[ContextT]], BaseModel],
SyncOrAsync[
[StateSchema, Runtime[ContextT]],
Awaitable[PreConfiguredChatModel],
],
],
tools: Union[Sequence[Union[BaseTool, Callable, dict[str, Any]]], ToolNode],
*,
@@ -249,27 +218,6 @@ class _AgentBuilder:
"in version 'v2' agents."
)
if isinstance(model, Runnable) and not isinstance(model, BaseChatModel):
# Then we allow for a preconfigured model at least for now.
if not hasattr(model, "bound") or not isinstance(
model.bound, BaseChatModel
):
raise TypeError(
"Expected `model` to be a BaseChatModel or a chat model that "
f"was pre-configured using `.bind()`. Instead got {type(model)}"
)
# Then it's a runnable binding. We don't want any pre-bound tools.
if (kwargs := getattr(model, "kwargs", {})) and "tools" in kwargs:
raise ValueError(
"The `model` parameter should not have pre-bound tools. "
"You are getting this error because the chat model you are using"
"was pre-bound with tools somewhere. The code that binds tools "
"looks like this: `model.bind_tools(...)`. "
"Remove the `bind_tools` call and pass the unbound model "
"and the `tools` parameter separately."
)
self.model = model
self.tools = tools
self.prompt = prompt
@@ -283,6 +231,12 @@ class _AgentBuilder:
self.store = store
self._use_individual_tool_nodes = use_individual_tool_nodes
if isinstance(model, Runnable) and not isinstance(model, BaseChatModel):
raise ValueError(
"Expected `model` to be a BaseChatModel or a string, got {type(model)}."
"The `model` parameter should not have pre-bound tools, simply pass the model and tools separately."
)
self._setup_tools()
self._setup_state_schema()
self._setup_structured_output_tools()
@@ -451,11 +405,11 @@ class _AgentBuilder:
tool_choice = "any"
if tool_choice:
model = cast(BaseChatModel, model).bind_tools(
model = cast(BaseChatModel, model).bind_tools( # type: ignore[assignment]
all_tools, tool_choice=tool_choice
)
else:
model = cast(BaseChatModel, model).bind_tools(all_tools)
model = cast(BaseChatModel, model).bind_tools(all_tools) # type: ignore[assignment]
# Extract just the model part for direct invocation
self._static_model: Optional[Runnable] = model # type: ignore[assignment]
else:
@@ -902,12 +856,7 @@ def create_react_agent(
model: Union[
str,
BaseChatModel,
PreConfiguredChatModel,
SyncOrAsync[[StateSchema, Runtime[ContextT]], BaseModel],
SyncOrAsync[
[StateSchema, Runtime[ContextT]],
Awaitable[PreConfiguredChatModel],
],
],
tools: Union[Sequence[Union[BaseTool, Callable, dict[str, Any]]], ToolNode],
*,
-43
View File
@@ -35,7 +35,6 @@ from langgraph.prebuilt.chat_agent_executor import (
AgentState,
AgentStatePydantic,
StateSchemaType,
_get_model,
_validate_chat_history,
)
from langgraph.prebuilt.tool_node import (
@@ -304,24 +303,6 @@ def test_model_with_tools(tool_style: str, version: str, include_builtin: bool)
)
def test_support_preconfigured_models_but_not_for_tools() -> None:
"""Support (at least temporarily) some model pre-configuration.
This is a temporary workaround to support pre-configured models
for things like temperature or api keys (done via .bind).
We do not want users to pre-bind tools to the models.
"""
model = FakeToolCallingModel()
@dec_tool
def tool1(some_val: int) -> str:
"""Tool 1 docstring."""
return f"Tool 1: {some_val}"
create_react_agent(model.bind(temperature=3), [tool1])
def test__validate_messages():
# empty input
_validate_chat_history([])
@@ -1229,30 +1210,6 @@ def test_tool_node_node_interrupt(
)
def test_get_model() -> None:
model = FakeToolCallingModel(tool_calls=[])
assert _get_model(model) == model
@dec_tool
def some_tool(some_val: int) -> str:
"""Tool docstring."""
return "meow"
model_with_tools = model.bind_tools([some_tool])
assert _get_model(model_with_tools) == model
seq = model | RunnableLambda(lambda message: message)
assert _get_model(seq) == model
seq_with_tools = model.bind_tools([some_tool]) | RunnableLambda(
lambda message: message
)
assert _get_model(seq_with_tools) == model
with pytest.raises(TypeError):
_get_model(RunnableLambda(lambda message: message))
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
def test_dynamic_model_basic(version: str) -> None:
"""Test basic dynamic model functionality."""