From 71d9d02d260ef05c8dcc169be2de98f3ee97213f Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Thu, 11 Jul 2024 17:03:22 -0700 Subject: [PATCH] lib: Add metadata arg to add_node() (#990) * lib: Add metadata arg to add_node() - use metadata when drawing graph - use metadata for tracing * Update core * Lock --- libs/langgraph/langgraph/graph/graph.py | 53 ++++-- libs/langgraph/langgraph/graph/state.py | 15 +- libs/langgraph/langgraph/pregel/read.py | 5 +- libs/langgraph/langgraph/utils.py | 32 ---- libs/langgraph/poetry.lock | 10 +- libs/langgraph/pyproject.toml | 2 +- .../tests/__snapshots__/test_pregel.ambr | 166 ++++++++++-------- libs/langgraph/tests/test_pregel.py | 3 +- 8 files changed, 156 insertions(+), 130 deletions(-) diff --git a/libs/langgraph/langgraph/graph/graph.py b/libs/langgraph/langgraph/graph/graph.py index 63405912f..89f5c4f32 100644 --- a/libs/langgraph/langgraph/graph/graph.py +++ b/libs/langgraph/langgraph/graph/graph.py @@ -20,9 +20,8 @@ from typing import ( from langchain_core.runnables import Runnable from langchain_core.runnables.base import RunnableLike from langchain_core.runnables.config import RunnableConfig -from langchain_core.runnables.graph import ( - Node as RunnableGraphNode, -) +from langchain_core.runnables.graph import Graph as DrawableGraph +from langchain_core.runnables.graph import Node as DrawableNode from langgraph.channels.ephemeral_value import EphemeralValue from langgraph.checkpoint import BaseCheckpointSaver @@ -32,11 +31,16 @@ from langgraph.pregel import Channel, Pregel from langgraph.pregel.read import PregelNode from langgraph.pregel.types import All from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry -from langgraph.utils import DrawableGraph, RunnableCallable, coerce_to_runnable +from langgraph.utils import RunnableCallable, coerce_to_runnable logger = logging.getLogger(__name__) +class NodeSpec(NamedTuple): + runnable: Runnable + metadata: Optional[dict[str, Any]] = None + + class Branch(NamedTuple): path: Runnable[Any, Union[Hashable, list[Hashable]]] ends: Optional[dict[Hashable, str]] @@ -114,7 +118,7 @@ class Branch(NamedTuple): class Graph: def __init__(self) -> None: - self.nodes: dict[str, Runnable] = {} + self.nodes: dict[str, NodeSpec] = {} self.edges = set[tuple[str, str]]() self.branches: defaultdict[str, dict[str, Branch]] = defaultdict(dict) self.support_multiple_edges = False @@ -125,15 +129,30 @@ class Graph: return self.edges @overload - def add_node(self, node: RunnableLike) -> None: + def add_node( + self, + node: RunnableLike, + *, + metadata: Optional[dict[str, Any]] = None, + ) -> None: ... @overload - def add_node(self, node: str, action: RunnableLike) -> None: + def add_node( + self, + node: str, + action: RunnableLike, + *, + metadata: Optional[dict[str, Any]] = None, + ) -> None: ... def add_node( - self, node: Union[str, RunnableLike], action: Optional[RunnableLike] = None + self, + node: Union[str, RunnableLike], + action: Optional[RunnableLike] = None, + *, + metadata: Optional[dict[str, Any]] = None, ) -> None: if self.compiled: logger.warning( @@ -148,7 +167,9 @@ class Graph: if node == END or node == START: raise ValueError(f"Node `{node}` is reserved.") - self.nodes[node] = coerce_to_runnable(action, name=node, trace=False) + self.nodes[node] = NodeSpec( + coerce_to_runnable(action, name=node, trace=False), metadata + ) def add_edge(self, start_key: str, end_key: str) -> None: if self.compiled: @@ -385,11 +406,11 @@ class Graph: class CompiledGraph(Pregel): builder: Graph - def attach_node(self, key: str, node: Runnable) -> None: + def attach_node(self, key: str, node: NodeSpec) -> None: self.channels[key] = EphemeralValue(Any) self.nodes[key] = ( - PregelNode(channels=[], triggers=[]) - | node + PregelNode(channels=[], triggers=[], metadata=node.metadata) + | node.runnable | ChannelWrite([ChannelWriteEntry(key)], tags=[TAG_HIDDEN]) ) cast(list[str], self.stream_channels).append(key) @@ -441,14 +462,14 @@ class CompiledGraph(Pregel): ) -> DrawableGraph: """Returns a drawable representation of the computation graph.""" graph = DrawableGraph() - start_nodes: dict[str, RunnableGraphNode] = { + start_nodes: dict[str, DrawableNode] = { START: graph.add_node(self.get_input_schema(config), START) } - end_nodes: dict[str, RunnableGraphNode] = { + end_nodes: dict[str, DrawableNode] = { END: graph.add_node(self.get_output_schema(config), END) } - for key, node in self.builder.nodes.items(): + for key, (node, metadata) in self.builder.nodes.items(): if xray: subgraph = ( node.get_graph( @@ -469,7 +490,7 @@ class CompiledGraph(Pregel): start_nodes[key] = n end_nodes[key] = n else: - n = graph.add_node(node, key) + n = graph.add_node(node, key, metadata=metadata) start_nodes[key] = n end_nodes[key] = n for start, end in sorted(self.builder._all_edges): diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index 93ec408d1..e8178bf28 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -28,7 +28,15 @@ from langgraph.channels.named_barrier_value import NamedBarrierValue from langgraph.checkpoint import BaseCheckpointSaver from langgraph.constants import TAG_HIDDEN from langgraph.errors import InvalidUpdateError -from langgraph.graph.graph import END, START, Branch, CompiledGraph, Graph, Send +from langgraph.graph.graph import ( + END, + START, + Branch, + CompiledGraph, + Graph, + NodeSpec, + Send, +) from langgraph.managed.base import ManagedValue, is_managed_value from langgraph.pregel.read import ChannelRead, PregelNode from langgraph.pregel.types import All @@ -332,7 +340,7 @@ class CompiledStateGraph(CompiledGraph): ) -> type[BaseModel]: return self.get_output_schema(config) - def attach_node(self, key: str, node: Optional[Runnable]) -> None: + def attach_node(self, key: str, node: Optional[NodeSpec]) -> None: state_keys = list(self.builder.channels) def _get_state_key(input: dict, config: RunnableConfig, *, key: str) -> Any: @@ -399,7 +407,8 @@ class CompiledStateGraph(CompiledGraph): require_at_least_one_of=state_keys, ), ], - ).pipe(node) + metadata=node.metadata, + ).pipe(node.runnable) def attach_edge(self, starts: Union[str, Sequence[str]], end: str) -> None: if isinstance(starts, str): diff --git a/libs/langgraph/langgraph/pregel/read.py b/libs/langgraph/langgraph/pregel/read.py index a14c704e4..a1b71c046 100644 --- a/libs/langgraph/langgraph/pregel/read.py +++ b/libs/langgraph/langgraph/pregel/read.py @@ -151,6 +151,7 @@ class PregelNode(RunnableBindingBase): mapper: Optional[Callable[[Any], Any]] = None, writers: Optional[list[Runnable]] = None, tags: Optional[list[str]] = None, + metadata: Optional[Mapping[str, Any]] = None, bound: Optional[Runnable[Any, Any]] = None, kwargs: Optional[Mapping[str, Any]] = None, config: Optional[RunnableConfig] = None, @@ -163,7 +164,9 @@ class PregelNode(RunnableBindingBase): writers=writers or [], bound=bound or DEFAULT_BOUND, kwargs=kwargs or {}, - config=merge_configs(config, {"tags": tags or []}), + config=merge_configs( + config, {"tags": tags or [], "metadata": metadata or {}} + ), **other_kwargs, ) diff --git a/libs/langgraph/langgraph/utils.py b/libs/langgraph/langgraph/utils.py index 621690e2d..64cb21128 100644 --- a/libs/langgraph/langgraph/utils.py +++ b/libs/langgraph/langgraph/utils.py @@ -18,7 +18,6 @@ from langchain_core.runnables.config import ( run_in_executor, var_child_runnable_config, ) -from langchain_core.runnables.graph import Edge, Graph, Node, is_uuid from langchain_core.runnables.utils import accepts_config from typing_extensions import TypeGuard @@ -132,37 +131,6 @@ class RunnableCallable(Runnable): return ret -class DrawableGraph(Graph): - def extend( - self, graph: Graph, prefix: str = "" - ) -> tuple[Optional[Node], Optional[Node]]: - if all(is_uuid(node.id) for node in graph.nodes.values()): - super().extend(graph) - return graph.first_node(), graph.last_node() - - new_nodes = { - f"{prefix}:{k}": Node(f"{prefix}:{k}", v.data) - for k, v in graph.nodes.items() - } - new_edges = [ - Edge( - f"{prefix}:{edge.source}", - f"{prefix}:{edge.target}", - edge.data, - edge.conditional, - ) - for edge in graph.edges - ] - self.nodes.update(new_nodes) - self.edges.extend(new_edges) - first = graph.first_node() - last = graph.last_node() - return ( - Node(f"{prefix}:{first.id}", first.data) if first else None, - Node(f"{prefix}:{last.id}", last.data) if last else None, - ) - - def is_async_callable( func: Any, ) -> TypeGuard[Callable[..., Awaitable]]: diff --git a/libs/langgraph/poetry.lock b/libs/langgraph/poetry.lock index e280ddc8c..4253402b6 100644 --- a/libs/langgraph/poetry.lock +++ b/libs/langgraph/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" @@ -1746,13 +1746,13 @@ langchain-core = ">=0.2.2rc1,<0.3" [[package]] name = "langchain-core" -version = "0.2.11" +version = "0.2.15" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langchain_core-0.2.11-py3-none-any.whl", hash = "sha256:c7ca4dc4d88e3c69fd7916c95a7027c2b1a11c2db5a51141c3ceb8afac212208"}, - {file = "langchain_core-0.2.11.tar.gz", hash = "sha256:7a4661b50604eeb20c3373fbfd8a4f1b74482a6ab4e0f9df11e96821ead8ef0c"}, + {file = "langchain_core-0.2.15-py3-none-any.whl", hash = "sha256:3bf7afaef96d7c1af0d9d223833bdee5fafc46755dc10f9c7576a85d4f6c5240"}, + {file = "langchain_core-0.2.15.tar.gz", hash = "sha256:ce03ab0a5c45b4ebfe5475eb07bf081cd21218421ff4cf26b8d2e5573ae2bd42"}, ] [package.dependencies] @@ -4130,4 +4130,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = ">=3.9.0,<4.0" -content-hash = "1ec1c06495c9a1a564c5082600a4fb335278341a19d11993b981b72684679948" +content-hash = "19250230952cb11ee6b5c820ae0a2b589ce520a59e0723204dcee0c46c3b739e" diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index 5c9f3d9ef..6d7dc260c 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -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.11,<0.3" +langchain-core = ">=0.2.15,<0.3" [tool.poetry.group.dev.dependencies] diff --git a/libs/langgraph/tests/__snapshots__/test_pregel.ambr b/libs/langgraph/tests/__snapshots__/test_pregel.ambr index f0511c5fe..68b1c0a04 100644 --- a/libs/langgraph/tests/__snapshots__/test_pregel.ambr +++ b/libs/langgraph/tests/__snapshots__/test_pregel.ambr @@ -15,21 +15,21 @@ ''' %%{init: {'flowchart': {'curve': 'linear'}}}%% graph TD; - __start__[__start__]:::startclass; - __end__[__end__]:::endclass; - prepare([prepare]):::otherclass; - tool_two_slow([tool_two_slow]):::otherclass; - tool_two_fast([tool_two_fast]):::otherclass; - finish([finish]):::otherclass; + __start__([__start__]):::first + __end__([__end__]):::last + prepare(prepare) + tool_two_slow(tool_two_slow) + tool_two_fast(tool_two_fast) + finish(finish) __start__ --> prepare; finish --> __end__; prepare -.-> tool_two_slow; tool_two_slow --> finish; prepare -.-> tool_two_fast; tool_two_fast --> finish; - classDef startclass fill:#ffdfba; - classDef endclass fill:#baffc9; - classDef otherclass fill:#fad7de; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc ''' # --- @@ -108,8 +108,8 @@ ''' graph TD; right --> __end__; - __start__ -. go-left .-> left; - __start__ -. go-right .-> right; + __start__ -.  go-left  .-> left; + __start__ -.  go-right  .-> right; left -.-> __end__; ''' @@ -189,8 +189,8 @@ ''' graph TD; right --> __end__; - __start__ -. go-left .-> left; - __start__ -. go-right .-> right; + __start__ -.  go-left  .-> left; + __start__ -.  go-right  .-> right; left -.-> __end__; ''' @@ -280,7 +280,7 @@ "runnable", "RunnableAssign" ], - "name": "RunnableAssign" + "name": "agent" } }, { @@ -293,6 +293,10 @@ "RunnableCallable" ], "name": "tools" + }, + "metadata": { + "version": 2, + "variant": "b" } } ], @@ -326,12 +330,32 @@ graph TD; __start__ --> agent; tools --> agent; - agent -. continue .-> tools; - agent -. exit .-> __end__; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; ''' # --- # name: test_conditional_graph.2 + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([__start__]):::first + __end__([__end__]):::last + agent(agent) + tools(tools
+ version = 2 + variant = b) + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_conditional_graph.3 ''' { "nodes": [ @@ -404,7 +428,7 @@ "runnable", "RunnablePassthrough" ], - "name": "RunnablePassthrough" + "name": "Passthrough" } }, { @@ -469,19 +493,19 @@ } ''' # --- -# name: test_conditional_graph.3 +# name: test_conditional_graph.4 ''' graph TD; PromptTemplate --> FakeStreamingListLLM; - FakeStreamingListLLM --> Lambda_agent_parser_; + FakeStreamingListLLM --> agent_parser; Parallel_agent_outcome_Input --> PromptTemplate; - Lambda_agent_parser_ --> Parallel_agent_outcome_Output; + agent_parser --> Parallel_agent_outcome_Output; Parallel_agent_outcome_Input --> Passthrough; Passthrough --> Parallel_agent_outcome_Output; __start__ --> Parallel_agent_outcome_Input; tools --> Parallel_agent_outcome_Input; - Parallel_agent_outcome_Output -. continue .-> tools; - Parallel_agent_outcome_Output -. exit .-> __end__; + Parallel_agent_outcome_Output -.  continue  .-> tools; + Parallel_agent_outcome_Output -.  exit  .-> __end__; ''' # --- @@ -515,7 +539,7 @@ "runnable", "RunnableSequence" ], - "name": "RunnableSequence" + "name": "agent" } }, { @@ -561,8 +585,8 @@ graph TD; __start__ --> agent; tools --> agent; - agent -. continue .-> tools; - agent -. exit .-> __end__; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; ''' # --- @@ -606,10 +630,10 @@ ''' # --- # name: test_message_graph - '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"ToolCall": {"title": "ToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"title": "Id", "type": "string"}}, "required": ["name", "args", "id"]}, "InvalidToolCall": {"title": "InvalidToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "string"}, "id": {"title": "Id", "type": "string"}, "error": {"title": "Error", "type": "string"}}, "required": ["name", "args", "id", "error"]}, "UsageMetadata": {"title": "UsageMetadata", "type": "object", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"]}, "AIMessage": {"title": "AIMessage", "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}, "tool_calls": {"title": "Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/ToolCall"}}, "invalid_tool_calls": {"title": "Invalid Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/InvalidToolCall"}}, "usage_metadata": {"$ref": "#/definitions/UsageMetadata"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A TooMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' + '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"ToolCall": {"title": "ToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"title": "Id", "type": "string"}}, "required": ["name", "args", "id"]}, "InvalidToolCall": {"title": "InvalidToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "string"}, "id": {"title": "Id", "type": "string"}, "error": {"title": "Error", "type": "string"}}, "required": ["name", "args", "id", "error"]}, "UsageMetadata": {"title": "UsageMetadata", "type": "object", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"]}, "AIMessage": {"title": "AIMessage", "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}, "tool_calls": {"title": "Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/ToolCall"}}, "invalid_tool_calls": {"title": "Invalid Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/InvalidToolCall"}}, "usage_metadata": {"$ref": "#/definitions/UsageMetadata"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to raw_output.\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n raw_output=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "raw_output": {"title": "Raw Output"}}, "required": ["content", "tool_call_id"]}}}' # --- # name: test_message_graph.1 - '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"ToolCall": {"title": "ToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"title": "Id", "type": "string"}}, "required": ["name", "args", "id"]}, "InvalidToolCall": {"title": "InvalidToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "string"}, "id": {"title": "Id", "type": "string"}, "error": {"title": "Error", "type": "string"}}, "required": ["name", "args", "id", "error"]}, "UsageMetadata": {"title": "UsageMetadata", "type": "object", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"]}, "AIMessage": {"title": "AIMessage", "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}, "tool_calls": {"title": "Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/ToolCall"}}, "invalid_tool_calls": {"title": "Invalid Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/InvalidToolCall"}}, "usage_metadata": {"$ref": "#/definitions/UsageMetadata"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A TooMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}}, "required": ["content", "tool_call_id"]}}}' + '{"title": "LangGraphOutput", "type": "array", "items": {"anyOf": [{"$ref": "#/definitions/AIMessage"}, {"$ref": "#/definitions/HumanMessage"}, {"$ref": "#/definitions/ChatMessage"}, {"$ref": "#/definitions/SystemMessage"}, {"$ref": "#/definitions/FunctionMessage"}, {"$ref": "#/definitions/ToolMessage"}]}, "definitions": {"ToolCall": {"title": "ToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"title": "Id", "type": "string"}}, "required": ["name", "args", "id"]}, "InvalidToolCall": {"title": "InvalidToolCall", "type": "object", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "string"}, "id": {"title": "Id", "type": "string"}, "error": {"title": "Error", "type": "string"}}, "required": ["name", "args", "id", "error"]}, "UsageMetadata": {"title": "UsageMetadata", "type": "object", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"]}, "AIMessage": {"title": "AIMessage", "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "ai", "enum": ["ai"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}, "tool_calls": {"title": "Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/ToolCall"}}, "invalid_tool_calls": {"title": "Invalid Tool Calls", "default": [], "type": "array", "items": {"$ref": "#/definitions/InvalidToolCall"}}, "usage_metadata": {"$ref": "#/definitions/UsageMetadata"}}, "required": ["content"]}, "HumanMessage": {"title": "HumanMessage", "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "human", "enum": ["human"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "example": {"title": "Example", "default": false, "type": "boolean"}}, "required": ["content"]}, "ChatMessage": {"title": "ChatMessage", "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "chat", "enum": ["chat"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"]}, "SystemMessage": {"title": "SystemMessage", "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "system", "enum": ["system"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content"]}, "FunctionMessage": {"title": "FunctionMessage", "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "function", "enum": ["function"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}}, "required": ["content", "name"]}, "ToolMessage": {"title": "ToolMessage", "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to raw_output.\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n raw_output=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "type": "object", "properties": {"content": {"title": "Content", "anyOf": [{"type": "string"}, {"type": "array", "items": {"anyOf": [{"type": "string"}, {"type": "object"}]}}]}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"title": "Type", "default": "tool", "enum": ["tool"], "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"title": "Id", "type": "string"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "raw_output": {"title": "Raw Output"}}, "required": ["content", "tool_call_id"]}}}' # --- # name: test_message_graph.2 ''' @@ -634,7 +658,7 @@ "test_pregel", "FakeFuntionChatModel" ], - "name": "FakeFuntionChatModel" + "name": "agent" } }, { @@ -681,8 +705,8 @@ graph TD; __start__ --> agent; tools --> agent; - agent -. continue .-> tools; - agent -. end .-> __end__; + agent -.  continue  .-> tools; + agent -.  end  .-> __end__; ''' # --- @@ -699,16 +723,16 @@ ''' %%{init: {'flowchart': {'curve': 'linear'}}}%% graph TD; - __start__[__start__]:::startclass; - __end__[__end__]:::endclass; - inner([inner]):::otherclass; - side([side]):::otherclass; + __start__([__start__]):::first + __end__([__end__]):::last + inner(inner) + side(side) __start__ --> inner; inner --> side; side --> __end__; - classDef startclass fill:#ffdfba; - classDef endclass fill:#baffc9; - classDef otherclass fill:#fad7de; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc ''' # --- @@ -801,7 +825,7 @@ 'utils', 'RunnableCallable', ]), - 'name': 'tool_two_slow', + 'name': 'tool_two:tool_two_slow', }), 'id': 'tool_two:tool_two_slow', 'type': 'runnable', @@ -813,7 +837,7 @@ 'utils', 'RunnableCallable', ]), - 'name': 'tool_two_fast', + 'name': 'tool_two:tool_two_fast', }), 'id': 'tool_two:tool_two_fast', 'type': 'runnable', @@ -837,14 +861,14 @@ ''' %%{init: {'flowchart': {'curve': 'linear'}}}%% graph TD; - __start__[__start__]:::startclass; - __end__[__end__]:::endclass; - tool_one([tool_one]):::otherclass; - tool_two___start__([__start__]):::otherclass; - tool_two___end__([__end__]):::otherclass; - tool_two_tool_two_slow([tool_two_slow]):::otherclass; - tool_two_tool_two_fast([tool_two_fast]):::otherclass; - tool_three([tool_three]):::otherclass; + __start__([__start__]):::first + __end__([__end__]):::last + tool_one(tool_one) + tool_two___start__(__start__) + tool_two___end__(__end__) + tool_two_tool_two_slow(tool_two_slow) + tool_two_tool_two_fast(tool_two_fast) + tool_three(tool_three) subgraph tool_two tool_two___start__ -.-> tool_two_tool_two_slow; tool_two_tool_two_slow --> tool_two___end__; @@ -857,9 +881,9 @@ tool_two___end__ --> __end__; __start__ -.-> tool_three; tool_three --> __end__; - classDef startclass fill:#ffdfba; - classDef endclass fill:#baffc9; - classDef otherclass fill:#fad7de; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc ''' # --- @@ -893,7 +917,7 @@ "base", "RunnableLambda" ], - "name": "call_model" + "name": "agent" } }, { @@ -906,7 +930,7 @@ "base", "RunnableLambda" ], - "name": "call_tool" + "name": "tools" } } ], @@ -940,8 +964,8 @@ graph TD; __start__ --> agent; tools --> agent; - agent -. continue .-> tools; - agent -. end .-> __end__; + agent -.  continue  .-> tools; + agent -.  end  .-> __end__; ''' # --- @@ -975,7 +999,7 @@ "base", "RunnableLambda" ], - "name": "call_model" + "name": "agent" } }, { @@ -1022,8 +1046,8 @@ graph TD; __start__ --> agent; tools --> agent; - agent -. continue .-> tools; - agent -. end .-> __end__; + agent -.  continue  .-> tools; + agent -.  end  .-> __end__; ''' # --- @@ -1031,13 +1055,13 @@ ''' graph TD; __start__ --> Researcher; - Researcher -. redo .-> Researcher; - Researcher -. continue .-> Chart_Generator; - Researcher -. call_tool .-> Call_Tool; - Researcher -. end .-> __end__; - Chart_Generator -. continue .-> Researcher; - Chart_Generator -. call_tool .-> Call_Tool; - Chart_Generator -. end .-> __end__; + Researcher -.  redo  .-> Researcher; + Researcher -.  continue  .-> Chart_Generator; + Researcher -.  call_tool  .-> Call_Tool; + Researcher -.  end  .-> __end__; + Chart_Generator -.  continue  .-> Researcher; + Chart_Generator -.  call_tool  .-> Call_Tool; + Chart_Generator -.  end  .-> __end__; Call_Tool -.-> Researcher; Call_Tool -.-> Chart_Generator; @@ -1058,17 +1082,17 @@ ''' %%{init: {'flowchart': {'curve': 'linear'}}}%% graph TD; - __start__[__start__]:::startclass; - __end__[__end__]:::endclass; - tool_two_slow([tool_two_slow]):::otherclass; - tool_two_fast([tool_two_fast]):::otherclass; + __start__([__start__]):::first + __end__([__end__]):::last + tool_two_slow(tool_two_slow) + tool_two_fast(tool_two_fast) __start__ -.-> tool_two_slow; tool_two_slow --> __end__; __start__ -.-> tool_two_fast; tool_two_fast --> __end__; - classDef startclass fill:#ffdfba; - classDef endclass fill:#baffc9; - classDef otherclass fill:#fad7de; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc ''' # --- diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index a304534f2..0c1ef98b2 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -1460,7 +1460,7 @@ def test_conditional_graph(snapshot: SnapshotAssertion) -> None: workflow = Graph() workflow.add_node("agent", agent) - workflow.add_node("tools", execute_tools) + workflow.add_node("tools", execute_tools, metadata={"version": 2, "variant": "b"}) workflow.set_entry_point("agent") @@ -1474,6 +1474,7 @@ def test_conditional_graph(snapshot: SnapshotAssertion) -> None: 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