02 Tracing
Este é o ponto inicial da etapa de tracing — o mesmo código de checkpoint/01-base-app, ainda sem integração com o Langfuse. Os pacotes já estão em package.json — execute npm inst...
O material do workshop é mantido no repositório público langfuse/langfuse-workshop. Use o repositório para executar a aplicação, acessar os branches de checkpoint e fazer a configuração local.
Ponto de partida
git checkout checkpoint/02-tracingEste é o ponto inicial da etapa de tracing — o mesmo código de checkpoint/01-base-app, ainda sem integração com o Langfuse. Os pacotes já estão em package.json; execute npm install se ainda não fez isso. Confirme que .env contém sua OPENAI_API_KEY e as chaves do Langfuse.
Por que fazemos tracing
Tracing registra cada etapa do agente — chamadas ao modelo, ferramentas invocadas, entradas e saídas — na ordem em que ocorreram. Ele transforma o agente de uma caixa-preta em algo que pode ser examinado depois; quando uma resposta estiver errada, você identifica a etapa exata em vez de adivinhar.
Para entender a motivação geral, consulte a lição da Langfuse Academy sobre tracing. Para detalhes técnicos — opções do SDK, internals do OpenTelemetry e atributos de spans — consulte a documentação de tracing.
Objetivo
Quando o pai pergunta "Como ligo o Bluetooth?", o agente não chama a OpenAI apenas uma vez. Ele pergunta o que fazer, chama get_support_context para buscar a configuração do iPhone, pergunta novamente, chama search_help_library para obter as etapas do Bluetooth e faz mais uma chamada à OpenAI para produzir a resposta numerada. Nada disso está visível hoje.
O objetivo é tornar todas essas etapas visíveis no Langfuse: um turno de chat vira um trace aninhado, com a execução do agente, as generations da OpenAI e as duas chamadas de ferramentas registradas em ordem.

Construiremos o trace em três etapas que refletem a estrutura do agente:
- Primeiro trace — registrar as generations da OpenAI.
- Traces aninhados — agrupar as generations em uma execução do agente por turno.
- Registro das ferramentas — transformar cada invocação em uma observação.
Etapa 1 — Primeiro trace
Queremos observar as chamadas à OpenAI para ver entradas, saídas, custo, tokens e tempo. Duas mudanças bastam.
src/server/index.ts
Inicie o processador de spans do Langfuse perto do início do arquivo:
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
new NodeSDK({ spanProcessors: [new LangfuseSpanProcessor()] }).start();O processador lê LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY e LANGFUSE_BASE_URL do ambiente do processo Node. O servidor carrega o .env do repositório antes do SDK; edite .env em vez de depender de valores exportados no shell.
Observação: se o último trace às vezes aparecer atrasado ao parar ou reiniciar npm run dev, volte a index.ts e transforme a linha acima nas variáveis langfuseSpanProcessor e sdk, permitindo que shutdown() faça flush:
const langfuseSpanProcessor = new LangfuseSpanProcessor();
const sdk = new NodeSDK({ spanProcessors: [langfuseSpanProcessor] });
sdk.start();
async function shutdown() {
server.close();
await langfuseSpanProcessor.forceFlush();
await sdk.shutdown();
}Isso não é necessário para compreender tracing, mas evita a confusão de perder o último trace no desenvolvimento local.
src/server/support-agent.ts
Adicione o import:
import { observeOpenAI } from "@langfuse/openai";Depois, envolva o cliente OpenAI no ponto em que ele é criado. Encontre esta linha em runSupportConversation:
const openai = new OpenAI({ apiKey: env.openaiApiKey });e altere para:
const openai = observeOpenAI(new OpenAI({ apiKey: env.openaiApiKey }));Essa é toda a mudança. Sem factory ou cliente bruto separado: observeOpenAI envolve o cliente inline, e openai.chat.completions.create(...) passa a emitir um trace em cada chamada.
Verifique: execute npm run dev, faça uma pergunta e atualize o Langfuse. Você verá uma generation por chamada, com prompt, resposta, tokens e latência. Cada generation ainda é um trace de nível superior; corrigiremos isso em seguida.

