Skip to main content

Command Palette

Search for a command to run...

Observabilidade pra Agentes de AI em Produção: do Zero ao Avançado

Como monitorar, debuggar e entender o que seus agentes autônomos estão fazendo com OpenTelemetry, Langfuse e ferramentas open-source

Updated
14 min read
Observabilidade pra Agentes de AI em Produção: do Zero ao Avançado

Software tradicional quebra de formas previsíveis. Um timeout, um null pointer, um 500. Você olha o log, acha a linha, corrige. Agentes de AI quebram de formas criativas. Eles alucinam, chamam a ferramenta errada, entram em loop, gastam tokens sem parar, e o pior: às vezes parecem funcionar quando na verdade estão fazendo besteira.

Se você tá colocando agentes em produção sem observabilidade, você tá voando cego. Este artigo é o guia completo pra resolver isso.

O problema é diferente

Quando um microsserviço falha, o erro é binário: funcionou ou não. Com agentes de AI, o espectro é muito mais amplo:

  • O agente respondeu, mas a resposta tá errada (alucinação)
  • O agente chamou 15 ferramentas quando precisava de 2 (custo explodindo)
  • O agente entrou em loop entre duas ferramentas e nunca convergiu
  • A latência da resposta subiu de 2s pra 45s sem motivo aparente
  • O prompt de sistema foi ignorado parcialmente

Nenhum desses problemas aparece num health check HTTP 200. Você precisa de traces, métricas e logs específicos pra GenAI.

Os três pilares aplicados a agentes

Observabilidade tem três pilares clássicos: traces, métricas e logs. Pra agentes de AI, cada um ganha um significado próprio.

Traces

Um trace de agente não é só "request → response". É uma árvore de decisões:

