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.
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
geralsubiu nos últimos 30 minutos? - O
critique_nodeinterveio em quantas respostas hoje? Por qual tipo de violação? - O
dspy.Refineprecisou 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, retornascope="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/totalcair 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_countsubir, o LLM começou a retornar formatos de output que a camada de coerção não consegue parsear. Correção: inspecionardspy.inspect_history()e revisar aSignature.
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=Falseem todos os spans que tocam mensagens do usuário.capture_output=Falseem 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.