O que já resolvi e o que ainda falta

No Meet interno, o agente que fica invisível numa call de vendas tem um endpoint de health check embutido, na porta 8787. Ele responde status ok enquanto o processo está vivo, e um teste de soak com Playwright fica lendo esse endpoint durante toda uma call simulada com múltiplos participantes, salvando cada leitura num arquivo de snapshot. Isso pegou problema de verdade: reconexão do Deepgram caindo, timeout de 60 segundos no resumo de fim de call, retry nos pontos de escrita no banco.

E ainda assim isso não é observabilidade de agente, é liveness check. Ele responde uma pergunta: o processo está de pé. Não responde a pergunta que interessa quando um vendedor reclama que o insight que apareceu no painel dele não fazia sentido: o que o modelo recebeu de contexto, o que ele decidiu, e em qual ponto da cadeia isso desandou. Para isso, health check não ajuda em nada.

O padrão que virou consenso: OpenTelemetry para GenAI

Desde 2024 existe um grupo de trabalho dentro do OpenTelemetry (o GenAI SIG) padronizando como representar chamada de LLM, execução de ferramenta e etapa de retrieval como spans de um trace distribuído comum, o mesmo formato já usado para instrumentar APIs REST e filas há anos. A convenção define uma hierarquia específica pra agente: um span invoke_agent no topo, com spans filhos chat pra cada chamada de LLM e execute_tool pra cada ferramenta executada.

Cada span carrega atributos com nome padronizado: gen_ai.request.model identifica o modelo usado, gen_ai.usage.input_tokens e gen_ai.usage.output_tokens contam tokens, gen_ai.response.finish_reasons registra se a resposta parou por fim natural ou por chamada de ferramenta. Quando a captura de conteúdo é ligada explicitamente, o span também guarda o prompt inteiro, a resposta e os argumentos de cada tool call em gen_ai.input.messages e gen_ai.output.messages.

O ganho prático de usar um padrão em vez de inventar o próprio formato de log é poder abrir qualquer ferramenta de tracing (Grafana Tempo, Jaeger, Honeycomb, ou uma das plataformas específicas de LLM) e ver a árvore inteira de decisão do agente: qual chamada de modelo levou a qual tool call, quanto cada etapa custou em tokens e em tempo, e onde exatamente a cadeia parou de fazer sentido.

Onde isso já existe: as plataformas

Três ferramentas dominam esse espaço hoje. Langfuse é open source, autohospedável em Postgres mais ClickHouse, e agnóstica de framework: qualquer SDK que exporte traces OpenTelemetry entra. Arize Phoenix também é open source, nativo de OpenTelemetry e do formato OpenInference, e vem com mais de 50 métricas de avaliação prontas (fidelidade, relevância, toxicidade, detecção de alucinação). LangSmith é proprietário, com integração profunda ao LangChain e LangGraph, incluindo diff de estado nó a nó e replay de trace contra uma versão nova de modelo.

Nenhuma delas resolve o problema sozinha se o agente não emite os spans certos pra começar. A ferramenta de visualização é a parte fácil; instrumentar cada chamada de modelo e cada tool call com os atributos certos é o trabalho de verdade, e é o que falta hoje no Meet interno e no Article Copilot, os dois projetos onde eu ainda dependo de log estruturado simples em vez de trace distribuído.

Por que log estruturado sozinho não basta

No Article Copilot eu resolvi auditoria de agente do jeito mais direto possível: uma tabela agent_runs no SQLite, com um helper run_tracked que qualquer agente chama pra registrar início, fim, sucesso ou falha. Um ADR específico (ADR-0011) tornou agent_runs.session_id opcional, porque agentes de inicialização e de ingestão rodam fora de uma sessão de análise e ainda assim precisam de auditoria.

Isso funciona bem pra responder “esse agente rodou, quanto tempo levou, teve erro”. Não funciona pra responder “por que ele escolheu essa ferramenta em vez daquela” ou “o que mudou entre essa execução e a anterior que deu certo”, porque uma linha de tabela por execução não guarda a relação de causa entre uma decisão e outra dentro da mesma execução. Isso é exatamente o que um span filho dentro de um trace guarda por natureza: a árvore de chamadas, não só o resultado final dela.

