Curso / Lição 27  ·  EN
Lição 27 · Avançado · controle de custo

Tiers, custo & o budget guard fail-closed

Um motor que distribui uma requisição a muitos modelos precisa de dois botões ortogonais: autonomia (quanta supervisão humana um trabalho exige) e custo (quanto dinheiro uma chamada de modelo pode gastar). O Alembic codifica o primeiro como a escada de Tiers T0→T4, o segundo como um registro de modelos precificado por 1k tokens mais um BudgetGuard fail-closed. Esta lição os conecta: como um tier roteia para o modelo qualificado mais barato, como toda chamada paga é medida, e por que um orçamento não-positivo significa "só free tier" — nunca "ilimitado".

A escada de autonomia: T0 → T4 (e o marcador LOCAL)

O enum Tier tem exatamente cinco degraus, autonomia-mais-barata primeiro. Eles são sobre supervisão humana, não dinheiro:

TierSignificadoAutônomo?
T0silencioso / totalmente autônomo, sem humano no loopsim (o caminho silencioso)
T1autônomo com logging levesim
T2autônomo, um revisor notificadosim
T3autônomo, revisão de council exigidasim
T4PARK — retido da execução autônoma; precisa de council + humanonão

Dois fatos tornam isto fail-closed: DEFAULT_TIER = T4 (trabalho não classificado estaciona, Lição 26) e isAutonomous retorna true só para T1–T3 (tier.ts:59). Separadamente, LOCAL não é um sexto tier — é um marcador ortogonal marcando trabalho que deve ficar em modelos local/$0 "independente do tier" (caminhos sensíveis a privacidade ou custo, tier.ts:36). Uma unidade pode ser T2 e LOCAL.

O registro de modelos: custo por 1k tokens

Cada modelo roteável é uma entrada de registro com um adapterId, um tier e dois preços — costPer1kInputUsd e costPer1kOutputUsd. Há 11 entradas espalhadas pelos tiers (o mapa registra isso; valores representativos mostrados):

// packages/contracts/src/registry.ts — forma + linhas representativas
{ modelId: 'local-default',  adapterId: 'local',       tier: T0, in: 0,      out: 0 }       // $0 hermético
{ modelId: 'local-extract',  adapterId: 'local',       tier: T1, in: 0,      out: 0 }
{ modelId: 'qwen3.7-plus',    adapterId: 'cliproxyapi', tier: T1, in: 0.00015,out: 0.0005 }
{ modelId: 'glm-5.2',         adapterId: 'cliproxyapi', tier: T2, in: 0.0002, out: 0.0006 }  // DEFAULT_MODEL_ID
{ modelId: 'gpt-5.5-xhigh',   adapterId: 'cliproxyapi', tier: T3, in: 0.015,  out: 0.045 }
{ modelId: 'claude-opus-4-8-max', adapterId: 'cliproxyapi', tier: T3, in: 0.03, out: 0.15 }

A divisão pelos tiers (conforme o complete-map §6): T0 = local-default, local-extract ($0); T1 = kimi-k2.7-code-highspeed, grok-composer-2.5-fast, qwen3.7-plus; T2 = deepseek-v4-pro, gemini-3.5-flash, glm-5.2, qwen3.7-max; T3 = gpt-5.5-xhigh, claude-opus-4-8-max. O adapter local mantém T0 em $0 para CI hermético. (Ids/preços de modelos são dados ilustrativos do registro e evoluem; a forma é o contrato.)

Roteamento: escolha o modelo qualificado mais barato

Quando um tier é escolhido mas nenhum modelId específico está fixado, pickCheapestForTier seleciona a entrada mais barata daquele tier pelo custo por-1k combinado. É pura sobre o registro — um fold determinístico, sem efeitos colaterais:

// packages/contracts/src/registry.ts:145-156
export const pickCheapestForTier = (tier, registry = MODEL_REGISTRY) => {
  const candidates = Object.values(registry).filter((e) => e.tier === tier);
  if (candidates.length === 0) return undefined;
  return candidates.reduce((cheapest, entry) => {
    const entryCost = entry.costPer1kInputUsd + entry.costPer1kOutputUsd;   // in + out
    const bestCost = cheapest.costPer1kInputUsd + cheapest.costPer1kOutputUsd;
    return entryCost < bestCost ? entry : cheapest;
  });
};
Por que input+output combinados? Um modelo barato no input mas caro no output poderia perder numa tarefa pesada de geração. Somar ambos dá um único escalar comparável. A ADR-0006 sobrepõe um piso de profundidade: o roteador deve escolher o modelo mais barato acima de um piso de qualidade, reservando modelos frontier (o par T3) para adjudicação difícil — então "mais barato" nunca significa "fraco demais".

