feat: capability-based routing (v8)
Policy carries an operator-declared model_capabilities directory and per-tier requires (tool_calling/reasoning/vision booleans, min_context_tokens). decide_routing checks the policy tier model in active mode: capable applies, incapable falls back to a capable worker default, and if both fail or are unknown it returns capability_blocked, which the worker turns into a failed task (v3 tools-gate shape, runner never called). Shadow mode only annotates the reason, never blocks. Explicit task and worker CLI models bypass the check by design. Invalid requires drops only that tier (fail-closed, like invalid tier mode). Dry-run reports per-tier capability status and blocks on active tiers that fail or cannot be verified.
This commit is contained in:
1 parent
362dffba4f
commit
cd33a929ca
4 files changed
+616
-11
No files matched your search
+49
-3
@@ -19,7 +19,13 @@ What it reports, per tier (plan / goal / default):
|
||||
that tier,
|
||||
* tiers absent from the document (worker default, not routed),
|
||||
* unknown tier names in the document (ignored by the parser),
|
||||
* whether any tier would actually activate on next dispatch.
|
||||
* whether any tier would actually activate on next dispatch,
|
||||
* capability checks (v8): per tier, whether its model meets
|
||||
its requires against the policy's model_capabilities
|
||||
directory -- an active tier that fails or cannot be verified
|
||||
is a BLOCKER (it would block dispatch at the worker
|
||||
capability gate); a shadow/off tier with the same problem
|
||||
is a warning only.
|
||||
|
||||
What it does NOT do, honestly:
|
||||
* It does NOT validate model names against a provider or relay
|
||||
@@ -56,11 +62,15 @@ import sys
|
||||
try:
|
||||
from cloud.models import ROUTING_MODES, ROUTING_TIERS
|
||||
from cloud.models import parse_routing_policy
|
||||
from cloud.models import parse_tier_requires
|
||||
from cloud.models import check_model_capabilities
|
||||
except ImportError: # run as a script from the repo root
|
||||
sys.path.insert(0, os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
from cloud.models import ROUTING_MODES, ROUTING_TIERS
|
||||
from cloud.models import parse_routing_policy
|
||||
from cloud.models import parse_tier_requires
|
||||
from cloud.models import check_model_capabilities
|
||||
|
||||
|
||||
def _tier_dropped_reason(raw_entry):
|
||||
@@ -68,6 +78,9 @@ def _tier_dropped_reason(raw_entry):
|
||||
if "mode" in raw_entry and \
|
||||
raw_entry.get("mode") not in ROUTING_MODES:
|
||||
return "invalid tier mode %r" % (raw_entry.get("mode"),)
|
||||
if "requires" in raw_entry and \
|
||||
parse_tier_requires(raw_entry.get("requires")) is None:
|
||||
return "invalid tier requires %r" % (raw_entry.get("requires"),)
|
||||
return "no usable model"
|
||||
if isinstance(raw_entry, str):
|
||||
return "empty model" if not raw_entry.strip() else "unknown"
|
||||
@@ -135,6 +148,7 @@ def dry_run_policy_document(data, env_mode_override=None):
|
||||
# without mutating the parsed policy: a tier with an explicit
|
||||
# mode keeps it, a tier without one follows the overridden global.
|
||||
effective_policy_mode = global_mode
|
||||
cap_blocked = False
|
||||
for tier in ROUTING_TIERS:
|
||||
raw_entry = raw_tiers.get(tier) if isinstance(raw_tiers,
|
||||
dict) else None
|
||||
@@ -166,11 +180,43 @@ def dry_run_policy_document(data, env_mode_override=None):
|
||||
mode_source = "global_mode"
|
||||
status = "active" if mode == "active" else (
|
||||
"shadow" if mode == "shadow" else "off")
|
||||
# Capability check (v8): the same check decide_routing
|
||||
# runs, reported before activation. "not_applicable"
|
||||
# when the tier carries no requirements; "unknown" when
|
||||
# requirements exist but the model (or a needed field)
|
||||
# is absent from the directory.
|
||||
requires = policy.tier_requirements.get(tier) or {}
|
||||
if not requires:
|
||||
cap = {"required": None, "missing": [], "unknown": False,
|
||||
"status": "not_applicable"}
|
||||
else:
|
||||
cap_ok, cap_missing, cap_unknown = check_model_capabilities(
|
||||
policy.model_capabilities, policy.tiers[tier], requires)
|
||||
cap = {"required": requires, "missing": cap_missing,
|
||||
"unknown": cap_unknown,
|
||||
"status": "ok" if cap_ok else (
|
||||
"unknown" if cap_unknown else "fail")}
|
||||
tiers_report[tier] = {
|
||||
"model": policy.tiers[tier], "mode": mode,
|
||||
"mode_source": mode_source, "status": status,
|
||||
"dropped_reason": None,
|
||||
"dropped_reason": None, "capabilities": cap,
|
||||
}
|
||||
if cap["status"] in ("fail", "unknown"):
|
||||
note = ("tier %r model %r %s capability: %s" % (
|
||||
tier, policy.tiers[tier],
|
||||
"cannot verify" if cap["status"] == "unknown"
|
||||
else "lacks",
|
||||
", ".join(cap["missing"])))
|
||||
if status == "active":
|
||||
# An active tier that fails/unknowns its capability
|
||||
# check would block dispatch (worker capability
|
||||
# gate), so it blocks the dry-run too.
|
||||
cap_blocked = True
|
||||
warnings.append(note + "; applying this policy "
|
||||
"would block %r tasks at dispatch" % tier)
|
||||
else:
|
||||
warnings.append(note + "; %s mode does not block "
|
||||
"dispatch" % status)
|
||||
would_activate = [t for t in ROUTING_TIERS
|
||||
if tiers_report[t]["status"] == "active"]
|
||||
would_shadow = [t for t in ROUTING_TIERS
|
||||
@@ -193,7 +239,7 @@ def dry_run_policy_document(data, env_mode_override=None):
|
||||
"tasks on next worker dispatch if this policy were "
|
||||
"applied; this dry-run does not activate anything" %
|
||||
", ".join(would_activate))
|
||||
blocked = bool(dropped or unknown)
|
||||
blocked = bool(dropped or unknown or cap_blocked)
|
||||
return {
|
||||
"valid": True, "blocked": blocked,
|
||||
"version": policy.version, "global_mode": global_mode,
|
||||
|
||||
Reference in new issue
Block a user