[Agent Run] ─── duração total: 12.3s
  ├── [LLM Call #1] ─── modelo: claude-sonnet-4-6, tokens: 1.2k in / 340 out
  │     └── decisão: chamar ferramenta "buscar_pedido"
  ├── [Tool Call: buscar_pedido] ─── duração: 230ms
  │     └── resultado: {pedido_id: 4521, status: "enviado"}
  ├── [LLM Call #2] ─── modelo: claude-sonnet-4-6, tokens: 1.8k in / 120 out
  │     └── decisão: chamar ferramenta "rastrear_envio"
  ├── [Tool Call: rastrear_envio] ─── duração: 890ms
  │     └── resultado: {codigo: "BR123456789", status: "em_transito"}
  └── [LLM Call #3] ─── modelo: claude-sonnet-4-6, tokens: 2.1k in / 250 out
        └── resposta final ao usuário

Cada nó dessa árvore é um span. O trace completo mostra o fluxo de execução do agente, passo a passo: chamadas ao modelo, uso de ferramentas, latência, tokens e resposta final. Isso vale independente do provider: OpenAI, Anthropic, Google, Mistral.

Métricas

As métricas que importam pra agentes são diferentes das tradicionais:

  • Token usage (input + output, por chamada e por execução completa)
  • Custo estimado (tokens × preço do modelo)
  • Latência por etapa (LLM call vs tool call vs total)
  • Número de steps por execução (agente convergiu rápido ou ficou rodando?)
  • Taxa de fallback (quantas vezes o agente não conseguiu resolver)
  • Tool call distribution (quais ferramentas são mais usadas)

Logs (eventos)

Logs estruturados capturam o conteúdo das interações:

  • Prompt de sistema enviado
  • Mensagens do usuário
  • Respostas do modelo (incluindo tool calls)
  • Argumentos e retornos de ferramentas
  • Erros e exceções

Isso é sensível. Por padrão, a maioria das ferramentas não captura o conteúdo das mensagens. Você liga isso explicitamente quando precisa debuggar.

OpenTelemetry: a fundação

OpenTelemetry (OTel) é o padrão open-source pra instrumentação. É vendor-agnostic: você instrumenta uma vez e exporta pra Jaeger, Grafana, Datadog, Honeycomb, ou qualquer backend.

O OTel já tem semantic conventions específicas pra GenAI, cobrindo spans de inferência, métricas de token usage e eventos de input/output. Também existe uma seção separada para agent spans. Detalhe importante: em março de 2026, essas convenções ainda estão em status Development, o que significa que nomes de atributos e estruturas podem evoluir em versões futuras.

As convenções cobrem providers específicos com documentação dedicada: OpenAI, Anthropic, AWS Bedrock e Azure AI Inference.

Os atributos principais:

AtributoO que captura
gen_ai.operation.nameTipo de operação (chat, invoke_agent, execute_tool, etc.)
gen_ai.request.modelModelo solicitado (gpt-4.1, claude-sonnet-4-6, etc.)
gen_ai.response.modelModelo que respondeu (pode diferir do solicitado)
gen_ai.usage.input_tokensTokens consumidos no input
gen_ai.usage.output_tokensTokens consumidos no output
gen_ai.provider.nameProvider (openai, anthropic, etc.)
gen_ai.response.finish_reasonsMotivo do fim da geração (stop, length, tool_calls, end_turn)

Pra fluxos agentic, o OTel documenta operações padronizadas como create_agent (criação do agente), invoke_agent (invocação) e execute_tool (execução de ferramenta), além dos spans tradicionais de inferência. Na prática, isso permite separar o trace do "agente como orquestrador" do trace das chamadas LLM e das ferramentas executadas.

Instrumentações disponíveis no ecossistema OTel

O ecossistema de instrumentação OTel pra GenAI cresceu rápido. Aqui tá o que existe e funciona:

Repositório oficial opentelemetry-python-contrib

Estas vivem no repositório oficial do OpenTelemetry (opentelemetry-python-contrib):

PacoteSDK suportado
opentelemetry-instrumentation-openai-v2openai >= 1.26.0
opentelemetry-instrumentation-anthropicanthropic >= 0.16.0
opentelemetry-instrumentation-google-genaigoogle-genai >= 1.32.0
opentelemetry-instrumentation-langchainlangchain >= 0.3.21
opentelemetry-instrumentation-vertexaigoogle-cloud-aiplatform >= 1.64

Os pacotes acima oferecem níveis diferentes de suporte a traces, eventos/logs e, em alguns casos, métricas. Como esse detalhe varia por pacote e versão, vale conferir a documentação do instrumentor exato antes de assumir cobertura completa.

Note também que pra o ecossistema Google existem pacotes distintos dependendo do SDK que você usa (google-genai vs google-cloud-aiplatform). Valide o nome do pacote contra o SDK exato do seu projeto.

OpenLLMetry (Traceloop)

Projeto open-source (Apache 2.0) que oferece instrumentação automática pra vários providers: OpenAI, Anthropic, Azure OpenAI, Google AI, Mistral, Cohere, Amazon Bedrock, além de vector DBs como Pinecone, Chroma e Weaviate. Disponível pra Python e Node.js/TypeScript.

A grande vantagem do OpenLLMetry: um init() e ele detecta e instrumenta automaticamente os SDKs compatíveis instalados.

Stack prática: montando do zero

Três opções, todas com exemplos multi-provider.

Opção 1: OpenTelemetry puro + Jaeger

Pra quem já usa OTel na infra ou quer controle total.

Setup base (compartilhado entre providers)

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

def setup_telemetry(endpoint: str = "localhost:4317") -> None:
    provider = TracerProvider()
    processor = BatchSpanProcessor(
        OTLPSpanExporter(endpoint=endpoint, insecure=True)
    )
    provider.add_span_processor(processor)
    trace.set_tracer_provider(provider)

O insecure=True é pra desenvolvimento local (sem TLS). Em produção, configure certificados.

Com OpenAI

pip install openai opentelemetry-api opentelemetry-sdk \
  opentelemetry-exporter-otlp \
  opentelemetry-instrumentation-openai-v2
from opentelemetry.instrumentation.openai_v2 import OpenAIInstrumentor
import openai

setup_telemetry()
OpenAIInstrumentor().instrument()

client = openai.OpenAI()
response = client.chat.completions.create(
    model="gpt-4.1",
    messages=[{"role": "user", "content": "Qual a capital da França?"}]
)

Com Anthropic (Claude)

pip install anthropic opentelemetry-api opentelemetry-sdk \
  opentelemetry-exporter-otlp \
  opentelemetry-instrumentation-anthropic
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
import anthropic

setup_telemetry()
AnthropicInstrumentor().instrument()

client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Qual a capital da França?"}]
)

O setup do OTel é idêntico. Só muda o instrumentor e o client. Os traces gerados seguem as mesmas semantic conventions, com gen_ai.provider.name setado pra "anthropic" ou "openai".

Múltiplos providers no mesmo projeto

from opentelemetry.instrumentation.openai_v2 import OpenAIInstrumentor
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor

setup_telemetry()
OpenAIInstrumentor().instrument()
AnthropicInstrumentor().instrument()

# A partir daqui, qualquer chamada a qualquer provider gera traces

Útil quando seu agente usa modelos diferentes pra tarefas diferentes (ex: Claude pra raciocínio complexo, GPT-4.1-nano pra classificação rápida).

Capturando conteúdo das mensagens

A captura de conteúdo varia por instrumentação. Nas instrumentações GenAI mais novas (como openai-v2 e google-genai), prompts e respostas normalmente não são capturados por padrão e precisam ser habilitados explicitamente.

O utilitário opentelemetry-util-genai define os seguintes modos via variável de ambiente:

# Não captura conteúdo (padrão)
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=NO_CONTENT

# Captura conteúdo apenas em atributos de span
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_ONLY

# Captura conteúdo apenas como eventos/logs
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=EVENT_ONLY

# Captura em ambos (spans e eventos)
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_AND_EVENT

Pra habilitar as features experimentais das semantic conventions, você também pode precisar de:

export OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental

Já alguns pacotes do ecossistema Traceloop, como a instrumentação do Anthropic via OpenLLMetry, podem capturar conteúdo por padrão e exigir disable explícito:

export TRACELOOP_TRACE_CONTENT=false  # pra desabilitar

Sempre confira a documentação do pacote exato que você instalou. O comportamento padrão e os valores aceitos variam.

Subindo Jaeger local

docker run -d --name jaeger \
  -p 16686:16686 \
  -p 4317:4317 \
  jaegertracing/jaeger:latest

Acesse http://localhost:16686 e você vai ver os traces de cada chamada LLM, independente do provider.

Opção 2: OpenLLMetry (Traceloop)

Pra quem quer instrumentação automática de múltiplos providers com o mínimo de código. O OpenLLMetry detecta os SDKs compatíveis instalados e instrumenta automaticamente.

Python

pip install traceloop-sdk openai anthropic
from traceloop.sdk import Traceloop
import openai
import anthropic

Traceloop.init(
    app_name="meu-agente",
    disable_batch=True  # pra ver traces imediatamente em dev
)

# OpenAI
oai_client = openai.OpenAI()
oai_response = oai_client.chat.completions.create(
    model="gpt-4.1",
    messages=[{"role": "user", "content": "Explique observabilidade."}]
)

# Anthropic
claude_client = anthropic.Anthropic()
claude_response = claude_client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Explique observabilidade."}]
)

# Ambas as chamadas geram traces automaticamente

Node.js / TypeScript

npm install @traceloop/node-server-sdk openai @anthropic-ai/sdk
import * as traceloop from "@traceloop/node-server-sdk";

// IMPORTANTE: inicializar ANTES de importar os clients
traceloop.initialize({ disableBatch: true });

import OpenAI from "openai";
import Anthropic from "@anthropic-ai/sdk";

const openaiClient = new OpenAI();
const anthropicClient = new Anthropic();

// Ambos são instrumentados automaticamente

Pra exportar traces, configure TRACELOOP_BASE_URL e, quando necessário, TRACELOOP_HEADERS, apontando para um collector ou backend OTLP compatível.

Opção 3: Langfuse (plataforma completa)

Langfuse é uma plataforma open-source de LLM engineering. Diferente das opções anteriores que são "só" instrumentação, o Langfuse entrega UI pronta: dashboard de custos, visualização de traces em árvore, sessões multi-turno, avaliação de qualidade e prompt management.

Self-hosted com Docker Compose

git clone https://github.com/langfuse/langfuse.git
cd langfuse
docker compose up -d

Acesse http://localhost:3000, crie um projeto e pegue as chaves da API.

Integração via OpenTelemetry (recomendada)

O Langfuse aceita traces via protocolo OTLP no endpoint /api/public/otel.

Importante: o endpoint OTLP do Langfuse é HTTP, não gRPC. Se você estiver usando o exportador otlp/http, funciona direto. Se estiver usando o exportador gRPC (como no setup base com Jaeger), vai precisar trocar o exporter ou colocar um OTel Collector no meio fazendo a conversão.

Configuração via variáveis de ambiente:

export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3000/api/public/otel
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(echo -n 'public_key:secret_key' | base64)"

Ou no código Python, usando o exportador HTTP:

import base64
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

credentials = base64.b64encode(b"public_key:secret_key").decode()
exporter = OTLPSpanExporter(
    endpoint="http://localhost:3000/api/public/otel/v1/traces",
    headers={"Authorization": f"Basic {credentials}"}
)

Pra usar o exportador HTTP, instale:

pip install opentelemetry-exporter-otlp-proto-http

Com isso, você pode usar o OpenLLMetry ou as instrumentações oficiais (OpenAI, Anthropic, Google) e enviar tudo pro Langfuse. Não importa qual provider.

Instrumentando um agente completo

As opções acima funcionam pra chamadas individuais. Mas um agente faz múltiplas chamadas em sequência, com decisões entre elas. Você precisa agrupar tudo num trace pai.

Vou usar a API da Anthropic neste exemplo pra mostrar que o padrão é o mesmo independente do provider.

Com OpenTelemetry manual + Claude

from opentelemetry import trace
import anthropic
import json

tracer = trace.get_tracer("meu-agente")
client = anthropic.Anthropic()

def executar_agente(pergunta: str):
    with tracer.start_as_current_span(
        "invoke_agent assistente-pedidos",
        attributes={
            "gen_ai.operation.name": "invoke_agent",
            "gen_ai.agent.name": "assistente-pedidos",
            "gen_ai.provider.name": "anthropic"
        }
    ) as agent_span:
        messages = [{"role": "user", "content": pergunta}]
        system_prompt = "Você ajuda com pedidos. Use as ferramentas disponíveis."

        tools = [
            {
                "name": "buscar_pedido",
                "description": "Busca info de um pedido pelo ID",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "pedido_id": {"type": "string"}
                    },
                    "required": ["pedido_id"]
                }
            }
        ]

        max_steps = 10
        for step in range(max_steps):
            # Cada chamada LLM gera um span filho automaticamente
            # via AnthropicInstrumentor
            response = client.messages.create(
                model="claude-sonnet-4-6",
                max_tokens=1024,
                system=system_prompt,
                messages=messages,
                tools=tools
            )

            # Claude retorna stop_reason: "end_turn" ou "tool_use"
            if response.stop_reason == "end_turn":
                texto_final = next(
                    (b.text for b in response.content if b.type == "text"), ""
                )
                agent_span.set_attribute("agent.steps", step + 1)
                return texto_final

            if response.stop_reason == "tool_use":
                # Adiciona a resposta do assistant ao histórico
                # no formato esperado pela SDK/API da Anthropic
                messages.append({
                    "role": "assistant",
                    "content": response.to_dict()["content"]
                })

                tool_results = []
                for block in response.content:
                    if block.type == "tool_use":
                        with tracer.start_as_current_span(
                            f"execute_tool {block.name}",
                            attributes={
                                "gen_ai.operation.name": "execute_tool",
                                "tool.name": block.name,
                                "tool.arguments": json.dumps(block.input)
                            }
                        ):
                            result = executar_ferramenta(
                                block.name, block.input
                            )
                            tool_results.append({
                                "type": "tool_result",
                                "tool_use_id": block.id,
                                "content": result
                            })

                messages.append({"role": "user", "content": tool_results})

        agent_span.set_attribute("agent.steps", max_steps)
        agent_span.set_attribute("agent.converged", False)
        return "Não consegui resolver."


