feat(routing): per-task rules on top of complexity tiers; hosted router is the default
The router has always returned two signals — task label and complexity tier — but only the tier picked models. [routing.tasks] adds `label → ordered models`: a matching rule's models lead the candidate chain and the tier chain follows as fallback, so pinning translations to a local model never weakens failover. Requests whose chain was headed by a task rule report route_source "router+task-rule". The Routing page grows a per-task editor built from the account's LIVE taxonomy (GET /v1/taxonomies via the new /admin/api/routing/labels proxy; built-in label set when the router is unreachable), with localized task names, each task's share of 7-day traffic, "by complexity (no rule)" as the explicit empty state, and custom labels for other taxonomies. Deleting a model scrubs it from task rules like it always did from tiers. Also fixes the default config pointing at a placeholder localhost:8080 router with a nonexistent taxonomy — a fresh install now targets the hosted allaigate router (IP endpoint, verify_tls off, data-assistant, 12 s oracle timeout) and degrades gracefully until a key is set. Verified end-to-end against the live router: translation → task-rule model, math → tier path. 16 new strings in all 7 languages.
This commit is contained in:
@@ -71,6 +71,23 @@ impl RouterClient {
|
||||
|
||||
/// Classify text. Returns `Ok(None)` on network error/timeout —
|
||||
/// the caller treats this as "router unavailable" and uses the default tier.
|
||||
/// `GET /v1/taxonomies` — the account's taxonomies with their label sets.
|
||||
/// `Ok(None)` when the router is disabled or has no key configured.
|
||||
pub async fn taxonomies(&self) -> anyhow::Result<Option<serde_json::Value>> {
|
||||
if self.url.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut req = self.http.get(format!("{}/v1/taxonomies", self.url));
|
||||
if let Some(k) = &self.api_key {
|
||||
req = req.bearer_auth(k);
|
||||
}
|
||||
let resp = req.send().await?;
|
||||
if !resp.status().is_success() {
|
||||
anyhow::bail!("router /v1/taxonomies: HTTP {}", resp.status());
|
||||
}
|
||||
Ok(Some(resp.json().await?))
|
||||
}
|
||||
|
||||
pub async fn route(&self, text: &str, profile: &str) -> anyhow::Result<Option<RouteDecision>> {
|
||||
let mut body = serde_json::json!({
|
||||
"input": { "text": text },
|
||||
|
||||
Reference in New Issue
Block a user