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:
infosave2007
2026-08-07 17:39:38 +03:00
parent 15de36be3b
commit 2ede0ee452
18 changed files with 507 additions and 58 deletions
+18
View File
@@ -545,4 +545,22 @@ export default {
"bench.running": "misst…",
"bench.failed": "fehlgeschlagen",
"bench.none": "Noch keine lokalen .cmf-Chat-Modelle.",
// task-based routing
"task.chitchat": "Smalltalk",
"task.code": "Code",
"task.creative-writing": "Kreatives Schreiben",
"task.extraction": "Datenextraktion",
"task.math": "Mathematik",
"task.qa": "Fragen & Antworten",
"task.summarization": "Zusammenfassung",
"task.translation": "Übersetzung",
"routing.tasks.title": "Aufgabenbasiertes Routing",
"routing.tasks.hint": "Die Modelle einer Regel kommen für ihre Aufgabe zuerst; die Komplexitätskette bleibt als Fallback. Leer = nur nach Komplexität.",
"routing.tasks.inherit": "nach Komplexität (keine Regel)",
"routing.tasks.share": "{p}% der Anfragen (7 T)",
"routing.tasks.fromRouter": "Labels live vom Router",
"routing.tasks.builtin": "Router nicht erreichbar — eingebauter Labelsatz",
"routing.tasks.addCustom": "Aufgabe hinzufügen",
"routing.tasks.addPlaceholder": "eigenes Label…",
};
+18
View File
@@ -538,4 +538,22 @@ export default {
"bench.running": "measuring…",
"bench.failed": "failed",
"bench.none": "No local .cmf chat models yet.",
// task-based routing
"task.chitchat": "Small talk",
"task.code": "Code",
"task.creative-writing": "Creative writing",
"task.extraction": "Data extraction",
"task.math": "Math",
"task.qa": "Q&A",
"task.summarization": "Summarization",
"task.translation": "Translation",
"routing.tasks.title": "Task-based routing",
"routing.tasks.hint": "A rule's models go first for that task; the complexity chain below still applies as fallback. Empty = route by complexity only.",
"routing.tasks.inherit": "by complexity (no rule)",
"routing.tasks.share": "{p}% of requests (7d)",
"routing.tasks.fromRouter": "labels live from the router",
"routing.tasks.builtin": "router unreachable — built-in label set",
"routing.tasks.addCustom": "Add task",
"routing.tasks.addPlaceholder": "custom label…",
};
+18
View File
@@ -545,4 +545,22 @@ export default {
"bench.running": "midiendo…",
"bench.failed": "falló",
"bench.none": "Aún no hay modelos de chat .cmf locales.",
// task-based routing
"task.chitchat": "Charla",
"task.code": "Código",
"task.creative-writing": "Escritura creativa",
"task.extraction": "Extracción de datos",
"task.math": "Matemáticas",
"task.qa": "Preguntas y respuestas",
"task.summarization": "Resumen",
"task.translation": "Traducción",
"routing.tasks.title": "Enrutado por tarea",
"routing.tasks.hint": "Los modelos de una regla van primero para su tarea; la cadena por complejidad sigue como respaldo. Vacío = solo por complejidad.",
"routing.tasks.inherit": "por complejidad (sin regla)",
"routing.tasks.share": "{p}% de las peticiones (7 d)",
"routing.tasks.fromRouter": "etiquetas en vivo del router",
"routing.tasks.builtin": "router inaccesible — conjunto integrado",
"routing.tasks.addCustom": "Añadir tarea",
"routing.tasks.addPlaceholder": "etiqueta propia…",
};
+18
View File
@@ -545,4 +545,22 @@ export default {
"bench.running": "mesure…",
"bench.failed": "échec",
"bench.none": "Pas encore de modèles de chat .cmf locaux.",
// task-based routing
"task.chitchat": "Bavardage",
"task.code": "Code",
"task.creative-writing": "Écriture créative",
"task.extraction": "Extraction de données",
"task.math": "Mathématiques",
"task.qa": "Questions-réponses",
"task.summarization": "Résumé",
"task.translation": "Traduction",
"routing.tasks.title": "Routage par tâche",
"routing.tasks.hint": "Les modèles d'une règle passent en premier pour leur tâche ; la chaîne par complexité reste en secours. Vide = complexité seule.",
"routing.tasks.inherit": "par complexité (aucune règle)",
"routing.tasks.share": "{p}% des requêtes (7 j)",
"routing.tasks.fromRouter": "étiquettes reçues du routeur",
"routing.tasks.builtin": "routeur injoignable — jeu d'étiquettes intégré",
"routing.tasks.addCustom": "Ajouter une tâche",
"routing.tasks.addPlaceholder": "étiquette personnalisée…",
};
+18
View File
@@ -537,4 +537,22 @@ export default {
"bench.running": "замер…",
"bench.failed": "ошибка",
"bench.none": "Локальных чат-моделей .cmf пока нет.",
// task-based routing
"task.chitchat": "Светская беседа",
"task.code": "Код",
"task.creative-writing": "Творческие тексты",
"task.extraction": "Извлечение данных",
"task.math": "Математика",
"task.qa": "Вопросы и ответы",
"task.summarization": "Суммаризация",
"task.translation": "Перевод",
"routing.tasks.title": "Распределение по задачам",
"routing.tasks.hint": "Модели правила идут первыми для своей задачи; цепочка по сложности остаётся резервом. Пусто = только по сложности.",
"routing.tasks.inherit": "по сложности (без правила)",
"routing.tasks.share": "{p}% запросов (7д)",
"routing.tasks.fromRouter": "метки получены с роутера",
"routing.tasks.builtin": "роутер недоступен — встроенный набор меток",
"routing.tasks.addCustom": "Добавить задачу",
"routing.tasks.addPlaceholder": "своя метка…",
};
+18
View File
@@ -545,4 +545,22 @@ export default {
"bench.running": "ölçülüyor…",
"bench.failed": "başarısız",
"bench.none": "Henüz yerel .cmf sohbet modeli yok.",
// task-based routing
"task.chitchat": "Sohbet",
"task.code": "Kod",
"task.creative-writing": "Yaratıcı yazarlık",
"task.extraction": "Veri çıkarma",
"task.math": "Matematik",
"task.qa": "Soru-cevap",
"task.summarization": "Özetleme",
"task.translation": "Çeviri",
"routing.tasks.title": "Göreve göre yönlendirme",
"routing.tasks.hint": "Kuralın modelleri kendi görevi için önce gelir; karmaşıklık zinciri yedek olarak kalır. Boş = yalnızca karmaşıklığa göre.",
"routing.tasks.inherit": "karmaşıklığa göre (kural yok)",
"routing.tasks.share": "isteklerin %{p}'i (7 g)",
"routing.tasks.fromRouter": "etiketler yönlendiriciden canlı",
"routing.tasks.builtin": "yönlendiriciye ulaşılamıyor — yerleşik etiket seti",
"routing.tasks.addCustom": "Görev ekle",
"routing.tasks.addPlaceholder": "özel etiket…",
};
+18
View File
@@ -545,4 +545,22 @@ export default {
"bench.running": "测速中……",
"bench.failed": "失败",
"bench.none": "还没有本地 .cmf 聊天模型。",
// task-based routing
"task.chitchat": "闲聊",
"task.code": "代码",
"task.creative-writing": "创意写作",
"task.extraction": "数据提取",
"task.math": "数学",
"task.qa": "问答",
"task.summarization": "摘要",
"task.translation": "翻译",
"routing.tasks.title": "按任务路由",
"routing.tasks.hint": "规则中的模型对该任务优先;下方的复杂度链仍作为回退。留空 = 仅按复杂度。",
"routing.tasks.inherit": "按复杂度(无规则)",
"routing.tasks.share": "{p}% 的请求(7 天)",
"routing.tasks.fromRouter": "标签实时来自路由器",
"routing.tasks.builtin": "路由器不可达 — 使用内置标签集",
"routing.tasks.addCustom": "添加任务",
"routing.tasks.addPlaceholder": "自定义标签……",
};