def executar_ferramenta(nome: str, argumentos: dict) -> str:
    if nome == "buscar_pedido":
        return json.dumps({"status": "enviado", "previsao": "2025-03-15"})
    return json.dumps({"error": "ferramenta desconhecida"})

Algumas diferenças importantes entre os SDKs da Anthropic e OpenAI que afetam a instrumentação:

  • Stop reason: Anthropic usa stop_reason com valores "end_turn" e "tool_use". OpenAI usa finish_reason com "stop" e "tool_calls".
  • Tool results: No Anthropic, tool results vão como conteúdo do role "user" com type: "tool_result". No OpenAI, vão como role "tool".
  • System prompt: No Anthropic, vai no parâmetro system separado. No OpenAI, vai como primeira mensagem com role "system".

Mas nos traces OTel, ambos geram os mesmos atributos padronizados. Essa é a beleza: a observabilidade é normalizada.

Métricas que você precisa ter em dashboard

Com traces funcionando, é hora de extrair métricas.

1. Custo por execução

Preços de API mudam frequentemente. Não faça hardcode de valores no código. Uma abordagem melhor:

from dataclasses import dataclass
from pathlib import Path
import json
import os


@dataclass
class ModelPricing:
    input_per_mtok: float   # USD por 1M tokens de input
    output_per_mtok: float  # USD por 1M tokens de output


