Novo Cadastre-se grátis, 10 chamadas por nossa conta. Até US$ 1, sem cartão.

Rastreie um agente LLM com 3 headers HTTP, sem SDK

Adicione três headers às chamadas de modelo de um agente e veja cada execução em cascata: chamadas paralelas, tempo no seu código, falhas e custo.

Nossas escolhas

preços em tempo real
Modelo Veredito Preço
GPT-6 Luna Executa o ciclo de chamadas de funções da demonstração. Suas duas chamadas por turno (planejamento e resposta) custam $0.0002 juntas na execução mostrada nesta página. Agente principal da demonstração desde $0.1/M
DeepSeek V4.1 Flash Executa as três verificações paralelas de uma frase com o raciocínio desativado. Suas seis chamadas na sessão mostrada nesta página custam $0.0004, e uma sétima chamada do subagente falhou com custo de $0. Subagente da demonstração desde $0.3/M
Conteúdo
  1. O que enviar para gerar um trace?
  2. O que o agente da demonstração faz?
  3. Como ler o gráfico em cascata?
  4. O que uma sessão acrescenta ao trace?
  5. Como separar custos por funcionalidade ou usuário?
  6. O que acontece quando uma chamada falha?
  7. Como obter o custo de cada chamada no código?
  8. E se o agente já envia headers de rastreamento?
  9. O que fazer depois de obter um trace?
  10. O que ficou de fora?
  11. Perguntas frequentes
  12. Preciso de OpenTelemetry para rastrear um agente LLM?
  13. Qual é a diferença entre um trace e uma sessão?
  14. Os headers de rastreamento alteram o roteamento, a cobrança ou o que o modelo vê?
  15. Por quanto tempo os traces são mantidos?
  16. Uma chamada que falha tem algum custo?
  17. Leituras relacionadas

É difícil depurar um agente que faz cinco chamadas de modelo para responder a uma mensagem olhando apenas um log de requisições: não dá para saber quais chamadas pertencem à mesma execução, quais rodaram em paralelo ou quanto da espera veio do seu próprio código. Neste guia, adicionamos três headers de requisição a um pequeno agente de suporte e examinamos o resultado no console do Synthorai como um trace (uma execução desenhada numa linha do tempo) e uma sessão (uma conversa com seu custo). Não há SDK de rastreamento para instalar nem coletor (um serviço separado que recebe traces) para manter. O código está em github.com/synthorai-io/use-cases, no diretório agent-tracing/. Você precisa de Python 3.10 ou posterior e de uma chave de API do console.

TL;DR

  • Três headers de requisição (X-Trace-Id, X-Span-Id, X-Session-Id) em cada chamada de modelo transformam o log de requisições em traces e sessões no console do Synthorai, sem SDK de rastreamento.
  • No trace da demonstração, os 8.5 s de tempo total se dividem em cerca de 7.0 s nas chamadas de modelo e 1.6 s fora delas, no código do agente e na rede.
  • Uma chamada que falhou com 404 permaneceu no trace e custou $0.
  • A execução de demonstração, com 21 chamadas de modelo, custou $0.0017.
git clone https://github.com/synthorai-io/use-cases
cd use-cases
cp .env.example .env    # put your API key in it
cd agent-tracing
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python run.py --fail

Quando rodamos a demonstração em 11 de outubro de 2026, ela fez 21 chamadas de modelo em menos de um minuto e custou $0.0017. O programa imprime dois IDs de sessão e quatro IDs de trace. Abra Traces no console e você encontrará os mesmos IDs. O console usa o fuso horário do navegador, por isso as capturas destas páginas mostram 19:24 para 11:24 UTC.

Um trace no console: tempo total de 8.5 s dividido entre 7.0 s no gateway e 1.6 s no aplicativo, 6 chamadas de modelo com 1 falha, seguidas de um gráfico em cascata com a chamada de planejamento, um intervalo para ferramentas locais, três chamadas de subagente sobrepostas, uma marca de 95 ms com o rótulo 404 e a chamada de resposta. O painel à direita mostra a chamada que falhou: 95 ms, custo de $0, nenhuma cobrança e o texto do erro

