langgraph: changes for compatibility pydantic v2 / langchain-core==0.3 (#1594)

This commit is contained in:
Vadym Barda
2024-09-05 15:26:35 -04:00
committed by GitHub
parent 7461978b8c
commit 7bf99a5d2f
18 changed files with 644 additions and 1779 deletions
+10 -2
View File
@@ -21,7 +21,11 @@ jobs:
- "3.10"
- "3.11"
- "3.12"
name: "test #${{ matrix.python-version }}"
core-version:
- ">=0.3.0.dev1,<0.4.0"
- "latest"
name: "test #${{ matrix.python-version }} (langchain-core: ${{ matrix.core-version }})"
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
@@ -35,7 +39,11 @@ jobs:
- name: Install dependencies
shell: bash
working-directory: ${{ inputs.working-directory }}
run: poetry install --with dev
run: |
poetry install --with dev
if [ "${{ matrix.core-version }}" != "latest" ]; then
poetry run pip install "langchain-core${{ matrix.core-version }}"
fi
- name: Run core tests
shell: bash
+1 -1
View File
@@ -6,10 +6,10 @@ import numexpr
from langchain.chains.openai_functions import create_structured_output_runnable
from langchain_core.messages import SystemMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import StructuredTool
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
_MATH_DESCRIPTION = (
"math(problem: str, context: Optional[list[str]]) -> float:\n"
+6 -5
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "annotated-types"
@@ -227,13 +227,13 @@ files = [
[[package]]
name = "langchain-core"
version = "0.2.24"
version = "0.2.38"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_core-0.2.24-py3-none-any.whl", hash = "sha256:9444fc082d21ef075d925590a684a73fe1f9688a3d90087580ec929751be55e7"},
{file = "langchain_core-0.2.24.tar.gz", hash = "sha256:f2e3fa200b124e8c45d270da9bf836bed9c09532612c96ff3225e59b9a232f5a"},
{file = "langchain_core-0.2.38-py3-none-any.whl", hash = "sha256:8a5729bc7e68b4af089af20eff44fe4e7ca21d0e0c87ec21cef7621981fd1a4a"},
{file = "langchain_core-0.2.38.tar.gz", hash = "sha256:eb69dbedd344f2ee1f15bcea6c71a05884b867588fadc42d04632e727c1238f3"},
]
[package.dependencies]
@@ -246,6 +246,7 @@ pydantic = [
]
PyYAML = ">=5.3"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
typing-extensions = ">=4.7"
[[package]]
name = "langsmith"
@@ -850,4 +851,4 @@ watchmedo = ["PyYAML (>=3.10)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.9.0,<4.0"
content-hash = "de79db3dc7701542739b3417e9d3f02c3b41167719603ee4d08b92e23b7443ee"
content-hash = "d4c13800471766fa9e2d11d2f1092f02fbf28cc507189aeea8a3d5b297286068"
+1 -1
View File
@@ -10,7 +10,7 @@ packages = [{ include = "langgraph" }]
[tool.poetry.dependencies]
python = "^3.9.0,<4.0"
langchain-core = ">=0.2.22,<0.3"
langchain-core = ">=0.2.38,<0.4"
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.2"
+2 -2
View File
@@ -10,9 +10,9 @@ from enum import Enum
from ipaddress import IPv4Address
import dataclasses_json
from langchain_core.pydantic_v1 import BaseModel as LcBaseModel
from langchain_core.runnables import RunnableMap
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from zoneinfo import ZoneInfo
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
@@ -23,7 +23,7 @@ class MyPydantic(BaseModel):
bar: int
class MyFunnyPydantic(LcBaseModel):
class MyFunnyPydantic(BaseModelV1):
foo: str
bar: int
+1 -1
View File
@@ -14,12 +14,12 @@ from langchain_core.messages import (
)
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.runnables import RunnableConfig, RunnableLambda
from langchain_core.runnables import chain as as_runnable
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langgraph.graph import END, StateGraph
from pydantic import BaseModel, Field
from typing_extensions import TypedDict
fast_llm = ChatOpenAI(model="gpt-3.5-turbo")
+4 -7
View File
@@ -21,6 +21,7 @@ from langchain_core.runnables import Runnable, RunnableConfig
from langchain_core.runnables.base import RunnableLike
from langchain_core.runnables.utils import create_model
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from langgraph.channels.base import BaseChannel
from langgraph.channels.binop import BinaryOperatorAggregate
@@ -473,10 +474,8 @@ class CompiledStateGraph(CompiledGraph):
def get_input_schema(
self, config: Optional[RunnableConfig] = None
) -> type[BaseModel]:
from pydantic import BaseModel as BaseModelP
if isclass(self.builder.input) and issubclass(
self.builder.input, (BaseModel, BaseModelP)
self.builder.input, (BaseModel, BaseModelV1)
):
return self.builder.input
else:
@@ -508,10 +507,8 @@ class CompiledStateGraph(CompiledGraph):
def get_output_schema(
self, config: Optional[RunnableConfig] = None
) -> type[BaseModel]:
from pydantic import BaseModel as BaseModelP
if isclass(self.builder.input) and issubclass(
self.builder.output, (BaseModel, BaseModelP)
if isclass(self.builder.output) and issubclass(
self.builder.output, (BaseModel, BaseModelV1)
):
return self.builder.output
@@ -24,20 +24,22 @@ from langchain_core.messages import (
ToolCall,
ToolMessage,
)
from langchain_core.pydantic_v1 import BaseModel, ValidationError
from langchain_core.runnables import (
RunnableConfig,
)
from langchain_core.runnables.config import get_executor_for_config
from langchain_core.tools import BaseTool, create_schema_from_function
from pydantic import BaseModel as BaseModelV2
from pydantic import ValidationError as ValidationErrorV2
from pydantic import BaseModel, ValidationError
from pydantic.v1 import BaseModel as BaseModelV1
from pydantic.v1 import ValidationError as ValidationErrorV1
from langgraph.utils.runnable import RunnableCallable
def _default_format_error(
error: BaseException, call: ToolCall, schema: Type[BaseModel]
error: BaseException,
call: ToolCall,
schema: Union[Type[BaseModel], Type[BaseModelV1]],
) -> str:
"""Default error formatting function."""
return f"{repr(error)}\n\nRespond after fixing all validation errors."
@@ -75,7 +77,7 @@ class ValidationNode(RunnableCallable):
>>> from typing import Literal, Annotated, TypedDict
...
>>> from langchain_anthropic import ChatAnthropic
>>> from langchain_core.pydantic_v1 import BaseModel, validator
>>> from pydantic import BaseModel, validator
...
>>> from langgraph.graph import END, START, StateGraph
>>> from langgraph.prebuilt import ValidationNode
@@ -176,7 +178,7 @@ class ValidationNode(RunnableCallable):
)
self.schemas_by_name[schema.name] = schema.args_schema
elif isinstance(schema, type) and issubclass(
schema, (BaseModel, BaseModelV2)
schema, (BaseModel, BaseModelV1)
):
self.schemas_by_name[schema.__name__] = cast(Type[BaseModel], schema)
elif callable(schema):
@@ -212,13 +214,22 @@ class ValidationNode(RunnableCallable):
def run_one(call: ToolCall):
schema = self.schemas_by_name[call["name"]]
try:
output = schema.validate(call["args"])
if issubclass(schema, BaseModel):
output = schema.model_validate(call["args"])
content = output.model_dump_json()
elif issubclass(schema, BaseModelV1):
output = schema.validate(call["args"])
content = output.json()
else:
raise ValueError(
f"Unsupported schema type: {type(schema)}. Expected BaseModel or BaseModelV1."
)
return ToolMessage(
content=output.json(),
content=content,
name=call["name"],
tool_call_id=cast(str, call["id"]),
)
except (ValidationError, ValidationErrorV2) as e:
except (ValidationError, ValidationErrorV1) as e:
return ToolMessage(
content=self._format_error(e, call, schema),
name=call["name"],
+13 -13
View File
@@ -59,19 +59,19 @@ class RunnableCallable(Runnable):
recurse: bool = True,
**kwargs: Any,
) -> None:
if name is not None:
self.name = name
elif func:
try:
if func.__name__ != "<lambda>":
self.name = func.__name__
except AttributeError:
pass
elif afunc:
try:
self.name = afunc.__name__
except AttributeError:
pass
self.name = name
if self.name is None:
if func:
try:
if func.__name__ != "<lambda>":
self.name = func.__name__
except AttributeError:
pass
elif afunc:
try:
self.name = afunc.__name__
except AttributeError:
pass
self.func = func
if func is not None:
self.func_accepts_config = accepts_config(func)
+11 -1236
View File
File diff suppressed because it is too large Load Diff
+1 -8
View File
@@ -9,7 +9,7 @@ repository = "https://www.github.com/langchain-ai/langgraph"
[tool.poetry.dependencies]
python = ">=3.9.0,<4.0"
langchain-core = ">=0.2.27,<0.3"
langchain-core = ">=0.2.38,<0.4"
langgraph-checkpoint = "^1.0.2"
@@ -21,14 +21,10 @@ pytest-mock = "^3.10.0"
syrupy = "^4.0.2"
httpx = "^0.26.0"
pytest-watcher = "^0.4.1"
langchain = ">=0.1.0"
grandalf = "^0.8"
mypy = "^1.6.0"
ruff = "^0.6.2"
jupyter = "^1.0.0"
langchainhub = "^0.1.14"
langchain-openai = ">=0.1.2"
langchain-anthropic = ">=0.1.8"
pytest-xdist = {extras = ["psutil"], version = "^3.6.1"}
pytest-repeat = "^0.9.3"
langgraph-checkpoint = {path = "../checkpoint", develop = true}
@@ -36,9 +32,6 @@ langgraph-checkpoint-sqlite = {path = "../checkpoint-sqlite", develop = true}
langgraph-checkpoint-postgres = {path = "../checkpoint-postgres", develop = true}
psycopg = {extras = ["binary"], version = ">=3.0.0"}
[tool.poetry.group.dev]
optional = true
[tool.ruff]
lint.select = [ "E", "F", "I" ]
lint.ignore = [ "E501" ]
File diff suppressed because one or more lines are too long
@@ -1204,15 +1204,15 @@
weather_graph_model_node(model_node)
weather_graph_weather_node(weather_node<hr/><small><em>__interrupt = before</em></small>)
__end__([__end__]):::last
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
__start__ --> router_node;
normal_llm_node --> __end__;
weather_graph_weather_node --> __end__;
router_node -.-> normal_llm_node;
router_node -.-> weather_graph_model_node;
router_node -.-> __end__;
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
@@ -1229,15 +1229,15 @@
weather_graph_model_node(model_node)
weather_graph_weather_node(weather_node<hr/><small><em>__interrupt = before</em></small>)
__end__([__end__]):::last
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
__start__ --> router_node;
normal_llm_node --> __end__;
weather_graph_weather_node --> __end__;
router_node -.-> normal_llm_node;
router_node -.-> weather_graph_model_node;
router_node -.-> __end__;
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
@@ -1254,15 +1254,15 @@
weather_graph_model_node(model_node)
weather_graph_weather_node(weather_node<hr/><small><em>__interrupt = before</em></small>)
__end__([__end__]):::last
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
__start__ --> router_node;
normal_llm_node --> __end__;
weather_graph_weather_node --> __end__;
router_node -.-> normal_llm_node;
router_node -.-> weather_graph_model_node;
router_node -.-> __end__;
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
@@ -1279,15 +1279,15 @@
weather_graph_model_node(model_node)
weather_graph_weather_node(weather_node<hr/><small><em>__interrupt = before</em></small>)
__end__([__end__]):::last
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
__start__ --> router_node;
normal_llm_node --> __end__;
weather_graph_weather_node --> __end__;
router_node -.-> normal_llm_node;
router_node -.-> weather_graph_model_node;
router_node -.-> __end__;
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
@@ -1304,15 +1304,15 @@
weather_graph_model_node(model_node)
weather_graph_weather_node(weather_node<hr/><small><em>__interrupt = before</em></small>)
__end__([__end__]):::last
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
__start__ --> router_node;
normal_llm_node --> __end__;
weather_graph_weather_node --> __end__;
router_node -.-> normal_llm_node;
router_node -.-> weather_graph_model_node;
router_node -.-> __end__;
subgraph weather_graph
weather_graph_model_node --> weather_graph_weather_node;
end
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
+4
View File
@@ -4,6 +4,8 @@ from typing import AsyncIterator, Optional
from uuid import UUID, uuid4
import pytest
from langchain_core import __version__ as core_version
from packaging import version
from psycopg import AsyncConnection, Connection
from psycopg_pool import AsyncConnectionPool, ConnectionPool
from pytest_mock import MockerFixture
@@ -16,6 +18,8 @@ from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
from tests.memory_assert import MemorySaverAssertImmutable
DEFAULT_POSTGRES_URI = "postgres://postgres:postgres@localhost:5442/"
# TODO: fix this once core is released
SHOULD_CHECK_SNAPSHOTS = version.parse(core_version) >= version.parse("0.3.0.dev0")
@pytest.fixture
+9 -2
View File
@@ -10,7 +10,7 @@ subclassed strings.
from typing import Any
from langchain_core.documents import Document
from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage
from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage, ToolMessage
from tests.any_str import AnyStr
@@ -37,7 +37,14 @@ def _AnyIdAIMessageChunk(**kwargs: Any) -> AIMessageChunk:
def _AnyIdHumanMessage(**kwargs: Any) -> HumanMessage:
"""Create a human with an any id field."""
"""Create a human message with an any id field."""
message = HumanMessage(**kwargs)
message.id = AnyStr()
return message
def _AnyIdToolMessage(**kwargs: Any) -> ToolMessage:
"""Create a tool message with an any id field."""
message = ToolMessage(**kwargs)
message.id = AnyStr()
return message
+4 -4
View File
@@ -14,11 +14,11 @@ from langchain_core.messages import (
ToolMessage,
)
from langchain_core.outputs import ChatGeneration, ChatResult
from langchain_core.pydantic_v1 import BaseModel
from langchain_core.runnables import Runnable, RunnableLambda
from langchain_core.tools import BaseTool
from langchain_core.tools import tool as dec_tool
from pydantic import BaseModel as BaseModelV2
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from langgraph.checkpoint.base import BaseCheckpointSaver
from langgraph.prebuilt import ToolNode, ValidationNode, create_react_agent
@@ -379,7 +379,7 @@ class MyModel(BaseModel):
some_other_val: str
class MyModelV2(BaseModelV2):
class MyModelV1(BaseModelV1):
some_val: int
some_other_val: str
@@ -395,7 +395,7 @@ def my_tool(some_val: int, some_other_val: str) -> str:
[
my_function,
MyModel,
MyModelV2,
MyModelV1,
my_tool,
],
)
+217 -190
View File
@@ -34,6 +34,7 @@ from langchain_core.runnables import (
RunnablePick,
)
from langsmith import traceable
from pydantic import BaseModel
from pytest_mock import MockerFixture
from syrupy import SnapshotAssertion
@@ -72,10 +73,50 @@ from langgraph.pregel.retry import RetryPolicy
from langgraph.pregel.types import PregelTask
from langgraph.store.memory import MemoryStore
from tests.any_str import AnyDict, AnyStr, AnyVersion, UnsortedSequence
from tests.conftest import ALL_CHECKPOINTERS_SYNC
from tests.conftest import ALL_CHECKPOINTERS_SYNC, SHOULD_CHECK_SNAPSHOTS
from tests.fake_tracer import FakeTracer
from tests.memory_assert import MemorySaverAssertCheckpointMetadata
from tests.messages import _AnyIdAIMessage, _AnyIdHumanMessage
from tests.messages import _AnyIdAIMessage, _AnyIdHumanMessage, _AnyIdToolMessage
# define these objects to avoid importing langchain_core.agents
# and therefore avoid relying on core Pydantic version
class AgentAction(BaseModel):
tool: str
tool_input: Union[str, dict]
log: str
type: Literal["AgentAction"] = "AgentAction"
model_config = {
"json_schema_extra": {
"description": (
"""Represents a request to execute an action by an agent.
The action consists of the name of the tool to execute and the input to pass
to the tool. The log is used to pass along extra information about the action."""
)
}
}
class AgentFinish(BaseModel):
"""Final return value of an ActionAgent.
Agents return an AgentFinish when they have reached a stopping condition.
"""
return_values: dict
log: str
type: Literal["AgentFinish"] = "AgentFinish"
model_config = {
"json_schema_extra": {
"description": (
"""Final return value of an ActionAgent.
Agents return an AgentFinish when they have reached a stopping condition."""
)
}
}
def test_graph_validation() -> None:
@@ -440,15 +481,23 @@ def test_invoke_single_process_in_out(mocker: MockerFixture) -> None:
graph.set_finish_point("add_one")
gapp = graph.compile()
assert app.input_schema.schema() == {"title": "LangGraphInput", "type": "integer"}
assert app.output_schema.schema() == {"title": "LangGraphOutput", "type": "integer"}
with warnings.catch_warnings():
warnings.simplefilter("error") # raise warnings as errors
assert app.config_schema().schema() == {
"properties": {},
"title": "LangGraphConfig",
"type": "object",
if SHOULD_CHECK_SNAPSHOTS:
assert app.input_schema.model_json_schema() == {
"title": "LangGraphInput",
"type": "integer",
}
assert app.output_schema.model_json_schema() == {
"title": "LangGraphOutput",
"type": "integer",
}
with warnings.catch_warnings():
warnings.simplefilter("error") # raise warnings as errors
assert app.config_schema().model_json_schema() == {
"properties": {},
"title": "LangGraphConfig",
"type": "object",
}
assert app.invoke(2) == 3
assert app.invoke(2, output_keys=["output"]) == {"output": 3}
assert repr(app), "does not raise recursion error"
@@ -489,16 +538,24 @@ def test_invoke_single_process_in_write_kwargs(mocker: MockerFixture) -> None:
input_channels="input",
)
assert app.input_schema.schema() == {"title": "LangGraphInput", "type": "integer"}
assert app.output_schema.schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {
"output": {"title": "Output", "type": "integer"},
"fixed": {"title": "Fixed", "type": "integer"},
"output_plus_one": {"title": "Output Plus One", "type": "integer"},
},
}
if SHOULD_CHECK_SNAPSHOTS:
assert app.input_schema.model_json_schema() == {
"title": "LangGraphInput",
"type": "integer",
}
assert app.output_schema.model_json_schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {
"output": {"title": "Output", "type": "integer", "default": None},
"fixed": {"title": "Fixed", "type": "integer", "default": None},
"output_plus_one": {
"title": "Output Plus One",
"type": "integer",
"default": None,
},
},
}
assert app.invoke(2) == {"output": 3, "fixed": 5, "output_plus_one": 4}
@@ -513,12 +570,18 @@ def test_invoke_single_process_in_out_dict(mocker: MockerFixture) -> None:
output_channels=["output"],
)
assert app.input_schema.schema() == {"title": "LangGraphInput", "type": "integer"}
assert app.output_schema.schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {"output": {"title": "Output", "type": "integer"}},
}
if SHOULD_CHECK_SNAPSHOTS:
assert app.input_schema.model_json_schema() == {
"title": "LangGraphInput",
"type": "integer",
}
assert app.output_schema.model_json_schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {
"output": {"title": "Output", "type": "integer", "default": None}
},
}
assert app.invoke(2) == {"output": 3}
@@ -532,17 +595,21 @@ def test_invoke_single_process_in_dict_out_dict(mocker: MockerFixture) -> None:
input_channels=["input"],
output_channels=["output"],
)
assert app.input_schema.schema() == {
"title": "LangGraphInput",
"type": "object",
"properties": {"input": {"title": "Input", "type": "integer"}},
}
assert app.output_schema.schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {"output": {"title": "Output", "type": "integer"}},
}
if SHOULD_CHECK_SNAPSHOTS:
assert app.input_schema.model_json_schema() == {
"title": "LangGraphInput",
"type": "object",
"properties": {
"input": {"title": "Input", "type": "integer", "default": None}
},
}
assert app.output_schema.model_json_schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {
"output": {"title": "Output", "type": "integer", "default": None}
},
}
assert app.invoke({"input": 2}) == {"output": 3}
@@ -1955,7 +2022,6 @@ def test_channel_enter_exit_timing(mocker: MockerFixture) -> None:
def test_conditional_graph(
snapshot: SnapshotAssertion, request: pytest.FixtureRequest, checkpointer_name: str
) -> None:
from langchain_core.agents import AgentAction, AgentFinish
from langchain_core.language_models.fake import FakeStreamingListLLM
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
@@ -2034,11 +2100,12 @@ def test_conditional_graph(
app = workflow.compile()
assert json.dumps(app.get_graph().to_json(), indent=2) == snapshot
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
assert app.get_graph().draw_mermaid() == snapshot
assert json.dumps(app.get_graph(xray=True).to_json(), indent=2) == snapshot
assert app.get_graph(xray=True).draw_mermaid(with_styles=False) == snapshot
if SHOULD_CHECK_SNAPSHOTS:
assert json.dumps(app.get_graph().to_json(), indent=2) == snapshot
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
assert app.get_graph().draw_mermaid() == snapshot
assert json.dumps(app.get_graph(xray=True).to_json(), indent=2) == snapshot
assert app.get_graph(xray=True).draw_mermaid(with_styles=False) == snapshot
assert app.invoke({"input": "what is weather in sf"}) == {
"input": "what is weather in sf",
@@ -2168,8 +2235,9 @@ def test_conditional_graph(
)
config = {"configurable": {"thread_id": "1"}}
assert app_w_interrupt.get_graph().to_json() == snapshot
assert app_w_interrupt.get_graph().draw_mermaid() == snapshot
if SHOULD_CHECK_SNAPSHOTS:
assert app_w_interrupt.get_graph().to_json() == snapshot
assert app_w_interrupt.get_graph().draw_mermaid() == snapshot
assert [
c for c in app_w_interrupt.stream({"input": "what is weather in sf"}, config)
@@ -2811,10 +2879,11 @@ def test_conditional_entrypoint_graph(snapshot: SnapshotAssertion) -> None:
app = workflow.compile()
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
if SHOULD_CHECK_SNAPSHOTS:
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
assert json.dumps(app.get_output_schema().model_json_schema()) == 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("what is weather in sf", debug=True)
@@ -2852,10 +2921,11 @@ def test_conditional_entrypoint_to_multiple_state_graph(
app = workflow.compile()
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
if SHOULD_CHECK_SNAPSHOTS:
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
assert json.dumps(app.get_output_schema().model_json_schema()) == 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({"locations": ["sf", "nyc"]}, debug=True) == {
"locations": ["sf", "nyc"],
@@ -2875,7 +2945,6 @@ def test_conditional_state_graph(
request: pytest.FixtureRequest,
checkpointer_name: str,
) -> None:
from langchain_core.agents import AgentAction, AgentFinish
from langchain_core.language_models.fake import FakeStreamingListLLM
from langchain_core.prompts import PromptTemplate
from langchain_core.tools import tool
@@ -2993,10 +3062,11 @@ def test_conditional_state_graph(
app = workflow.compile()
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
if SHOULD_CHECK_SNAPSHOTS:
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
assert json.dumps(app.get_output_schema().model_json_schema()) == snapshot
assert json.dumps(app.get_graph().to_json(), indent=2) == snapshot
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
with assert_ctx_once():
assert app.invoke({"input": "what is weather in sf"}) == {
@@ -3687,7 +3757,6 @@ def test_conditional_state_graph_with_list_edge_inputs(snapshot: SnapshotAsserti
def test_state_graph_w_config_inherited_state_keys(snapshot: SnapshotAssertion) -> None:
from langchain_core.agents import AgentAction, AgentFinish
from langchain_core.language_models.fake import FakeStreamingListLLM
from langchain_core.prompts import PromptTemplate
from langchain_core.tools import tool
@@ -3777,9 +3846,10 @@ def test_state_graph_w_config_inherited_state_keys(snapshot: SnapshotAssertion)
app = builder.compile()
assert app.config_schema().schema_json() == snapshot
assert app.get_input_schema().schema_json() == snapshot
assert app.get_output_schema().schema_json() == snapshot
if SHOULD_CHECK_SNAPSHOTS:
assert json.dumps(app.config_schema().model_json_schema()) == snapshot
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
assert json.dumps(app.get_output_schema().model_json_schema()) == snapshot
assert builder.channels.keys() == {"input", "agent_outcome", "intermediate_steps"}
@@ -3842,10 +3912,11 @@ def test_conditional_entrypoint_graph_state(snapshot: SnapshotAssertion) -> None
app = workflow.compile()
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
if SHOULD_CHECK_SNAPSHOTS:
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
assert json.dumps(app.get_output_schema().model_json_schema()) == 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({"input": "what is weather in sf"}) == {
"input": "what is weather in sf",
@@ -3862,7 +3933,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
from langchain_core.language_models.fake_chat_models import (
FakeMessagesListChatModel,
)
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.tools import tool
class FakeFuntionChatModel(FakeMessagesListChatModel):
@@ -3909,18 +3980,18 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
app = create_tool_calling_executor(model, 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
if SHOULD_CHECK_SNAPSHOTS:
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
assert json.dumps(app.get_output_schema().model_json_schema()) == 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(),
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -3930,14 +4001,12 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
),
AIMessage(
id=AnyStr(),
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -3952,13 +4021,12 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call234",
id=AnyStr(),
),
ToolMessage(
_AnyIdToolMessage(
content="result for a third one",
name="search_api",
tool_call_id="tool_call567",
@@ -3988,7 +4056,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"agent": {
"messages": [
AIMessage(
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -3997,7 +4065,6 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
"args": {"query": "query"},
},
],
id=AnyStr(),
)
]
}
@@ -4005,11 +4072,10 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"tools": {
"messages": [
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
)
]
}
@@ -4017,7 +4083,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"agent": {
"messages": [
AIMessage(
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -4031,7 +4097,6 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
"args": {"query": "a third one"},
},
],
id=AnyStr(),
)
]
}
@@ -4039,17 +4104,15 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"tools": {
"messages": [
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call234",
id=AnyStr(),
),
ToolMessage(
_AnyIdToolMessage(
content="result for a third one",
name="search_api",
tool_call_id="tool_call567",
id=AnyStr(),
),
]
}
@@ -4063,8 +4126,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"agent": {
"messages": [
AIMessage(
id=AnyStr(),
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -4080,11 +4142,10 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"tools": {
"messages": [
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
)
]
}
@@ -4092,8 +4153,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"agent": {
"messages": [
AIMessage(
id=AnyStr(),
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -4114,17 +4174,15 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
{
"tools": {
"messages": [
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call234",
id=AnyStr(),
),
ToolMessage(
_AnyIdToolMessage(
content="result for a third one",
name="search_api",
tool_call_id="tool_call567",
id=AnyStr(),
),
]
}
@@ -4272,10 +4330,9 @@ def test_state_graph_packets(
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
),
AIMessage(
@@ -4294,16 +4351,14 @@ def test_state_graph_packets(
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call234",
),
ToolMessage(
_AnyIdToolMessage(
content="result for a third one",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call567",
),
AIMessage(content="answer", id="ai3"),
@@ -4333,10 +4388,9 @@ def test_state_graph_packets(
},
{
"tools": {
"messages": ToolMessage(
"messages": _AnyIdToolMessage(
content="result for query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
)
}
@@ -4363,20 +4417,18 @@ def test_state_graph_packets(
},
{
"tools": {
"messages": ToolMessage(
"messages": _AnyIdToolMessage(
content="result for another",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call234",
)
},
},
{
"tools": {
"messages": ToolMessage(
"messages": _AnyIdToolMessage(
content="result for a third one",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call567",
),
},
@@ -4513,10 +4565,9 @@ def test_state_graph_packets(
assert [c for c in app_w_interrupt.stream(None, config)] == [
{
"tools": {
"messages": ToolMessage(
"messages": _AnyIdToolMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
)
}
@@ -4558,10 +4609,9 @@ def test_state_graph_packets(
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
),
AIMessage(
@@ -4638,10 +4688,9 @@ def test_state_graph_packets(
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
),
AIMessage(content="answer", id="ai2"),
@@ -4683,7 +4732,6 @@ def test_message_graph(
AIMessage,
BaseMessage,
HumanMessage,
ToolMessage,
)
from langchain_core.outputs import ChatGeneration, ChatResult
from langchain_core.tools import tool
@@ -4797,15 +4845,15 @@ def test_message_graph(
# meaning you can use it as you would any other runnable
app = workflow.compile()
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
if SHOULD_CHECK_SNAPSHOTS:
assert json.dumps(app.get_input_schema().model_json_schema()) == snapshot
assert json.dumps(app.get_output_schema().model_json_schema()) == 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(HumanMessage(content="what is weather in sf")) == [
HumanMessage(
_AnyIdHumanMessage(
content="what is weather in sf",
id="00000000-0000-4000-8000-000000000002", # adds missing ids
),
AIMessage(
content="",
@@ -4818,11 +4866,10 @@ def test_message_graph(
],
id="ai1", # respects ids passed in
),
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id="00000000-0000-4000-8000-000000000010",
),
AIMessage(
content="",
@@ -4835,11 +4882,10 @@ def test_message_graph(
],
id="ai2",
),
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call456",
id="00000000-0000-4000-8000-000000000018",
),
AIMessage(content="answer", id="ai3"),
]
@@ -4860,11 +4906,10 @@ def test_message_graph(
},
{
"tools": [
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id="00000000-0000-4000-8000-000000000033",
)
]
},
@@ -4883,11 +4928,10 @@ def test_message_graph(
},
{
"tools": [
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call456",
id="00000000-0000-4000-8000-000000000041",
)
]
},
@@ -5007,11 +5051,10 @@ def test_message_graph(
assert [c for c in app_w_interrupt.stream(None, config)] == [
{
"tools": [
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
)
]
},
@@ -5044,11 +5087,10 @@ def test_message_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
),
AIMessage(
content="",
@@ -5107,11 +5149,10 @@ def test_message_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
),
AIMessage(content="answer", id="ai2"),
],
@@ -5240,11 +5281,10 @@ def test_message_graph(
assert [c for c in app_w_interrupt.stream(None, config)] == [
{
"tools": [
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
)
]
},
@@ -5277,11 +5317,10 @@ def test_message_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
),
AIMessage(
content="",
@@ -5340,7 +5379,7 @@ def test_message_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
@@ -5380,7 +5419,7 @@ def test_message_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
@@ -5537,9 +5576,8 @@ def test_root_graph(
app = workflow.compile()
assert app.invoke(HumanMessage(content="what is weather in sf")) == [
HumanMessage(
_AnyIdHumanMessage(
content="what is weather in sf",
id="00000000-0000-4000-8000-000000000002", # adds missing ids
),
AIMessage(
content="",
@@ -5552,11 +5590,10 @@ def test_root_graph(
],
id="ai1", # respects ids passed in
),
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id="00000000-0000-4000-8000-000000000010",
),
AIMessage(
content="",
@@ -5569,11 +5606,10 @@ def test_root_graph(
],
id="ai2",
),
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call456",
id="00000000-0000-4000-8000-000000000018",
),
AIMessage(content="answer", id="ai3"),
]
@@ -5741,11 +5777,10 @@ def test_root_graph(
assert [c for c in app_w_interrupt.stream(None, config)] == [
{
"tools": [
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
)
]
},
@@ -5778,7 +5813,7 @@ def test_root_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
@@ -5841,7 +5876,7 @@ def test_root_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
@@ -5974,11 +6009,10 @@ def test_root_graph(
assert [c for c in app_w_interrupt.stream(None, config)] == [
{
"tools": [
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
)
]
},
@@ -6011,7 +6045,7 @@ def test_root_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
@@ -6074,11 +6108,10 @@ def test_root_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
),
AIMessage(content="answer", id="ai2"),
],
@@ -6114,7 +6147,7 @@ def test_root_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
@@ -6186,11 +6219,10 @@ def test_root_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
),
AIMessage(content="answer", id="ai2"),
_AnyIdAIMessage(content="an extra message"),
@@ -6234,10 +6266,9 @@ def test_root_graph(
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
id="00000000-0000-4000-8000-000000000082",
tool_call_id="tool_call123",
),
AIMessage(content="answer", id="ai2"),
@@ -7531,7 +7562,7 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1(
request: pytest.FixtureRequest,
checkpointer_name: str,
) -> None:
from langchain_core.pydantic_v1 import BaseModel, ValidationError
from pydantic.v1 import BaseModel, ValidationError
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
setup = mocker.Mock()
@@ -7632,6 +7663,7 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1(
app = workflow.compile()
# because it's a v1 pydantic, we're using .schema() here instead of the new methods
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
assert app.get_input_schema().schema() == snapshot
assert app.get_output_schema().schema() == snapshot
@@ -7799,9 +7831,10 @@ def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2(
app = workflow.compile()
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
assert app.get_input_schema().schema() == snapshot
assert app.get_output_schema().schema() == snapshot
if SHOULD_CHECK_SNAPSHOTS:
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
assert app.get_input_schema().model_json_schema() == snapshot
assert app.get_output_schema().model_json_schema() == snapshot
with pytest.raises(ValidationError), assert_ctx_once():
app.invoke({"query": {}})
@@ -10103,7 +10136,7 @@ def test_weather_subgraph(
from langchain_core.language_models.fake_chat_models import (
FakeMessagesListChatModel,
)
from langchain_core.messages import AIMessage, HumanMessage, ToolCall
from langchain_core.messages import AIMessage, ToolCall
from langchain_core.tools import tool
from langgraph.graph import MessagesState
@@ -10222,7 +10255,7 @@ def test_weather_subgraph(
state = graph.get_state(config)
assert state == StateSnapshot(
values={
"messages": [HumanMessage(content="what's the weather in sf", id=AnyStr())],
"messages": [_AnyIdHumanMessage(content="what's the weather in sf")],
"route": "weather",
},
next=("weather_graph",),
@@ -10284,8 +10317,8 @@ def test_weather_subgraph(
{
"weather_graph": {
"messages": [
HumanMessage(content="what's the weather in sf", id=AnyStr()),
AIMessage(content="I'ts sunny in la!", id=AnyStr()),
_AnyIdHumanMessage(content="what's the weather in sf"),
_AnyIdAIMessage(content="I'ts sunny in la!"),
]
}
},
@@ -10307,7 +10340,7 @@ def test_weather_subgraph(
state = graph.get_state(config, subgraphs=True)
assert state == StateSnapshot(
values={
"messages": [HumanMessage(content="what's the weather in sf", id=AnyStr())],
"messages": [_AnyIdHumanMessage(content="what's the weather in sf")],
"route": "weather",
},
next=("weather_graph",),
@@ -10339,9 +10372,7 @@ def test_weather_subgraph(
state=StateSnapshot(
values={
"messages": [
HumanMessage(
content="what's the weather in sf", id=AnyStr()
)
_AnyIdHumanMessage(content="what's the weather in sf")
],
"city": "San Francisco",
},
@@ -10386,7 +10417,7 @@ def test_weather_subgraph(
state = graph.get_state(config, subgraphs=True)
assert state == StateSnapshot(
values={
"messages": [HumanMessage(content="what's the weather in sf", id=AnyStr())],
"messages": [_AnyIdHumanMessage(content="what's the weather in sf")],
"route": "weather",
},
next=("weather_graph",),
@@ -10418,10 +10449,8 @@ def test_weather_subgraph(
state=StateSnapshot(
values={
"messages": [
HumanMessage(
content="what's the weather in sf", id=AnyStr()
),
AIMessage(content="rainy", id=AnyStr()),
_AnyIdHumanMessage(content="what's the weather in sf"),
_AnyIdAIMessage(content="rainy"),
],
"city": "San Francisco",
},
@@ -10473,8 +10502,8 @@ def test_weather_subgraph(
{
"weather_graph": {
"messages": [
HumanMessage(content="what's the weather in sf", id=AnyStr()),
AIMessage(content="rainy", id=AnyStr()),
_AnyIdHumanMessage(content="what's the weather in sf"),
_AnyIdAIMessage(content="rainy"),
]
}
},
@@ -10534,7 +10563,7 @@ def test_checkpoint_metadata() -> None:
from langchain_core.language_models.fake_chat_models import (
FakeMessagesListChatModel,
)
from langchain_core.messages import AIMessage, AnyMessage, ToolMessage
from langchain_core.messages import AIMessage, AnyMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool
@@ -10621,9 +10650,8 @@ def test_checkpoint_metadata() -> None:
) == {
"messages": [
_AnyIdHumanMessage(content="what is weather in sf"),
AIMessage(
_AnyIdAIMessage(
content="",
id=AnyStr(),
tool_calls=[
{
"name": "search_api",
@@ -10633,10 +10661,9 @@ def test_checkpoint_metadata() -> None:
}
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
),
_AnyIdAIMessage(content="answer"),
@@ -10767,7 +10794,7 @@ def test_remove_message_from_node():
def test_xray_lance(snapshot: SnapshotAssertion):
from langchain_core.messages import AnyMessage, HumanMessage
from langchain_core.pydantic_v1 import BaseModel, Field
from pydantic import BaseModel, Field
class Analyst(BaseModel):
affiliation: str = Field(
+240 -212
View File
@@ -1,5 +1,4 @@
import asyncio
import json
import operator
import re
import sys
@@ -60,7 +59,6 @@ from langgraph.managed.shared_value import SharedValue
from langgraph.prebuilt.chat_agent_executor import (
create_tool_calling_executor,
)
from langgraph.prebuilt.tool_executor import ToolExecutor
from langgraph.prebuilt.tool_node import ToolNode
from langgraph.pregel import (
Channel,
@@ -75,6 +73,7 @@ from tests.any_str import AnyDict, AnyStr, AnyVersion, UnsortedSequence
from tests.conftest import (
ALL_CHECKPOINTERS_ASYNC,
ALL_CHECKPOINTERS_ASYNC_PLUS_NONE,
SHOULD_CHECK_SNAPSHOTS,
awith_checkpointer,
)
from tests.fake_tracer import FakeTracer
@@ -82,7 +81,7 @@ from tests.memory_assert import (
MemorySaverAssertCheckpointMetadata,
MemorySaverNoPending,
)
from tests.messages import _AnyIdAIMessage, _AnyIdHumanMessage
from tests.messages import _AnyIdAIMessage, _AnyIdHumanMessage, _AnyIdToolMessage
pytestmark = pytest.mark.anyio
@@ -657,8 +656,15 @@ async def test_invoke_single_process_in_out(mocker: MockerFixture) -> None:
graph.set_finish_point("add_one")
gapp = graph.compile()
assert app.input_schema.schema() == {"title": "LangGraphInput", "type": "integer"}
assert app.output_schema.schema() == {"title": "LangGraphOutput", "type": "integer"}
if SHOULD_CHECK_SNAPSHOTS:
assert app.input_schema.model_json_schema() == {
"title": "LangGraphInput",
"type": "integer",
}
assert app.output_schema.model_json_schema() == {
"title": "LangGraphOutput",
"type": "integer",
}
assert await app.ainvoke(2) == 3
assert await app.ainvoke(2, output_keys=["output"]) == {"output": 3}
@@ -698,16 +704,24 @@ async def test_invoke_single_process_in_write_kwargs(mocker: MockerFixture) -> N
input_channels="input",
)
assert app.input_schema.schema() == {"title": "LangGraphInput", "type": "integer"}
assert app.output_schema.schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {
"output": {"title": "Output", "type": "integer"},
"fixed": {"title": "Fixed", "type": "integer"},
"output_plus_one": {"title": "Output Plus One", "type": "integer"},
},
}
if SHOULD_CHECK_SNAPSHOTS:
assert app.input_schema.model_json_schema() == {
"title": "LangGraphInput",
"type": "integer",
}
assert app.output_schema.model_json_schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {
"output": {"title": "Output", "type": "integer", "default": None},
"fixed": {"title": "Fixed", "type": "integer", "default": None},
"output_plus_one": {
"title": "Output Plus One",
"type": "integer",
"default": None,
},
},
}
assert await app.ainvoke(2) == {"output": 3, "fixed": 5, "output_plus_one": 4}
@@ -722,12 +736,18 @@ async def test_invoke_single_process_in_out_dict(mocker: MockerFixture) -> None:
output_channels=["output"],
)
assert app.input_schema.schema() == {"title": "LangGraphInput", "type": "integer"}
assert app.output_schema.schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {"output": {"title": "Output", "type": "integer"}},
}
if SHOULD_CHECK_SNAPSHOTS:
assert app.input_schema.model_json_schema() == {
"title": "LangGraphInput",
"type": "integer",
}
assert app.output_schema.model_json_schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {
"output": {"title": "Output", "type": "integer", "default": None}
},
}
assert await app.ainvoke(2) == {"output": 3}
@@ -742,16 +762,21 @@ async def test_invoke_single_process_in_dict_out_dict(mocker: MockerFixture) ->
output_channels=["output"],
)
assert app.input_schema.schema() == {
"title": "LangGraphInput",
"type": "object",
"properties": {"input": {"title": "Input", "type": "integer"}},
}
assert app.output_schema.schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {"output": {"title": "Output", "type": "integer"}},
}
if SHOULD_CHECK_SNAPSHOTS:
assert app.input_schema.model_json_schema() == {
"title": "LangGraphInput",
"type": "object",
"properties": {
"input": {"title": "Input", "type": "integer", "default": None}
},
}
assert app.output_schema.model_json_schema() == {
"title": "LangGraphOutput",
"type": "object",
"properties": {
"output": {"title": "Output", "type": "integer", "default": None}
},
}
assert await app.ainvoke({"input": 2}) == {"output": 3}
@@ -3823,7 +3848,7 @@ async def test_prebuilt_tool_chat() -> None:
from langchain_core.language_models.fake_chat_models import (
FakeMessagesListChatModel,
)
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.tools import tool
class FakeFuntionChatModel(FakeMessagesListChatModel):
@@ -3876,8 +3901,7 @@ async def test_prebuilt_tool_chat() -> None:
) == {
"messages": [
_AnyIdHumanMessage(content="what is weather in sf"),
AIMessage(
id=AnyStr(),
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -3887,14 +3911,12 @@ async def test_prebuilt_tool_chat() -> None:
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
),
AIMessage(
id=AnyStr(),
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -3909,13 +3931,12 @@ async def test_prebuilt_tool_chat() -> None:
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call234",
id=AnyStr(),
),
ToolMessage(
_AnyIdToolMessage(
content="result for a third one",
name="search_api",
tool_call_id="tool_call567",
@@ -3934,8 +3955,7 @@ async def test_prebuilt_tool_chat() -> None:
{
"agent": {
"messages": [
AIMessage(
id=AnyStr(),
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -3951,11 +3971,10 @@ async def test_prebuilt_tool_chat() -> None:
{
"tools": {
"messages": [
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id=AnyStr(),
)
]
}
@@ -3963,8 +3982,7 @@ async def test_prebuilt_tool_chat() -> None:
{
"agent": {
"messages": [
AIMessage(
id=AnyStr(),
_AnyIdAIMessage(
content="",
tool_calls=[
{
@@ -3985,17 +4003,15 @@ async def test_prebuilt_tool_chat() -> None:
{
"tools": {
"messages": [
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call234",
name="search_api",
id=AnyStr(),
),
ToolMessage(
_AnyIdToolMessage(
content="result for a third one",
tool_call_id="tool_call567",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call567",
),
]
}
@@ -4132,10 +4148,9 @@ async def test_state_graph_packets(checkpointer_name: str) -> None:
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
),
AIMessage(
@@ -4154,16 +4169,14 @@ async def test_state_graph_packets(checkpointer_name: str) -> None:
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for another",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call234",
),
ToolMessage(
_AnyIdToolMessage(
content="result for a third one",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call567",
),
AIMessage(content="answer", id="ai3"),
@@ -4193,10 +4206,9 @@ async def test_state_graph_packets(checkpointer_name: str) -> None:
},
{
"tools": {
"messages": ToolMessage(
"messages": _AnyIdToolMessage(
content="result for query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
)
}
@@ -4223,20 +4235,18 @@ async def test_state_graph_packets(checkpointer_name: str) -> None:
},
{
"tools": {
"messages": ToolMessage(
"messages": _AnyIdToolMessage(
content="result for another",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call234",
)
},
},
{
"tools": {
"messages": ToolMessage(
"messages": _AnyIdToolMessage(
content="result for a third one",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call567",
),
},
@@ -4378,10 +4388,9 @@ async def test_state_graph_packets(checkpointer_name: str) -> None:
assert [c async for c in app_w_interrupt.astream(None, config)] == [
{
"tools": {
"messages": ToolMessage(
"messages": _AnyIdToolMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
)
}
@@ -4424,10 +4433,9 @@ async def test_state_graph_packets(checkpointer_name: str) -> None:
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
),
AIMessage(
@@ -4504,10 +4512,9 @@ async def test_state_graph_packets(checkpointer_name: str) -> None:
},
],
),
ToolMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
),
AIMessage(content="answer", id="ai2"),
@@ -4522,7 +4529,9 @@ async def test_state_graph_packets(checkpointer_name: str) -> None:
"source": "update",
"step": 5,
"writes": {
"agent": {"messages": AIMessage(content="answer", id="ai2")}
"agent": {
"messages": AIMessage(content="answer", id="ai2"),
}
},
},
parent_config=[
@@ -4533,11 +4542,10 @@ async def test_state_graph_packets(checkpointer_name: str) -> None:
@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC)
async def test_message_graph(checkpointer_name: str) -> None:
from langchain_core.agents import AgentAction
from langchain_core.language_models.fake_chat_models import (
FakeMessagesListChatModel,
)
from langchain_core.messages import AIMessage, FunctionMessage, HumanMessage
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.tools import tool
class FakeFuntionChatModel(FakeMessagesListChatModel):
@@ -4555,63 +4563,46 @@ async def test_message_graph(checkpointer_name: str) -> None:
responses=[
AIMessage(
content="",
additional_kwargs={
"function_call": {
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"arguments": json.dumps("query"),
"args": {"query": "query"},
}
},
],
id="ai1",
),
AIMessage(
content="",
additional_kwargs={
"function_call": {
tool_calls=[
{
"id": "tool_call456",
"name": "search_api",
"arguments": json.dumps("another"),
"args": {"query": "another"},
}
},
],
id="ai2",
),
AIMessage(content="answer", id="ai3"),
]
)
tool_executor = ToolExecutor(tools)
# Define the function that determines whether to continue or not
def should_continue(messages):
last_message = messages[-1]
# If there is no function call, then we finish
if "function_call" not in last_message.additional_kwargs:
if not last_message.tool_calls:
return "end"
# Otherwise if there is, we continue
else:
return "continue"
async def call_tool(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
action = AgentAction(
tool=last_message.additional_kwargs["function_call"]["name"],
tool_input=json.loads(
last_message.additional_kwargs["function_call"]["arguments"]
),
log="",
)
# We call the tool_executor and get back a response
response = await tool_executor.ainvoke(action)
# We use the response to create a FunctionMessage
return FunctionMessage(content=str(response), name=action.tool)
# Define a new graph
workflow = MessageGraph()
# Define the two nodes we will cycle between
workflow.add_node("agent", model)
workflow.add_node("tools", call_tool)
workflow.add_node("tools", ToolNode(tools))
# Set the entrypoint as `agent`
# This means that this node is the first one called
@@ -4648,23 +4639,41 @@ async def test_message_graph(checkpointer_name: str) -> None:
app = workflow.compile()
assert await app.ainvoke(HumanMessage(content="what is weather in sf")) == [
_AnyIdHumanMessage(content="what is weather in sf"),
AIMessage(
content="",
additional_kwargs={
"function_call": {"name": "search_api", "arguments": '"query"'}
},
id="ai1",
_AnyIdHumanMessage(
content="what is weather in sf",
),
FunctionMessage(content="result for query", name="search_api", id=AnyStr()),
AIMessage(
content="",
additional_kwargs={
"function_call": {"name": "search_api", "arguments": '"another"'}
},
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"args": {"query": "query"},
}
],
id="ai1", # respects ids passed in
),
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
),
AIMessage(
content="",
tool_calls=[
{
"id": "tool_call456",
"name": "search_api",
"args": {"query": "another"},
}
],
id="ai2",
),
FunctionMessage(content="result for another", name="search_api", id=AnyStr()),
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call456",
),
AIMessage(content="answer", id="ai3"),
]
@@ -4674,30 +4683,46 @@ async def test_message_graph(checkpointer_name: str) -> None:
{
"agent": AIMessage(
content="",
additional_kwargs={
"function_call": {"name": "search_api", "arguments": '"query"'}
},
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"args": {"query": "query"},
}
],
id="ai1",
)
},
{
"tools": FunctionMessage(
content="result for query", name="search_api", id=AnyStr()
)
"tools": [
_AnyIdToolMessage(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
)
]
},
{
"agent": AIMessage(
content="",
additional_kwargs={
"function_call": {"name": "search_api", "arguments": '"another"'}
},
tool_calls=[
{
"id": "tool_call456",
"name": "search_api",
"args": {"query": "another"},
}
],
id="ai2",
)
},
{
"tools": FunctionMessage(
content="result for another", name="search_api", id=AnyStr()
)
"tools": [
_AnyIdToolMessage(
content="result for another",
name="search_api",
tool_call_id="tool_call456",
)
]
},
{"agent": AIMessage(content="answer", id="ai3")},
]
@@ -4718,9 +4743,13 @@ async def test_message_graph(checkpointer_name: str) -> None:
{
"agent": AIMessage(
content="",
additional_kwargs={
"function_call": {"name": "search_api", "arguments": '"query"'}
},
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"args": {"query": "query"},
}
],
id="ai1",
)
},
@@ -4732,9 +4761,13 @@ async def test_message_graph(checkpointer_name: str) -> None:
_AnyIdHumanMessage(content="what is weather in sf"),
AIMessage(
content="",
additional_kwargs={
"function_call": {"name": "search_api", "arguments": '"query"'}
},
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"args": {"query": "query"},
}
],
id="ai1",
),
],
@@ -4749,12 +4782,13 @@ async def test_message_graph(checkpointer_name: str) -> None:
"writes": {
"agent": AIMessage(
content="",
additional_kwargs={
"function_call": {
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"arguments": '"query"',
"args": {"query": "query"},
}
},
],
id="ai1",
)
},
@@ -4766,9 +4800,7 @@ async def test_message_graph(checkpointer_name: str) -> None:
# modify ai message
last_message = (await app_w_interrupt.aget_state(config)).values[-1]
last_message.additional_kwargs["function_call"]["arguments"] = (
'"a different query"'
)
last_message.tool_calls[0]["args"] = {"query": "a different query"}
await app_w_interrupt.aupdate_state(config, last_message)
# message was replaced instead of appended
@@ -4778,13 +4810,14 @@ async def test_message_graph(checkpointer_name: str) -> None:
_AnyIdHumanMessage(content="what is weather in sf"),
AIMessage(
content="",
additional_kwargs={
"function_call": {
"name": "search_api",
"arguments": '"a different query"',
}
},
id="ai1",
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"args": {"query": "a different query"},
}
],
),
],
tasks=(PregelTask(AnyStr(), "tools"),),
@@ -4798,12 +4831,13 @@ async def test_message_graph(checkpointer_name: str) -> None:
"writes": {
"agent": AIMessage(
content="",
additional_kwargs={
"function_call": {
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"arguments": '"a different query"',
"args": {"query": "a different query"},
}
},
],
id="ai1",
)
},
@@ -4815,21 +4849,24 @@ async def test_message_graph(checkpointer_name: str) -> None:
assert [c async for c in app_w_interrupt.astream(None, config)] == [
{
"tools": FunctionMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
)
"tools": [
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
tool_call_id="tool_call123",
)
]
},
{
"agent": AIMessage(
content="",
additional_kwargs={
"function_call": {
tool_calls=[
{
"id": "tool_call456",
"name": "search_api",
"arguments": '"another"',
"args": {"query": "another"},
}
},
],
id="ai2",
)
},
@@ -4841,27 +4878,29 @@ async def test_message_graph(checkpointer_name: str) -> None:
_AnyIdHumanMessage(content="what is weather in sf"),
AIMessage(
content="",
additional_kwargs={
"function_call": {
"name": "search_api",
"arguments": '"a different query"',
}
},
id="ai1",
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"args": {"query": "a different query"},
}
],
),
FunctionMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
),
AIMessage(
content="",
additional_kwargs={
"function_call": {
tool_calls=[
{
"id": "tool_call456",
"name": "search_api",
"arguments": '"another"',
"args": {"query": "another"},
}
},
],
id="ai2",
),
],
@@ -4876,12 +4915,13 @@ async def test_message_graph(checkpointer_name: str) -> None:
"writes": {
"agent": AIMessage(
content="",
additional_kwargs={
"function_call": {
tool_calls=[
{
"id": "tool_call456",
"name": "search_api",
"arguments": '"another"',
"args": {"query": "another"},
}
},
],
id="ai2",
)
},
@@ -4903,18 +4943,19 @@ async def test_message_graph(checkpointer_name: str) -> None:
_AnyIdHumanMessage(content="what is weather in sf"),
AIMessage(
content="",
additional_kwargs={
"function_call": {
"name": "search_api",
"arguments": '"a different query"',
}
},
id="ai1",
tool_calls=[
{
"id": "tool_call123",
"name": "search_api",
"args": {"query": "a different query"},
}
],
),
FunctionMessage(
_AnyIdToolMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
tool_call_id="tool_call123",
),
AIMessage(content="answer", id="ai2"),
],
@@ -6215,7 +6256,7 @@ async def test_in_one_fan_out_state_graph_waiting_edge_via_branch(
async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
snapshot: SnapshotAssertion, mocker: MockerFixture, checkpointer_name: str
) -> None:
from langchain_core.pydantic_v1 import BaseModel, ValidationError
from pydantic.v1 import BaseModel, ValidationError
setup = mocker.Mock()
teardown = mocker.Mock()
@@ -6467,9 +6508,10 @@ async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydant
app = workflow.compile()
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
assert app.get_input_schema().schema() == snapshot
assert app.get_output_schema().schema() == snapshot
if SHOULD_CHECK_SNAPSHOTS:
assert app.get_graph().draw_mermaid(with_styles=False) == snapshot
assert app.get_input_schema().model_json_schema() == snapshot
assert app.get_output_schema().model_json_schema() == snapshot
with pytest.raises(ValidationError):
await app.ainvoke({"query": {}})
@@ -8688,7 +8730,7 @@ async def test_weather_subgraph(
from langchain_core.language_models.fake_chat_models import (
FakeMessagesListChatModel,
)
from langchain_core.messages import AIMessage, HumanMessage, ToolCall
from langchain_core.messages import AIMessage, ToolCall
from langchain_core.tools import tool
from langgraph.graph import MessagesState
@@ -8812,9 +8854,7 @@ async def test_weather_subgraph(
state = await graph.aget_state(config)
assert state == StateSnapshot(
values={
"messages": [
HumanMessage(content="what's the weather in sf", id=AnyStr())
],
"messages": [_AnyIdHumanMessage(content="what's the weather in sf")],
"route": "weather",
},
next=("weather_graph",),
@@ -8880,10 +8920,8 @@ async def test_weather_subgraph(
{
"weather_graph": {
"messages": [
HumanMessage(
content="what's the weather in sf", id=AnyStr()
),
AIMessage(content="I'ts sunny in la!", id=AnyStr()),
_AnyIdHumanMessage(content="what's the weather in sf"),
_AnyIdAIMessage(content="I'ts sunny in la!"),
]
}
},
@@ -8905,9 +8943,7 @@ async def test_weather_subgraph(
state = await graph.aget_state(config, subgraphs=True)
assert state == StateSnapshot(
values={
"messages": [
HumanMessage(content="what's the weather in sf", id=AnyStr())
],
"messages": [_AnyIdHumanMessage(content="what's the weather in sf")],
"route": "weather",
},
next=("weather_graph",),
@@ -8939,9 +8975,7 @@ async def test_weather_subgraph(
state=StateSnapshot(
values={
"messages": [
HumanMessage(
content="what's the weather in sf", id=AnyStr()
)
_AnyIdHumanMessage(content="what's the weather in sf")
],
"city": "San Francisco",
},
@@ -8986,9 +9020,7 @@ async def test_weather_subgraph(
state = await graph.aget_state(config, subgraphs=True)
assert state == StateSnapshot(
values={
"messages": [
HumanMessage(content="what's the weather in sf", id=AnyStr())
],
"messages": [_AnyIdHumanMessage(content="what's the weather in sf")],
"route": "weather",
},
next=("weather_graph",),
@@ -9020,10 +9052,8 @@ async def test_weather_subgraph(
state=StateSnapshot(
values={
"messages": [
HumanMessage(
content="what's the weather in sf", id=AnyStr()
),
AIMessage(content="rainy", id=AnyStr()),
_AnyIdHumanMessage(content="what's the weather in sf"),
_AnyIdAIMessage(content="rainy"),
],
"city": "San Francisco",
},
@@ -9077,10 +9107,8 @@ async def test_weather_subgraph(
{
"weather_graph": {
"messages": [
HumanMessage(
content="what's the weather in sf", id=AnyStr()
),
AIMessage(content="rainy", id=AnyStr()),
_AnyIdHumanMessage(content="what's the weather in sf"),
_AnyIdAIMessage(content="rainy"),
]
}
},