Conteúdo da trilha
RefatoraçãoOperação

Adicionar Trace

Add Tracing

Adicionar Trace (Add Tracing) é uma refatoração de IA para sistemas com LLM, do grupo Operação. Registrar cada chamada, ferramenta, versão de prompt e custo em um trace navegável.

Elemento
32/35
Nível
Iniciante
Leitura
5 min
Atualizado
Nesta página 8
  1. 01Problema
  2. 02Solução
  3. 03Antes e depois
  4. 04Por que funciona
  5. 05Passo a passo seguro
  6. 06Riscos
  7. 07Desafio
  8. 08Teste seu entendimento
  9. ··Perguntas rápidas

Problema

Às 3h12, o agente de incidentes reiniciou o serviço de catálogo, que estava saudável. O serviço com problema era o de pagamentos. Na manhã seguinte, o log tinha duas linhas: “alert received” e “done”. Ninguém conseguiu dizer o que o agente leu, o que pensou nem qual versão do prompt estava no ar.

Quando algo dá errado, ninguém consegue ver quais chamadas, ferramentas e contexto levaram à resposta. São os smells Voo Cego e Prompt Sem Versão.

Solução

Envolva cada execução num trace, e cada chamada ao modelo, ferramenta e decisão de código num span filho, com os atributos que explicam a decisão: prompt e versão, entrada, saída, tokens, custo, latência e resultado das ferramentas.

Antes e depois

 def handle_alert(alert):
-    log.info(f"alert received: {alert.name}")
-    result = agent.run(alert)
-    log.info("done")
-    return result
+    with tracer.start_as_current_span("incident.handle") as root:
+        root.set_attributes({"alert.name": alert.name, "agent.version": AGENT_VERSION,
+                             "prompt.version": PROMPT.version})
+        result = agent.run(alert)     # o agente cria spans filhos de modelo e de ferramenta
+        root.set_attributes({"result.action": result.action,
+                             "cost.usd": result.cost_usd, "stop.reason": result.stop_reason})
+        return result
+
+# dentro do loop do agente
+with tracer.start_as_current_span(f"chat {MODEL}") as span:
+    reply = client.messages.create(...)
+    span.set_attributes({"gen_ai.operation.name": "chat",
+        "gen_ai.request.model": MODEL,
+        "gen_ai.usage.input_tokens": reply.usage.input_tokens,
+        "gen_ai.usage.output_tokens": reply.usage.output_tokens,
+        "gen_ai.response.finish_reasons": [reply.stop_reason]})

Antes: o log diz que começou e que acabou.

flowchart LR
  A(["1. Alerta"]):::io --> M(["2. Agente"]):::llm
  M --> L[("3. Duas linhas de log")]:::store

Depois: uma árvore de spans que conta a execução inteira.

flowchart TD
  R(["1. Trace: incident.handle"]):::io --> S1(["2. chat"]):::llm
  R --> T1["3. execute_tool read_logs"]:::tool
  R --> S2(["2. chat"]):::llm
  R --> T2["3. execute_tool restart_service"]:::tool
  S1 --> A1["4. prompt, versão, tokens, custo"]:::tool
  T1 --> A2["4. argumentos, resultado, erro"]:::tool
  R --> D[("5. Armazenamento de traces")]:::store
  1. Toda tarefa vira um trace, com identificador, usuário e versões.
  2. Cada chamada ao modelo é um span com modelo, tokens, custo, motivo de parada e (quando permitido) entrada e saída.
  3. Cada ferramenta é um span com argumentos, resultado ou resumo, erro e duração.
  4. Os atributos seguem uma convenção, para que painéis e buscas funcionem em todos os serviços.
  5. Os traces vão para um armazenamento com busca, com retenção e acesso definidos.

Por que funciona

A decisão de um modelo é função do que entrou no contexto. Um trace guarda exatamente isso, em ordem, ligado à ferramenta que gerou cada pedaço. Com ele, “por que o agente reiniciou o catálogo?” vira uma leitura.

Lendo o trace do incidente

Este é o trace da madrugada das 3h12, depois da refatoração (resumido):