class CostCalculator:
    """Calcula custo de chamadas LLM a partir de um arquivo de config.

    Preços mudam frequentemente. Mantenha o arquivo de config atualizado
    consultando as páginas oficiais:
    - OpenAI: https://openai.com/api/pricing
    - Anthropic: https://docs.anthropic.com/en/docs/about-claude/pricing
    """

    def __init__(self, config_path: str | None = None):
        path = config_path or os.environ.get(
            "LLM_PRICING_CONFIG", "pricing.json"
        )
        self._pricing: dict[str, ModelPricing] = {}
        self._load(path)

    def _load(self, path: str) -> None:
        config_file = Path(path)
        if not config_file.exists():
            return
        data = json.loads(config_file.read_text())
        for model, prices in data.items():
            self._pricing[model] = ModelPricing(
                input_per_mtok=prices["input"],
                output_per_mtok=prices["output"]
            )

    def calculate(
        self, model: str, input_tokens: int, output_tokens: int
    ) -> float | None:
        pricing = self._pricing.get(model)
        if not pricing:
            return None
        return (
            input_tokens * pricing.input_per_mtok
            + output_tokens * pricing.output_per_mtok
        ) / 1_000_000

Com um pricing.json separado:

{
  "gpt-5.4": {"input": 2.50, "output": 15.00},
  "gpt-5-mini": {"input": 0.25, "output": 2.00},
  "gpt-4.1": {"input": 2.00, "output": 8.00},
  "gpt-4.1-mini": {"input": 0.40, "output": 1.60},
  "gpt-4.1-nano": {"input": 0.10, "output": 0.40},
  "claude-opus-4-6": {"input": 5.00, "output": 25.00},
  "claude-sonnet-4-6": {"input": 3.00, "output": 15.00},
  "claude-haiku-4-5": {"input": 1.00, "output": 5.00}
}