O que enviar para gerar um trace?

Envie três headers em cada chamada de modelo, usando strings que você mesmo define. O gateway já registra todas as requisições. Os headers apenas indicam quais delas pertencem ao mesmo grupo.

HeaderUse o mesmo valor emResultado
X-Trace-Idtodas as chamadas de uma execução (o processamento completo de uma mensagem do usuário)as chamadas aparecem em um único gráfico em cascata
X-Span-Iduma única chamadao nome da linha dessa chamada no gráfico
X-Session-Idtodas as chamadas de uma conversaas execuções ficam agrupadas, com os totais da conversa

Um span é uma etapa de um trace. Neste guia, cada span corresponde a uma chamada de modelo. Cada ID pode ter até 128 caracteres ASCII imprimíveis. O gateway devolve os IDs na resposta e não os encaminha ao provedor do modelo.

Há mais três headers opcionais. X-Agent-Id identifica o subagente que fez a chamada, X-Parent-Agent-Id identifica o agente que iniciou esse subagente, e X-Synthorai-Metadata carrega rótulos como a funcionalidade ou o usuário final. Todos estão na referência de headers de requisição. A demonstração envia X-Agent-Id e rótulos, mas não envia X-Parent-Agent-Id: seu único subagente é iniciado pelo agente principal, sem outros níveis de aninhamento para descrever.

Na demonstração, uma função monta os headers de cada chamada. Essa função é toda a camada de rastreamento:

@dataclass
class Run:
    """One agent run, which the gateway shows as one trace."""
    session_id: str
    labels: dict = field(default_factory=dict)
    trace_id: str = field(default_factory=lambda: new_id("trace"))

    def headers(self, span: str, agent: str | None = None, parent: str | None = None) -> dict:
        h = {
            "X-Session-Id": self.session_id,
            "X-Trace-Id": self.trace_id,
            "X-Span-Id": span,
        }
        if agent:
            # Only sub-agents need an id. Calls without one belong to the main agent.
            h["X-Agent-Id"] = agent
        if parent:
            h["X-Parent-Agent-Id"] = parent
        if self.labels:
            h["X-Synthorai-Metadata"] = json.dumps(self.labels, separators=(",", ":"))
        return h

Todas as chamadas de modelo do agente passam por um wrapper que adiciona esses headers. No SDK Python da OpenAI, os headers por requisição vão em extra_headers:

raw = await client.chat.completions.with_raw_response.create(
    model=model, messages=messages,
    extra_headers=run.headers(span, agent, parent), **kwargs)
resp = raw.parse()
request_id = raw.headers.get("x-request-id")   # the gateway's id for this call

with_raw_response serve apenas para ler os headers da resposta. Se você não precisa do ID da requisição, basta usar create(..., extra_headers=...).

O que o agente da demonstração faz?

Ele responde à mensagem de um cliente em quatro etapas e cinco chamadas de modelo, escolhidas para tornar o trace útil. O agente principal usa GPT-6 Luna e o subagente usa DeepSeek V4.1 Flash; ambos são configurados por variáveis de ambiente. As chamadas do subagente usam reasoning_effort="none", porque uma verificação de uma frase não exige que o modelo raciocine antes. Se você configurar o subagente com um modelo que rejeita esse valor, altere ou remova ANALYST_REASONING.

SpanResponsávelO que acontece
planagente principallê a mensagem e solicita ferramentas (function calling: o modelo retorna um nome de função e argumentos para seu código executar)
nenhumseu códigoexecuta duas ferramentas locais em paralelo: uma consulta de pedido (1.2 s) e uma busca de políticas (0.6 s)
sentiment, policy-check, risksubagente analysttrês verificações de uma frase, enviadas ao mesmo tempo
answeragente principalescreve a resposta com base nos resultados das ferramentas e nas três verificações