incident.handle            4.8s   alert=payments-5xx  agent=v14  prompt=triage@7  cost=$0.052
├─ chat claude-...         1.9s   in=11.840 out=212  finish=tool_use  tools=[read_logs]
├─ execute_tool read_logs  0.3s   args={"service": "catalog"}  result="1.204 linhas, 0 erros"
├─ chat claude-...         2.1s   in=13.102 out=148  finish=tool_use  tools=[restart_service]
└─ execute_tool restart_service  0.5s  args={"service": "catalog"}  result="ok"

A leitura leva um minuto. O alerta era de pagamentos, mas o primeiro chat pediu os logs do catálogo. O log veio limpo, e o modelo, em vez de desconfiar, concluiu que o catálogo precisava de reinício. A causa está no primeiro span: o prompt triage@7 traz um exemplo em que o serviço é sempre catalog.

Sem o trace, o time teria discutido “o modelo alucinou”. Com ele, a correção é concreta: trocar o exemplo do prompt e criar um caso de eval com esse alerta.

Trace não é log com mais campos

A estrutura em árvore é o que diferencia trace de log. Cada span sabe quem é o pai, então dá para somar o custo por tarefa, ver o caminho crítico de latência e comparar execuções boas e ruins do mesmo tipo de tarefa. Duas tarefas rodando ao mesmo tempo não se misturam, porque cada linha carrega o identificador da execução.

A versão do prompt em cada span fecha o ciclo: uma resposta ruim aponta para o texto exato que a gerou, e o caso vai para o eval com tudo que é preciso para reproduzir.

Checagem +20 XP de primeiraSeu time já grava um log JSON por chamada ao modelo e por ferramenta, com horário e duração, mas sem identificador de execução. Numa noite, dois alertas disparam juntos e um deles termina com a ação errada. O que impede você de explicar o que houve?

Passo a passo seguro

  1. Meça o que existe. Escolha três incidentes recentes e tente explicar cada um com os registros atuais. Anote o que faltou: era o prompt? o resultado da ferramenta? a versão? Essa lista vira o requisito da instrumentação.
  2. Escolha o padrão. OpenTelemetry com as convenções semânticas gen_ai.* funciona com a maioria das ferramentas de observabilidade (Jaeger, Grafana Tempo, Langfuse, Arize Phoenix, Datadog, entre outras). Essas convenções ainda estão marcadas como em desenvolvimento e já renomearam atributos entre versões, então fixe a versão que você segue.
  3. Defina a política de dados. O que é gravado por inteiro, o que é mascarado, retenção e quem pode ler. Mascare antes de exportar, nunca depois.
  4. Crie o span raiz em cada ponto de entrada (requisição, job, evento). Se a execução atravessa serviços, propague o contexto (o cabeçalho traceparent do padrão W3C Trace Context) para que tudo caia no mesmo trace.
  5. Instrumente as chamadas ao modelo, à mão ou com uma biblioteca pronta (OpenLLMetry e OpenInference instrumentam os SDKs mais comuns).
  6. Instrumente as ferramentas com argumentos, resultado (ou resumo) e erro. O resultado da ferramenta é o que o modelo leu, e quase sempre é ali que mora a explicação.
  7. Versione os prompts e registre a versão em todo span de chamada.
  8. Calcule o custo a partir dos tokens e registre por span.
  9. Meça de novo. Volte aos três incidentes (ou a novos) e confira se agora dá para explicar cada passo.

Conta de exemplo para o custo

Suponha um modelo que cobre US$ 3 por milhão de tokens de entrada e US$ 15 por milhão de saída (valores ilustrativos; use a tabela do seu provedor). Um span com 11.840 tokens de entrada e 212 de saída custa:

11.840 × 3 / 1.000.000  = US$ 0,0355
   212 × 15 / 1.000.000 = US$ 0,0032
total do span            = US$ 0,0387

Gravado em cada span, esse número soma sozinho no span raiz. Aí “qual tipo de alerta custa mais?” vira uma consulta, não uma planilha.

O que gravar e o que amostrar

Separe atributos de conteúdo. Atributos (modelo, tokens, custo, versão, motivo de parada, duração, ids) são pequenos e devem existir em 100% das execuções. Conteúdo (prompt inteiro, resposta, retorno de ferramenta) pesa e tem dados pessoais, então pode ser amostrado.