Os valores acima são referência de março/2026. Consulte sempre as páginas oficiais (OpenAI, Anthropic) antes de usar.

Vantagens dessa abordagem: o arquivo de preços pode ser atualizado sem tocar no código, versionado separadamente, e até carregado de uma URL se quiser automatizar.

2. Token acumulado por conversa

A cada step do agente, o context window cresce. Se você não monitora, um agente de 5 steps pode estar mandando 50k tokens no último call porque acumula todo o histórico.

Capture gen_ai.usage.input_tokens em cada chamada e plote a progressão. Se o crescimento é linear, tudo certo. Se é exponencial, tem algo errado no gerenciamento de contexto.

Isso é especialmente relevante em modelos com janelas de contexto muito grandes (200k+ tokens). Só porque cabe, não significa que deveria estar lá.

3. Steps até convergência

Histograma de quantos passos o agente leva pra resolver uma tarefa. Se a maioria resolve em 2-3 steps mas tem um tail de 10+, investigue esses casos.

4. Taxa de uso de ferramentas

Qual ferramenta é mais chamada? Alguma nunca é usada? Alguma falha frequentemente? Isso mostra se o agente tá usando bem o que tem disponível.

5. Latência decomposta

Não basta saber que a execução total levou 15s. Decompor:

  • Tempo em chamadas LLM (latência do provider)
  • Tempo em tool calls (latência da sua infra)
  • Tempo de processamento local

