Blog

Observabilidade de LLM: tracing, custo e qualidade

Sistema com LLM em producao sem observabilidade e uma caixa preta que voce paga sem entender. A resposta piorou? Nao sabe dizer. A fatura triplicou? Nao sabe qual rota. O bot ficou lento? Nao sabe se e a fila, o modelo ou a tool. Diferente de um servico tradicional, onde bastam latencia e taxa de erro, um LLM tem tres eixos que precisam ser observados juntos: latencia (quanto demora), custo (quanto gasta em tokens) e qualidade (se a resposta presta). Observar so um deles engana: um modelo mais barato pode alucinar mais, um mais rapido pode custar o dobro. Este artigo mostra como instrumentar as tres dimensoes sem virar um projeto de plataforma: o modelo de tracing certo para LLM, como calcular custo por chamada de verdade, como medir qualidade em producao sem gabarito, o que logar sem vazar dado sensivel e quais alertas evitam a surpresa no fim do mes. O foco e o minimo que torna o sistema operavel.

2026-07-08 / IA Aplicada / 13 min

01

Por que os tres pilares tradicionais nao bastam

A observabilidade classica se apoia em logs, metricas e traces, e mira latencia, throughput e taxa de erro. Isso responde "o servico esta de pe?", mas nao responde nenhuma das perguntas que importam num sistema com LLM. Uma chamada pode retornar 200 OK, dentro do SLA de latencia, e ainda assim ter alucinado a resposta, recusado indevidamente ou gasto tres vezes mais tokens do que o esperado. O sucesso HTTP nao diz nada sobre o sucesso semantico.

Um LLM precisa de tres eixos observados em conjunto, porque eles se movem em direcoes opostas. Trocar de modelo para reduzir custo pode derrubar a qualidade. Encurtar o prompt para reduzir latencia pode remover contexto e aumentar o retrabalho. Cada decisao mexe nos tres ao mesmo tempo, e sem medir os tres voce otimiza um numero e degrada outro sem perceber. A tabela abaixo mostra o que cada eixo exige que a observabilidade tradicional nao entrega.

EixoO que medeSinal que importaPor que APM classico nao pega
LatenciaTempo por fase: fila, prompt, modelo, tools, streamingPercentil p95 por rota, nao a mediaNao separa tempo de modelo de tempo de tool
CustoTokens de entrada e saida por chamada, convertidos em moedaCusto por rota e por usuario, tendencia diariaNao existe o conceito de token no APM padrao
QualidadeA resposta esta correta, util e no formato esperadoTaxa de alucinacao, recusa, formato invalidoHTTP 200 nao significa resposta boa

A regra pratica: nunca olhe um eixo isolado. Um dashboard de LLM util mostra latencia, custo e qualidade lado a lado por rota, para que qualquer mudanca revele o trade-off imediatamente. Melhorar a media sem olhar o p95, ou baixar custo sem olhar qualidade, e trocar um problema visivel por um invisivel.

02

O modelo de tracing certo para LLM

O tracing distribuido resolve a pergunta "onde o tempo foi gasto?", e num sistema com LLM a resposta quase nunca e obvia. Uma unica requisicao do usuario pode disparar um retrieval, uma ou mais chamadas ao modelo, varias tools e um passo de pos-processamento. Sem span por fase, tudo vira um bloco unico de latencia e voce nao sabe se o gargalo e o modelo, a busca vetorial ou a API externa que a tool chamou. O modelo mental e o mesmo do tracing de microservico, mas os atributos do span sao especificos de LLM.

// observability/trace.js
// Envelopa uma chamada de LLM em um span com atributos especificos:
// tokens, custo, modelo e fase. O span vira a unidade que voce agrega depois.

export async function tracedCompletion(tracer, { route, model }, call) {
  const span = tracer.startSpan('llm.completion', {
    attributes: { route, model },
  });

  try {
    const res = await call(); // chama o provedor (ex.: Anthropic)

    // Atributos que tornam o span util para custo e qualidade,
    // nao so para latencia.
    span.setAttributes({
      'llm.tokens.input': res.usage.input_tokens,
      'llm.tokens.output': res.usage.output_tokens,
      'llm.cost.usd': estimateCost(model, res.usage),
      'llm.finish_reason': res.stop_reason, // end_turn, max_tokens, tool_use...
      'llm.cache.read': res.usage.cache_read_input_tokens ?? 0,
    });
    span.setStatus({ code: 'OK' });
    return res;
  } catch (err) {
    // Erro do provedor tambem e um sinal de qualidade e custo.
    span.recordException(err);
    span.setStatus({ code: 'ERROR', message: err.message });
    throw err;
  } finally {
    span.end(); // fecha o span mesmo em erro, senao o trace fica quebrado
  }
}

