4.0 KiB
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:
- Sets
is_validation=Trueon the result - Extracts
passedfrom the tool output - Includes the
validation_modeif 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 |