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

142 lines
4.0 KiB
Markdown

# 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
```python
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
```python
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
```python
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
```python
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`:
```python
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:
```python
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 |