mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-30 11:49:38 +02:00
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:
@@ -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],
|
||||
*,
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user