02 Tracing
C’est le point de départ du tracing — le même code que checkpoint/01-base-app, sans intégration Langfuse. Les paquets sont déjà dans package.json — exécutez npm inst...
Le contenu du workshop est maintenu dans le dépôt public langfuse/langfuse-workshop. Utilisez ce dépôt pour exécuter l’application, accéder aux branches de checkpoint et effectuer la configuration locale.
Point de départ
git checkout checkpoint/02-tracingC’est le point de départ du tracing — le même code que checkpoint/01-base-app, sans intégration Langfuse. Les paquets sont déjà dans package.json ; exécutez npm install si nécessaire. Vérifiez que .env contient votre OPENAI_API_KEY et les clés Langfuse.
Pourquoi tracer
Le tracing enregistre chaque étape de l’agent — appels au modèle, outils invoqués, entrées et sorties — dans l’ordre où elles se produisent. Il transforme l’agent d’une boîte noire en un système que vous pouvez examiner après coup ; lorsqu’une réponse est incorrecte, vous identifiez l’étape précise au lieu de deviner.
Pour la motivation générale, consultez la leçon Langfuse Academy sur le tracing. Pour les détails techniques — options du SDK, fonctionnement interne d’OpenTelemetry et attributs de spans — consultez la documentation du tracing.
Objectif
Lorsque le père demande « Comment activer le Bluetooth ? », l’agent ne contacte pas OpenAI une seule fois. Il demande quoi faire, appelle get_support_context pour obtenir la configuration de l’iPhone, interroge à nouveau OpenAI, appelle search_help_library pour les étapes Bluetooth, puis interroge encore OpenAI afin de produire la réponse numérotée. Rien de tout cela n’est visible aujourd’hui.
L’objectif est de rendre chaque étape visible dans Langfuse : un tour de chat devient un trace imbriqué qui enregistre dans l’ordre l’exécution de l’agent, les generations OpenAI et les deux appels d’outils.

Nous construirons le trace en trois étapes qui reflètent la structure de l’agent :
- Premier trace — enregistrer les generations OpenAI.
- Traces imbriqués — regrouper les generations sous une exécution d’agent par tour.
- Enregistrement des outils — faire de chaque invocation une observation.
Étape 1 — Premier trace
Nous voulons observer les appels OpenAI pour voir les entrées, sorties, coûts, tokens et durées. Deux changements suffisent.
src/server/index.ts
Démarrez le processeur de spans Langfuse vers le début du fichier :
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
new NodeSDK({ spanProcessors: [new LangfuseSpanProcessor()] }).start();Le processeur lit LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY et LANGFUSE_BASE_URL depuis l’environnement du processus Node. Le serveur charge le .env du dépôt avant le SDK ; modifiez .env au lieu de dépendre des valeurs exportées dans le shell.
Remarque : si le dernier trace apparaît parfois tard lors de l’arrêt ou du redémarrage de npm run dev, revenez à index.ts et transformez la ligne précédente en variables langfuseSpanProcessor et sdk, afin que shutdown() puisse effectuer le flush :
const langfuseSpanProcessor = new LangfuseSpanProcessor();
const sdk = new NodeSDK({ spanProcessors: [langfuseSpanProcessor] });
sdk.start();
async function shutdown() {
server.close();
await langfuseSpanProcessor.forceFlush();
await sdk.shutdown();
}Ce n’est pas nécessaire pour comprendre le tracing, mais cela évite de se demander où est passé le dernier trace pendant le développement local.
src/server/support-agent.ts
Ajoutez l’import :
import { observeOpenAI } from "@langfuse/openai";Puis enveloppez le client OpenAI à l’endroit où vous le créez. Recherchez cette ligne dans runSupportConversation :
const openai = new OpenAI({ apiKey: env.openaiApiKey });et remplacez-la par :
const openai = observeOpenAI(new OpenAI({ apiKey: env.openaiApiKey }));C’est toute la modification. Ni factory ni client brut séparé : observeOpenAI enveloppe le client inline et openai.chat.completions.create(...) émet désormais un trace à chaque appel.
Vérifiez : exécutez npm run dev, posez une question et actualisez Langfuse. Vous devez voir une generation par appel avec le prompt, la réponse, les tokens et la latence. Chaque generation reste encore un trace de premier niveau ; nous corrigerons cela ensuite.

