mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
langgraph: remove deprecations and add new warnings (#1196)
* langgraph: remove deprecations and add new warnings
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import functools
|
||||
import warnings
|
||||
from typing import Any, Callable, TypeVar, cast
|
||||
from typing import Any, Callable, Type, TypeVar, Union, cast
|
||||
|
||||
|
||||
class LangGraphDeprecationWarning(DeprecationWarning):
|
||||
@@ -8,31 +8,57 @@ class LangGraphDeprecationWarning(DeprecationWarning):
|
||||
|
||||
|
||||
F = TypeVar("F", bound=Callable[..., Any])
|
||||
C = TypeVar("C", bound=Type[Any])
|
||||
|
||||
|
||||
def deprecated(
|
||||
since: str, alternative: str, *, removal: str = "", example: str = ""
|
||||
) -> Callable[[F], F]:
|
||||
def decorator(func: F) -> F:
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
removal_str = removal if removal else "a future version"
|
||||
message = (
|
||||
f"{func.__name__} is deprecated as of version {since} and will be"
|
||||
f" removed in {removal_str}. Use {alternative} instead.{example}"
|
||||
)
|
||||
warnings.warn(message, LangGraphDeprecationWarning, stacklevel=2)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
docstring = (
|
||||
f"**Deprecated**: This function is deprecated as of version {since}. "
|
||||
f"Use `{alternative}` instead."
|
||||
def decorator(obj: Union[F, C]) -> Union[F, C]:
|
||||
removal_str = removal if removal else "a future version"
|
||||
message = (
|
||||
f"{obj.__name__} is deprecated as of version {since} and will be"
|
||||
f" removed in {removal_str}. Use {alternative} instead.{example}"
|
||||
)
|
||||
if func.__doc__:
|
||||
docstring = docstring + f"\n\n{func.__doc__}"
|
||||
wrapper.__doc__ = docstring
|
||||
if isinstance(obj, type):
|
||||
original_init = obj.__init__
|
||||
|
||||
return cast(F, wrapper)
|
||||
@functools.wraps(original_init)
|
||||
def new_init(self, *args: Any, **kwargs: Any) -> None:
|
||||
warnings.warn(message, LangGraphDeprecationWarning, stacklevel=2)
|
||||
original_init(self, *args, **kwargs)
|
||||
|
||||
obj.__init__ = new_init
|
||||
|
||||
docstring = (
|
||||
f"**Deprecated**: This class is deprecated as of version {since}. "
|
||||
f"Use `{alternative}` instead."
|
||||
)
|
||||
if obj.__doc__:
|
||||
docstring = docstring + f"\n\n{obj.__doc__}"
|
||||
obj.__doc__ = docstring
|
||||
|
||||
return cast(C, obj)
|
||||
elif callable(obj):
|
||||
|
||||
@functools.wraps(obj)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
warnings.warn(message, LangGraphDeprecationWarning, stacklevel=2)
|
||||
return obj(*args, **kwargs)
|
||||
|
||||
docstring = (
|
||||
f"**Deprecated**: This function is deprecated as of version {since}. "
|
||||
f"Use `{alternative}` instead."
|
||||
)
|
||||
if obj.__doc__:
|
||||
docstring = docstring + f"\n\n{obj.__doc__}"
|
||||
wrapper.__doc__ = docstring
|
||||
|
||||
return cast(F, wrapper)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Can only add deprecation decorator to classes or callables, got '{type(obj)}' instead."
|
||||
)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
"""langgraph.prebuilt exposes a higher-level API for creating and executing agents and tools."""
|
||||
from langgraph.prebuilt import chat_agent_executor
|
||||
from langgraph.prebuilt.agent_executor import create_agent_executor
|
||||
from langgraph.prebuilt.chat_agent_executor import create_react_agent
|
||||
from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation
|
||||
from langgraph.prebuilt.tool_node import InjectedState, ToolNode, tools_condition
|
||||
from langgraph.prebuilt.tool_validator import ValidationNode
|
||||
|
||||
__all__ = [
|
||||
"create_agent_executor",
|
||||
"chat_agent_executor",
|
||||
"create_react_agent",
|
||||
"ToolExecutor",
|
||||
"ToolInvocation",
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
import operator
|
||||
from typing import Annotated, Sequence, TypedDict, Union
|
||||
|
||||
from langchain_core.agents import AgentAction, AgentFinish
|
||||
from langchain_core.messages import BaseMessage
|
||||
|
||||
from langgraph._api.deprecation import deprecated
|
||||
from langgraph.graph import END, StateGraph
|
||||
from langgraph.graph.state import CompiledStateGraph
|
||||
from langgraph.prebuilt.tool_executor import ToolExecutor
|
||||
from langgraph.utils import RunnableCallable
|
||||
|
||||
|
||||
def _get_agent_state(input_schema=None):
|
||||
if input_schema is None:
|
||||
|
||||
class AgentState(TypedDict):
|
||||
# The input string
|
||||
input: str
|
||||
# The list of previous messages in the conversation
|
||||
chat_history: Sequence[BaseMessage]
|
||||
# The outcome of a given call to the agent
|
||||
# Needs `None` as a valid type, since this is what this will start as
|
||||
agent_outcome: Union[AgentAction, AgentFinish, None]
|
||||
# List of actions and corresponding observations
|
||||
# Here we annotate this with `operator.add` to indicate that operations to
|
||||
# this state should be ADDED to the existing values (not overwrite it)
|
||||
intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]
|
||||
|
||||
else:
|
||||
|
||||
class AgentState(input_schema):
|
||||
# The outcome of a given call to the agent
|
||||
# Needs `None` as a valid type, since this is what this will start as
|
||||
agent_outcome: Union[AgentAction, AgentFinish, None]
|
||||
# List of actions and corresponding observations
|
||||
# Here we annotate this with `operator.add` to indicate that operations to
|
||||
# this state should be ADDED to the existing values (not overwrite it)
|
||||
intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]
|
||||
|
||||
return AgentState
|
||||
|
||||
|
||||
@deprecated(
|
||||
"0.0.44",
|
||||
alternative="create_react_agent",
|
||||
removal="0.2.0",
|
||||
example="""
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
create_react_agent(...)
|
||||
""",
|
||||
)
|
||||
def create_agent_executor(
|
||||
agent_runnable, tools, input_schema=None
|
||||
) -> CompiledStateGraph:
|
||||
"""This is a helper function for creating a graph that works with LangChain Agents.
|
||||
|
||||
Args:
|
||||
agent_runnable (RunnableLike): The agent runnable.
|
||||
tools (list): A list of tools to be used by the agent.
|
||||
input_schema (dict, optional): The input schema for the agent. Defaults to None.
|
||||
|
||||
Returns:
|
||||
The `CompiledStateGraph` object.
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
# Since this is deprecated, you should use `create_react_agent` instead.
|
||||
# Example usage:
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
|
||||
tools = [TavilySearchResults(max_results=1)]
|
||||
model = ChatOpenAI()
|
||||
|
||||
app = create_react_agent(model, tools)
|
||||
|
||||
inputs = {"messages": [("user", "what is the weather in sf")]}
|
||||
for s in app.stream(inputs):
|
||||
print(list(s.values())[0])
|
||||
print("----")
|
||||
"""
|
||||
|
||||
if isinstance(tools, ToolExecutor):
|
||||
tool_executor = tools
|
||||
else:
|
||||
tool_executor = ToolExecutor(tools)
|
||||
|
||||
state = _get_agent_state(input_schema)
|
||||
|
||||
# Define logic that will be used to determine which conditional edge to go down
|
||||
|
||||
def should_continue(data):
|
||||
# If the agent outcome is an AgentFinish, then we return `exit` string
|
||||
# This will be used when setting up the graph to define the flow
|
||||
if isinstance(data["agent_outcome"], AgentFinish):
|
||||
return "end"
|
||||
# Otherwise, an AgentAction is returned
|
||||
# Here we return `continue` string
|
||||
# This will be used when setting up the graph to define the flow
|
||||
else:
|
||||
return "continue"
|
||||
|
||||
def run_agent(data, config):
|
||||
agent_outcome = agent_runnable.invoke(data, config)
|
||||
return {"agent_outcome": agent_outcome}
|
||||
|
||||
async def arun_agent(data, config):
|
||||
agent_outcome = await agent_runnable.ainvoke(data, config)
|
||||
return {"agent_outcome": agent_outcome}
|
||||
|
||||
# Define the function to execute tools
|
||||
def execute_tools(data, config):
|
||||
# Get the most recent agent_outcome - this is the key added in the `agent` above
|
||||
agent_action = data["agent_outcome"]
|
||||
if not isinstance(agent_action, list):
|
||||
agent_action = [agent_action]
|
||||
output = tool_executor.batch(agent_action, config, return_exceptions=True)
|
||||
return {
|
||||
"intermediate_steps": [
|
||||
(action, str(out)) for action, out in zip(agent_action, output)
|
||||
]
|
||||
}
|
||||
|
||||
async def aexecute_tools(data, config):
|
||||
# Get the most recent agent_outcome - this is the key added in the `agent` above
|
||||
agent_action = data["agent_outcome"]
|
||||
if not isinstance(agent_action, list):
|
||||
agent_action = [agent_action]
|
||||
output = await tool_executor.abatch(
|
||||
agent_action, config, return_exceptions=True
|
||||
)
|
||||
return {
|
||||
"intermediate_steps": [
|
||||
(action, str(out)) for action, out in zip(agent_action, output)
|
||||
]
|
||||
}
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(state)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", RunnableCallable(run_agent, arun_agent))
|
||||
workflow.add_node("tools", RunnableCallable(execute_tools, aexecute_tools))
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
workflow.set_entry_point("agent")
|
||||
|
||||
# We now add a conditional edge
|
||||
workflow.add_conditional_edges(
|
||||
# First, we define the start node. We use `agent`.
|
||||
# This means these are the edges taken after the `agent` node is called.
|
||||
"agent",
|
||||
# Next, we pass in the function that will determine which node is called next.
|
||||
should_continue,
|
||||
# Finally we pass in a mapping.
|
||||
# The keys are strings, and the values are other nodes.
|
||||
# END is a special node marking that the graph should finish.
|
||||
# What will happen is we will call `should_continue`, and then the output of that
|
||||
# will be matched against the keys in this mapping.
|
||||
# Based on which one it matches, that node will then be called.
|
||||
{
|
||||
# If `tools`, then we call the tool node.
|
||||
"continue": "tools",
|
||||
# Otherwise we finish.
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
|
||||
# We now add a normal edge from `tools` to `agent`.
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
workflow.add_edge("tools", "agent")
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable
|
||||
return workflow.compile()
|
||||
@@ -1,5 +1,3 @@
|
||||
import json
|
||||
import types
|
||||
from typing import (
|
||||
Annotated,
|
||||
Callable,
|
||||
@@ -15,20 +13,18 @@ from langchain_core.language_models import LanguageModelLike
|
||||
from langchain_core.messages import (
|
||||
AIMessage,
|
||||
BaseMessage,
|
||||
FunctionMessage,
|
||||
SystemMessage,
|
||||
)
|
||||
from langchain_core.runnables import Runnable, RunnableConfig, RunnableLambda
|
||||
from langchain_core.tools import BaseTool
|
||||
from langchain_core.utils.function_calling import convert_to_openai_function
|
||||
|
||||
from langgraph._api.deprecation import deprecated, deprecated_parameter
|
||||
from langgraph._api.deprecation import deprecated_parameter
|
||||
from langgraph.checkpoint.base import BaseCheckpointSaver
|
||||
from langgraph.graph import END, StateGraph
|
||||
from langgraph.graph.graph import CompiledGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.managed import IsLastStep
|
||||
from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation
|
||||
from langgraph.prebuilt.tool_executor import ToolExecutor
|
||||
from langgraph.prebuilt.tool_node import ToolNode
|
||||
|
||||
|
||||
@@ -64,136 +60,6 @@ StateModifier = Union[
|
||||
]
|
||||
|
||||
|
||||
@deprecated("0.0.44", "create_react_agent", removal="0.2.0")
|
||||
def create_function_calling_executor(
|
||||
model: LanguageModelLike, tools: Union[ToolExecutor, Sequence[BaseTool]]
|
||||
) -> CompiledGraph:
|
||||
"""Creates a graph that works with a chat model that utilizes function calling.
|
||||
|
||||
Examples:
|
||||
```pycon
|
||||
>>> # Since this is deprecated, you should use `create_react_agent` instead.
|
||||
>>> # Example usage:
|
||||
>>> from langgraph.prebuilt import create_react_agent
|
||||
>>> from langchain_openai import ChatOpenAI
|
||||
>>> from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
>>>
|
||||
>>> tools = [TavilySearchResults(max_results=1)]
|
||||
>>> model = ChatOpenAI()
|
||||
>>>
|
||||
>>> app = create_react_agent(model, tools)
|
||||
>>> inputs = {"messages": [("user", "what is the weather in sf")]}
|
||||
>>> for s in app.stream(inputs):
|
||||
... print(list(s.values())[0])
|
||||
... print("----")
|
||||
```
|
||||
"""
|
||||
if isinstance(tools, ToolExecutor):
|
||||
tool_executor = tools
|
||||
tool_classes = tools.tools
|
||||
else:
|
||||
tool_executor = ToolExecutor(tools)
|
||||
tool_classes = tools
|
||||
model = model.bind(functions=[convert_to_openai_function(t) for t in tool_classes])
|
||||
|
||||
# Define the function that determines whether to continue or not
|
||||
def should_continue(state: AgentState):
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
# If there is no function call, then we finish
|
||||
if "function_call" not in last_message.additional_kwargs:
|
||||
return "end"
|
||||
# Otherwise if there is, we continue
|
||||
else:
|
||||
return "continue"
|
||||
|
||||
# Define the function that calls the model
|
||||
def call_model(state: AgentState, config: RunnableConfig):
|
||||
messages = state["messages"]
|
||||
response = model.invoke(messages, config)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
|
||||
async def acall_model(state: AgentState, config: RunnableConfig):
|
||||
messages = state["messages"]
|
||||
response = await model.ainvoke(messages, config)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [response]}
|
||||
|
||||
# Define the function to execute tools
|
||||
def _get_action(state: AgentState):
|
||||
messages = state["messages"]
|
||||
# Based on the continue condition
|
||||
# we know the last message involves a function call
|
||||
last_message = messages[-1]
|
||||
# We construct an AgentAction from the function_call
|
||||
return ToolInvocation(
|
||||
tool=last_message.additional_kwargs["function_call"]["name"],
|
||||
tool_input=json.loads(
|
||||
last_message.additional_kwargs["function_call"]["arguments"]
|
||||
),
|
||||
)
|
||||
|
||||
def call_tool(state: AgentState, config: RunnableConfig):
|
||||
action = _get_action(state)
|
||||
# We call the tool_executor and get back a response
|
||||
response = tool_executor.invoke(action, config)
|
||||
# We use the response to create a FunctionMessage
|
||||
function_message = FunctionMessage(content=str(response), name=action.tool)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [function_message]}
|
||||
|
||||
async def acall_tool(state: AgentState, config: RunnableConfig):
|
||||
action = _get_action(state)
|
||||
# We call the tool_executor and get back a response
|
||||
response = await tool_executor.ainvoke(action, config)
|
||||
# We use the response to create a FunctionMessage
|
||||
function_message = FunctionMessage(content=str(response), name=action.tool)
|
||||
# We return a list, because this will get added to the existing list
|
||||
return {"messages": [function_message]}
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(AgentState)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", RunnableLambda(call_model, acall_model))
|
||||
workflow.add_node("tools", RunnableLambda(call_tool, acall_tool))
|
||||
|
||||
# Set the entrypoint as `agent`
|
||||
# This means that this node is the first one called
|
||||
workflow.set_entry_point("agent")
|
||||
|
||||
# We now add a conditional edge
|
||||
workflow.add_conditional_edges(
|
||||
# First, we define the start node. We use `agent`.
|
||||
# This means these are the edges taken after the `agent` node is called.
|
||||
"agent",
|
||||
# Next, we pass in the function that will determine which node is called next.
|
||||
should_continue,
|
||||
# Finally we pass in a mapping.
|
||||
# The keys are strings, and the values are other nodes.
|
||||
# END is a special node marking that the graph should finish.
|
||||
# What will happen is we will call `should_continue`, and then the output of that
|
||||
# will be matched against the keys in this mapping.
|
||||
# Based on which one it matches, that node will then be called.
|
||||
{
|
||||
# If `tools`, then we call the tool node.
|
||||
"continue": "tools",
|
||||
# Otherwise we finish.
|
||||
"end": END,
|
||||
},
|
||||
)
|
||||
|
||||
# We now add a normal edge from `tools` to `agent`.
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
workflow.add_edge("tools", "agent")
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable
|
||||
return workflow.compile()
|
||||
|
||||
|
||||
def _get_state_modifier_runnable(state_modifier: Optional[StateModifier]) -> Runnable:
|
||||
state_modifier_runnable: Runnable
|
||||
if state_modifier is None:
|
||||
@@ -231,7 +97,7 @@ def _convert_messages_modifier_to_state_modifier(
|
||||
state_modifier: StateModifier
|
||||
if isinstance(messages_modifier, (str, SystemMessage)):
|
||||
return messages_modifier
|
||||
elif isinstance(messages_modifier, types.FunctionType):
|
||||
elif callable(messages_modifier):
|
||||
|
||||
def state_modifier(state: AgentState) -> Sequence[BaseMessage]:
|
||||
return messages_modifier(state["messages"])
|
||||
@@ -261,7 +127,7 @@ def _get_model_preprocessing_runnable(
|
||||
return _get_state_modifier_runnable(state_modifier)
|
||||
|
||||
|
||||
@deprecated_parameter("messages_modifier", "0.1.9", "state_modifier", removal="0.2.0")
|
||||
@deprecated_parameter("messages_modifier", "0.1.9", "state_modifier", removal="0.3.0")
|
||||
def create_react_agent(
|
||||
model: LanguageModelLike,
|
||||
tools: Union[ToolExecutor, Sequence[BaseTool]],
|
||||
@@ -656,6 +522,5 @@ create_tool_calling_executor = create_react_agent
|
||||
__all__ = [
|
||||
"create_react_agent",
|
||||
"create_tool_calling_executor",
|
||||
"create_function_calling_executor",
|
||||
"AgentState",
|
||||
]
|
||||
|
||||
@@ -5,6 +5,7 @@ from langchain_core.runnables import RunnableConfig
|
||||
from langchain_core.tools import BaseTool
|
||||
from langchain_core.tools import tool as create_tool
|
||||
|
||||
from langgraph._api.deprecation import deprecated
|
||||
from langgraph.utils import RunnableCallable
|
||||
|
||||
INVALID_TOOL_MSG_TEMPLATE = (
|
||||
@@ -13,6 +14,7 @@ INVALID_TOOL_MSG_TEMPLATE = (
|
||||
)
|
||||
|
||||
|
||||
@deprecated("0.2.0", "langgraph.prebuilt.ToolNode", removal="0.3.0")
|
||||
class ToolInvocationInterface:
|
||||
"""Interface for invoking a tool.
|
||||
|
||||
@@ -26,6 +28,7 @@ class ToolInvocationInterface:
|
||||
tool_input: Union[str, dict]
|
||||
|
||||
|
||||
@deprecated("0.2.0", "langgraph.prebuilt.ToolNode", removal="0.3.0")
|
||||
class ToolInvocation(Serializable):
|
||||
"""Information about how to invoke a tool.
|
||||
|
||||
@@ -47,6 +50,7 @@ class ToolInvocation(Serializable):
|
||||
tool_input: Union[str, dict]
|
||||
|
||||
|
||||
@deprecated("0.2.0", "langgraph.prebuilt.ToolNode", removal="0.3.0")
|
||||
class ToolExecutor(RunnableCallable):
|
||||
"""Executes a tool invocation.
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ from langgraph.graph.graph import START
|
||||
from langgraph.graph.message import MessageGraph, add_messages
|
||||
from langgraph.graph.state import StateGraph
|
||||
from langgraph.prebuilt.chat_agent_executor import (
|
||||
create_function_calling_executor,
|
||||
create_tool_calling_executor,
|
||||
)
|
||||
from langgraph.prebuilt.tool_node import ToolNode
|
||||
@@ -3719,140 +3718,6 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_prebuilt_chat(snapshot: SnapshotAssertion) -> None:
|
||||
from langchain_core.language_models.fake_chat_models import (
|
||||
FakeMessagesListChatModel,
|
||||
)
|
||||
from langchain_core.messages import AIMessage, FunctionMessage, HumanMessage
|
||||
from langchain_core.tools import tool
|
||||
|
||||
class FakeFuntionChatModel(FakeMessagesListChatModel):
|
||||
def bind_functions(self, functions: list):
|
||||
return self
|
||||
|
||||
@tool()
|
||||
def search_api(query: str) -> str:
|
||||
"""Searches the API for the query."""
|
||||
return f"result for {query}"
|
||||
|
||||
tools = [search_api]
|
||||
|
||||
app = create_function_calling_executor(
|
||||
FakeFuntionChatModel(
|
||||
responses=[
|
||||
AIMessage(
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {
|
||||
"name": "search_api",
|
||||
"arguments": json.dumps("query"),
|
||||
}
|
||||
},
|
||||
),
|
||||
AIMessage(
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {
|
||||
"name": "search_api",
|
||||
"arguments": json.dumps("another"),
|
||||
}
|
||||
},
|
||||
),
|
||||
AIMessage(content="answer"),
|
||||
]
|
||||
),
|
||||
tools,
|
||||
)
|
||||
|
||||
assert app.get_input_schema().schema_json() == snapshot
|
||||
assert app.get_output_schema().schema_json() == snapshot
|
||||
assert json.dumps(app.get_graph().to_json(), indent=2) == snapshot
|
||||
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
|
||||
|
||||
assert app.invoke(
|
||||
{"messages": [HumanMessage(content="what is weather in sf")]}
|
||||
) == {
|
||||
"messages": [
|
||||
_AnyIdHumanMessage(content="what is weather in sf"),
|
||||
AIMessage(
|
||||
id=AnyStr(),
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {"name": "search_api", "arguments": '"query"'}
|
||||
},
|
||||
),
|
||||
FunctionMessage(content="result for query", name="search_api", id=AnyStr()),
|
||||
AIMessage(
|
||||
id=AnyStr(),
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {"name": "search_api", "arguments": '"another"'}
|
||||
},
|
||||
),
|
||||
FunctionMessage(
|
||||
content="result for another", name="search_api", id=AnyStr()
|
||||
),
|
||||
_AnyIdAIMessage(content="answer"),
|
||||
]
|
||||
}
|
||||
|
||||
assert [
|
||||
*app.stream({"messages": [HumanMessage(content="what is weather in sf")]})
|
||||
] == [
|
||||
{
|
||||
"agent": {
|
||||
"messages": [
|
||||
AIMessage(
|
||||
id=AnyStr(),
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {
|
||||
"name": "search_api",
|
||||
"arguments": '"query"',
|
||||
}
|
||||
},
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tools": {
|
||||
"messages": [
|
||||
FunctionMessage(
|
||||
content="result for query", name="search_api", id=AnyStr()
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"agent": {
|
||||
"messages": [
|
||||
AIMessage(
|
||||
id=AnyStr(),
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {
|
||||
"name": "search_api",
|
||||
"arguments": '"another"',
|
||||
}
|
||||
},
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tools": {
|
||||
"messages": [
|
||||
FunctionMessage(
|
||||
content="result for another", name="search_api", id=AnyStr()
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
{"agent": {"messages": [_AnyIdAIMessage(content="answer")]}},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("serde", [NoopSerializer(), JsonPlusSerializer()])
|
||||
def test_state_graph_packets(serde: SerializerProtocol) -> None:
|
||||
from langchain_core.language_models.fake_chat_models import (
|
||||
|
||||
@@ -52,7 +52,6 @@ from langgraph.graph import END, Graph, StateGraph
|
||||
from langgraph.graph.graph import START
|
||||
from langgraph.graph.message import MessageGraph, add_messages
|
||||
from langgraph.prebuilt.chat_agent_executor import (
|
||||
create_function_calling_executor,
|
||||
create_tool_calling_executor,
|
||||
)
|
||||
from langgraph.prebuilt.tool_executor import ToolExecutor
|
||||
@@ -3507,138 +3506,6 @@ async def test_prebuilt_tool_chat() -> None:
|
||||
]
|
||||
|
||||
|
||||
async def test_prebuilt_chat() -> None:
|
||||
from langchain_core.language_models.fake_chat_models import (
|
||||
FakeMessagesListChatModel,
|
||||
)
|
||||
from langchain_core.messages import AIMessage, FunctionMessage, HumanMessage
|
||||
from langchain_core.tools import tool
|
||||
|
||||
class FakeFuntionChatModel(FakeMessagesListChatModel):
|
||||
def bind_functions(self, functions: list):
|
||||
return self
|
||||
|
||||
@tool()
|
||||
def search_api(query: str) -> str:
|
||||
"""Searches the API for the query."""
|
||||
return f"result for {query}"
|
||||
|
||||
tools = [search_api]
|
||||
|
||||
app = create_function_calling_executor(
|
||||
FakeFuntionChatModel(
|
||||
responses=[
|
||||
AIMessage(
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {
|
||||
"name": "search_api",
|
||||
"arguments": json.dumps("query"),
|
||||
}
|
||||
},
|
||||
),
|
||||
AIMessage(
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {
|
||||
"name": "search_api",
|
||||
"arguments": json.dumps("another"),
|
||||
}
|
||||
},
|
||||
),
|
||||
AIMessage(content="answer"),
|
||||
]
|
||||
),
|
||||
tools,
|
||||
)
|
||||
|
||||
assert await app.ainvoke(
|
||||
{"messages": [HumanMessage(content="what is weather in sf")]}
|
||||
) == {
|
||||
"messages": [
|
||||
_AnyIdHumanMessage(content="what is weather in sf"),
|
||||
AIMessage(
|
||||
id=AnyStr(),
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {"name": "search_api", "arguments": '"query"'}
|
||||
},
|
||||
),
|
||||
FunctionMessage(content="result for query", name="search_api", id=AnyStr()),
|
||||
AIMessage(
|
||||
id=AnyStr(),
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {"name": "search_api", "arguments": '"another"'}
|
||||
},
|
||||
),
|
||||
FunctionMessage(
|
||||
content="result for another", name="search_api", id=AnyStr()
|
||||
),
|
||||
_AnyIdAIMessage(content="answer"),
|
||||
]
|
||||
}
|
||||
|
||||
assert [
|
||||
c
|
||||
async for c in app.astream(
|
||||
{"messages": [HumanMessage(content="what is weather in sf")]}
|
||||
)
|
||||
] == [
|
||||
{
|
||||
"agent": {
|
||||
"messages": [
|
||||
AIMessage(
|
||||
id=AnyStr(),
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {
|
||||
"name": "search_api",
|
||||
"arguments": '"query"',
|
||||
}
|
||||
},
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tools": {
|
||||
"messages": [
|
||||
FunctionMessage(
|
||||
content="result for query", name="search_api", id=AnyStr()
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"agent": {
|
||||
"messages": [
|
||||
AIMessage(
|
||||
id=AnyStr(),
|
||||
content="",
|
||||
additional_kwargs={
|
||||
"function_call": {
|
||||
"name": "search_api",
|
||||
"arguments": '"another"',
|
||||
}
|
||||
},
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tools": {
|
||||
"messages": [
|
||||
FunctionMessage(
|
||||
content="result for another", name="search_api", id=AnyStr()
|
||||
)
|
||||
]
|
||||
}
|
||||
},
|
||||
{"agent": {"messages": [_AnyIdAIMessage(content="answer")]}},
|
||||
]
|
||||
|
||||
|
||||
async def test_state_graph_packets() -> None:
|
||||
from langchain_core.language_models.fake_chat_models import (
|
||||
FakeMessagesListChatModel,
|
||||
|
||||
Reference in New Issue
Block a user