# 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 |