O detalhe que a maioria esquece: registrar o finish_reason. Um pico de respostas terminando em max_tokens indica prompt ou saida mal dimensionados, custo desnecessario e resposta cortada, tudo invisivel se voce so olha latencia. E propagar um traceId unico do inicio da requisicao ate a resposta final amarra retrieval, modelo e tools no mesmo trace, permitindo abrir uma reclamacao de usuario e ver a arvore inteira daquela conversa.

03

Custo: o eixo que ninguem mede ate a fatura chegar

Custo de LLM e a metrica mais facil de ignorar e a mais cara de ignorar. Ele nao aparece no APM, nao dispara erro, e cresce silenciosamente ate a fatura do fim do mes. A base e simples: cada chamada tem tokens de entrada e de saida, cada um com um preco por milhao de tokens que difere por modelo. O erro comum e medir custo agregado da conta inteira, quando o que importa e custo por rota e por usuario, porque e ali que voce descobre qual funcionalidade esta cara e qual usuario esta abusando.

// observability/cost.js
// Calcula o custo de uma chamada a partir dos tokens e da tabela de precos.
// Precos sao por milhao de tokens; entrada e saida tem valores distintos.
// Tokens lidos do cache custam uma fracao do preco de entrada.

const PRICING = {
  // valores ilustrativos, por milhao de tokens (input, output, cacheRead)
  'fast':     { input: 0.8,  output: 4.0,  cacheRead: 0.08 },
  'balanced': { input: 3.0,  output: 15.0, cacheRead: 0.30 },
};

export function estimateCost(model, usage) {
  const p = PRICING[model];
  if (!p) return 0; // modelo desconhecido: nao chuta, sinaliza depois

  const input = usage.input_tokens ?? 0;
  const output = usage.output_tokens ?? 0;
  const cached = usage.cache_read_input_tokens ?? 0;

  // Tokens cacheados nao pagam preco de entrada cheio: contam a parte.
  const billableInput = Math.max(input - cached, 0);

  const usd =
    (billableInput / 1_000_000) * p.input +
    (cached / 1_000_000) * p.cacheRead +
    (output / 1_000_000) * p.output;

  return Number(usd.toFixed(6)); // precisao suficiente para somar milhares
}

Com o custo por chamada calculado e anexado ao span, o resto e agregacao: some por rota para achar a funcionalidade cara, por usuario para achar abuso, por dia para ver a tendencia. O prompt cache e o maior alavanca de custo em prompts longos e repetidos (system prompt fixo, contexto reaproveitado), e so aparece se voce medir os tokens de cache separado. Sem observar custo por dimensao, a unica alavanca que sobra e cortar features depois do susto.

04

Qualidade em producao: medir sem gabarito

Qualidade e o eixo mais dificil, porque em producao voce raramente tem a resposta certa para comparar. Diferente do eval offline, onde existe um dataset com gabarito, em producao a resposta acabou de ser gerada e ninguem sabe se esta correta. A saida e medir sinais indiretos de qualidade que nao precisam de gabarito, combinados: nenhum e definitivo sozinho, mas juntos desenham um retrato confiavel de degradacao.

  • Validacao de formato: se a resposta deveria ser JSON com um schema, valide e conte quantas falham. Taxa de formato invalido subindo e degradacao mensuravel sem gabarito.
  • Taxa de recusa: quantas respostas foram "nao posso ajudar" ou similar. Recusa subindo pode ser prompt quebrado, guardrail agressivo ou mudanca de modelo.
  • finish_reason por max_tokens: respostas cortadas por limite de saida sao qualidade degradada e custo desperdicado ao mesmo tempo.
  • Sinais do usuario: retentativa na mesma sessao, reformulacao da pergunta, thumbs down, escalada para humano. Sao o eval humano de graca, se voce os captura.
  • LLM como juiz amostrado: rode um modelo avaliador sobre uma amostra do trafego real (1 a 5 por cento), pontuando fidelidade e utilidade, para ter um numero continuo de qualidade sem avaliar tudo.