Etapa 2 — Traces aninhados
Para contextualizar as generations, vamos agrupá-las em uma execução de agente por turno. São três edições em src/server/support-agent.ts, sem mudar o corpo da função.
1. Adicione o import:
import { observe } from "@langfuse/tracing";2. Rebaixe a função existente. Encontre:
export async function runSupportConversation(request: ChatRequest): Promise<ChatResponse> {Remova o export e renomeie:
async function runSupportConversationInner(request: ChatRequest): Promise<ChatResponse> {O corpo permanece igual.
3. Adicione o export envolvido ao fim do arquivo:
export const runSupportConversation = observe(runSupportConversationInner, {
name: "dad-it-support-chat-turn",
asType: "agent"
});index.ts continua importando runSupportConversation da mesma forma. observe(...) captura automaticamente o argumento como entrada do trace e o retorno como saída.
Verifique: um turno agora deve aparecer como uma única observação dad-it-support-chat-turn, com a generation da OpenAI aninhada.

Etapa 3 — Registrar chamadas de ferramentas
A generation já menciona as ferramentas na saída tool_calls, mas não há uma observação da execução real — não vemos a entrada nem a saída. O mesmo padrão observe(...) pode envolver cada ferramenta.
src/server/tools.ts
Adicione o import e os dois helpers observados acima de executeTool. Depois, substitua o executeTool existente pela versão abaixo, para que o switch chame os helpers. Não altere TOOL_DEFINITIONS.
import { observe } from "@langfuse/tracing";
const getSupportContextTool = observe(
async () => {
const context = getSupportContext();
return {
ok: true,
context: {
id: context.id,
label: context.label,
devices: context.devices,
deviceSummary: context.deviceSummary,
responseStyle: context.responseStyle,
scopeHighlights: context.scopeHighlights,
notableApps: context.notableApps
}
};
},
{ name: "get_support_context", asType: "tool" }
);
const searchHelpLibraryTool = observe(
async (input: { question: string }) => {
const guides = searchGuides(input.question);
return {
ok: true,
results: guides.map((guide) => ({
id: guide.id,
title: guide.title,
summary: guide.summary,
steps: guide.steps,
caution: guide.caution ?? null
}))
};
},
{ name: "search_help_library", asType: "tool" }
);
export async function executeTool(name: string, input: Record<string, unknown>): Promise<ToolResult> {
switch (name) {
case "get_support_context":
return getSupportContextTool();
case "search_help_library":
return searchHelpLibraryTool({ question: String(input.question ?? "") });
default:
return { ok: false, error: `Unsupported tool: ${name}` };
}
}Se npm run dev parar com Multiple exports with the same name "executeTool", o executeTool original ainda está mais abaixo no arquivo. Exclua-o e mantenha apenas a versão acima.

Como verificar a conclusão
- Um turno cria um trace no Langfuse.
- Observação raiz:
dad-it-support-chat-turn(tipoagent). - Generation filha de
observeOpenAI(...)com prompt, resposta, tokens e latência. - Observações filhas de ferramentas:
get_support_context,search_help_library. - A entrada raiz é a requisição do chat; a saída raiz é a resposta.
Encerramento
Mesmo padrão, tipos de observação diferentes: observe(fn, { asType }) envolve uma função e emite um span com o nome e tipo informados. observeOpenAI(client) é uma versão especializada para o SDK da OpenAI.
Uma forma mais direta de adicionar tracing rico seguindo as práticas recomendadas é a skill do Langfuse (/langfuse). Ela aplica os padrões ao seu código sem que você implemente cada wrapper manualmente. Este percurso mostra o que acontece por baixo dos panos.
observeOpenAI envolve o SDK oficial da OpenAI — por baixo, é equivalente à instrumentação automática para OpenAI JS. Se você usa outro SDK — Anthropic, Vercel AI SDK ou seu próprio cliente HTTP — o catálogo de integrações oferece o wrapper ou guia equivalente.
Apêndice/Bônus — IDs de usuário e sessão
O percurso acima produz um trace limpo no formato pai → generation → ferramenta. Em seguida, muitas equipes querem segmentar por usuário e por sessão — para ver "todos os turnos deste usuário" ou "a sessão completa de ontem". Para simplificar a apresentação, pulamos essa etapa ao vivo, mas os checkpoints posteriores a incluem para que os capítulos seguintes usem as visualizações sem mais código. Consulte Sessions e Users na documentação.
Em resumo:
import { propagateAttributes } from "@langfuse/tracing";
return propagateAttributes(
{
userId: request.userId ?? `workshop-${context.id}`,
sessionId: request.sessionId,
tags: ["langfuse-workshop", "dad-it-support"]
},
async () => {
// ...the same tool-calling loop...
}
);Tudo dentro de propagateAttributes(...) — inclusive os spans filhos emitidos por observeOpenAI — recebe automaticamente userId, sessionId e tags. As visualizações Users, Sessions e os filtros de tags do Langfuse passam a funcionar assim que esses atributos existem.
Estado final
Esta aplicação com tracing concluído é o ponto de partida para 03-prompt-management e 04-monitoring.
01 Aplicação base
Você pode personalizar a experiência alterando as especificações do telefone em support-data.ts. Ao adicionar as informações do telefone do seu pai, as respostas serão adequadas ao aparelho correto.
03 Gerenciamento de prompts
Você tem uma aplicação funcional com tracing. O prompt de sistema está na constante SYSTEMPROMPT em src/server/support-agent.ts e é usado diretamente como mensagem de sistema.