Pular para o conteúdo

LLM gateway, não só API gateway: a política que decide quem atende cada request

LLM gateway não é só API gateway com outro nome. A política de roteamento com gatilho, trava de fallback e teto de gasto por classe.

Avatar de DK
DKTrabalha com Linux e Unix a mais de 23 anos e possui as certificações LPI 3, RHCE, AIX e VIO.

06 ago, 2026
14 min de leitura

Trocar de provedor por preço é o argumento de venda de todo LLM gateway. É também o motivo mais fraco pra instalar um. API gateway roteia por rota. LLM gateway roteia por modelo, e modelo não é peça intercambiável: trocar o destino troca a resposta junto.

O ganho real não está na portabilidade. Está em ter um único lugar onde teto de gasto, fallback e atribuição viram política versionada, fora do código da aplicação. O artefato pra levar pra equipe é essa política de roteamento, com a coluna que nenhum tutorial traz: o tráfego que nunca faz fallback.

O que a analogia entrega de graça

O API gateway resolveu um problema conhecido. Um endpoint na frente de várias upstreams. Auth centralizada, com a credencial do backend fora do código do cliente. Rate limit, retry, log e métrica num ponto só.

O LLM gateway herda tudo isso intacto. A chave da OpenAI sai do .env de cada serviço e vira uma chave virtual por time. O rate limit deixa de ser uma constante espalhada em três repositórios. O log de request vira base de custo, que é o assunto do artigo de atribuição. Até aqui, nada novo sob o sol.

O interessante começa onde a analogia quebra. São cinco pontos, e é neles que mora a decisão de projeto.

Ruptura 1: upstream não é peça intercambiável

No API gateway, duas instâncias atrás do mesmo pool respondem a mesma coisa. Se não respondem, é bug. Em LLM gateway, a diferença entre dois destinos é o produto.

Dois modelos diferentes devolvem respostas diferentes pro mesmo prompt. Isso é óbvio. O que pega gente desprevenida é a variação dentro do mesmo nome de modelo. A doc de provider routing do OpenRouter expõe o campo quantizations. O aviso que acompanha o campo é literal, em tradução nossa. Modelos quantizados podem apresentar performance degradada pra certos prompts, dependendo do método usado. Nem todo par de provedores difere na precisão numérica. O ponto é que pode diferir, e o mesmo slug de modelo não te avisa.

O mesmo doc traz require_parameters, um booleano que restringe o roteamento a provedores que suportam todos os parâmetros do seu request. Ele existe porque o default não faz isso. Sem ele, um provedor que ignora o seu parâmetro ainda é candidato válido.

Ruptura 2: falha de LLM não vem só como 5xx

Circuit breaker de HTTP tem vida fácil. Status code diz se deu errado. Em LLM, o erro se distribui em famílias que pedem tratamento diferente, e a doc de fallbacks do LiteLLM reconhece isso na própria API. Existem três listas separadas.

Chave de config Dispara quando
fallbacks erro genérico, cobre 429, 500 e afins
context_window_fallbacks o prompt estourou a janela do modelo
content_policy_fallbacks o provedor barrou por política de conteúdo

A distinção não é decorativa. Prompt que estourou janela precisa de modelo com janela maior, não de outro provedor. Bloqueio por política de conteúdo pode significar que o request está certo e o filtro é que é rígido demais. Ou o oposto, e aí o fallback vira contorno de guardrail.

E fica de fora dessa lista a falha mais cara de todas. O modelo responde 200, em JSON válido, com conteúdo errado. Nenhum gatilho de fallback pega isso. Health check de gateway confirma que o provedor está de pé, não que ele está certo.

Ruptura 3: fallback custa dinheiro, não só milissegundo

Retry em API gateway custa CPU e latência. Aqui custa fatura. A doc de fallbacks do Portkey avisa em três linhas secas, na seção de considerações. Um único request pode invocar múltiplos LLMs. Cada LLM tem latência e preço diferentes.

Some retry com fallback e a conta cresce rápido. No LiteLLM, num_retries roda no modelo primário antes de a cadeia de fallback começar. Três destinos na cadeia, três tentativas em cada, e um único request chega a nove chamadas ao provedor.

Nem toda tentativa vira fatura, e vale separar os casos. Request barrado por 429 não gera token nenhum. O que cobra é a tentativa que completa a geração e é descartada, por timeout do cliente ou por resposta rejeitada depois. E cobra o degrau final da cadeia, quando o destino do fallback é um modelo mais caro que o primário. O pior dia do provedor primário vira o seu pior dia de fatura por esse caminho.

O antídoto é o teto que já discutimos no artigo do token budget. Cadeia de fallback sem teto de gasto é um amplificador de incidente.

Ruptura 4: normalizar é achatar

Todo gateway promete API compatível com OpenAI. A tradução funciona pro caminho comum e vaza nas bordas. O LiteLLM documenta o dilema numa flag só, o drop_params.