Log estruturado audita o quê. Trace explica o como. As duas coisas coexistem: o log continua útil pra alertar rápido (“esse agente falhou”), o trace é pra quando a pergunta é mais funda do que isso.

Avaliação contínua tem um ponto cego documentado

Usar um modelo como juiz automático da qualidade das respostas em produção virou prática comum, e existe um estudo recente medindo o quanto isso realmente funciona num agente de pedidos em produção, comparando contra revisão humana exaustiva dos mesmos transcritos. O resultado: o juiz detectou 2 de 9 padrões de defeito confirmados num lote, e zero de 23 defeitos distintos confirmados em outro.

O padrão da falha não é aleatório. O juiz pega bem problema local, como estatística inventada ou idioma errado numa resposta isolada. O que ele deixa passar sistematicamente são problemas que só existem olhando vários turnos juntos: travamento de confirmação, item fantasma no carrinho, escalonamento que nunca acontece, referência a algo que já mudou de estado. A rubrica do juiz cobria intenção, tom de marca e personalização, três eixos que não têm categoria nenhuma pra rastrear estado ao longo da conversa.

Isso importa especificamente pra agente, mais do que pra uma chamada única de LLM, porque o erro de agente tende a ser acumulado: uma decisão errada no turno 2 só vira problema visível no turno 5. Um juiz que avalia turno a turno, ou mesmo a conversa inteira sem visibilidade da árvore de tool calls, tem o mesmo ponto cego estrutural.

Falha silenciosa não aparece em teste

Um estudo longitudinal de 8 semanas sobre um runtime de agente pessoal em produção catalogou 22 incidentes reais em cinco classes: peculiaridade de ambiente, pressuposto de design que não bateu com a realidade, supressão de erro, alucinação encadeada (o modelo transforma a própria falha numa explicação plausível e entrega pro usuário como se fosse resposta certa) e ponto cego de instrumentação. O sistema tinha 4.286 testes unitários e 827 verificações de governança rodando. Mesmo assim, cerca de 70% das falhas catalogadas foram descobertas por observação humana direta, não pelos testes nem pelas auditorias automatizadas.

Isso não é argumento contra teste. É argumento contra achar que teste substitui observação em produção. Os dois resolvem problemas diferentes: teste prova que o sistema se comporta como esperado nos casos que você pensou em escrever; observabilidade mostra o que o sistema fez nos casos que você não pensou.

O que eu mudaria hoje

Primeiro passo prático, antes de qualquer plataforma: instrumentar cada chamada de LLM e cada tool call com um span próprio, seguindo a convenção do OpenTelemetry em vez de inventar um formato. Isso é trabalho de instrumentação, não de escolha de ferramenta, e dá pra fazer incremental, projeto por projeto, sem esperar migrar tudo de uma vez.

Segundo, tratar LLM-as-judge como um primeiro filtro barato, não como gate final de qualidade. Ele pega o problema óbvio e libera tempo humano pra olhar amostra do resto, principalmente conversas com mais de um turno e mais de uma tool call, onde o ponto cego documentado é maior.

Terceiro, aceitar que uma fração dos incidentes só vai aparecer quando alguém olhar o trace de verdade depois que o usuário reclamou. Isso não é falha do processo de observabilidade, é o comportamento esperado de qualquer sistema complexo o suficiente. O objetivo realista não é chegar a zero falha silenciosa, é reduzir o tempo entre a falha acontecer e alguém com contexto suficiente conseguir reconstruir a árvore de decisão que levou até ela.

O que eu tiro disso

O padrão se repete nos três artigos que escrevi até agora: tokenizador, RAG contra CAG, e agora observabilidade. Toda vez que uma prática nova promete resolver um problema antigo por completo, vale abrir o mecanismo e comparar com o que a literatura já mediu, não com o que o marketing da ferramenta promete. Health check não é trace, log estruturado não é trace, e LLM-as-judge não substitui olho humano em conversa de múltiplos turnos.

Instrumentar direito desde o início é simples de enunciar e chato de executar, e ainda assim custa bem menos do que reconstruir a árvore de decisão de um incidente a partir de log solto, depois que o cliente já reclamou.