feat(routing)!: task rules become per-complexity model maps

The single per-task «Complexity» pin was the wrong shape — what you actually
decide is WHICH model serves a task at WHICH complexity. A task rule is now a
per-tier map, edited in the console as rows of “model ▾ + complexity ▾” pairs
(plus ×/add) inside each task:

  [routing.tasks.code]
  low  = ["cheap-coder"]
  high = ["best-coder"]
  any  = ["generic-coder"]   # leads at every tier after the tier pick

Chain order: the task's tier-specific list → (at high: the global high chain,
so escalation still beats any-tier pins) → the task's any-tier list → the
global tier chain → default as the last resort; deduplicated. Plain-array
rules keep meaning "any tier" unchanged, and the short-lived {models, tier}
form from the previous commit is normalized into the map on load.

Verified live: legacy config migrated visibly in the editor, pairs survive
save+reload, a math prompt at low ignored the math@high pair, per-tier leads
confirmed by unit tests (19 passing).
This commit is contained in:
infosave2007
2026-08-08 08:54:27 +03:00
parent 95e560dc72
commit 06813b3e97
15 changed files with 316 additions and 183 deletions
+10 -9
View File
@@ -179,19 +179,20 @@ A matching rule's models go **first** in the candidate chain; the complexity
chain follows as fallback, so a task pin never weakens failover. Tasks without
a rule route by complexity alone.
**Pinning complexity per task.** A task rule also accepts a full form with a
forced tier — the router's verdict is replaced for that task before anything
else happens:
**Per-complexity models for a task.** A task rule also accepts a per-tier
map — "which model serves this task at each complexity":
```toml
[routing.tasks.legal]
models = ["strong-model"]
tier = "high" # legal review is always high, whatever the router scored
[routing.tasks.code]
low = ["cheap-coder"] # routine code stays cheap
high = ["best-coder"] # hard code goes to the strongest coder
any = ["generic-coder"] # leads at every tier after the tier-specific pick
```
Responses whose tier was pinned report `route_source: "router+task-rule@high"`.
The admin console's Routing page exposes this as a per-task «Complexity»
selector (Auto / low / medium / high).
The tier-specific list leads its own tier; `any` (and the legacy plain-array
form) leads at every tier; the global tier chain and `default` always remain
as fallback, so a task rule never weakens failover. The admin console's
Routing page edits this as rows of “model + complexity” pairs per task.
**Exception — high complexity escalates.** When the router rates a request
`high` (and a `high` chain is configured), the tier chain leads and the task