A tecnica que mais rende e o LLM como juiz amostrado: voce nao avalia cem por cento do trafego (caro e lento), avalia uma fatia representativa e trata o resultado como uma metrica de qualidade que sobe e desce ao longo do tempo. Combinado com validacao de formato e sinais do usuario, isso transforma qualidade de "acho que piorou" em uma linha no dashboard que dispara alerta quando cai. Nenhum sinal e perfeito, mas o conjunto e o suficiente para saber que algo mudou antes do cliente reclamar.

05

O que logar sem vazar dado sensivel

Logar prompt e resposta e o que torna a depuracao possivel: sem ver o que entrou e o que saiu, todo bug de qualidade vira adivinhacao. Mas prompt de producao carrega dado do usuario (nome, telefone, documento, historico), e jogar isso cru no log e um incidente de privacidade esperando para acontecer. O equilibrio e logar o suficiente para depurar, com redacao de dados sensiveis antes de persistir, e retencao curta para o conteudo bruto.

// observability/redact.js
// Redige dados sensiveis do prompt/resposta ANTES de logar.
// Mantem o texto legivel para depurar, sem persistir PII crua.

const PATTERNS = [
  { name: 'email', re: /[\w.+-]+@[\w-]+\.[\w.-]+/g, tag: '[EMAIL]' },
  { name: 'phone', re: /\b\d{2}[\s-]?\d{4,5}[\s-]?\d{4}\b/g, tag: '[PHONE]' },
  { name: 'cpf',   re: /\b\d{3}\.?\d{3}\.?\d{3}-?\d{2}\b/g, tag: '[CPF]' },
  { name: 'card',  re: /\b(?:\d[ -]?){13,16}\b/g, tag: '[CARD]' },
];

export function redact(text) {
  if (typeof text !== 'string') return text;
  return PATTERNS.reduce((acc, p) => acc.replace(p.re, p.tag), text);
}

// O log guarda metadados sempre, e o conteudo redigido com retencao curta.
export function buildLogRecord({ traceId, route, model, usage, prompt, output }) {
  return {
    traceId,
    route,
    model,
    tokens: { input: usage.input_tokens, output: usage.output_tokens },
    // Conteudo redigido: util para depurar, seguro para reter por pouco tempo.
    prompt: redact(prompt),
    output: redact(output),
  };
}

A separacao que importa: metadados (tokens, custo, latencia, rota, finish_reason) sao baratos e seguros, entao guarde por muito tempo para analise de tendencia. Conteudo bruto (prompt e resposta) e caro e sensivel, entao redija sempre e retenha por pouco (dias, nao meses), o suficiente para depurar o incidente recente. Nunca logue chave de API, token de sessao ou credencial, e trate o log de LLM com o mesmo cuidado de qualquer store de dado pessoal, porque e exatamente isso que ele e.

06

Alertas que evitam a surpresa

Dashboard voce olha quando lembra; alerta te avisa quando voce nao esta olhando. O objetivo dos alertas de LLM e o mesmo dos guardrails: transformar um problema silencioso (custo subindo, qualidade caindo) em um evento acionavel antes de virar prejuizo ou reclamacao. O erro e alertar so em erro tecnico (5xx, timeout) e ignorar os sinais que sao unicos de LLM.

  1. Custo por dia acima do orcamento esperado, ou custo por rota subindo mais de X por cento semana a semana: pega abuso, loop e regressao de prompt antes da fatura.
  2. Taxa de formato invalido ou de recusa acima da linha de base: sinal direto de que o prompt ou o modelo mudou de comportamento.
  3. p95 de latencia por rota estourando o SLA, separando tempo de modelo de tempo de tool, para saber onde agir.
  4. Proporcao de respostas terminando em max_tokens subindo: prompt ou limite de saida mal dimensionados, gerando custo e resposta cortada.
  5. Score do juiz amostrado caindo abaixo do limiar: a metrica de qualidade continua que dispara antes do cliente perceber.
