mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-26 11:35:02 +02:00
## Summary Extends `ToolNode` so that a single tool invocation can return `list[Command | ToolMessage]` instead of only a single `Command` or `ToolMessage`. This brings `ToolNode`'s per-tool-call contract in line with the rest of LangGraph, where nodes can already return multiple Commands. Depends on langchain-ai/langchain#36963 which allows `list[ToolOutputMixin]` to pass through `BaseTool._format_output` unchanged. ## Changes ### `libs/prebuilt/langgraph/prebuilt/tool_node.py` **New list-return gate in `_execute_tool_sync` / `_execute_tool_async`** — After the existing `Command` and `ToolMessage` checks, a new branch accepts `list[Command | ToolMessage]` and routes it through `_validate_tool_command_list`. Lists with non-`Command`/`ToolMessage` elements raise `TypeError`. Both sync and async paths are updated symmetrically. **`_validate_tool_command_list`** — Enforces the terminating-ToolMessage rule: exactly one `ToolMessage` in the list must carry `tool_call_id == <outer_id>` (top-level or nested inside a `Command.update["messages"]`). Zero or multiple terminators raise `_MissingToolMessageError`. Individual Commands in the list are validated via the existing `_validate_tool_command`; when a Command lacks the terminator (which is allowed since the list-level check handles it), the `_MissingToolMessageError` is caught and the already-normalized command from the exception is used. **`_MissingToolMessageError`** — A `ValueError` subclass raised by `_validate_tool_command` (and `_validate_tool_command_list`) when no matching `ToolMessage` is found. Carries the already-normalized command so callers can recover without re-doing deepcopy/message-conversion work. Using a typed exception avoids brittle string-matching on error messages. **`_combine_tool_outputs`** — Flattens list entries at the top of the method so downstream combiner logic (parent-`goto` accumulation, ToolMessage wrapping) is unchanged. **Response processing moved inside try/except** — In both sync and async execute methods, the response validation (Command/ToolMessage/list checks) now runs inside the existing error-handling try block, so validation errors from the list path go through `_handle_tool_errors` like other tool errors. **Return type signatures** widened on `_execute_tool_sync`, `_execute_tool_async`, `_run_one`, `_arun_one` to include `list[Command | ToolMessage]`. ### `libs/prebuilt/tests/test_tool_node.py` New tests covering: valid list returns (top-level terminator, nested terminator, parent-goto + terminator), regression tests for single Command/ToolMessage returns, invalid cases (no terminator, multiple terminators), async parity, integration with mixed list/non-list tool calls, and `_handle_tool_errors` interaction. --------- Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>
97 lines
2.5 KiB
TOML
97 lines
2.5 KiB
TOML
[build-system]
|
|
requires = ["hatchling"]
|
|
build-backend = "hatchling.build"
|
|
|
|
[project]
|
|
name = "langgraph-prebuilt"
|
|
version = "1.0.10"
|
|
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
|
|
authors = []
|
|
requires-python = ">=3.10"
|
|
readme = "README.md"
|
|
license = "MIT"
|
|
license-files = ['LICENSE']
|
|
classifiers = [
|
|
'Development Status :: 5 - Production/Stable',
|
|
'Programming Language :: Python',
|
|
'Programming Language :: Python :: Implementation :: CPython',
|
|
'Programming Language :: Python :: Implementation :: PyPy',
|
|
'Programming Language :: Python :: 3',
|
|
'Programming Language :: Python :: 3 :: Only',
|
|
'Programming Language :: Python :: 3.10',
|
|
'Programming Language :: Python :: 3.11',
|
|
'Programming Language :: Python :: 3.12',
|
|
'Programming Language :: Python :: 3.13',
|
|
]
|
|
dependencies = [
|
|
"langgraph-checkpoint>=2.1.0,<5.0.0",
|
|
"langchain-core>=1.3.1",
|
|
]
|
|
|
|
[project.urls]
|
|
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/prebuilt"
|
|
Twitter = "https://x.com/LangChain"
|
|
Slack = "https://www.langchain.com/join-community"
|
|
Reddit = "https://www.reddit.com/r/LangChain/"
|
|
|
|
[dependency-groups]
|
|
test = [
|
|
"pytest",
|
|
"pytest-asyncio",
|
|
"pytest-mock",
|
|
"pytest-watcher",
|
|
"langchain-core",
|
|
"langgraph",
|
|
"langgraph-checkpoint",
|
|
"langgraph-checkpoint-sqlite",
|
|
"langgraph-checkpoint-postgres",
|
|
"syrupy",
|
|
"psycopg-binary",
|
|
]
|
|
lint = [
|
|
"ruff",
|
|
"codespell",
|
|
"mypy",
|
|
]
|
|
dev = [
|
|
{include-group = "test"},
|
|
{include-group = "lint"},
|
|
]
|
|
|
|
[tool.uv]
|
|
default-groups = ['dev']
|
|
|
|
[tool.uv.sources]
|
|
langgraph = { path = "../langgraph", editable = true }
|
|
langgraph-checkpoint = { path = "../checkpoint", editable = true }
|
|
langgraph-checkpoint-sqlite = { path = "../checkpoint-sqlite", editable = true }
|
|
langgraph-checkpoint-postgres = { path = "../checkpoint-postgres", editable = true }
|
|
|
|
[tool.hatch.build.targets.wheel]
|
|
include = ["langgraph"]
|
|
|
|
[tool.pytest.ini_options]
|
|
addopts = "--strict-markers --strict-config --durations=5 -vv"
|
|
asyncio_mode = "auto"
|
|
|
|
[tool.ruff]
|
|
lint.select = [ "E", "F", "I", "TID251", "UP" ]
|
|
lint.ignore = [ "E501" ]
|
|
target-version = "py310"
|
|
|
|
[tool.pytest-watcher]
|
|
now = true
|
|
delay = 0.1
|
|
runner_args = ["--ff", "-v", "--tb", "short"]
|
|
patterns = ["*.py"]
|
|
|
|
[tool.mypy]
|
|
# https://mypy.readthedocs.io/en/stable/config_file.html
|
|
disallow_untyped_defs = "True"
|
|
explicit_package_bases = "True"
|
|
warn_no_return = "False"
|
|
warn_unused_ignores = "True"
|
|
warn_redundant_casts = "True"
|
|
allow_redefinition = "True"
|
|
disable_error_code = "typeddict-item, return-value"
|