O budget guard: medição fail-closed

O roteamento decide qual modelo; o BudgetGuard decide se a chamada pode acontecer. Ele é criado com um teto rígido em USD e três métodos — e seus defaults são deliberadamente paranoicos:

// packages/etl/src/budget.ts:120-152 (condensado)
export const createBudgetGuard = (capUsd) => {
  const cap = Math.max(0, capUsd);   // um teto não-positivo vira 0 = só free-tier
  let spent = 0;
  return {
    check(estimate) {
      const projectedUsd = priceEstimate(estimate);
      if (projectedUsd === 0) return { ok: true, projectedUsd: 0, … };   // chamada grátis sempre passa
      if (roundUsd(spent + projectedUsd) > cap)
        return { ok: false, reason: 'budget_exceeded', … };       // estouraria ⇒ BLOQUEIA
      return { ok: true, projectedUsd, remainingUsd: remaining() };
    },
    record(spend) { spent = roundUsd(spent + costOf(spend)); return spent; },
  };
};
A jogada fail-closed: cap = Math.max(0, capUsd)

Um teto não-positivo não significa "sem limite" — ele vira 0, que significa só free tier: qualquer chamada com projectedUsd > 0 é bloqueada, porque 0 + qualquer > 0. Então o default seguro (sem orçamento definido) é a postura mais barata possível, nunca um gasto descontrolado. Chamadas free-tier (projectedUsd === 0) sempre passam, que é por que o funil inteiro pode rodar $0 e hermético no adapter LOCAL (Lição 15).

O preço é sempre aplicado na medição

O mapa declara um invariante sutil: o preço é sempre aplicado. Olhe o helper costOf do record — mesmo quando um ModelRunResult carrega um costUsd, um modelo free-tier é medido como 0 (isFreeTierModel(spend.modelId), budget.ts:110), e um modelo pago recorre a costForModel(modelId, usage) se nenhum custo explícito estiver presente. O gasto nunca é adivinhado e nunca pulado: chamadas T2/T3 são medidas ao preço do registro, então o total corrente spent é autoritativo.

pickCheapestForTierin+out, ≥ piso guard.check(est)cabe no teto? run() (cintura)só se ok guard.record()mede ao preço check falha ⇒ budget_exceeded, a chamada nunca roda
1. Você cria createBudgetGuard(0) (ou um teto negativo). O que pode rodar?
Correto: c. cap = Math.max(0, capUsd) vira 0, e check passa uma chamada só se projectedUsd === 0 ou se ela cabe no teto. Com teto 0, só chamadas grátis (LOCAL/T0) passam — fail-closed: o default não gasta nada.
2. Dois modelos T2 custam (in+out) $0.0006 e $0.0009 por 1k. Sem modelId fixado, qual pickCheapestForTier(T2) retorna, e a escolha é determinística?
Correto: b. O reduce mantém a entrada com o menor costPer1kInputUsd + costPer1kOutputUsd. É puro sobre MODEL_REGISTRY (sem Date/random), então o mesmo registro sempre dá a mesma escolha — o que importa para replay (Lição 28).
3. Um ModelRunResult de um modelo free-tier carrega um costUsd: 0.01 perdido. Como record o mede?
Correto: d. costOf (budget.ts:109-111) retorna 0 para um modelo free-tier antes de ler costUsd. O preço é sempre aplicado a partir do tier/registro do modelo, então um campo perdido não pode inflar o total de gasto — a medição é autoritativa, não consultiva.

Confusões comuns

"LOCAL é o sexto tier." Não — Tier é exatamente T0–T4. LOCAL é um marcador ortogonal que fixa trabalho em modelos $0 independente do seu tier. Uma unidade pode ser T2 e LOCAL ao mesmo tempo: autônoma-com-um-revisor, mas mantida em modelos locais por privacidade ou custo.
"Tier define o custo." São relacionados mas separados. Tier é autonomia/supervisão; custo é precificação por-modelo. Um passo de council T3 usa modelos mais caros, sim — mas o budget guard, não o tier, é o que impede uma chamada paga de exceder o teto. Dois botões, dois mecanismos.