Vicco LabsVicco Labs
Construindo um assistente conversacional em produção · Parte 8
Detectar degradação antes do usuário

Observabilidade num grafo LangGraph: o que o Langfuse vê que o log não vê

Logs cobrem o que aconteceu dentro de cada nó. Não respondem a 'a taxa de fallback subiu nos últimos 30 minutos?'. Para isso, o Langfuse.

8 ABR 2026·4 min de leitura·Observability / Langfuse / LangGraph / DSPy
OBSERVABILITY

Nos posts anteriores mostrei como o DSPy classifica a intenção e como o roteador customizado por embeddings decide a macro-intent. Os dois têm uma coisa em comum: podem degradar silenciosamente.

O DSPy degrada quando o provider atualiza o modelo. O semantic router degrada quando o vocabulário dos usuários muda. Nenhum dos dois lança exceção quando isso acontece. Você descobre pela qualidade da resposta (ou pela reclamação do usuário).

Observabilidade é o que muda esse cenário. Esse post é sobre como instrumentar um grafo LangGraph com Langfuse para rastrear exatamente onde cada decisão aconteceu e como detectar degradação antes que chegue ao usuário.

O que o log estruturado não resolve

O logger estruturado cobre o que aconteceu dentro de cada nó. Ele não responde perguntas como:

  • Para qual scope o DSPy roteou nas últimas 500 queries? Qual a distribuição?
  • A taxa de fallback para geral subiu nos últimos 30 minutos?
  • O critique_node interveio em quantas respostas hoje? Por qual tipo de violação?
  • O dspy.Refine precisou de mais de uma tentativa em quantas requisições?

Essas perguntas exigem uma visão agregada sobre múltiplas execuções, não o log de uma execução individual. É aí que o Langfuse entra.

A instrumentação base: @observe nos nós

O ponto de entrada mais simples é o decorator @observe nos nós do grafo:

capture_input=False e capture_output=False não são paranoia. Em qualquer sistema com dados de usuário, capturar o estado completo do grafo no Langfuse significa capturar histórico de conversa, identificadores de sessão e potencialmente dados pessoais. O que você quer no trace não é o estado, são os metadados da decisão.

update_trace_metadata: injetando contexto na trace ativa

Dentro de qualquer nó, update_trace_metadata acumula metadados na trace da requisição atual:

Cada nó contribui com seus metadados para a mesma trace. No Langfuse, você vê o trace completo de uma requisição com todos esses campos agregados, sem precisar cruzar logs de diferentes workers.

Tags e user_id: segmentação sem dados pessoais

No supervisor, ao montar a configuração do grafo:

O langfuse_user_id é um identificador interno, nunca um CPF, e-mail ou número de telefone. As tags permitem filtrar traces por canal, ambiente e modo degradado no dashboard sem expor dados pessoais.

RouterMetricsCollector: métricas in-process com backend Redis

O Langfuse rastreia execuções individuais. Para métricas agregadas em tempo real - taxa de fallback, distribuição de scopes, hits de anáfora - o sistema tem um coletor próprio:

O backend Redis usa HINCRBY, compatível com deploys multi-worker onde cada processo tem seu próprio in-process counter, mas todos escrevem na mesma HASH:

O increment é fire-and-forget: não bloqueia o hot path do router_node. Se o Redis estiver indisponível, o contador in-process ainda funciona, e o alerta de fallback ainda dispara.

O que monitorar para detectar degradação do DSPy

No router_node, após cada inferência:

Os três indicadores que mais importam:

  • Taxa de fallback para geral: quando o DSPy não consegue classificar a query, retorna scope="geral". Uma taxa acima de 25% é sinal de que os demos compilados estão desatualizados em relação ao vocabulário dos usuários. Correção: expandir o dataset e recompilar.
  • Taxa de anáfora resolvida: se anaphora_hit_count/total cair abruptamente, os padrões regex do resolver de anáforas pararam de casar com as mensagens dos usuários. Correção: revisar os patterns.
  • Taxa de coerção forçada: se coercion_fallback_count subir, o LLM começou a retornar formatos de output que a camada de coerção não consegue parsear. Correção: inspecionar dspy.inspect_history() e revisar a Signature.

O que o Langfuse mostra que o log não mostra

Com update_trace_metadata em todos os nós, o Langfuse agrega num único trace:

Em 5 segundos de execução você sabe: a anáfora foi resolvida, o DSPy roteou para busca de catálogo, o Refine precisou de 2 tentativas, e o critique injetou um disclaimer ausente.

Sem esse trace, você tem 5 logs em arquivos diferentes, sem correlação direta, sem timeline.

Itens importantes

  • capture_input=False em todos os spans que tocam mensagens do usuário. capture_output=False em todos os spans que retornam estado do grafo.
  • O que vai para o Langfuse são os metadados da decisão - scope, tool selecionada, latência, flags de compliance - não os dados em si.
  • Dados de usuário pertencem ao arquivo legal. Metadados de decisão pertencem ao sistema de observabilidade. Misturar os dois cria problemas de privacidade e infla o custo do Langfuse sem adicionar valor para debugar.

Na semana que vem: como o módulo de geração usa autocorreção em runtime com função de recompensa, e o trade-off de latência que isso cria.