A amostragem precisa olhar para o fim da execução, não para o começo. Amostrar 5% ao acaso no início perde justamente o caso ruim. Decida depois que a execução termina e guarde sempre as que tiveram erro, custo alto, parada por limite ou ação com efeito.

Checagem +20 XP de primeiraPara economizar, seu time grava só 5% dos traces, sorteados no início de cada execução. Um cliente reclama de uma resposta absurda de ontem, e o trace dela não existe. Qual é o ajuste certo?

Riscos

  • Dados pessoais nos traces. Prompts e respostas carregam CPF, endereço, saldo e conversa de WhatsApp. Pela LGPD, isso é tratamento de dado pessoal, inclusive na ferramenta de observabilidade. Mascare antes de exportar, restrinja o acesso e defina retenção.
  • Custo de armazenamento. Guardar prompts inteiros de todo tráfego pesa. Amostre o conteúdo e mantenha os atributos numéricos de todos.
  • Latência. Exportar traces de forma síncrona atrasa a resposta. No SDK do OpenTelemetry, o SimpleSpanProcessor envia cada span na hora; em produção use o BatchSpanProcessor, que envia em lote em segundo plano.
  • Amostragem que esconde o que importa. Amostrar 1% pode perder justamente o caso ruim. Guarde sempre as execuções com erro, custo alto ou parada por limite.
  • Trace partido. Um serviço que não propaga o contexto cria um trace novo, e a execução aparece em dois pedaços sem ligação. Confira, no passo 8, que uma tarefa que cruza serviços aparece inteira.
Checagem +20 XP de primeiraSeu agente de cobrança exporta prompts completos, com CPF e valor da dívida, para uma ferramenta SaaS de observabilidade. Todo o time de engenharia tem acesso, e não há prazo de retenção. Qual é a correção?

Desafio

Este span tem informação, mas não ajuda a explicar a decisão. O que falta?

with tracer.start_as_current_span("llm") as span:
    reply = client.messages.create(model=MODEL, system=SYSTEM, messages=msgs, tools=TOOLS)
    span.set_attribute("duration_ms", elapsed_ms)
    span.set_attribute("ok", True)
Ver solução

Faltam a versão do prompt, os tokens (para custo), o motivo de parada, as ferramentas pedidas e, quando a política permitir, a entrada e a saída. Duração já é medida pelo próprio span, e ok=True não diz nada sobre a qualidade da decisão.

with tracer.start_as_current_span(f"chat {MODEL}") as span:
    reply = client.messages.create(model=MODEL, system=SYSTEM, messages=msgs, tools=TOOLS)
    span.set_attributes({
        "gen_ai.operation.name": "chat",
        "gen_ai.request.model": MODEL,
        "prompt.version": SYSTEM_VERSION,
        "gen_ai.usage.input_tokens": reply.usage.input_tokens,
        "gen_ai.usage.output_tokens": reply.usage.output_tokens,
        "gen_ai.response.finish_reasons": [reply.stop_reason],
        "tools.requested": [b.name for b in reply.content if b.type == "tool_use"],
    })

Teste seu entendimento

Checagem +20 XP de primeiraAs respostas do agente pioraram na terça. Os spans têm modelo e tokens, mas não a versão do prompt, e dois PRs mexeram no prompt na segunda. O que a versão em cada span teria dado a você?

Checagem +20 XP de primeiraDepois de instrumentar o agente, o p95 de latência subiu 300 ms. O exportador está configurado com SimpleSpanProcessor. O que você faz primeiro?

Checagem +20 XP de primeiraSeu gestor pede para 'colocar observabilidade no agente' e você tem dois dias. Qual é o primeiro passo mais útil?

Perguntas rápidas

O que é Adicionar Trace?

Adicionar Trace (Add Tracing) é uma refatoração de IA para sistemas com LLM, do grupo Operação. Registrar cada chamada, ferramenta, versão de prompt e custo em um trace navegável.

Que problema Adicionar Trace resolve?

Trata Prompt Sem Versão e Voo Cego.

Aonde Adicionar Trace leva?

Ao padrão de projeto Trilha de Auditoria.

Para ir além

Relações

Padrões de destino 1
Buscar