mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-01 22:15:11 +02:00
stop naming ToolCallWithContext in lifecycle docstrings
`ToolCallWithContext` is an internal prebuilt type — public-facing docstrings shouldn't be calling it out by name. Describe the two recognised per-call envelope shapes (single-element list of tool-call dicts, dict wrapping a `tool_call`) by structure instead, and lead with the list shape since that's what `langchain.agents.create_agent` currently dispatches.
This commit is contained in:
@@ -343,16 +343,17 @@ def _parse_ns_segment(segment: str) -> tuple[str, str | None]:
|
||||
def _extract_per_call_args(payload: Any) -> tuple[dict[str, Any], str | None] | None:
|
||||
"""Pull `(args, tool_call_id)` out of a per-call dispatched task's `input`.
|
||||
|
||||
Two shapes are recognised; both are duck-typed so 3rd-party tool
|
||||
runners that mimic the layout participate without importing prebuilt
|
||||
types:
|
||||
Two shapes are recognised; both are duck-typed so any tool runner
|
||||
that mimics the layout participates without naming any specific
|
||||
dispatcher's types:
|
||||
|
||||
1. `ToolCallWithContext`-style envelope used by
|
||||
`langgraph.prebuilt.ToolNode` Send-fan-out:
|
||||
`{"tool_call": {"id": ..., "args": {...}, ...}, ...}`.
|
||||
2. Single-element list of tool-call dicts used by langchain v1's
|
||||
`create_agent` Send-fan-out:
|
||||
`[{"id": ..., "name": ..., "args": {...}}]`.
|
||||
1. Single-element list of tool-call dicts:
|
||||
`[{"id": ..., "name": ..., "args": {...}}]`. The current public
|
||||
shape — `langchain.agents.create_agent` Send-fans this out per
|
||||
pending tool call.
|
||||
2. Dict envelope wrapping a tool call:
|
||||
`{"tool_call": {"id": ..., "args": {...}, ...}, ...}`. Older
|
||||
prebuilt agent paths Send-fan-out this shape.
|
||||
|
||||
Returns `None` if the payload doesn't match either shape or its
|
||||
`args` isn't a dict. `tool_call_id` is `None` when the envelope
|
||||
@@ -395,10 +396,11 @@ class LifecyclePayload(TypedDict, total=False):
|
||||
Shape:
|
||||
|
||||
- `{"type": "tool_call", "subagent_type": "<name>", "description": "<text>",
|
||||
"tool_call_id": "<id>"}` — set when the subgraph was triggered by a tool
|
||||
invocation routed through `langgraph.prebuilt.ToolNode`. Mined from the
|
||||
per-call dispatched task's `input.tool_call`. `subagent_type` and
|
||||
`description` describe the invoking intent; `tool_call_id` is the
|
||||
"tool_call_id": "<id>"}` — set when the subgraph was triggered by a
|
||||
per-call tool dispatch (a model tool call routed through whatever
|
||||
tool node the agent uses). Mined from the per-call task's `input` —
|
||||
see `_extract_per_call_args` for the recognised shapes. `subagent_type`
|
||||
and `description` describe the invoking intent; `tool_call_id` is the
|
||||
model-side id of the originating tool call, exposed so UI consumers
|
||||
can anchor the lifecycle event back to the AI message that dispatched
|
||||
it (the per-call Send fan-out gives each tool_call its own pregel task,
|
||||
@@ -410,7 +412,7 @@ class LifecyclePayload(TypedDict, total=False):
|
||||
a `cause` if at least one field was extractable.
|
||||
|
||||
Absent for structurally-triggered subgraphs (parallel branches via
|
||||
`Send` without ToolNode, nested `graph.invoke()`, etc.)."""
|
||||
`Send` with non-tool-call payloads, nested `graph.invoke()`, etc.)."""
|
||||
error: NotRequired[str]
|
||||
|
||||
|
||||
@@ -451,12 +453,11 @@ class _TasksLifecycleBase(StreamTransformer):
|
||||
# Maps tracked namespace -> task_id of the parent task whose
|
||||
# `TaskResultPayload` will close it.
|
||||
self._open: dict[tuple[str, ...], str] = {}
|
||||
# Maps task_id -> invocation metadata for tasks whose `input` looked
|
||||
# like a `{"tool_call": {...}, ...}` envelope (the shape
|
||||
# `langgraph.prebuilt.ToolNode` Send-fans out via
|
||||
# `ToolCallWithContext`). The lifecycle hook joins on this
|
||||
# when a child subgraph fires its first task event so it can
|
||||
# attribute the invocation to the model tool call's args.
|
||||
# Maps task_id -> invocation metadata for tasks whose `input` matched
|
||||
# a recognized per-call tool-dispatch shape (see `_extract_per_call_args`
|
||||
# for the accepted layouts). The lifecycle hook joins on this when a
|
||||
# child subgraph fires its first task event so it can attribute the
|
||||
# invocation to the model tool call's args.
|
||||
self._invocation_metadata: dict[str, dict[str, str]] = {}
|
||||
|
||||
# --- Template-method hooks (subclass overrides) ---
|
||||
|
||||
@@ -39,11 +39,12 @@ def _tasks_start(
|
||||
) -> dict[str, Any]:
|
||||
"""Build a `tasks` ProtocolEvent carrying a TaskPayload (start).
|
||||
|
||||
Pass `input={"tool_call": {"args": {...}}}` (or any envelope with
|
||||
that shape) to exercise the lifecycle transformer's input mining of
|
||||
invocation-intent metadata (`subagent_type`, `description`) — this
|
||||
mirrors the `ToolCallWithContext` payload `langgraph.prebuilt.ToolNode`
|
||||
Send-fans out per tool call.
|
||||
Pass `input=[{"id": ..., "name": ..., "args": {...}}]` (the per-call
|
||||
list shape `langchain.agents.create_agent` Send-fans out) or
|
||||
`input={"tool_call": {"args": {...}}, ...}` (the dict envelope older
|
||||
prebuilt agent paths emit) to exercise the lifecycle transformer's
|
||||
input mining of invocation-intent metadata (`subagent_type`,
|
||||
`description`).
|
||||
"""
|
||||
return {
|
||||
"type": "event",
|
||||
@@ -135,14 +136,14 @@ def test_started_emitted_on_first_direct_child_task() -> None:
|
||||
|
||||
|
||||
def test_started_carries_cause_when_parent_input_has_invocation_metadata() -> None:
|
||||
"""When a parent task's `input` is a `ToolCallWithContext`-shaped
|
||||
envelope (`{"tool_call": {"args": {...}}, ...}`, the layout
|
||||
`langgraph.prebuilt.ToolNode` Send-fans out per call), the
|
||||
transformer mines `subagent_type`, `description`, and `tool_call_id`
|
||||
from `tool_call` and remembers them keyed by `parent_task_id`.
|
||||
When that parent task triggers a subgraph (the child's namespace
|
||||
ends in `name:<parent_task_id>`), the `lifecycle.started` payload
|
||||
carries `cause = {"type": "tool_call", "subagent_type": ..., "description": ...,
|
||||
"""When a parent task's `input` is a dict envelope with a `tool_call`
|
||||
field (`{"tool_call": {"args": {...}}, ...}`, the layout older
|
||||
prebuilt agent paths Send-fan out per call), the transformer mines
|
||||
`subagent_type`, `description`, and `tool_call_id` from `tool_call`
|
||||
and remembers them keyed by `parent_task_id`. When that parent task
|
||||
triggers a subgraph (the child's namespace ends in
|
||||
`name:<parent_task_id>`), the `lifecycle.started` payload carries
|
||||
`cause = {"type": "tool_call", "subagent_type": ..., "description": ...,
|
||||
"tool_call_id": ...}`. Identity-level correlation still uses
|
||||
`trigger_call_id`; `tool_call_id` is exposed so UI consumers can
|
||||
anchor the lifecycle event back to the originating AI message."""
|
||||
@@ -210,12 +211,12 @@ def test_started_cause_with_description_but_no_subagent_type() -> None:
|
||||
|
||||
|
||||
def test_started_carries_cause_for_list_shape_per_call_input() -> None:
|
||||
"""langchain v1's `create_agent` Send-fans out a per-call task whose
|
||||
`input` is a single-element list of tool-call dicts:
|
||||
"""`langchain.agents.create_agent` Send-fans out a per-call task
|
||||
whose `input` is a single-element list of tool-call dicts:
|
||||
`[{"id": ..., "name": ..., "args": {...}}]`. The transformer mines
|
||||
`subagent_type`, `description`, and `tool_call_id` exactly as for the
|
||||
`ToolCallWithContext` dict envelope, so `lifecycle.started.cause`
|
||||
fires regardless of which agent factory drove the dispatch."""
|
||||
`subagent_type`, `description`, and `tool_call_id` exactly as for
|
||||
the dict envelope shape, so `lifecycle.started.cause` fires
|
||||
regardless of which agent factory drove the dispatch."""
|
||||
mux = _build_lifecycle_mux()
|
||||
mux.push(
|
||||
_tasks_start(
|
||||
|
||||
Reference in New Issue
Block a user