As ferramentas são funções Python simples com um sleep, simulando um banco de dados e um índice de busca. Com --fail, uma chamada policy-check primeiro solicita um modelo inexistente, recebe um 404 e é repetida com o nome de span policy-check.retry.

run.py simula duas conversas com dois turnos cada. Cada conversa recebe um ID de sessão, e cada turno recebe um novo ID de trace:

session_id, history = new_id("support"), []
for text in turns:
    run = Run(session_id, labels={"feature": "support-agent", "release": "v2", "_user": user})
    reply = await handle(run, history, text)

Como ler o gráfico em cascata?

Cada linha representa uma chamada de modelo ou um intervalo no seu próprio código. Elas ficam numa linha do tempo que começa quando a primeira chamada da execução chega ao gateway. A captura acima mostra o segundo turno da primeira conversa. De cima para baixo:

  • plan levou 2.3 s. A barra mostra o tempo que o gateway gastou nessa requisição, do recebimento até o fim da resposta.
  • your app levou 1.4 s. Não havia requisições em andamento nesse intervalo, então o tempo foi gasto fora do gateway: no seu código e na rede entre ele e o gateway. Aqui são as duas ferramentas locais, e a mais lenta espera 1.2 s. Num agente real, é nessa linha que uma consulta lenta ao banco ou uma busca vetorial fica visível.
  • sentiment, risk e policy-check.retry se sobrepõem. O agente enviou as três verificações com asyncio.gather; chamadas paralelas aparecem como barras sobrepostas. A seta antes dos nomes identifica chamadas feitas por um subagente.
  • policy-check aparece como uma marca hachurada de 95 ms com 404. Uma chamada que falha permanece no trace com o código de status recebido pelo seu código.
  • answer levou 3.0 s e foi a chamada mais lenta, como também indica o resumo acima do gráfico.
  • A coluna Output mostra os tokens de saída de cada chamada. A geração da saída ocupa o tempo da chamada, então essa coluna costuma explicar o comprimento da barra ao lado: answer gerou 101 tokens, e cada uma das três verificações gerou de 14 a 57. O console sublinha a contagem quando a maior parte de uma saída mais longa foi usada para raciocínio, como ocorre com os 101 tokens aqui.

O resumo divide os 8.5 s de tempo total em 7.0 s no gateway e 1.6 s no aplicativo. Essa divisão responde à primeira dúvida na maioria das investigações de latência: olhar as chamadas de modelo ou o próprio código. Nesta execução, 82% da espera veio dos modelos, e as duas chamadas do agente principal, plan e answer, respondem por 5.3 s desse tempo. Portanto, usar um modelo mais rápido no agente principal ajudaria mais do que acelerar as três verificações.

O gateway não enxerga o que acontece dentro do seu código. Ele deduz as linhas your app pelos intervalos entre requisições, então um intervalo informa quanto tempo seu código rodou, mas não o que ele fez. Se você precisa de spans para suas próprias funções, use um SDK de OpenTelemetry, o padrão aberto de rastreamento. Os dois podem compartilhar IDs: quando você não envia X-Trace-Id, o gateway lê traceparent, o header padrão que carrega um ID de trace.

O que uma sessão acrescenta ao trace?

Uma sessão agrupa as execuções de uma conversa e soma seus dados. Abra uma pela lista de traces ou use a opção de abrir sessão em qualquer trace.

Uma sessão no console: 11 chamadas com custo de $0.0008 e um 404, os rótulos _user, feature e release, uma tabela de agentes com a conversa principal em gpt-6-luna e o subagente analyst em deepseek-v4.1-flash, e os dois traces da sessão com duração, entrada, acertos de cache e custo