Fluxo de observabilidade de uma chamada de LLM

  requisicao do usuario
        |
        v
  [ trace inicia: traceId ]
        |
        +--> span: retrieval        120ms
        +--> span: llm.completion    infos: tokens, custo, finish_reason
        |         840ms
        +--> span: tool getStatus    retry=0   210ms
        |
        v
  [ trace fecha ]
        |
        +--> metricas agregadas:  custo/rota   p95/rota   qualidade/rota
        +--> log redigido:        prompt/output (retencao curta)
        +--> alertas:             custo, formato, recusa, latencia, juiz

Cada alerta deve apontar para a rota e o trace, nao para um numero global. "Custo subiu" nao ajuda; "custo da rota /suporte subiu 40 por cento, veja o trace abc123" leva direto a causa. Observabilidade so vale quando encurta o caminho do sintoma ate a origem, e um alerta sem contexto e so mais um numero que a equipe aprende a ignorar.

07

Comecar pequeno sem virar projeto de plataforma

A armadilha e achar que observabilidade de LLM exige uma plataforma inteira antes de dar valor. Nao exige. Um middleware que envelopa a chamada, calcula custo, mede latencia por fase e loga redigido ja entrega noventa por cento do valor em poucas centenas de linhas. O caminho e adicionar por camadas, na ordem de retorno.

  • Comece pelo custo por chamada: e o mais barato de instrumentar e o que mais surpreende, porque ninguem sabia o numero real por rota.
  • Adicione o span por fase logo depois: separa tempo de modelo de tempo de tool e retrieval, o que torna a latencia acionavel.
  • Ligue o log redigido cedo, com retencao curta: e o que permite depurar o primeiro bug de qualidade sem virar risco de privacidade.
  • Instrumente os sinais de qualidade baratos (formato invalido, recusa, max_tokens) antes do juiz amostrado, que e mais caro de montar.
  • Ponha os alertas por ultimo, quando ja tem linha de base: alertar sem baseline gera ruido, alertar com baseline gera acao.

A diferenca entre operar um sistema com LLM e reza-lo esta em enxergar os tres eixos juntos: quanto demora, quanto custa e se presta. Quem instrumenta isso cedo descobre a regressao de qualidade em um dashboard e o pico de custo em um alerta; quem deixa para depois descobre os dois no lugar errado, o primeiro na reclamacao do cliente e o segundo na fatura.

FAQ

Perguntas frequentes

Preciso de uma plataforma dedicada de observabilidade de LLM?

Nao para comecar. Um middleware que envelopa a chamada do modelo, calcula custo por tokens, emite span por fase e loga com redacao cabe em poucas centenas de linhas e entrega a maior parte do valor. Plataformas dedicadas ajudam quando o volume cresce e voce quer visualizacao de trace pronta e eval integrado, mas adotar uma cedo demais adiciona custo e dependencia antes de voce entender o que precisa medir. Instrumente os tres eixos primeiro; escolha a ferramenta depois, sabendo o que ela resolve.

Como meco qualidade se nao tenho a resposta certa em producao?

Com sinais indiretos combinados, nenhum definitivo sozinho: taxa de formato invalido, taxa de recusa, respostas cortadas por max_tokens, sinais do usuario (retentativa, reformulacao, thumbs down, escalada) e um LLM como juiz rodando sobre uma amostra do trafego (1 a 5 por cento). Cada um e um proxy imperfeito, mas o conjunto vira uma metrica de qualidade continua que sobe e desce ao longo do tempo e dispara alerta quando cai, permitindo detectar degradacao antes do cliente reclamar.

Como calculo o custo real de cada chamada?

Cada resposta do provedor traz o numero de tokens de entrada e de saida; multiplique cada um pelo preco por milhao de tokens do modelo (entrada e saida tem precos diferentes) e some. Tokens lidos do prompt cache custam uma fracao do preco de entrada, entao conte-os a parte. Anexe esse custo ao span da chamada e agregue por rota, por usuario e por dia. O erro comum e olhar so o total da conta; o valor esta em saber qual rota e qual usuario geram o gasto.

Observabilidade e o que transforma um sistema de LLM de caixa preta em operacao

Tracing por fase, custo por chamada, sinais de qualidade e alertas com contexto sao o minimo para operar LLM sem surpresa de fatura nem regressao invisivel. Posso instrumentar essas tres dimensoes no seu produto, do middleware ao dashboard, integradas ao seu stack e prontas para escalar.