O comportamento padrão está na doc. Por padrão o LiteLLM levanta exceção se você manda um parâmetro que o modelo não suporta. Com drop_params=True, ele descarta o parâmetro em vez de levantar erro. A doc explica a razão. Assim o seu código roda em vários provedores sem customização por provedor.

Repare no que a flag decide. Ligada, o request passa e o parâmetro some. Você pediu temperature=0 e recebeu uma resposta que não honra isso, sem nenhum aviso. Desligada, você tem erro explícito e alguém precisa tratar a diferença.

Existe meio-termo, e ele mora no config. O additional_drop_params aceita uma lista de parâmetros pra descartar, por deployment, inclusive campos aninhados. A decisão sai do código da aplicação e vira linha declarada no gateway. O que não muda é o silêncio. Parâmetro descartado não vira aviso na resposta, então a lista de descarte precisa de review como qualquer regra de negócio.

Ruptura 5: balancear por preço briga com o cache do prompt

Prompt caching é desconto por prefixo repetido no mesmo provedor. O cache não atravessa provedor. Um roteador que escolhe o mais barato a cada request espalha o tráfego, e prefixo espalhado não acumula releitura.

O default do OpenRouter é esse balanceamento. A doc descreve a mecânica. Provedores estáveis nos últimos 30 segundos entram no sorteio, com peso pelo inverso do quadrado do preço. A tensão é tão real que virou feature dedicada. Quando o request usa caching, o OpenRouter aplica provider sticky routing. Ele lembra qual provedor atendeu e manda os requests seguintes do mesmo modelo pro mesmo endpoint, pra manter o cache quente. A doc registra o gatilho. O sticky só ativa quando a leitura de cache do provedor sai mais barata que o preço normal de prompt.

Guarde a lição, não o nome da feature. Roteamento por preço e cache de prefixo puxam pra lados opostos. LLM gateway hospedado já resolve isso por você. LLM gateway próprio não resolve sozinho, e a mecânica de afinidade passa a ser código seu.

O custo do hop

Um proxy a mais no caminho preocupa. O projeto LiteLLM publica os próprios benchmarks e declara 8ms de latência P95 a 1k RPS. Duas ressalvas vêm da mesma página. O teste roda contra um endpoint OpenAI falso, então o número mede overhead de proxy, não chamada real. E o claim aponta pro cenário de quatro instâncias, não pra uma caixa solitária.

Compare com a ordem de grandeza do outro lado. Time-to-first-token de modelo grande vive na casa das centenas de milissegundos. Oito milissegundos de overhead não é o seu problema de latência. Número de vendor merece ceticismo, e a régua certa é medir no seu tráfego. Ainda assim, a conclusão de projeto se sustenta. O gateway se paga em controle muito antes de aparecer no P95 do usuário.

O artefato: a política de roteamento

Config de LLM gateway espalhada em YAML não é política. Política é a tabela que o time lê e discute antes de virar YAML. Esta é uma proposta nossa, e cada campo carrega obrigatoriedade explícita.

Campo Obrigatório Definição operacional
classe de tráfego sim fatia do tráfego que a linha governa, nomeada por feature
modelo primário sim quem atende no caminho feliz
gatilho de fallback sim lista fechada de erros que autorizam a troca
destino do fallback sim quem atende quando o gatilho dispara
trava de fallback sim condição em que nenhuma troca é permitida
teto de gasto sim limite da classe na janela declarada
tag de atribuição sim rótulo que liga o gasto a um dono
tentativas no primário não quantas vezes insistir antes de descer a cadeia

A trava é o campo que muda a conversa. Fallback é bom pra tráfego onde qualquer resposta razoável serve. É péssimo pra tráfego onde a forma da resposta é contrato. Exemplo ilustrativo, com quatro classes de um produto de atendimento:

Classe Primário Gatilho Destino Trava Teto Tag
chat de atendimento Claude Sonnet 429, 5xx, timeout 20s GPT flagship nunca por content policy $200/24h feature:chat
extração de JSON GPT flagship 429, 5xx mesmo modelo, outra região nunca muda de família $50/24h feature:extracao
classificação em lote Gemini Flash 429, 5xx, janela estourada modelo de janela maior nenhuma $30/24h feature:batch
ação que move dinheiro Claude Sonnet nenhum nenhum sempre, falha alto $20/24h feature:pagamento

Leia a última linha de novo. Ação que cobra alguém não faz fallback. Um segundo modelo tentando a mesma chamada de ferramenta é retry disfarçado, e retry de cobrança sem chave de idempotência é cobrança dobrada. A idempotência de ação de agente ganha artigo próprio mais à frente na série.

A linha de extração também merece atenção. O fallback existe, mas amarrado à mesma família de modelo. Trocar de família num caminho que depende de schema válido troca um erro de disponibilidade por um erro de formato, que é mais difícil de detectar.

A política em config

A tabela vira YAML sem mistério. Duas classes, em config de LiteLLM, com comentário no que importa:

