Início / Trilha 2 — Construção de Agentes / Módulo 2.8
🔍
Módulo 2.8 Último da Trilha

Depuração e Rastreamento

Observe, meça e corrija o comportamento dos seus agentes com LangSmith, logging estruturado e métricas de qualidade em produção.

6 tópicos
~40 minutos
Intermediário
Prático
1

Por que agentes são difíceis de debugar

Não-determinismo, dependência de LLM e loops: os três inimigos do desenvolvedor.

🎲

Não-determinismo

O mesmo input pode gerar outputs diferentes. Temperature > 0 garante variação. Reproduzir um bug exige capturar o estado exato.

🔗

Dependência de LLM

O comportamento muda com versões de modelo, mudanças de prompt e até carga do servidor. O que funciona hoje pode falhar amanhã.

🔄

Loops infinitos

O agente chama a mesma tool repetidamente, interpreta o resultado errado ou nunca decide que terminou. Sem limite, o custo explode.

Por que print() não é suficiente

Abordagem ingênua

agent.run("Pesquise sobre IA")
# Saída: uma string final
# Você não sabe:
# - Quantas chamadas ao LLM?
# - Qual tool foi chamada?
# - Qual foi o custo?
# - Por que parou?

Com rastreamento

with langsmith.trace("agent-run"):
    agent.run("Pesquise sobre IA")
# Você vê:
# - 3 chamadas ao GPT-4o
# - 2 tool calls (search + calc)
# - $0.042 de custo
# - 8.3s de latência total

O Princípio da Observabilidade em Agentes

Um sistema é observável quando você consegue entender seu estado interno apenas analisando suas saídas externas. Para agentes, isso significa capturar:

📥

Input exato de cada chamada

📤

Output e tokens usados

⏱️

Latência por etapa

Erros e exceções

2

Configurando LangSmith em 3 minutos

Da conta gratuita ao primeiro trace capturado — sem alterar código de produção.

1

Crie a conta e gere a API key

Acesse smith.langchain.com, crie uma conta gratuita e vá em Settings → API Keys → Create API Key.

# Tier gratuito: 5.000 traces/mês
# Sem necessidade de cartão de crédito
2

Configure as variáveis de ambiente

export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="ls__xxxxxxxxxxxxxxxx"
export LANGCHAIN_PROJECT="meu-agente-producao"

# Ou via .env + python-dotenv
# NUNCA commite sua API key no git
3

Execute seu agente normalmente

from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate

# NENHUMA mudança de código necessária!
# O tracing é automático quando LANGCHAIN_TRACING_V2=true
llm = ChatOpenAI(model="gpt-4o-mini")
agent = create_openai_tools_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)

result = executor.invoke({"input": "Qual é a capital do Brasil?"})
# → Trace aparece automaticamente no LangSmith
💡

Dica: Tracing seletivo em produção

Ative o tracing apenas para uma amostra das requisições em produção (ex: 10%) para controlar custos e volume. Use a variável LANGCHAIN_SAMPLING_RATE=0.1.

3

Visualizando uma trace completa

Anatomia de um trace no LangSmith: o que cada nó representa e como lê-lo.

Estrutura de um trace hierárquico

AgentExecutor 8.3s | $0.042
LLM: gpt-4o-mini 1.2s | 423 tokens
Input: [SystemMessage, HumanMessage, AIMessage...]
Output: tool_call → search("clima São Paulo")
Tool: search 3.1s | $0.001
Input: "clima São Paulo"
Output: "28°C, parcialmente nublado..."
LLM: gpt-4o-mini 0.9s | 312 tokens
Input: [histórico + tool result]
Output: "O clima em São Paulo está 28°C..."

O que inspecionar primeiro

  • Status: Success, Error ou Timeout?
  • Latência total: onde está o gargalo?
  • Número de iterações: loops excessivos?
  • Custo: dentro do orçamento esperado?

Adicionando metadata ao trace

from langsmith import traceable

@traceable(
    run_type="chain",
    name="email-classifier",
    tags=["prod", "v2.1"],
    metadata={
        "user_id": "usr_123",
        "feature_flag": "gpt4o"
    }
)
def classify_email(text: str) -> str:
    return agent.run(text)

Usando o decorador @traceable para funções customizadas

from langsmith import traceable
from langchain_openai import ChatOpenAI

@traceable  # Aparece como nó separado na árvore do trace
def my_preprocessing(text: str) -> str:
    """Limpeza e normalização do input."""
    text = text.strip().lower()
    return text[:2000]  # Trunca textos muito longos