A sessão acima é uma conversa de dois turnos com um cliente: 11 chamadas, 1,890 tokens de entrada e 750 de saída, $0.0008 e um erro. A página tem três partes:

  • Agentes nesta sessão separa chamadas e custos por agente. O agente principal fez 4 chamadas ao GPT-6 Luna por $0.0004, e o subagente analyst fez 7 chamadas por $0.0004: seis ao DeepSeek V4.1 Flash e uma que falhou. Essa tabela aparece porque as chamadas do subagente enviaram X-Agent-Id: analyst. Chamadas sem esse header são contabilizadas na conversa principal.
  • Traces nesta sessão lista cada turno com seus erros, duração, tokens de entrada, acertos de cache e custo. Assim você encontra o turno lento, com falha ou caro de uma conversa longa sem percorrer um log.
  • Acertos de cache é a proporção de tokens de entrada lidos do cache de prompts do provedor. Aqui ela é 0%, porque os prompts têm poucas centenas de tokens, e os provedores só armazenam prefixos acima de um tamanho mínimo (1,024 tokens na maioria deles; veja cache de prompts).

O ID de sessão tem outra função quando seus prompts ficam longos o suficiente para usar cache. Envie o mesmo X-Session-Id em todas as chamadas de uma conversa: o gateway o usa para manter a consistência dos acertos de cache de prompts, de modo que o segundo turno leia o cache gravado pelo primeiro. Em /v1/chat/completions e /v1/responses, você pode enviar o valor no corpo da requisição como session_id se for difícil configurar headers no seu framework.

Como separar custos por funcionalidade ou usuário?

Envie X-Synthorai-Metadata com um objeto JSON de strings, sem aninhamento, e depois agrupe ou filtre por ele. A demonstração rotula todas as chamadas com uma funcionalidade, uma versão e o usuário final:

labels = {"feature": "support-agent", "release": "v2", "_user": "u_1002"}

Os rótulos aparecem como pequenas tags no trace e na sessão. Para ver os totais, abra a área de análise, selecione a aba de detalhamento e encontre o cartão de custo por metadados. Escolha uma chave para listar todos os seus valores com requisições, erros, tokens de entrada e custo:

O cartão de custo por metadados com a chave _user selecionada: u_1001 com 22 requisições, 2 erros e $0.0018, e u_1002 com 20 requisições, 0 erros e $0.0017

O cartão acima cobre uma hora em que a demonstração rodou duas vezes: 22 requisições e $0.0018 para u_1001, cujas execuções incluem as duas chamadas que falharam, e 20 requisições e $0.0017 para u_1002. Após uma execução, você verá 11 e 10. Escolha feature como chave para ver o custo de cada funcionalidade, ou release para ver o efeito de uma mudança de prompt no custo. O cartão também permite comparar dois valores de uma chave lado a lado. Na mesma aba, o cartão de custo por agente soma cada X-Agent-Id entre sessões.

Para encontrar as execuções por trás de um número, use os rótulos na busca. Digite metadata._user = u_1002 na caixa de busca da lista de traces e pressione Enter para mostrar apenas as sessões e os traces desse usuário. A caixa de filtros dos logs aceita a mesma expressão para requisições individuais.

As regras dos rótulos são simples:

  • No máximo 10 rótulos por requisição. Uma chave tem de 1 a 32 caracteres entre a-z, 0-9, _, . e -. Um valor é uma string de até 128 bytes.
  • _user identifica o usuário final para quem a requisição foi feita. Outras chaves que começam com sublinhado são reservadas.
  • Um rótulo que viola uma regra é descartado, mas a requisição continua. O header de resposta X-Synthorai-Metadata-Dropped lista as chaves descartadas, então registre esse header durante a configuração. Para testar, enviamos uma chave chamada Bad?Key, e o header retornou com Bad?Key.
  • Não coloque texto de prompts, dados pessoais ou segredos nos rótulos. Use seu ID interno de usuário em _user, não um endereço de e-mail.

O que acontece quando uma chamada falha?

Ela permanece no trace como uma marca hachurada com seu código de status e não custa nada. Na demonstração, a chamada policy-check que falhou solicitou um ID de modelo inexistente. Selecioná-la no gráfico abre o painel à direita da captura acima, com o erro recebido pelo código: The model "model-that-does-not-exist" does not exist or is not available to this workspace, latência de 95 ms, custo de $0 e a indicação de que nada foi cobrado.

