feat(core): add subagent_name attribute to BaseTool

Declares the subagent a tool dispatches to. Accepts a static string
or a Callable[[ToolCall], str] for dynamic resolution at dispatch time.

This is a pure attribute addition with no behavior. Downstream
consumers (langgraph ToolNode, langchain create_agent's typed
projection) will read this attribute to surface subagent identity on
streaming lifecycle events.
This commit is contained in:
Nick Hollon
2026-05-27 18:49:08 -04:00
parent 84e3c795ec
commit b36d8ffaf7
2 changed files with 53 additions and 0 deletions

View File

@@ -530,6 +530,20 @@ class ChildTool(BaseTool):
```
"""
subagent_name: str | Callable[[ToolCall], str] | None = None
"""Declared name of the subagent this tool dispatches to, if any.
A static string declares a fixed dispatch target (one tool -> one subagent).
A callable resolves the name from the parsed `ToolCall` at dispatch time,
enabling registry-style dispatching (one tool -> many subagents based on
LLM-supplied args).
Read by `ToolNode` at dispatch time and surfaced on the wire as
`LifecyclePayload.graphName`. Purely declarative: the framework stamps the
resolved value into runtime config metadata; consumers project it onto
typed surfaces. Setting this does not change tool execution behavior.
"""
def __init__(self, **kwargs: Any) -> None:
"""Initialize the tool.

View File

@@ -3741,3 +3741,42 @@ def test_tool_invoke_returns_list_of_mixin() -> None:
assert isinstance(result, list)
assert len(result) == 3
assert all(isinstance(m, ToolMessage) for m in result)
# --- subagent_name attribute tests ---
class _SubagentNameDummyTool(BaseTool):
"""Minimal BaseTool subclass for subagent_name attribute tests."""
name: str = "dummy"
description: str = "A dummy tool used in subagent_name tests."
def _run(self, *args: Any, **kwargs: Any) -> str:
return "dummy result"
def test_basetool_subagent_name_default_none() -> None:
tool = _SubagentNameDummyTool()
assert tool.subagent_name is None
def test_basetool_subagent_name_accepts_static_string() -> None:
tool = _SubagentNameDummyTool(subagent_name="weather_agent")
assert tool.subagent_name == "weather_agent"
def test_basetool_subagent_name_accepts_callable() -> None:
def resolver(call: ToolCall) -> str:
return str(call["args"]["subagent_type"])
tool = _SubagentNameDummyTool(subagent_name=resolver)
assert tool.subagent_name is resolver
fake_call: ToolCall = {
"name": "task",
"args": {"subagent_type": "researcher"},
"id": "id-1",
"type": "tool_call",
}
assert callable(tool.subagent_name)
assert tool.subagent_name(fake_call) == "researcher"