400 lines
22 KiB
Python
Executable File
400 lines
22 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Seleção conservadora por papel; custos OpenRouter nunca são tarifa de cobrança."""
|
|
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
import json
|
|
from decimal import Decimal, InvalidOperation
|
|
from pathlib import Path
|
|
from typing import Any, Iterable
|
|
|
|
from model_catalog import (AA_METRIC, AA_VERSION, CACHE_DIR, decimal_text, decimal_value, discover_local_models,
|
|
load_snapshot, openrouter_price_for_profile, reference_cost)
|
|
|
|
ROLE_BANDS = {
|
|
"explorer": "leve", "verifier": "leve", "summarizer": "leve",
|
|
"implementer": "media", "ui-critic": "media",
|
|
"planner": "alta", "reviewer": "alta", "security-auditor": "alta",
|
|
}
|
|
DEFAULT_DELTA = {"leve": Decimal(3), "media": Decimal(3), "alta": Decimal(2)}
|
|
EXPECTED_HARNESSES = {"opencode": "OpenCode", "omp": "OMP", "claude": "Claude Code"}
|
|
WINDOWS = ("5h", "weekly", "monthly")
|
|
FIXED_BANDS = {
|
|
"leve": (Decimal(10), Decimal(20)),
|
|
"media": (Decimal(20), Decimal(50)),
|
|
"alta": (Decimal(50), Decimal(100)),
|
|
}
|
|
|
|
|
|
def validate_bands(bands: dict[str, Any] | None) -> dict[str, tuple[Decimal, Decimal]]:
|
|
"""Aceita a configuração declarativa apenas se preservar as faixas normativas."""
|
|
if bands is None:
|
|
return FIXED_BANDS.copy()
|
|
if not isinstance(bands, dict) or set(bands) != set(FIXED_BANDS):
|
|
raise ValueError("bands deve conter exatamente leve/media/alta")
|
|
normalized = {}
|
|
for name, limits in bands.items():
|
|
if not isinstance(limits, (list, tuple)) or len(limits) != 2:
|
|
raise ValueError(f"faixa {name} deve declarar dois limites")
|
|
low, high = (_decimal(value) for value in limits)
|
|
if low is None or high is None:
|
|
raise ValueError(f"limites da faixa {name} devem ser decimais")
|
|
normalized[name] = (low, high)
|
|
if normalized != FIXED_BANDS:
|
|
raise ValueError("bands customizadas são recusadas; use 10..20, 20..50 e 50..100")
|
|
return normalized
|
|
|
|
|
|
def _score_band(score: Decimal | None, bands: dict[str, tuple[Decimal, Decimal]]) -> str | None:
|
|
if score is None:
|
|
return None
|
|
for name in ("leve", "media"):
|
|
low, high = bands[name]
|
|
if low <= score < high:
|
|
return name
|
|
low, high = bands["alta"]
|
|
return "alta" if low <= score <= high else None
|
|
|
|
|
|
def _time(value: Any) -> dt.datetime | None:
|
|
if not isinstance(value, str) or not value:
|
|
return None
|
|
try:
|
|
result = dt.datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
except ValueError:
|
|
return None
|
|
if result.tzinfo is None:
|
|
return None
|
|
return result.astimezone(dt.timezone.utc)
|
|
|
|
|
|
def _decimal(value: Any) -> Decimal | None:
|
|
try:
|
|
return decimal_value(value)
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def _has_token_profile(tokens: dict[str, Any]) -> bool:
|
|
return any((_decimal(tokens.get(key)) or Decimal(0)) > 0 for key in ("input", "output", "cache_read", "cache_write"))
|
|
|
|
|
|
def _staleness(row: dict[str, Any], now: dt.datetime, max_price_age_hours: Decimal, max_score_age_days: Decimal,
|
|
tokens: dict[str, Any]) -> list[str]:
|
|
reasons: list[str] = []
|
|
mode = row.get("billing_mode")
|
|
paid = mode in ("zen_balance", "go_quota", "omp_payg", "claude_subscription")
|
|
price_record = row.get("openrouter") or {"price_per_token_usd": row.get("or_price_per_token") or {}}
|
|
price = openrouter_price_for_profile(price_record, tokens)
|
|
native = row.get("native_tariff") or {}
|
|
has_reference_price = any(price.get("price_per_token_usd", {}).get(k) is not None
|
|
for k in ("input", "output", "cache_read", "cache_write"))
|
|
if paid:
|
|
if has_reference_price:
|
|
price_time = _time(price.get("captured_at"))
|
|
if price_time is None or price_time > now or (now - price_time).total_seconds() > float(max_price_age_hours * Decimal(3600)):
|
|
reasons.append("preco_referencial_obsoleto_ou_ausente")
|
|
score = row.get("score") or {}
|
|
score_time = _time(score.get("observed_at"))
|
|
if score_time is None or score_time > now or (now - score_time).total_seconds() > float(max_score_age_days * Decimal(86400)):
|
|
reasons.append("benchmark_obsoleto_ou_ausente")
|
|
if mode in ("zen_balance", "omp_payg"):
|
|
native_time = _time(native.get("captured_at"))
|
|
if native.get("account_confirmed") is not True or not str(native.get("source_url", "")).startswith("https://") or native_time is None or native_time > now or now - native_time > dt.timedelta(hours=float(max_price_age_hours)):
|
|
reasons.append("tarifa_nativa_obsoleta_ou_nao_confirmada")
|
|
if mode == "go_quota":
|
|
quota_time = _time(row.get("quota_verified_at"))
|
|
if quota_time is None or quota_time > now or now - quota_time > dt.timedelta(seconds=60) or not str(row.get("quota_source_url", "")).startswith("https://"):
|
|
reasons.append("quota_go_obsoleta_ou_nao_confirmada")
|
|
availability = row.get("availability") or {}
|
|
local_time = _time(availability.get("local_verified_at") or availability.get("verified_at"))
|
|
if local_time is None or local_time > now or now - local_time > dt.timedelta(hours=24):
|
|
reasons.append("disponibilidade_local_precisa_revalidacao")
|
|
account_time = _time(availability.get("account_verified_at"))
|
|
if availability.get("authenticated") is True and availability.get("entitled") is True:
|
|
if account_time is None or account_time > now or now - account_time > dt.timedelta(hours=24):
|
|
reasons.append("entitlement_precisa_revalidacao")
|
|
if mode in ("zen_balance", "omp_payg"):
|
|
balance_time = _time(availability.get("balance_verified_at"))
|
|
if availability.get("balance_confirmed") is not True or balance_time is None or balance_time > now or now - balance_time > dt.timedelta(seconds=60):
|
|
reasons.append("saldo_nao_confirmado_ou_obsoleto")
|
|
return reasons
|
|
|
|
|
|
def _profile_cost(row: dict[str, Any], tokens: dict[str, Any]) -> Decimal | None:
|
|
record = row.get("openrouter") or {"price_per_token_usd": row.get("or_price_per_token") or {}}
|
|
profile = openrouter_price_for_profile(record, tokens)
|
|
return reference_cost(profile.get("price_per_token_usd") or {}, tokens)
|
|
|
|
|
|
def _native_cost_and_headroom(row: dict[str, Any], tokens: dict[str, Any]) -> tuple[Decimal | None, Decimal | None, str | None]:
|
|
mode = row.get("billing_mode")
|
|
native = row.get("native_tariff")
|
|
if mode in ("zen_balance", "omp_payg"):
|
|
if not _has_token_profile(tokens):
|
|
return None, None, "perfil_tokens_native_nao_configurado"
|
|
if not isinstance(native, dict) or not isinstance(native.get("price_per_token_usd"), dict) or native.get("account_confirmed") is not True:
|
|
return None, None, "tarifa_native_nao_confirmada"
|
|
amount = reference_cost(native["price_per_token_usd"], tokens)
|
|
if amount is None:
|
|
return None, None, "tarifa_native_incompleta_para_perfil"
|
|
balance = _decimal(native.get("available_balance_usd"))
|
|
availability = row.get("availability") or {}
|
|
if availability.get("balance_confirmed") is not True:
|
|
return None, None, "saldo_nao_confirmado"
|
|
if balance is None or balance < 0:
|
|
return None, None, "saldo_desconhecido"
|
|
if balance < amount:
|
|
return None, balance, "saldo_insuficiente"
|
|
return amount, balance - amount, None
|
|
if mode == "go_quota":
|
|
if not _has_token_profile(tokens):
|
|
return None, None, "perfil_tokens_go_nao_configurado"
|
|
remaining = row.get("quota_window_remaining")
|
|
debit = row.get("quota_window_debit")
|
|
if row.get("use_balance_fallback"):
|
|
return None, None, "fallback_go_para_saldo_zen_nao_autorizado"
|
|
if not isinstance(remaining, dict) or debit is None:
|
|
return None, None, "quota_go_nao_confirmada"
|
|
debit_map = debit if isinstance(debit, dict) else {window: debit for window in WINDOWS}
|
|
headrooms: list[Decimal] = []
|
|
for window in WINDOWS:
|
|
available = _decimal(remaining.get(window))
|
|
used = _decimal(debit_map.get(window))
|
|
if available is None or used is None or available < 0 or used < 0:
|
|
return None, None, f"janela_go_{window}_desconhecida"
|
|
if used > available:
|
|
return None, None, f"quota_go_{window}_insuficiente"
|
|
headrooms.append(available - used)
|
|
representative = _decimal(debit_map.get("monthly"))
|
|
if representative is None:
|
|
return None, min(headrooms), "debito_go_nao_confirmado"
|
|
return representative, min(headrooms), None
|
|
if mode == "claude_subscription":
|
|
amount = _decimal(row.get("subscription_consumption_usd"))
|
|
if amount is None:
|
|
return None, None, "consumo_assinatura_nao_mensuravel"
|
|
return amount, None, None
|
|
return None, None, "modo_de_cobranca_desconhecido"
|
|
|
|
|
|
def route(candidates: Iterable[dict[str, Any]], role: str, *, allowed_providers: Iterable[str] = (),
|
|
tokens: dict[str, Any] | None = None, budget_usd: Any = None, allow_proxy: bool = False,
|
|
delta_points: Any = None, max_price_age_hours: Any = 24, max_score_age_days: Any = 30,
|
|
now: dt.datetime | None = None, band_override: str | None = None,
|
|
bands: dict[str, Any] | None = None,
|
|
benchmark_metric: str = "aa_coding_agent_index_v1.5") -> dict[str, Any]:
|
|
"""Returns the stable API: selected, ranking, reason. Never dispatches a model."""
|
|
default_band = ROLE_BANDS.get(role)
|
|
if default_band is None:
|
|
return {"selected": None, "ranking": [], "reason": f"papel_desconhecido:{role}"}
|
|
normalized_bands = validate_bands(bands)
|
|
band = band_override or default_band
|
|
if band not in normalized_bands:
|
|
raise ValueError("band deve ser uma faixa fixa: leve, media ou alta")
|
|
if benchmark_metric != "aa_coding_agent_index_v1.5":
|
|
raise ValueError("benchmark_metric incompatível; somente aa_coding_agent_index_v1.5 é aceito")
|
|
if now is None:
|
|
now = dt.datetime.now(dt.timezone.utc)
|
|
if now.tzinfo is None:
|
|
raise ValueError("now precisa de timezone")
|
|
now = now.astimezone(dt.timezone.utc)
|
|
tokens = tokens or {}
|
|
providers = set(allowed_providers)
|
|
if not providers:
|
|
return {"selected": None, "ranking": [], "reason": "provider_nao_autorizado; informe --providers explicitamente"}
|
|
if budget_usd is None:
|
|
budget = None
|
|
else:
|
|
budget = _decimal(budget_usd)
|
|
if budget is None or budget < 0:
|
|
raise ValueError("budget_usd precisa ser decimal não negativo")
|
|
max_price_age = _decimal(max_price_age_hours)
|
|
max_score_age = _decimal(max_score_age_days)
|
|
if max_price_age is None or max_score_age is None:
|
|
raise ValueError("TTL precisa ser decimal não negativo")
|
|
delta = _decimal(delta_points) if delta_points is not None else DEFAULT_DELTA[band]
|
|
if delta is None or delta < 0 or max_price_age < 0 or max_score_age < 0:
|
|
raise ValueError("delta e TTL devem ser decimais não negativos")
|
|
|
|
ranking = []
|
|
eligible = []
|
|
reasons_seen: list[str] = []
|
|
for row in candidates:
|
|
if not isinstance(row, dict):
|
|
continue
|
|
provider = row.get("provider")
|
|
score = row.get("score") or {}
|
|
parsed_score = _decimal(score.get("score"))
|
|
row_band = _score_band(parsed_score, normalized_bands)
|
|
reference = _profile_cost(row, tokens)
|
|
native_cost, headroom, native_error = _native_cost_and_headroom(row, tokens)
|
|
excluded: list[str] = []
|
|
availability = row.get("availability") or {}
|
|
if provider not in providers:
|
|
excluded.append("provider_nao_autorizado")
|
|
if availability.get("local_listed") is not True:
|
|
excluded.append("nao_listado_localmente")
|
|
if availability.get("authenticated") is not True:
|
|
excluded.append("autenticacao_nao_confirmada")
|
|
if availability.get("entitled") is not True:
|
|
excluded.append("entitlement_nao_confirmado")
|
|
required_provenance = all(score.get(field) not in (None, "", {}) for field in ("agent", "harness", "settings", "source_url", "observed_at"))
|
|
if not required_provenance or not str(score.get("source_url", "")).startswith("https://artificialanalysis.ai/"):
|
|
excluded.append("proveniencia_do_benchmark_incompleta")
|
|
if score.get("variant") != row.get("variant"):
|
|
excluded.append("mapeamento_de_variante_incompativel")
|
|
expected_harness = EXPECTED_HARNESSES.get(row.get("tool"))
|
|
if score.get("match_grade") == "exact" and score.get("harness") != expected_harness:
|
|
excluded.append("harness_incompativel")
|
|
if score.get("metric_id") != AA_METRIC or str(score.get("version")) != AA_VERSION:
|
|
excluded.append("indice_incompativel_ou_ausente")
|
|
if parsed_score is None:
|
|
excluded.append("no_scored_candidate")
|
|
if score.get("match_grade") not in (("exact", "proxy") if allow_proxy else ("exact",)):
|
|
excluded.append("match_proxy_nao_autorizado" if score.get("match_grade") == "proxy" else "mapeamento_de_variante_incompativel")
|
|
if row_band != band:
|
|
excluded.append("fora_da_faixa" if row_band else "sem_faixa_de_nota")
|
|
excluded.extend(_staleness(row, now, max_price_age, max_score_age, tokens))
|
|
if native_error:
|
|
excluded.append(native_error)
|
|
if row.get("billing_mode") in ("zen_balance", "omp_payg") and budget is None:
|
|
excluded.append("orcamento_zen_nao_configurado" if row.get("billing_mode") == "zen_balance" else "orcamento_payg_omp_nao_configurado")
|
|
if native_cost is not None and budget is not None and native_cost > budget:
|
|
excluded.append("orcamento_excedido")
|
|
if row.get("billing_mode") == "claude_subscription" and native_cost is None:
|
|
excluded.append("consumo_assinatura_nao_mensuravel")
|
|
if row.get("billing_mode") == "go_quota" and headroom is None and native_error is None:
|
|
excluded.append("cota_go_nao_confirmada")
|
|
key = row.get("model_key") or f"{provider}/{row.get('model_id', '')}"
|
|
entry = {
|
|
"model_key": key, "provider": provider, "billing_mode": row.get("billing_mode"),
|
|
"score": decimal_text(parsed_score), "band": row_band, "match_grade": score.get("match_grade"),
|
|
"eligible": not excluded, "exclusion_reasons": excluded,
|
|
"reference_cost_usd": decimal_text(reference), "native_cost_or_quota_debit_usd": decimal_text(native_cost),
|
|
"quota_headroom_usd": decimal_text(headroom),
|
|
}
|
|
ranking.append(entry)
|
|
if not excluded:
|
|
eligible.append((row, entry, parsed_score, native_cost, headroom))
|
|
else:
|
|
reasons_seen.extend(excluded)
|
|
ranking.sort(key=lambda entry: (
|
|
not entry["eligible"],
|
|
_decimal(entry.get("native_cost_or_quota_debit_usd")) if _decimal(entry.get("native_cost_or_quota_debit_usd")) is not None else Decimal("Infinity"),
|
|
-(_decimal(entry.get("score")) or Decimal(0)),
|
|
-(_decimal(entry.get("quota_headroom_usd")) or Decimal(0)),
|
|
entry["model_key"],
|
|
))
|
|
if not ranking:
|
|
return {"selected": None, "ranking": [], "reason": "no_scored_candidate"}
|
|
|
|
if not eligible:
|
|
if "no_scored_candidate" in reasons_seen or "indice_incompativel_ou_ausente" in reasons_seen:
|
|
reason = "no_scored_candidate_or_compatible_index"
|
|
elif "fora_da_faixa" in reasons_seen or "sem_faixa_de_nota" in reasons_seen:
|
|
reason = f"faixa_vazia:{band}"
|
|
elif "quota_go_5h_insuficiente" in reasons_seen or "quota_go_weekly_insuficiente" in reasons_seen or "quota_go_monthly_insuficiente" in reasons_seen:
|
|
reason = "quota_go_insuficiente; execução pausada antes da chamada"
|
|
elif "fallback_go_para_saldo_zen_nao_autorizado" in reasons_seen:
|
|
reason = "fallback_go_para_saldo_zen_nao_autorizado"
|
|
else:
|
|
reason = "nenhum_candidato_elegivel:" + ",".join(sorted(set(reasons_seen)))
|
|
return {"selected": None, "ranking": ranking, "reason": reason}
|
|
|
|
top_score = max(item[2] for item in eligible)
|
|
plateau = [item for item in eligible if item[2] >= top_score - delta]
|
|
# custo real da ferramenta; desempate: score, folga de quota, ID lexical.
|
|
plateau.sort(key=lambda item: (item[3], -item[2], -(item[4] if item[4] is not None else Decimal(0)), item[1]["model_key"]))
|
|
chosen = plateau[0]
|
|
row, entry, score_value, native_cost, headroom = chosen
|
|
selected = {
|
|
"model_key": entry["model_key"],
|
|
"billing_mode": {"zen_balance": "zen", "go_quota": "go", "omp_payg": "payg", "claude_subscription": "subscription"}.get(row.get("billing_mode"), row.get("billing_mode")),
|
|
"billing_mode_detail": row.get("billing_mode"), "score": entry["score"],
|
|
"band": band, "match_grade": entry["match_grade"],
|
|
"reference_cost_usd": entry["reference_cost_usd"],
|
|
"native_cost_or_quota_debit_usd": decimal_text(native_cost),
|
|
"estimated_native_usd": decimal_text(native_cost) if row.get("billing_mode") in ("zen_balance", "omp_payg") else None,
|
|
"native_tariff": row.get("native_tariff"),
|
|
"available_balance_usd": (row.get("native_tariff") or {}).get("available_balance_usd"),
|
|
"balance_confirmed": (row.get("availability") or {}).get("balance_confirmed") is True,
|
|
"balance_verified_at": (row.get("availability") or {}).get("balance_verified_at"),
|
|
"quota_window_remaining": row.get("quota_window_remaining"),
|
|
"quota_window_debit": row.get("quota_window_debit"),
|
|
"use_balance_fallback": row.get("use_balance_fallback") if row.get("billing_mode") == "go_quota" else False,
|
|
"quota_headroom_usd": decimal_text(headroom),
|
|
"score_top": decimal_text(top_score), "plateau_delta_points": decimal_text(delta),
|
|
}
|
|
if row.get("tool") == "omp":
|
|
variant = row.get("variant")
|
|
selected["dispatch_model_key"] = f"{row['provider']}/{row['model_id']}" + (f":{variant}" if variant else "")
|
|
reason = f"menor consumo relevante dentro do platô {band} (score >= {decimal_text(top_score - delta)}); custo OpenRouter é apenas referência"
|
|
if len(plateau) > 1:
|
|
reason += "; desempate determinístico por consumo, score, folga de cota e model_key"
|
|
return {"selected": selected, "ranking": ranking, "reason": reason}
|
|
|
|
|
|
def route_from_catalog(tool: str, role: str, allowed_providers: Iterable[str], cache_dir: str | Path | None = None,
|
|
*, explain: bool = False, tokens: dict[str, Any] | None = None, budget_usd: Any = None,
|
|
allow_proxy: bool = False, delta_points: Any = None, max_price_age_hours: Any = 24,
|
|
max_score_age_days: Any = 30, band_override: str | None = None,
|
|
bands: dict[str, Any] | None = None,
|
|
benchmark_metric: str = "aa_coding_agent_index_v1.5",
|
|
now: dt.datetime | None = None,
|
|
local_discoverer=discover_local_models, omp_discoverer=None) -> dict[str, Any]:
|
|
from model_catalog import discover_omp_models
|
|
clock = now or dt.datetime.now(dt.timezone.utc)
|
|
snapshot = load_snapshot(tool, cache_dir)
|
|
if snapshot is None:
|
|
return {"selected": None, "ranking": [], "reason": "snapshot_de_catalogo_ausente; execute models sync"}
|
|
if tool not in ("opencode", "omp"):
|
|
return {"selected": None, "ranking": [], "reason": f"catalogo_local_nao_disponivel_para_{tool}"}
|
|
providers = tuple(allowed_providers)
|
|
if not providers:
|
|
return route(snapshot.get("models", []), role, allowed_providers=providers, tokens=tokens, budget_usd=budget_usd,
|
|
allow_proxy=allow_proxy, delta_points=delta_points, max_price_age_hours=max_price_age_hours,
|
|
max_score_age_days=max_score_age_days, band_override=band_override, bands=bands,
|
|
benchmark_metric=benchmark_metric, now=clock)
|
|
discoverer = local_discoverer if tool == "opencode" else (omp_discoverer or discover_omp_models)
|
|
discoveries = {provider: discoverer(provider) for provider in sorted(set(providers))}
|
|
verified_at = clock.astimezone(dt.timezone.utc).isoformat().replace("+00:00", "Z")
|
|
candidates = []
|
|
for original in snapshot.get("models", []):
|
|
row = dict(original)
|
|
availability = dict(row.get("availability") or {})
|
|
provider = row.get("provider")
|
|
result = discoveries.get(provider)
|
|
if result is not None and result.get("ok"):
|
|
local_ids = set(result.get("models", []))
|
|
local_model_key = row["model_key"] if tool == "omp" else row["model_id"] + (f"#{row['variant']}" if row.get("variant") else "")
|
|
availability["local_listed"] = local_model_key in local_ids
|
|
availability["local_verified_at"] = result.get("verified_at", verified_at)
|
|
elif provider in discoveries:
|
|
availability["local_listed"] = None
|
|
availability["local_verified_at"] = None
|
|
row["availability"] = availability
|
|
candidates.append(row)
|
|
return route(candidates, role, allowed_providers=providers, tokens=tokens, budget_usd=budget_usd,
|
|
allow_proxy=allow_proxy, delta_points=delta_points, max_price_age_hours=max_price_age_hours,
|
|
max_score_age_days=max_score_age_days, band_override=band_override, bands=bands,
|
|
benchmark_metric=benchmark_metric, now=clock)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
import argparse
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
parser.add_argument("--tool", choices=("opencode", "omp", "claude"), default="opencode")
|
|
parser.add_argument("--role", required=True)
|
|
parser.add_argument("--providers", default="")
|
|
parser.add_argument("--format", choices=("json",), default="json")
|
|
parser.add_argument("--cache-dir")
|
|
args = parser.parse_args(argv)
|
|
result = route_from_catalog(args.tool, args.role, [p for p in args.providers.split(",") if p], args.cache_dir, explain=True)
|
|
print(json.dumps(result, ensure_ascii=False))
|
|
return 0 if result["selected"] is not None else 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|