A demonstração dá à nova tentativa um nome de span próprio:

try:
    await call(run, span, model=BROKEN_MODEL, messages=messages, agent="analyst")
except APIStatusError:
    span = f"{span}.retry"      # the retry is its own span, so both show in the waterfall
resp = await call(run, span, model=ANALYST_MODEL, messages=messages, agent="analyst", max_tokens=400,
                  **analyst_options())   # reasoning_effort="none" unless ANALYST_REASONING says otherwise

Manter o ID de trace e mudar o nome do span coloca a falha e a nova tentativa lado a lado na mesma visualização. Se a lógica de retry estiver numa biblioteca que reutiliza os mesmos headers, as duas tentativas ainda aparecem, como duas linhas com o mesmo nome.

Os erros também são agregados. O resumo do trace mostra “6 chamadas de modelo, 1 falha”, a sessão lista seus erros por código de status (1 × 404 Not found), e a lista de traces tem uma coluna de erros que você pode ordenar para encontrar as execuções que precisam de atenção.

Como obter o custo de cada chamada no código?

Consulte o registro de cobrança da requisição no gateway usando o ID retornado na resposta. GET /v1/generation?id=<request id> retorna o custo, as contagens de tokens, a latência e o status de uma requisição.

async def cost_of(http: httpx.AsyncClient, request_id: str) -> float | None:
    """The billing record of one request. It is written a moment after the call returns."""
    if not request_id:
        return None                                  # a call that got no response has no record
    for _ in range(6):
        try:
            r = await http.get(f"{BASE_URL}/generation", params={"id": request_id})
            if r.status_code == 200:
                return r.json()["data"]["total_cost"]
        except (httpx.HTTPError, KeyError, TypeError, ValueError):
            pass                                     # a dropped connection or a half-written record: try again
        await asyncio.sleep(2)
    return None

O registro é gravado pouco depois do fim da resposta, então a função faz novas tentativas até receber um 200. Ela recebe um httpx.AsyncClient que o chamador cria uma única vez, com o header Authorization configurado, e passa a cada consulta. Consultar vinte chamadas pelo mesmo cliente reutiliza algumas conexões. Criar um cliente para cada consulta abre vinte conexões de uma vez; nas nossas execuções, uma ou duas delas caíram. A demonstração imprime uma linha por chamada com seus próprios tempos e esses registros. Este é o primeiro turno da sessão nas capturas:

trace trace-5258a3328d7d  session support-d8ac3cdd4411
  plan                 main     gpt-6-luna                 200   1878 ms    153 in   22 out  $0.000026
  sentiment            analyst  deepseek-v4.1-flash        200   1431 ms     91 in   28 out  $0.000061
  risk                 analyst  deepseek-v4.1-flash        200   1877 ms     96 in   33 out  $0.000068
  policy-check         analyst  deepseek-v4.1-flash        200   1885 ms     96 in   39 out  $0.000076
  answer               main     gpt-6-luna                 200   4142 ms    350 in  339 out  $0.000205

Os tempos aqui são medidos no cliente e incluem a rede, por isso são maiores que as barras do console, que medem a requisição dentro do gateway. No segundo turno, mostrado no gráfico acima, plan levou 2,499 ms no cliente e 2.3 s no gateway, enquanto answer levou 3,161 ms e 3.0 s. A diferença pode ser maior nas chamadas paralelas: a chamada que falhou levou 694 ms no cliente e 95 ms no gateway.

Copie essa função se quiser registrar o custo cobrado de cada chamada nos seus próprios logs. Ela não serve para interromper uma execução que está gastando demais: cada registro chega um pouco depois da chamada, e a função pode esperar vários segundos por ele. Para controlar o orçamento durante a execução, calcule o preço do usage de cada resposta conforme ela chega e use esses registros depois para conferir as contas.

E se o agente já envia headers de rastreamento?

Nesse caso, talvez você não precise adicionar nada. Para cada um dos três IDs ausente na requisição, o gateway lê o header equivalente que ferramentas comuns já enviam:

