Add a small YAML routing config schema (schema_version 1) and a parser-only loader at src/infospace_bench/routing_config.py. The loader validates the declarative shape — task_types with candidates, optional per-task quality_floor, optional default_quality_floor, optional ledger_path, optional stage_to_task_type override map — and refuses bad shapes before any network or workspace work happens. Supported provider names: openrouter, claude_code, openai, gemini. Unknown providers, missing required candidate fields, out-of-range quality floors, negative max_cost_per_1k, duplicate candidate ids within a task type, and non-mapping stage_to_task_type all raise focused InfospaceError codes that callers can pattern-match. docs/routing-config.md documents the schema with two annotated examples (OpenRouter-only two-tier, and adaptive with a ClaudeCode baseline) plus the full "what fails fast" list. 16 parser tests cover happy-path round-trip, file load, missing file, malformed YAML, and every validation surface (wrong/missing schema version, empty task_types, empty candidates, missing required fields, unsupported provider, negative cost, out-of-range quality_floor, duplicate ids, non-mapping stage_map, non-string ledger_path). T02 will turn a RoutingConfig into a live llm-connect RoutingPolicy / AdaptiveRoutingPolicy with constructed LLMAdapter instances. 160 tests pass, 1 skipped. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4.7 KiB
Routing Config Schema
Workplan: IB-WP-0020 (T01 schema, T02 loader)
Module: src/infospace_bench/routing_config.py
A routing config is a small YAML file that names the candidate adapters
per task type and (optionally) the quality floor, the
QualityLedger path, and a stage-to-task-type override map. The file
is the consumer side of llm-connect LLM-WP-0004's routing primitives:
it does not embed model selection logic, just declares the universe
the policy can choose from.
The schema_version is pinned to 1. Bump it (and the parser) before
making backward-incompatible changes.
Top-level fields
| Field | Type | Notes |
|---|---|---|
schema_version |
int (required) | Currently 1. Mismatch fails fast. |
task_types |
mapping (required) | At least one entry. Each entry has candidates and an optional quality_floor. |
default_quality_floor |
float (optional) | Falls back when a task type does not name its own. Must be 0..1. |
ledger_path |
string (optional) | Path to a QualityLedger JSONL. Relative paths resolve against the workspace by default. Required when any quality_floor is non-null. |
stage_to_task_type |
mapping (optional) | Caller-supplied mapping from infospace-bench stage ids to task types. Falls through to identity when omitted. |
Candidate fields
Each entry under task_types.<task_type>.candidates[]:
| Field | Type | Notes |
|---|---|---|
id |
string (required) | Stable adapter id used for the QualityLedger and the per-stage adapter-choice line of the generation report. |
provider |
string (required) | One of openrouter, claude_code, openai, gemini. |
model |
string (required) | Provider-specific model id, e.g. openai/gpt-4o-mini. |
api_key_env |
string (optional) | Env var that holds the API key. Defaults to a provider-specific name (OPENROUTER_API_KEY etc.) in the T02 loader. |
max_cost_per_1k |
float (optional) | Static cost cap. Static RoutingPolicy falls back to a cheaper candidate when the caller-supplied estimate exceeds this. |
Example A — OpenRouter-only, two-tier
A pragmatic Lefevre-style config. Cheap model for summaries, mid model for entities/relations, cheap again for evaluation. No adaptive routing, no ledger.
schema_version: 1
stage_to_task_type:
summarize-source: cheap
extract-entities: smart
extract-relations: smart
evaluate-entity: cheap
synthesize-report: smart
task_types:
cheap:
candidates:
- id: openrouter:gpt-4o-mini
provider: openrouter
model: openai/gpt-4o-mini
api_key_env: OPENROUTER_API_KEY
smart:
candidates:
- id: openrouter:claude-3.5-sonnet
provider: openrouter
model: anthropic/claude-3.5-sonnet
api_key_env: OPENROUTER_API_KEY
Example B — Adaptive with a ClaudeCode baseline
A two-candidate-per-stage adaptive config. The QualityLedger
accumulates observations; over time, the cheaper qualifying model is
preferred per stage. ClaudeCodeAdapter is wired into a separate
task_types.baseline rule so it can be referenced by a
ShadowingAdapter builder (T05).
schema_version: 1
default_quality_floor: 0.80
ledger_path: output/routing/quality.jsonl
task_types:
summarize-source:
quality_floor: 0.70
candidates:
- id: openrouter:gpt-4o-mini
provider: openrouter
model: openai/gpt-4o-mini
api_key_env: OPENROUTER_API_KEY
max_cost_per_1k: 0.001
- id: openrouter:claude-3.5-haiku
provider: openrouter
model: anthropic/claude-3.5-haiku
api_key_env: OPENROUTER_API_KEY
max_cost_per_1k: 0.003
extract-entities:
quality_floor: 0.85
candidates:
- id: openrouter:claude-3.5-haiku
provider: openrouter
model: anthropic/claude-3.5-haiku
api_key_env: OPENROUTER_API_KEY
- id: openrouter:claude-3.5-sonnet
provider: openrouter
model: anthropic/claude-3.5-sonnet
api_key_env: OPENROUTER_API_KEY
baseline:
candidates:
- id: claude-code
provider: claude_code
model: claude-opus-4-7
What fails fast
The parser refuses, before any network or workspace work, when:
schema_versionis missing or not1task_typesis missing or empty- Any
task_typehas nocandidates - A candidate is missing
id,provider, ormodel - A
provideris not one of the supported names max_cost_per_1kis non-numeric or negative- Any
quality_floor(top-level or per-task) is outside 0..1 - A
task_typehas duplicate candidateids ledger_pathorstage_to_task_typehas the wrong YAML shape
api_key_env resolution and live adapter construction happen in T02.
This file only validates the declarative shape.