@traceable
def my_postprocessing(response: str) -> dict:
    """Estrutura a resposta final."""
    return {"answer": response, "confidence": 0.9}

# Estas funções agora aparecem na hierarquia do trace LangSmith
4

Identificando a chamada de LLM problemática

Técnicas para isolar qual etapa causou falha, resposta incorreta ou custo elevado.

Sintoma 1: Resposta incorreta

Inspecione o prompt enviado ao LLM. O contexto estava correto? A instrução era clara?

→ Clique no nó LLM
→ Tab "Input"
→ Veja o System Message
→ Veja o contexto injetado

Sintoma 2: Custo alto

Qual chamada consumiu mais tokens? Contexto desnecessariamente longo?

→ Ordene nós por tokens
→ Identifique o maior
→ Verifique se todos os
mensagens são necessárias

Sintoma 3: Timeout

Qual nó demorou mais? Tool externa lenta? LLM sobrecarregado?

→ Veja latência por nó
→ Identifique gargalo
→ Tool ou LLM?
→ Adicione timeout local

Replay e comparação de traces

Um dos superpoderes do LangSmith é comparar dois traces lado a lado para ver exatamente o que mudou entre versões de prompt:

from langsmith import Client

client = Client()

# Listar traces de um projeto
runs = client.list_runs(
    project_name="meu-agente",
    filter='and(eq(status, "error"), gt(total_tokens, 1000))',
    limit=10
)

for run in runs:
    print(f"ID: {run.id}")
    print(f"Status: {run.status}")
    print(f"Tokens: {run.total_tokens}")
    print(f"Latência: {run.end_time - run.start_time}")
    print("---")

# Acessar input/output de uma run específica
run = client.read_run("run-id-aqui")
print(run.inputs)   # Input exato
print(run.outputs)  # Output retornado
💡

Adicione feedback humano ao trace

Com client.create_feedback(run_id, "quality", score=1) você pode associar avaliações humanas (thumbs up/down) a traces específicos e criar datasets para fine-tuning ou avaliação automática.

5

Métricas de qualidade para agentes

Latência, custo e success rate: as três métricas que todo agente em produção precisa monitorar.

Latência

⏱️
P50 (mediana) < 5s
P95 < 15s
P99 < 30s

Monitore percentis, não só médias. Uma média de 5s pode esconder P99 de 60s.

Custo

💰
Por tarefa < $0.05
Por usuário/dia < $0.50
Alerta de anomalia 3× média

Defina alertas para tarefas que custam 3× acima da média — indica loop ou prompt infinito.

Success Rate

Excelente > 95%
Aceitável 85–95%
Revisar agente < 85%

Inclua falhas silenciosas: respostas fora do formato esperado também são falhas.

Dashboard de métricas com LangSmith SDK

from langsmith import Client
from datetime import datetime, timedelta
import statistics

client = Client()

# Coleta runs das últimas 24h
runs = list(client.list_runs(
    project_name="meu-agente",
    start_time=datetime.now() - timedelta(hours=24)
))

# Calcula métricas
latencias = [
    (r.end_time - r.start_time).seconds
    for r in runs if r.end_time
]
custos = [r.total_cost for r in runs if r.total_cost]
sucessos = sum(1 for r in runs if r.status == "success")

print(f"Total de runs: {len(runs)}")
print(f"Success rate: {sucessos/len(runs)*100:.1f}%")
print(f"Latência mediana: {statistics.median(latencias):.1f}s")
print(f"Latência P95: {sorted(latencias)[int(len(latencias)*0.95)]:.1f}s")
print(f"Custo médio: ${statistics.mean(custos):.4f}")
print(f"Custo total: ${sum(custos):.2f}")

Avaliação automática com LangSmith Evaluators

from langsmith.evaluation import evaluate, LangChainStringEvaluator

# Cria um evaluator baseado em LLM
qa_evaluator = LangChainStringEvaluator(
    "qa",
    config={"llm": ChatOpenAI(model="gpt-4o-mini")}
)

# Roda avaliação em um dataset existente
results = evaluate(
    lambda x: agent.invoke(x),
    data="meu-dataset-de-teste",
    evaluators=[qa_evaluator],
    experiment_prefix="v2.1-test"
)

print(results.to_pandas()[["input", "output", "score"]].head())
6

Logging estruturado para produção

Registre cada tool call com input, output e custo — e combine com métricas de negócio.

Logging não estruturado (evitar)