Ferramenta usadaHeader enviadoInterpretado como
Instrumentação HTTP do OpenTelemetrytraceparenttrace, com o ID de pai usado como span
SDK LiteLLMX-Litellm-Trace-Id, X-Litellm-Session-Idtrace, sessão
SDK Langfusebaggage: langfuse_trace_id=…, langfuse_session_id=…trace, sessão
Open WebUIX-OpenWebUI-Chat-Idsessão
Claude CodeX-Claude-Code-Session-Idsessão
OpenAI Codex CLISession-Idsessão

Se ambos estiverem presentes, seus próprios X-Trace-Id, X-Span-Id e X-Session-Id têm prioridade. A lista completa está na referência de headers de requisição. Frameworks de agentes que não enviam nenhum desses headers precisam recebê-los pela configuração de headers personalizados que oferecem.

O que fazer depois de obter um trace?

O uso mais comum é compará-lo com outro. Toda página de trace tem um botão de comparação: escolha um segundo trace, e o console mostra os dois numa página, com cada métrica como A, B e a diferença, cada chamada pareada com a chamada de mesmo nome e os dois gráficos na mesma linha do tempo. Assim você verifica o efeito de uma mudança no prompt, no modelo ou no seu código sobre a execução.

Três guias seguintes usam o mesmo agente e os mesmos headers, com problemas reproduzidos, corrigidos e comparados dessa forma:

Outros três examinam o mesmo log por outros ângulos:

O que ficou de fora?

A demonstração cobre chamadas de modelo. Uma configuração de produção costuma acrescentar quatro pontos:

  • Spans para seu próprio código. O gateway mostra os intervalos entre chamadas de modelo, mas não o que aconteceu neles. Instrumente ferramentas, recuperação de dados e filas com OpenTelemetry e propague traceparent, para que um único ID de trace conecte as duas visualizações.
  • Corpos dos prompts e das respostas. Este guia usa os registros de tempo, tokens e custo de cada chamada. Ele não cobre a inspeção do conteúdo enviado ao modelo.
  • Orçamentos. cost_of fornece o valor cobrado depois da chamada. Interromper uma execução ao atingir um limite exige calcular o preço do usage de cada resposta assim que ela chega, com algumas linhas no loop do agente que a demonstração não inclui.
  • Amostragem. Todas as chamadas são registradas, porque o trace é montado a partir do log de requisições que o gateway já mantém. Não há nada para amostrar, e a única sobrecarga no agente são alguns headers.

Perguntas frequentes

Preciso de OpenTelemetry para rastrear um agente LLM?

Não. Três headers de requisição (X-Trace-Id, X-Span-Id, X-Session-Id) em cada chamada de modelo bastam para gerar um gráfico em cascata das chamadas, com os intervalos entre elas atribuídos ao aplicativo. OpenTelemetry passa a ser útil quando você também quer spans para suas próprias funções.

Qual é a diferença entre um trace e uma sessão?

Um trace é uma execução do agente: tudo o que ele faz para processar uma mensagem do usuário ou uma tarefa. Uma sessão é uma conversa composta de várias execuções. Na demonstração, cada conversa com um cliente é uma sessão com dois traces, e cada trace tem cinco ou seis chamadas de modelo.

Os headers de rastreamento alteram o roteamento, a cobrança ou o que o modelo vê?

X-Trace-Id e X-Span-Id não alteram nada. X-Session-Id mantém a consistência dos acertos de cache de prompts de uma conversa. Nenhum desses headers é encaminhado ao provedor do modelo, e a cobrança usa o ID de requisição do próprio gateway.

Por quanto tempo os traces são mantidos?

Traces e sessões são montados a partir do log de requisições e cobrem os mesmos 90 dias. A lista de traces abre mostrando os últimos 7 dias.

Uma chamada que falha tem algum custo?

A chamada que falhou na demonstração, um 404 por ID de modelo desconhecido, foi registrada com custo de $0 e a indicação de que nada foi cobrado.

Leituras relacionadas

← Casos de uso