model_list:
  # classe: extracao. Fallback so entre regioes da mesma familia.
  - model_name: extracao
    litellm_params:
      model: openai/gpt-5.6
      api_key: os.environ/OPENAI_API_KEY
  - model_name: extracao-backup
    litellm_params:
      model: azure/nome-do-deployment
      api_base: os.environ/AZURE_API_BASE
      api_key: os.environ/AZURE_API_KEY

  # classe: pagamento. Sem backup declarado, por decisao de projeto.
  - model_name: pagamento
    litellm_params:
      model: anthropic/claude-sonnet-5
      api_key: os.environ/ANTHROPIC_API_KEY

router_settings:
  num_retries: 2
  fallbacks: [{"extracao": ["extracao-backup"]}]
  allowed_fails: 3
  cooldown_time: 30

litellm_settings:
  request_timeout: 20

Duas notas de leitura. A entrada Azure precisa de api_base, e o nome depois de azure/ é o deployment criado na sua instância, não o slug público do modelo. E allowed_fails com cooldown_time estão escritos de propósito. O default do router é 3 falhas por minuto e cooldown de 5 segundos. Cooldown curto devolve tráfego rápido demais a um provedor que ainda está mal. Escrever o número no config é mais barato que descobrir o default num incidente.

Falta a parte incômoda. O grupo pagamento não aparece em nenhuma lista de fallback, e isso sozinho não é trava. O num_retries: 2 é do router e alcança todo grupo, então a chamada ainda repete duas vezes. O LiteLLM também aceita fallbacks no corpo do request, o que devolve a decisão pro cliente. Trava de verdade se fecha em três pontos. Uma chave virtual dedicada pra classe, num_retries zerado nessa rota e rejeição de fallback vindo do cliente. Config sozinha declara intenção. A garantia exige fechar as três portas.

O teto de gasto mora em outro lugar, na chave virtual. O LiteLLM aceita janelas múltiplas na mesma chave, por exemplo $10 por 24h e $100 por 30 dias, cada janela com reset próprio. É o mecanismo direto pro campo de teto da nossa tabela.

Fechar o loop: da política ao relatório

Config sem leitura de volta é fé. O loop fecha em quatro passos.

  1. Request entra no LLM gateway com a tag da classe.
  2. Política escolhe o destino, aplica retry, cai pro fallback se o gatilho disparar.
  3. Resposta volta com o header x-litellm-model-id, que identifica o deployment que de fato atendeu. O valor é o ID do deployment, então declare model_info.id se quiser um nome legível no log.
  4. Log de spend guarda custo, chave, time e a metadata que você anexou ao request.

Com esses quatro passos gravados, uma pergunta nova fica disponível. Qual a taxa de fallback por classe de tráfego? É a métrica que quase ninguém coleta, e ela destrava duas decisões concretas. Classe com fallback acima de alguns por cento tem primário errado, ou teto de retry frouxo. Classe com fallback em zero há meses tem um destino de backup que nunca foi exercitado, e backup não testado não é backup.

O degrau pago

Gateway open source tem fronteira comercial, e ela merece leitura antes do desenho. No LiteLLM, a doc marca “Enterprise” no roteamento por tag de time e na geração de relatórios de spend. Outras peças que a demo mostra podem ter marcação parecida em outras páginas. A recomendação prática é conferir o rótulo de cada recurso do seu desenho na doc, um por um, antes de escrever o config.

O impacto disso é de arquitetura, não de licença. Suponha que a sua política seja por time, e que o roteamento por tag de time esteja atrás do plano pago. O caminho alternativo é uma chave virtual por classe de tráfego. Fica igualmente funcional, com mais chaves pra administrar.

Do lado hospedado, a conta muda de forma. A FAQ do OpenRouter declara repasse do preço do provedor, com a mesma tabela que você pagaria direto, mais o pooling de uptime. A cobrança aparece na compra de crédito, e a FAQ registra que taxas de plataforma não são reembolsáveis. Na Cloudflare o desenho é outro. A página de preços diz que os recursos centrais de hoje são gratuitos, e lista analytics, caching e rate limiting entre eles. A cobrança aparece no Unified Billing, com taxa de 5% sobre o crédito comprado e inferência repassada sem markup. O dynamic routing entrega os nós de condicional, percentual, rate limit e budget, com versões e rollback. Modelos de cobrança diferentes, e nenhum deles cobra por onde você espera.

Fechando

O LLM gateway não é infra neutra na frente do provedor. Ele é onde o produto decide quem atende cada request, e trocar quem atende troca a resposta. Quem instala um só pra perseguir preço leva a portabilidade e deixa o resto na mesa. O valor está na política. Comece pela tabela de campos obrigatórios, preencha a trava antes do gatilho e meça a taxa de fallback por classe. Quando o provedor primário tiver o dia ruim dele, a diferença entre um incidente e um susto vai estar escrita nesse YAML.

Fontes

Avatar de DK

Comentários

Deixe um comentário

Seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Ir para