diff --git a/libs/prebuilt/langgraph/prebuilt/_internal/_typing.py b/libs/prebuilt/langgraph/prebuilt/_internal/_typing.py index 64e3947cd..bf2121d7a 100644 --- a/libs/prebuilt/langgraph/prebuilt/_internal/_typing.py +++ b/libs/prebuilt/langgraph/prebuilt/_internal/_typing.py @@ -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]]] diff --git a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py b/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py index 8bd8f33c8..4137a227e 100644 --- a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py +++ b/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py @@ -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], *, diff --git a/libs/prebuilt/tests/test_react_agent.py b/libs/prebuilt/tests/test_react_agent.py index 5bcf582ca..80af0759f 100644 --- a/libs/prebuilt/tests/test_react_agent.py +++ b/libs/prebuilt/tests/test_react_agent.py @@ -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."""