Se 80% do tempo tá em tool calls, o gargalo é a sua API, não o modelo.

Alertas que salvam

Configure alertas pra cenários críticos:

# Exemplo conceitual (adapte pro seu sistema de alertas)
alertas:
  - nome: agente_em_loop
    condição: agent.steps > 8
    ação: matar execução + notificar

  - nome: custo_alto
    condição: custo_execucao > 0.50  # USD
    ação: notificar

  - nome: latencia_alta
    condição: duracao_total > 30s
    ação: notificar

  - nome: token_explosion
    condição: input_tokens_ultima_chamada > 100000
    ação: matar execução + notificar

Na prática, implemente isso com métricas OTel + Prometheus + Alertmanager, ou use os alertas nativos do Grafana/Datadog/seu backend.

Grafana: visualizando tudo

Se você usa Grafana (e deveria, é grátis e open-source), aqui vai uma stack completa:

# docker-compose.yml
services:
  jaeger:
    image: jaegertracing/jaeger:latest
    ports:
      - "16686:16686"  # UI
      - "4317:4317"    # OTLP gRPC

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3001:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true

Configurando o Prometheus pra coletar métricas OTel, você consegue dashboards com:

  • Custo acumulado por hora/dia, segmentado por provider e modelo
  • Latência P50/P95/P99 por modelo (compare Claude vs GPT lado a lado)
  • Token usage ao longo do tempo
  • Top ferramentas mais chamadas
  • Execuções que excederam o limite de steps

Considerações de segurança

Capturar prompts e respostas em produção levanta questões sérias:

  1. PII (dados pessoais): se o usuário manda dados pessoais pro agente e você loga tudo, agora você tem PII no seu sistema de observabilidade. Defina políticas de retenção e considere mascaramento.

  2. Prompts de sistema: seu prompt de sistema é propriedade intelectual. Cuidado com quem tem acesso aos traces completos.

  3. Retenção: traces de LLM são grandes (tokens de input/output). Defina TTL agressivo pra dados de conteúdo (7-30 dias) e mantenha métricas agregadas por mais tempo.

  4. Ambiente: capture conteúdo de mensagens só em staging/debug. Em produção, capture apenas métricas e metadados.

Comparativo rápido

Pra fechar, quando usar o quê:

OTel puro + Jaeger/Grafana: você já tem infra de observabilidade OTel. Quer controle total. Escolhe o instrumentor específico do provider que usa.

OpenLLMetry: quer instrumentação automática de múltiplos providers e frameworks sem escrever código de instrumentação. Instala, inicializa, e tudo que passa pelo SDK é trackeado.

Langfuse: quer uma plataforma completa com UI pronta, dashboard de custos, prompt management e avaliação. Self-hosted ou cloud. Aceita dados via OTLP (HTTP), então combina com qualquer instrumentação.

As três opções não são excludentes. Você pode usar OpenLLMetry pra instrumentar tanto OpenAI quanto Anthropic e exportar pro Langfuse via OTLP. Ou usar instrumentações oficiais do OTel e mandar pro Jaeger + Grafana. Mix and match.

Conclusão

Agentes de AI em produção sem observabilidade é receita pra dor de cabeça. O ecossistema já tem ferramentas maduras o suficiente pra resolver isso, e todas suportam os principais providers.

Comece simples: instrumente suas chamadas LLM com o instrumentor do seu provider, exporte pra um Jaeger local, e veja os traces. Depois adicione métricas de custo e alertas. Quando sentir necessidade, suba pra uma plataforma como Langfuse.

O investimento é pequeno. A alternativa é descobrir que seu agente gastou $200 em tokens fazendo loop às 3 da manhã.