Étape 2 — Traces imbriqués
Pour contextualiser les generations, nous les regroupons sous une exécution d’agent par tour. Trois modifications dans src/server/support-agent.ts, sans changer le corps de la fonction.
1. Ajoutez l’import :
import { observe } from "@langfuse/tracing";2. Rétrogradez la fonction existante. Recherchez :
export async function runSupportConversation(request: ChatRequest): Promise<ChatResponse> {Retirez export et renommez-la :
async function runSupportConversationInner(request: ChatRequest): Promise<ChatResponse> {Le corps reste identique.
3. Ajoutez l’export enveloppé à la fin du fichier :
export const runSupportConversation = observe(runSupportConversationInner, {
name: "dad-it-support-chat-turn",
asType: "agent"
});index.ts continue d’importer runSupportConversation de la même façon. observe(...) capture automatiquement l’argument de la fonction comme entrée du trace et la valeur renvoyée comme sortie.
Vérifiez : un tour de chat doit maintenant apparaître comme une observation dad-it-support-chat-turn unique, avec la generation OpenAI imbriquée.

Étape 3 — Enregistrer les appels d’outils
La generation mentionne déjà les appels dans sa sortie tool_calls, mais nous n’avons aucune observation de leur exécution réelle — impossible de voir l’entrée ou la sortie. Le même modèle observe(...) peut envelopper chaque outil.
src/server/tools.ts
Ajoutez l’import et les deux helpers observés au-dessus de executeTool. Ensuite, remplacez le executeTool existant par la version ci-dessous, afin que le switch appelle les helpers. Ne modifiez pas 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}` };
}
}Si npm run dev s’arrête avec Multiple exports with the same name "executeTool", le executeTool d’origine existe encore plus bas. Supprimez-le et ne gardez que la version ci-dessus.

Comment vérifier que vous avez terminé
- Un tour utilisateur crée un trace dans Langfuse.
- Observation racine :
dad-it-support-chat-turn(typeagent). - Generation enfant provenant de
observeOpenAI(...), avec prompt, réponse, tokens et latence. - Observations d’outils enfants :
get_support_context,search_help_library. - L’entrée racine est la requête de chat ; la sortie racine est la réponse.
Conclusion
Même modèle, différents types d’observations : observe(fn, { asType }) enveloppe une fonction et émet un span avec le nom et le type indiqués. observeOpenAI(client) est une version spécialisée pour le SDK OpenAI.
Une méthode plus directe pour ajouter un tracing riche selon les bonnes pratiques est la skill Langfuse (/langfuse). Elle applique les modèles recommandés à votre code sans vous obliger à créer chaque wrapper manuellement. Ce parcours montre ce qu’elle fait en coulisses.
observeOpenAI enveloppe le SDK OpenAI officiel — en coulisses, c’est l’équivalent de l’instrumentation automatique pour OpenAI JS. Si vous utilisez un autre SDK — Anthropic, Vercel AI SDK ou votre propre client HTTP —, le catalogue des intégrations fournit le wrapper ou le guide équivalent.
Annexe/Bonus — IDs utilisateur et session
Le parcours précédent produit un trace propre de forme parent → generation → outil. La demande suivante de nombreuses équipes est de segmenter les traces par utilisateur et par session — pour afficher « tous les tours de cet utilisateur » ou « la session complète d’hier matin ». Pour simplifier la démonstration en direct, nous sautons cette étape, mais les checkpoints suivants l’incluent afin que les chapitres ultérieurs utilisent ces vues sans autre modification. Consultez Sessions et Users dans la documentation Langfuse.
En résumé :
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...
}
);Tout ce qui se trouve dans le bloc propagateAttributes(...) — y compris les spans enfants émis par observeOpenAI — reçoit automatiquement userId, sessionId et les tags. Les vues Users, Sessions et les filtres de tags Langfuse deviennent disponibles dès que les attributs sont présents.
État final
Cette application terminée avec tracing est le point de départ de 03-prompt-management et 04-monitoring.
01 Application de base
Vous pouvez personnaliser l’expérience en modifiant les caractéristiques du téléphone dans support-data.ts. En ajoutant les informations du téléphone de votre père, vous obtiendrez des réponses adaptées au bon modèle.
03 Gestion des prompts
Vous disposez d’une application fonctionnelle avec tracing. Le prompt système réside dans la constante SYSTEMPROMPT de src/server/support-agent.ts et sert directement de message système.