print("Iniciando agente...")
print("Chamando search tool")
print(f"Resultado: {result}")
print("Agente terminou")

# Problemas:
# - Impossível filtrar por user_id
# - Sem timestamp preciso
# - Sem custo ou tokens
# - Impossível agregar em dashboards

Logging estruturado (fazer)

import structlog, time

logger = structlog.get_logger()

logger.info("tool_call",
    tool="search",
    input=query[:200],
    output=result[:200],
    duration_ms=int((end-start)*1000),
    tokens_used=423,
    cost_usd=0.0012,
    user_id="usr_123",
    session_id="sess_456",
    trace_id=run_id
)

Callback handler completo para produção

import time, structlog
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.outputs import LLMResult

logger = structlog.get_logger()

class ProductionCallbackHandler(BaseCallbackHandler):
    def __init__(self, user_id: str, session_id: str):
        self.user_id = user_id
        self.session_id = session_id
        self._tool_start_times = {}
        self._llm_start_times = {}

    def on_llm_start(self, serialized, prompts, run_id, **kwargs):
        self._llm_start_times[str(run_id)] = time.time()
        logger.info("llm_start",
            model=serialized.get("name"),
            prompt_chars=sum(len(p) for p in prompts),
            user_id=self.user_id
        )

    def on_llm_end(self, response: LLMResult, run_id, **kwargs):
        duration = time.time() - self._llm_start_times.get(str(run_id), time.time())
        usage = response.llm_output.get("token_usage", {})
        logger.info("llm_end",
            duration_ms=int(duration * 1000),
            prompt_tokens=usage.get("prompt_tokens", 0),
            completion_tokens=usage.get("completion_tokens", 0),
            total_tokens=usage.get("total_tokens", 0),
            user_id=self.user_id,
            session_id=self.session_id
        )

    def on_tool_start(self, serialized, input_str, run_id, **kwargs):
        self._tool_start_times[str(run_id)] = time.time()
        logger.info("tool_start",
            tool_name=serialized.get("name"),
            input_preview=str(input_str)[:300],
            user_id=self.user_id
        )

    def on_tool_end(self, output, run_id, **kwargs):
        duration = time.time() - self._tool_start_times.get(str(run_id), time.time())
        logger.info("tool_end",
            duration_ms=int(duration * 1000),
            output_preview=str(output)[:300],
            user_id=self.user_id,
            session_id=self.session_id
        )

    def on_tool_error(self, error, run_id, **kwargs):
        logger.error("tool_error",
            error=str(error),
            user_id=self.user_id,
            session_id=self.session_id
        )

# Uso
handler = ProductionCallbackHandler(user_id="usr_123", session_id="sess_456")
result = executor.invoke(
    {"input": "Analise este contrato"},
    config={"callbacks": [handler]}
)

Boas práticas de logging

  • Logue input e output truncados (máx. 300 chars)
  • Sempre inclua user_id, session_id e trace_id
  • Registre duração em milissegundos
  • Use formato JSON estruturado (structlog, loguru)
  • Capture erros com stack trace completo

Anti-padrões de logging

  • Logar o conteúdo completo de cada mensagem
  • Ignorar erros de tools (só logar sucesso)
  • Logar dados pessoais sensíveis (PII)
  • Usar print() sem contexto de sessão
  • Logar de forma síncrona em hot path

Stack de observabilidade recomendada

🔬

LangSmith

Traces detalhados, comparação de experimentos, datasets de avaliação

📊

Grafana + Loki

Dashboards de métricas de negócio, alertas de anomalia, logs de produção

🚨

Sentry / Datadog

Alertas de erros em tempo real, APM, correlação com traces do LangSmith

Resumo do Módulo 2.8

Você concluiu o último módulo da Trilha 2. Agora sabe como tornar seus agentes observáveis, mensuráveis e depuráveis em produção.

O que você aprendeu

  • Por que agentes são difíceis de debugar (não-determinismo, loops, dependência de LLM)
  • Como configurar o LangSmith em menos de 3 minutos
  • Como ler e interpretar uma trace hierárquica completa
  • Como identificar a chamada problemática usando filtros e metadata
  • Métricas de qualidade: latência (P50/P95/P99), custo e success rate
  • Logging estruturado com callback handler para produção

Checklist de conclusão

  • Criou conta no LangSmith e configurou variáveis
  • Executou um agente e visualizou o trace
  • Identificou o custo de uma run completa
  • Implementou o ProductionCallbackHandler
  • Definiu alertas de latência e custo
  • Calculou o success rate do seu agente