Files
cleveragents-core/docs/reference/tool_router.md
T

4.0 KiB

Tool Call Router

The ToolCallRouter translates between LLM provider-specific tool call formats and the internal ToolRunner. It supports OpenAI, Anthropic, and LangChain formats, producing normalized NormalizedToolCallResult objects regardless of the input format.

Provider Format Mapping

Provider Tool Call Shape Arguments Key Arguments Type
OpenAI {"name": "...", "arguments": "..."} arguments JSON string
Anthropic {"name": "...", "input": {...}} input dict
LangChain {"name": "...", "type": "tool_call", "args": {...}} args dict

Architecture

Provider (OpenAI/Anthropic/LangChain)
    |
    v
ToolCallRouter.route(payload)
    |-- detect_provider_format(payload)
    |-- normalize_tool_call(payload) -> ToolCallRequest
    |-- generate_tool_call_id(plan_id, sequence)
    |-- ToolRunner.execute(name, args)
    |-- classify_tool_error(error) [if failed]
    |-- _check_is_validation(spec) [validation surfacing]
    v
NormalizedToolCallResult

Usage

Basic Routing

from cleveragents.tool.registry import ToolRegistry
from cleveragents.tool.runner import ToolRunner
from cleveragents.tool.router import ToolCallRouter

registry = ToolRegistry()
# ... register tools ...
runner = ToolRunner(registry)
router = ToolCallRouter(registry=registry, runner=runner, plan_id="plan-001")

# Route an OpenAI-format tool call
result = router.route({
    "name": "my-ns/my-tool",
    "arguments": '{"key": "value"}'
})
print(result.result.success)
print(result.tool_call_id)
print(result.provider_format)  # "openai"

Batch Routing

results = router.route_batch([
    {"name": "ns/tool-a", "arguments": '{"x": 1}'},
    {"name": "ns/tool-b", "input": {"y": 2}},
])
for r in results:
    print(f"{r.tool_name}: success={r.result.success}")

Streaming Execution

for update in router.route_streaming(payload):
    if isinstance(update, StreamingToolUpdate):
        print(f"Status: {update.status}")
    elif isinstance(update, NormalizedToolCallResult):
        print(f"Final: success={update.result.success}")

Schema Export

from cleveragents.tool.router import ProviderFormat

schemas = router.export_schemas(ProviderFormat.OPENAI)
# Returns provider-normalized schemas for all registered tools

Stable ID Generation

Tool call IDs are generated deterministically from plan_id + sequence:

from cleveragents.tool.router import generate_tool_call_id

call_id = generate_tool_call_id("plan-001", 0)
# Returns: "tc_<24-char-hex>"

The same inputs always produce the same ID, enabling reproducible tracking.

Error Classification

Errors are classified into structured categories:

Category Matches
timeout "timeout", "timed out"
permission "permission", "access denied", "forbidden"
not_found "not found"
resource "resource", "memory", "disk"
schema "schema", "validation", "json"
parse "parse", "decode", "deserializ"
execution Everything else
unknown Empty error messages

Validation Surfacing

When a tool is detected as a validation (read-only tool with "valid" in its name), the router:

  1. Sets is_validation=True on the result
  2. Extracts passed from the tool output
  3. Includes the validation_mode if available in the output schema

Provider Metadata

The router captures provider-level metadata passed during routing:

result = router.route(payload, provider_metadata={
    "model": "gpt-4",
    "provider_id": "openai",
    "latency_ms": 150,
})
print(result.provider_metadata["model"])  # "gpt-4"

Schema Normalization

Schemas are normalized per provider with field pruning and description truncation:

Provider Schema Key Default Max Description
OpenAI parameters 1024 chars
Anthropic input_schema 1024 chars
LangChain args_schema 1024 chars