> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreweave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Envoyer des traces OpenTelemetry vers Weave

> Importer des données de trace compatibles OpenTelemetry via un point de terminaison dédié

<Warning>
  Nous vous recommandons d’utiliser le [nouveau flux de travail natif OTel de Weave](/fr/products/wandb/weave/guides/tracking/trace-agents-otel) pour envoyer des spans OTel vers la nouvelle vue Agents de Weave. N’utilisez ce guide que si vous souhaitez ingérer des traces OTLP brutes dans la vue Traces standard de Weave.
</Warning>

Weave prend en charge l’importation de données de trace compatibles OpenTelemetry via un point de terminaison dédié. Ce point de terminaison vous permet d’envoyer des données de trace au format OTLP (OpenTelemetry Protocol) directement vers votre projet Weave. Utilisez cette intégration si vous souhaitez instrumenter votre application selon la norme OpenTelemetry et afficher ces traces aux côtés de vos autres données Weave, sans remplacer votre pipeline d’observabilité existant basé sur OTel.

Cette page présente les détails du point de terminaison, l’authentification, des exemples complets en Python et en TypeScript, la manière de transférer des traces via un OpenTelemetry Collector, l’organisation des traces en threads Weave, ainsi que les mappages d’attributs que Weave applique aux spans entrants.

<Note>
  Ce point de terminaison alimente les vues **Traces** et **Threads**. Pour envoyer des spans OTel d’un agent sur plusieurs tours de conversation vers la vue **Agents**, utilisez plutôt le point de terminaison des agents. Voir [Envoyer des spans OpenTelemetry vers la vue Agents](/fr/products/wandb/weave/guides/tracking/trace-agents-otel).
</Note>

<h2 id="endpoint-details">
  Détails du point de terminaison
</h2>

* **Chemin** : `/otel/v1/traces`
* **Méthode** : `POST`
* **Content-Type** : `application/x-protobuf`
* **URL de base** : l’URL de base du point de terminaison OTel pour les traces dépend de votre type de déploiement W\&B :

- Cloud mutualisé : `https://trace.wandb.ai/otel/v1/traces`.
- Instances Cloud dédié et instances autogérées : `https://<your-subdomain>.wandb.io/traces/otel/v1/traces`.

Remplacez `<your-subdomain>` par le domaine W\&B propre à votre organisation, par exemple `acme.wandb.io`.

<h2 id="authentication-and-routing">
  Authentification et acheminement
</h2>

Weave utilise l’en-tête `wandb-api-key` pour authentifier les requêtes, et les attributs de ressource de votre `TracerProvider` pour acheminer les spans vers l’entity et le projet appropriés. Transmettez votre clé API W\&B dans l’en-tête `wandb-api-key`, puis spécifiez les clés suivantes comme attributs Resource OpenTelemetry dans votre classe `TracerProvider` :

* `wandb.entity` : le nom de votre équipe ou de votre utilisateur CoreWeave Forge.
* `wandb.project` : le nom du projet auquel envoyer les traces.

L’exemple suivant montre comment configurer l’authentification et l’acheminement vers le projet :

<Warning>
  `WEAVE_OTLP_ENDPOINT` ci-dessous est une variable locale, et non la variable d’environnement `OTEL_EXPORTER_OTLP_ENDPOINT` du SDK OTel. Elle contient le chemin complet des traces (`.../otel/v1/traces`) et est transmise explicitement à `endpoint=`/`url:` sur l’exportateur, si bien que le SDK envoie les requêtes à cette URL telle quelle. Si vous définissez plutôt une valeur avec `export` dans la véritable variable `OTEL_EXPORTER_OTLP_ENDPOINT` et que vous construisez l’exportateur sans arguments (par exemple, `OTLPSpanExporter()`), le SDK ajoute lui-même `/v1/traces`. Cette variable doit alors contenir l’URL de base (`.../otel`), faute de quoi vous obtiendrez un chemin dupliqué (`.../otel/v1/traces/v1/traces`), une erreur `404` et des spans silencieusement ignorés. Il en va de même pour `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` : elle doit elle aussi contenir le chemin complet des traces, car le SDK n’y ajoute pas non plus /v1/traces.
</Warning>

<CodeGroup>
  ```python Python lines {7,8} theme={"system"}
  import os
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
  from opentelemetry.sdk import trace as trace_sdk
  from opentelemetry.sdk.resources import Resource

  WANDB_BASE_URL = "https://trace.wandb.ai"
  ENTITY = "<your-team-name>"
  PROJECT = "<your-project-name>"

  WEAVE_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

  # Créez une clé API sur https://forge.coreweave.com/settings
  WANDB_API_KEY = os.environ["WANDB_API_KEY"]

  exporter = OTLPSpanExporter(
      endpoint=WEAVE_OTLP_ENDPOINT,
      headers={"wandb-api-key": WANDB_API_KEY},
  )

  tracer_provider = trace_sdk.TracerProvider(resource=Resource({
      "wandb.entity": ENTITY,
      "wandb.project": PROJECT,
  }))
  ```

  ```typescript TypeScript lines theme={"system"}
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
  import { Resource } from "@opentelemetry/resources";

  const WANDB_BASE_URL = "https://trace.wandb.ai";
  const ENTITY = "<your-team-name>";
  const PROJECT = "<your-project-name>";

  const WEAVE_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

  // Créez une clé API sur https://forge.coreweave.com/settings
  const WANDB_API_KEY = process.env.WANDB_API_KEY!;

  const exporter = new OTLPTraceExporter({
    url: WEAVE_OTLP_ENDPOINT,
    headers: { "wandb-api-key": WANDB_API_KEY },
  });

  const provider = new NodeTracerProvider({
    resource: new Resource({
      "wandb.entity": ENTITY,
      "wandb.project": PROJECT,
    }),
  });
  ```
</CodeGroup>

<h2 id="examples">
  Exemples
</h2>

Les exemples suivants montrent comment envoyer des traces OpenTelemetry vers Weave avec Python et TypeScript. Chaque exemple illustre une approche différente : la bibliothèque d’instrumentation OpenInference, l’instrumentation OpenLLMetry, ou le SDK OpenTelemetry utilisé directement, sans package d’instrumentation.

Avant d’exécuter les exemples de code ci-dessous, définissez les champs suivants :

* `WANDB_API_KEY` : vous pouvez l’obtenir dans les [Paramètres utilisateur](https://forge.coreweave.com/settings).
* Entity : vous ne pouvez journaliser des traces que dans un projet appartenant à une équipe/entity à laquelle vous avez accès. Pour trouver le nom de votre entity, accédez à votre [tableau de bord W\&B](https://forge.coreweave.com/wandb) et consultez le champ **Teams** dans la barre latérale gauche.
* Nom du projet : choisissez un nom.
* `OPENAI_API_KEY` : vous pouvez l’obtenir depuis le [tableau de bord OpenAI](https://platform.openai.com/api-keys).

<h3 id="openinference-instrumentation">
  Instrumentation OpenInference
</h3>

[OpenInference](https://github.com/Arize-ai/openinference) est une bibliothèque d’instrumentation open source d’Arize AI qui capture les appels LLM sous forme de spans OpenTelemetry. Cet exemple montre comment utiliser l’instrumentation OpenAI. D’autres instrumentations sont disponibles dans le [dépôt officiel](https://github.com/Arize-ai/openinference).

Commencez par installer les dépendances requises :

<Tabs>
  <Tab title="Python">
    ```bash theme={"system"}
    pip install openai openinference-instrumentation-openai opentelemetry-exporter-otlp-proto-http
    ```
  </Tab>

  <Tab title="TypeScript">
    ```bash theme={"system"}
    npm install openai @opentelemetry/sdk-trace-node @opentelemetry/sdk-trace-base @opentelemetry/resources @opentelemetry/exporter-trace-otlp-proto @arizeai/openinference-instrumentation-openai @opentelemetry/api
    ```
  </Tab>
</Tabs>

<Warning>
  **Recommandation relative aux performances** : utilisez toujours `BatchSpanProcessor` plutôt que `SimpleSpanProcessor` lorsque vous envoyez des traces vers Weave. `SimpleSpanProcessor` exporte les spans de manière synchrone, ce qui peut nuire aux performances des autres charges de travail. Ces exemples utilisent `BatchSpanProcessor`, recommandé en production, car il regroupe les spans en lots de manière asynchrone et efficace.
</Warning>

<Tabs>
  <Tab title="Python">
    Collez le code suivant dans un fichier Python, par exemple `openinference_example.py` :

    ```python lines theme={"system"}
    import os
    import openai
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
    from openinference.instrumentation.openai import OpenAIInstrumentor

    OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
    WANDB_BASE_URL = "https://trace.wandb.ai"
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"

    WEAVE_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

    # Créez une clé API sur https://forge.coreweave.com/settings
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    exporter = OTLPSpanExporter(
        endpoint=WEAVE_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # Facultatif : affichez les spans dans la console.
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

    def main():
        client = openai.OpenAI(api_key=OPENAI_API_KEY)
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": "Describe OTel in a single sentence."}],
            max_tokens=20,
            stream=True,
            stream_options={"include_usage": True},
        )
        for chunk in response:
            if chunk.choices and (content := chunk.choices[0].delta.content):
                print(content, end="")

    if __name__ == "__main__":
        main()
    ```

    Exécutez le code :

    ```bash theme={"system"}
    python openinference_example.py
    ```
  </Tab>

  <Tab title="TypeScript">
    L’implémentation TypeScript de cet exemple présente les principales différences suivantes par rapport à l’implémentation Python :

    * OpenAI doit être importé avant l’enregistrement de l’instrumentation (les modules ESM l’exigent).
    * Elle utilise `@opentelemetry/exporter-trace-otlp-proto` (format protobuf) au lieu de l’exportateur HTTP, car le point de terminaison de W\&B n’accepte que le format protobuf.
    * Elle nécessite un appel explicite à `provider.shutdown()`, précédé d’un délai, afin de garantir le vidage des spans, car `BatchSpanProcessor` les vide de manière asynchrone.

    Collez le code suivant dans un fichier TypeScript, par exemple `openinference_example.ts` :

    ```typescript lines {11,12} theme={"system"}
    // IMPORTANT : importez OpenAI EN PREMIER pour que l’instrumentation puisse lui appliquer un patch
    import OpenAI from "openai";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { resourceFromAttributes } from "@opentelemetry/resources";
    import { OpenAIInstrumentation, isPatched } from "@arizeai/openinference-instrumentation-openai";

    const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
    const WANDB_BASE_URL = "https://trace.wandb.ai";
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";

    const WEAVE_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

    // Créez une clé API sur https://forge.coreweave.com/settings
    const WANDB_API_KEY = process.env.WANDB_API_KEY!;

    const exporter = new OTLPTraceExporter({
      url: WEAVE_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    const provider = new NodeTracerProvider({
      resource: resourceFromAttributes({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter)
      ],
    });

    provider.register();

    // Enregistrer l’instrumentation OpenAI auprès du fournisseur de traceurs
    const openAIInstrumentation = new OpenAIInstrumentation();
    openAIInstrumentation.setTracerProvider(provider);

    // Instrumenter OpenAI manuellement, car nous utilisons ESM
    openAIInstrumentation.manuallyInstrument(OpenAI);

    async function main() {
      console.log("OpenAI is patched?", isPatched());

      const client = new OpenAI({ apiKey: OPENAI_API_KEY });

      console.log("Making OpenAI API call...");
      const response = await client.chat.completions.create({
        model: "gpt-3.5-turbo",
        messages: [{ role: "user", content: "Describe OTel in a single sentence." }],
        max_tokens: 50,
      });

      console.log("Response:", response.choices[0]?.message?.content);
      console.log("Waiting for spans to flush...");
    }

    (async () => {
      await main();

      // Laisser aux spans le temps d’être vidés
      console.log("Waiting 2 seconds for spans to flush...");
      await new Promise(resolve => setTimeout(resolve, 2000));

      await provider.shutdown(); // vider tous les spans en attente avant de quitter
      console.log("Shutdown complete");
    })();
    ```

    Exécutez le code :

    ```bash theme={"system"}
    npx ts-node openinference_example.ts
    ```
  </Tab>
</Tabs>

<h3 id="openllmetry-instrumentation">
  Instrumentation OpenLLMetry
</h3>

[OpenLLMetry](https://github.com/traceloop/openllmetry) est une bibliothèque d’observabilité open source de Traceloop qui fournit une instrumentation OpenTelemetry pour les fournisseurs de LLM et les frameworks les plus répandus. L’exemple suivant montre comment utiliser son instrumentation OpenAI. D’autres exemples sont disponibles dans le [dépôt OpenLLMetry](https://github.com/traceloop/openllmetry).

Commencez par installer les dépendances requises :

<Tabs>
  <Tab title="Python">
    ```bash theme={"system"}
    pip install openai opentelemetry-instrumentation-openai opentelemetry-exporter-otlp-proto-http
    ```
  </Tab>

  <Tab title="TypeScript">
    ```bash theme={"system"}
    npm install openai @traceloop/instrumentation-openai @opentelemetry/sdk-trace-node @opentelemetry/resources @opentelemetry/exporter-trace-otlp-http
    ```
  </Tab>
</Tabs>

<Tabs>
  <Tab title="Python">
    Collez le code suivant dans un fichier Python, par exemple `openllmetry_example.py`. Ce code est identique à celui de l’exemple précédent, à ceci près que `OpenAIInstrumentor` est importé depuis `opentelemetry.instrumentation.openai` au lieu de `openinference.instrumentation.openai` :

    ```python lines theme={"system"}
    import os
    import openai
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
    from opentelemetry.instrumentation.openai import OpenAIInstrumentor

    OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
    WANDB_BASE_URL = "https://trace.wandb.ai"
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"

    WEAVE_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

    # Créez une clé API sur https://forge.coreweave.com/settings
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    exporter = OTLPSpanExporter(
        endpoint=WEAVE_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # Facultatif : affichez les spans dans la console.
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

    def main():
        client = openai.OpenAI(api_key=OPENAI_API_KEY)
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": "Describe OTel in a single sentence."}],
            max_tokens=20,
            stream=True,
            stream_options={"include_usage": True},
        )
        for chunk in response:
            if chunk.choices and (content := chunk.choices[0].delta.content):
                print(content, end="")

    if __name__ == "__main__":
        main()
    ```

    Exécutez le code :

    ```bash theme={"system"}
    python openllmetry_example.py
    ```
  </Tab>

  <Tab title="TypeScript">
    Collez le code suivant dans un fichier TypeScript, par exemple `openllmetry_example.ts`. Ce code utilise le package d’instrumentation OpenAI de Traceloop :

    ```typescript lines theme={"system"}
    import OpenAI from "openai";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import { BatchSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { Resource } from "@opentelemetry/resources";
    import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
    import { registerInstrumentations } from "@opentelemetry/instrumentation";

    const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
    const WANDB_BASE_URL = "https://trace.wandb.ai";
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";

    const WEAVE_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

    // Créez une clé API sur https://forge.coreweave.com/settings
    const WANDB_API_KEY = process.env.WANDB_API_KEY!;

    const exporter = new OTLPTraceExporter({
      url: WEAVE_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    const provider = new NodeTracerProvider({
      resource: new Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter),
        // Facultatif : afficher les spans dans la console.
        new BatchSpanProcessor(new ConsoleSpanExporter()),
      ],
    });

    provider.register();

    // Enregistrer l’instrumentation OpenAI auprès du fournisseur de traceurs
    const openAIInstrumentation = new OpenAIInstrumentation();
    registerInstrumentations({
      tracerProvider: provider,
      instrumentations: [openAIInstrumentation],
    });

    // Instrumenter OpenAI manuellement, car le code utilise ESM
    openAIInstrumentation.manuallyInstrument(OpenAI);

    async function main() {
      const client = new OpenAI({ apiKey: OPENAI_API_KEY });
      const stream = await client.chat.completions.create({
        model: "gpt-3.5-turbo",
        messages: [{ role: "user", content: "Describe OTel in a single sentence." }],
        max_tokens: 20,
        stream: true,
      });

      for await (const chunk of stream) {
        const content = chunk.choices[0]?.delta?.content;
        if (content) {
          process.stdout.write(content);
        }
      }
      console.log(); // saut de ligne à la fin du streaming
    }

    (async () => {
      await main();

      // Laisser aux spans le temps d’être vidés
      await new Promise(resolve => setTimeout(resolve, 2000));

      await provider.shutdown(); // vider tous les spans en attente avant de quitter
    })();
    ```

    Exécutez le code :

    ```bash theme={"system"}
    npx ts-node openllmetry_example.ts
    ```
  </Tab>
</Tabs>

<h3 id="without-instrumentation">
  Sans instrumentation
</h3>

Si vous préférez utiliser OTel directement plutôt qu’un package d’instrumentation, c’est possible. Cette approche vous donne un contrôle complet sur les attributs définis pour chaque span. Weave analyse les attributs de span conformément aux conventions sémantiques OpenTelemetry décrites à l’adresse [https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/).

Commencez par installer les dépendances requises :

<Tabs>
  <Tab title="Python">
    ```bash theme={"system"}
    pip install openai opentelemetry-sdk opentelemetry-api opentelemetry-exporter-otlp-proto-http
    ```
  </Tab>

  <Tab title="TypeScript">
    ```bash theme={"system"}
    npm install openai @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/resources @opentelemetry/exporter-trace-otlp-http
    ```
  </Tab>
</Tabs>

<Tabs>
  <Tab title="Python">
    Collez le code suivant dans un fichier Python, par exemple `opentelemetry_example.py` :

    ```python lines theme={"system"}
    import json
    import os
    import openai
    from opentelemetry import trace
    from opentelemetry.sdk import trace as trace_sdk
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor

    OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
    WANDB_BASE_URL = "https://trace.wandb.ai"
    ENTITY = "<your-team-name>"
    PROJECT = "<your-project-name>"

    WEAVE_OTLP_ENDPOINT = f"{WANDB_BASE_URL}/otel/v1/traces"

    # Créez une clé API sur https://forge.coreweave.com/settings
    WANDB_API_KEY = os.environ["WANDB_API_KEY"]

    # Configurer l’exportateur OTLP
    exporter = OTLPSpanExporter(
        endpoint=WEAVE_OTLP_ENDPOINT,
        headers={"wandb-api-key": WANDB_API_KEY},
    )

    tracer_provider = trace_sdk.TracerProvider(resource=Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
    }))
    tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

    # Facultatif : afficher les spans dans la console.
    tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    # Définir le fournisseur de traceurs
    trace.set_tracer_provider(tracer_provider)

    # Créer un traceur à partir du fournisseur de traceurs global
    tracer = trace.get_tracer(__name__)

    def my_function():
        with tracer.start_as_current_span("outer_span") as outer_span:
            client = openai.OpenAI()
            input_messages = [{"role": "user", "content": "Describe OTel in a single sentence."}]
            outer_span.set_attribute("input.value", json.dumps(input_messages))
            outer_span.set_attribute("gen_ai.system", "openai")
            response = client.chat.completions.create(
                model="gpt-3.5-turbo",
                messages=input_messages,
                max_tokens=20,
                stream=True,
                stream_options={"include_usage": True},
            )
            out = ""
            for chunk in response:
                if chunk.choices and (content := chunk.choices[0].delta.content):
                    out += content
            outer_span.set_attribute("output.value", json.dumps({"content": out}))

    if __name__ == "__main__":
        my_function()
    ```

    Exécutez le code :

    ```bash theme={"system"}
    python opentelemetry_example.py
    ```
  </Tab>

  <Tab title="TypeScript">
    Collez le code suivant dans un fichier TypeScript, par exemple `opentelemetry_example.ts` :

    ```typescript lines theme={"system"}
    import OpenAI from "openai";
    import { trace } from "@opentelemetry/api";
    import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
    import { BatchSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
    import { Resource } from "@opentelemetry/resources";

    const OPENAI_API_KEY = "YOUR_OPENAI_API_KEY";
    const WANDB_BASE_URL = "https://trace.wandb.ai";
    const ENTITY = "<your-team-name>";
    const PROJECT = "<your-project-name>";

    const WEAVE_OTLP_ENDPOINT = `${WANDB_BASE_URL}/otel/v1/traces`;

    // Créez une clé API sur https://forge.coreweave.com/settings
    const WANDB_API_KEY = process.env.WANDB_API_KEY!;

    const exporter = new OTLPTraceExporter({
      url: WEAVE_OTLP_ENDPOINT,
      headers: { "wandb-api-key": WANDB_API_KEY },
    });

    const provider = new NodeTracerProvider({
      resource: new Resource({
        "wandb.entity": ENTITY,
        "wandb.project": PROJECT,
      }),
      spanProcessors: [
        new BatchSpanProcessor(exporter),
        // Facultatif : afficher les spans dans la console.
        new BatchSpanProcessor(new ConsoleSpanExporter()),
      ],
    });

    provider.register();

    // Crée un traceur à partir du fournisseur de traceurs global
    const tracer = trace.getTracer("my-app");

    async function myFunction() {
      const span = tracer.startSpan("outer_span");

      try {
        const client = new OpenAI({ apiKey: OPENAI_API_KEY });
        const inputMessages = [
          { role: "user" as const, content: "Describe OTel in a single sentence." },
        ];

        // N’apparaîtra que dans le panneau latéral
        span.setAttribute("input.value", JSON.stringify(inputMessages));
        
        // Conforme aux conventions : apparaîtra dans le tableau de bord
        span.setAttribute("gen_ai.system", "openai");

        const stream = await client.chat.completions.create({
          model: "gpt-3.5-turbo",
          messages: inputMessages,
          max_tokens: 20,
          stream: true,
        });

        let output = "";
        for await (const chunk of stream) {
          const content = chunk.choices[0]?.delta?.content;
          if (content) {
            output += content;
          }
          }

        // N’apparaîtra que dans le panneau latéral
        span.setAttribute("output.value", JSON.stringify({ content: output }));
      } finally {
        span.end();
      }
    }

    myFunction();
    ```

    Exécutez le code :

    ```bash theme={"system"}
    npx ts-node opentelemetry_example.ts
    ```
  </Tab>
</Tabs>

Weave utilise les préfixes d’attributs de span `gen_ai` et `openinference` pour déterminer quelle convention appliquer, le cas échéant, lors de l’interprétation de la trace. Si aucune de ces clés n’est détectée, tous les attributs de span sont visibles dans la vue de trace. Le span complet est disponible dans le panneau latéral lorsque vous sélectionnez une trace.

<h2 id="use-an-opentelemetry-collector">
  Utiliser un OpenTelemetry Collector
</h2>

Les exemples précédents exportent les traces directement depuis votre application vers Weave. En production, vous pouvez placer un [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) entre votre application et Weave, en tant qu’intermédiaire. Le collecteur reçoit les traces de votre application, puis les transmet à un ou plusieurs backends. Ce modèle permet de centraliser la logique d’authentification, de traitement par lots et d’acheminement hors du code de votre application, et de répartir les traces vers plusieurs backends d’observabilité à partir d’un pipeline unique.

<h3 id="set-up-a-collector">
  Configurer un collecteur
</h3>

Cette section explique comment exécuter un OpenTelemetry Collector local dans Docker et configurer une application pour qu’elle lui envoie des traces. L’exemple suivant montre comment :

* Créer un fichier de configuration Docker qui déploie un serveur local (collecteur) chargé d’écouter les traces OTLP, de les regrouper par lots et de les transmettre vers Weave.
* Exécuter le collecteur localement avec Docker.
* Envoyer un appel de base à OpenAI qui transmet les traces au collecteur exécuté dans le conteneur Docker.

Pour utiliser un collecteur, créez d’abord un fichier `collector-config.yaml` qui configure le collecteur pour recevoir les traces OTLP et les exporter vers Weave :

```yaml lines {23,26} collector-config.yaml title="collector-config.yaml"  theme={"system"}
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

exporters:
  otlphttp/weave:
    endpoint: ${env:WANDB_OTLP_ENDPOINT}
    headers:
      wandb-api-key: ${env:WANDB_API_KEY}
    sending_queue:
      batch:

processors:
  resource:
    attributes:
      - key: wandb.entity # Champ des attributs de ressource
        value: ${env:DEFAULT_WANDB_ENTITY}  # Valeur à injecter
        action: insert # Injecter uniquement si l'attribut est absent
      - key: wandb.project
        value: ${env:DEFAULT_WANDB_PROJECT}
        action: insert 

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resource]
      exporters: [otlphttp/weave]
```

Ce fichier de configuration :

* Écoute les traces OTLP sur le port `4318` (HTTP).
* Exporte les traces vers le point de terminaison OTLP de Weave à l'aide de l'en-tête `wandb-api-key`, en lisant l'URL du point de terminaison dans `WANDB_OTLP_ENDPOINT` et la clé API dans `WANDB_API_KEY`.
* Définit `wandb.entity` et `wandb.project` comme attributs de ressource à l'aide du processeur `resource`, en lisant les valeurs dans `DEFAULT_WANDB_ENTITY` et `DEFAULT_WANDB_PROJECT`. L'action `insert` n'injecte ces attributs que si le code de votre application ne les définit pas déjà.
* Active la file d'attente intégrée `sending_queue` de l'exportateur, avec traitement par lots, afin de réduire la surcharge réseau.

Après avoir configuré les paramètres du collecteur, mettez à jour les valeurs de la clé API et de l'entity dans la commande Docker suivante, puis exécutez-la :

```bash lines {3,5} theme={"system"}
docker run \
  -v ./config.yaml:/etc/otelcol-contrib/config.yaml \
  -e WANDB_API_KEY="<your-wandb-api-key>" \
  -e WANDB_OTLP_ENDPOINT="https://trace.wandb.ai/otel" \
  -e DEFAULT_WANDB_ENTITY="<your-team-name>" \
  -e DEFAULT_WANDB_PROJECT="YOUR_PROJECT" \
  -p 4318:4318 \
  otel/opentelemetry-collector-contrib:latest
```

Une fois le collecteur démarré, configurez votre application pour qu'elle y exporte ses traces en définissant la variable d'environnement `OTEL_EXPORTER_OTLP_ENDPOINT`. Le SDK OTel lit automatiquement cette variable : vous n'avez donc pas besoin de transmettre le point de terminaison à l'exportateur.

Si vous définissez `wandb.entity` ou `wandb.project` comme attributs de ressource dans le `TracerProvider` de votre application, ils priment sur les valeurs par défaut définies dans la configuration du collecteur.

<CodeGroup>
  ```python Python lines theme={"system"}
  import os
  import openai
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
  from opentelemetry.sdk import trace as trace_sdk
  from opentelemetry.sdk.trace.export import BatchSpanProcessor
  from openinference.instrumentation.openai import OpenAIInstrumentor

  os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:4318"

  OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"

  tracer_provider = trace_sdk.TracerProvider()
  tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))

  OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

  def main():
      client = openai.OpenAI(api_key=OPENAI_API_KEY)
      response = client.chat.completions.create(
          model="gpt-3.5-turbo",
          messages=[{"role": "user", "content": "Describe OTel in a single sentence."}],
          max_tokens=20,
      )
      print(response.choices[0].message.content)

  if __name__ == "__main__":
      main()
  ```

  ```typescript TypeScript lines theme={"system"}
  import OpenAI from "openai";
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
  import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
  import { OpenAIInstrumentation, isPatched } from "@arizeai/openinference-instrumentation-openai";

  process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4318";

  const OPENAI_API_KEY = process.env.OPENAI_API_KEY;

  const provider = new NodeTracerProvider({
    spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())],
  });

  provider.register();

  const openAIInstrumentation = new OpenAIInstrumentation();
  openAIInstrumentation.setTracerProvider(provider);
  openAIInstrumentation.manuallyInstrument(OpenAI);

  async function main() {
    console.log("OpenAI is patched?", isPatched());

    const client = new OpenAI({ apiKey: OPENAI_API_KEY });
    const response = await client.chat.completions.create({
      model: "gpt-3.5-turbo",
      messages: [{ role: "user", content: "Describe OTel in a single sentence." }],
      max_tokens: 20,
    });

    console.log("Response:", response.choices[0]?.message?.content);
  }

  (async () => {
    await main();
    await new Promise(resolve => setTimeout(resolve, 2000));
    await provider.shutdown();
  })();
  ```
</CodeGroup>

`OpenAIInstrumentor` encapsule les appels OpenAI, crée des traces et les exporte vers le collecteur. Celui-ci se charge de l’authentification et de l’acheminement vers Weave.

Une fois le script exécuté, vous pouvez [afficher les traces](/fr/products/wandb/weave/guides/tracking/trace-tree) dans l’interface de Weights & Biases.

Pour envoyer les traces vers d’autres backends, ajoutez des exportateurs et incluez-les dans la liste `service.pipelines.traces.exporters`. Vous pouvez par exemple exporter à la fois vers Weave et vers Jaeger à partir d’une même instance du collecteur.

<h2 id="organize-otel-traces-into-threads">
  Organiser les traces OTel en threads
</h2>

Les [threads Weave](/fr/products/wandb/weave/guides/tracking/threads) vous permettent de regrouper des traces liées afin d’analyser des conversations sur plusieurs tours de conversation ou des sessions utilisateur comme un tout. Ajoutez des attributs de span spécifiques pour organiser vos traces OpenTelemetry en threads, puis utilisez l’interface Thread de Weave pour analyser des opérations liées, comme des conversations sur plusieurs tours de conversation ou des sessions utilisateur.

Ajoutez les attributs suivants à vos spans OTel pour activer le regroupement en threads :

* `wandb.thread_id` : regroupe les spans dans un thread donné.
* `wandb.is_turn` : marque un span comme un tour de conversation (il apparaît sous forme de ligne dans la vue du thread).

Les exemples suivants montrent comment organiser des traces OTel en threads Weave. Ils utilisent `wandb.thread_id` pour regrouper les opérations liées et `wandb.is_turn` pour marquer les opérations de haut niveau, qui apparaissent sous forme de lignes dans la vue du thread.

<Accordion title="Configuration initiale">
  Utilisez cette configuration pour exécuter ces exemples :

  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      import json
      import os
      from opentelemetry import trace
      from opentelemetry.sdk import trace as trace_sdk
      from opentelemetry.sdk.resources import Resource
      from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
      from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor

      # Configuration
      ENTITY = "<your-team-name>"
      PROJECT = "<your-project-name>"
      WANDB_API_KEY = os.environ["WANDB_API_KEY"]

      WEAVE_OTLP_ENDPOINT = "https://trace.wandb.ai/otel/v1/traces"

      exporter = OTLPSpanExporter(
          endpoint=WEAVE_OTLP_ENDPOINT,
          headers={"wandb-api-key": WANDB_API_KEY},
      )

      tracer_provider = trace_sdk.TracerProvider(resource=Resource({
          "wandb.entity": ENTITY,
          "wandb.project": PROJECT,
      }))
      tracer_provider.add_span_processor(BatchSpanProcessor(exporter))

      # Facultatif : afficher les spans dans la console
      tracer_provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

      trace.set_tracer_provider(tracer_provider)

      # Crée un traceur à partir du fournisseur de traceurs global
      tracer = trace.get_tracer(__name__)
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript lines theme={"system"}
      import { trace, context } from "@opentelemetry/api";
      import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
      import {
        BatchSpanProcessor,
        ConsoleSpanExporter,
      } from "@opentelemetry/sdk-trace-base";
      import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
      import { Resource } from "@opentelemetry/resources";

      // Configuration
      const ENTITY = "<your-team-name>";
      const PROJECT = "<your-project-name>";
      const WANDB_API_KEY = process.env.WANDB_API_KEY;

      if (!WANDB_API_KEY) {
        console.error("Error: WANDB_API_KEY environment variable is not set");
        console.error("Run: export WANDB_API_KEY=your_api_key_here");
        process.exit(1);
      }

      // Configuration OTel
      const WEAVE_OTLP_ENDPOINT = "https://trace.wandb.ai/otel/v1/traces";

      const exporter = new OTLPTraceExporter({
        url: WEAVE_OTLP_ENDPOINT,
        headers: { "wandb-api-key": WANDB_API_KEY },
      });

      // Initialiser le fournisseur de traceurs avec des processeurs de spans
      const provider = new NodeTracerProvider({
        resource: new Resource({
          "wandb.entity": ENTITY,
          "wandb.project": PROJECT,
        }),
        spanProcessors: [
          new BatchSpanProcessor(exporter),
          new BatchSpanProcessor(new ConsoleSpanExporter()),
        ],
      });

      // Enregistrer le fournisseur de traceurs
      provider.register();

      // Créer un traceur à partir du fournisseur de traceurs global
      const tracer = trace.getTracer("threads-examples");
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="Tracer un thread de base avec un seul tour de conversation">
  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      def example_1_basic_thread_and_turn():
          """Example 1: Basic thread with a single turn"""
          print("\n=== Example 1: Basic Thread and Turn ===")

          # Créer un contexte de thread
          thread_id = "thread_example_1"

          # Ce span représente un tour de conversation (enfant direct du thread)
          with tracer.start_as_current_span("process_user_message") as turn_span:
              # Définir les attributs du thread
              turn_span.set_attribute("wandb.thread_id", thread_id)
              turn_span.set_attribute("wandb.is_turn", True)

              # Ajouter quelques attributs d'exemple
              turn_span.set_attribute("input.value", "Hello, help me with setup")

              # Simuler un traitement avec des spans imbriqués
              with tracer.start_as_current_span("generate_response") as nested_span:
                  # Il s'agit d'un appel imbriqué dans le tour de conversation : is_turn doit donc être false ou non défini
                  nested_span.set_attribute("wandb.thread_id", thread_id)
                  # Pour les appels imbriqués, wandb.is_turn n'est pas défini ou est défini sur False

                  response = "I'll help you get started with the setup process."
                  nested_span.set_attribute("output.value", response)

              turn_span.set_attribute("output.value", response)
              print(f"Turn completed in thread: {thread_id}")

      def main():
          example_1_basic_thread_and_turn()

      if __name__ == "__main__":
          main()
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript twoslash lines theme={"system"}
      // @noErrors
      function example_1_basic_thread_and_turn() {
        console.log("\n=== Example 1: Basic Thread and Turn ===");

        // Créer un contexte de thread
        const threadId = "thread_example_1";

        // Ce span représente un tour de conversation (enfant direct du thread)
        tracer.startActiveSpan("process_user_message", (turnSpan) => {
          // Définir les attributs du thread
          turnSpan.setAttribute("wandb.thread_id", threadId);
          turnSpan.setAttribute("wandb.is_turn", true);

          // Ajouter quelques attributs d'exemple
          turnSpan.setAttribute("input.value", "Hello, help me with setup");

          let response: string;
          
          // Simuler un traitement avec des spans imbriqués
          tracer.startActiveSpan("generate_response", (nestedSpan) => {
            // Il s'agit d'un appel imbriqué dans le tour de conversation : is_turn doit donc être false ou non défini
            nestedSpan.setAttribute("wandb.thread_id", threadId);
            // Pour les appels imbriqués, wandb.is_turn n'est pas défini ou est défini sur false

            response = "I'll help you get started with the setup process.";
            nestedSpan.setAttribute("output.value", response);
            nestedSpan.end();
          });
          
          turnSpan.setAttribute("output.value", response!);
          console.log(`Turn completed in thread: ${threadId}`);
          turnSpan.end();
        });
      }

      function main() {
        example_1_basic_thread_and_turn();
      }

      main();
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="Tracer une conversation sur plusieurs tours de conversation partageant un même ID de thread">
  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      def example_2_multiple_turns():
          """Example 2: Multiple turns in a single thread"""
          print("\n=== Example 2: Multiple Turns in Thread ===")

          thread_id = "thread_conversation_123"

          # Tour de conversation 1
          with tracer.start_as_current_span("process_message_turn1") as turn1_span:
              turn1_span.set_attribute("wandb.thread_id", thread_id)
              turn1_span.set_attribute("wandb.is_turn", True)
              turn1_span.set_attribute("input.value", "What programming languages do you recommend?")

              # Opérations imbriquées
              with tracer.start_as_current_span("analyze_query") as analyze_span:
                  analyze_span.set_attribute("wandb.thread_id", thread_id)
                  # Pas d'attribut is_turn, ou défini sur False, pour les spans imbriqués

              response1 = "I recommend Python for beginners and JavaScript for web development."
              turn1_span.set_attribute("output.value", response1)
              print(f"Turn 1 completed in thread: {thread_id}")

          # Tour de conversation 2
          with tracer.start_as_current_span("process_message_turn2") as turn2_span:
              turn2_span.set_attribute("wandb.thread_id", thread_id)
              turn2_span.set_attribute("wandb.is_turn", True)
              turn2_span.set_attribute("input.value", "Can you explain Python vs JavaScript?")

              # Opérations imbriquées
              with tracer.start_as_current_span("comparison_analysis") as compare_span:
                  compare_span.set_attribute("wandb.thread_id", thread_id)
                  compare_span.set_attribute("wandb.is_turn", False)  # Explicitement False pour les spans imbriqués

              response2 = "Python excels at data science while JavaScript dominates web development."
              turn2_span.set_attribute("output.value", response2)
              print(f"Turn 2 completed in thread: {thread_id}")

      def main():
          example_2_multiple_turns()

      if __name__ == "__main__":
          main()
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript twoslash lines theme={"system"}
      // @noErrors
      function example_2_multiple_turns() {
        console.log("\n=== Example 2: Multiple Turns in Thread ===");

        const threadId = "thread_conversation_123";

        // Tour de conversation 1
        tracer.startActiveSpan("process_message_turn1", (turn1Span) => {
          turn1Span.setAttribute("wandb.thread_id", threadId);
          turn1Span.setAttribute("wandb.is_turn", true);
          turn1Span.setAttribute(
            "input.value",
            "What programming languages do you recommend?"
          );

          // Opérations imbriquées
          tracer.startActiveSpan("analyze_query", (analyzeSpan) => {
            analyzeSpan.setAttribute("wandb.thread_id", threadId);
            // Pas d'attribut is_turn, ou défini sur false, pour les spans imbriqués
            analyzeSpan.end();
          });

          const response1 =
            "I recommend Python for beginners and JavaScript for web development.";
          turn1Span.setAttribute("output.value", response1);
          console.log(`Turn 1 completed in thread: ${threadId}`);
          turn1Span.end();
        });

        // Tour de conversation 2
        tracer.startActiveSpan("process_message_turn2", (turn2Span) => {
          turn2Span.setAttribute("wandb.thread_id", threadId);
          turn2Span.setAttribute("wandb.is_turn", true);
          turn2Span.setAttribute("input.value", "Can you explain Python vs JavaScript?");

          // Opérations imbriquées
          tracer.startActiveSpan("comparison_analysis", (compareSpan) => {
            compareSpan.setAttribute("wandb.thread_id", threadId);
            compareSpan.setAttribute("wandb.is_turn", false); // Explicitement false pour les spans imbriqués
            compareSpan.end();
          });

          const response2 =
            "Python excels at data science while JavaScript dominates web development.";
          turn2Span.setAttribute("output.value", response2);
          console.log(`Turn 2 completed in thread: ${threadId}`);
          turn2Span.end();
        });
      }

      function main() {
        example_2_multiple_turns();
      }

      main();
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="Tracer des opérations profondément imbriquées et marquer uniquement le span le plus externe comme tour de conversation">
  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      def example_3_complex_nested_structure():
          """Example 3: Complex nested structure with multiple levels"""
          print("\n=== Example 3: Complex Nested Structure ===")

          thread_id = "thread_complex_456"

          # Tour de conversation avec plusieurs niveaux d'imbrication
          with tracer.start_as_current_span("handle_complex_request") as turn_span:
              turn_span.set_attribute("wandb.thread_id", thread_id)
              turn_span.set_attribute("wandb.is_turn", True)
              turn_span.set_attribute("input.value", "Analyze this code and suggest improvements")

              # Opération imbriquée de niveau 1
              with tracer.start_as_current_span("code_analysis") as analysis_span:
                  analysis_span.set_attribute("wandb.thread_id", thread_id)
                  # Pas de is_turn pour les opérations imbriquées

                  # Opération imbriquée de niveau 2
                  with tracer.start_as_current_span("syntax_check") as syntax_span:
                      syntax_span.set_attribute("wandb.thread_id", thread_id)
                      syntax_span.set_attribute("result", "No syntax errors found")

                  # Autre opération imbriquée de niveau 2
                  with tracer.start_as_current_span("performance_check") as perf_span:
                      perf_span.set_attribute("wandb.thread_id", thread_id)
                      perf_span.set_attribute("result", "Found 2 optimization opportunities")

              # Autre opération imbriquée de niveau 1
              with tracer.start_as_current_span("generate_suggestions") as suggest_span:
                  suggest_span.set_attribute("wandb.thread_id", thread_id)
                  suggestions = ["Use list comprehension", "Consider caching results"]
                  suggest_span.set_attribute("suggestions", json.dumps(suggestions))

              turn_span.set_attribute("output.value", "Analysis complete with 2 improvement suggestions")
              print(f"Complex turn completed in thread: {thread_id}")

      def main():
          example_3_complex_nested_structure()

      if __name__ == "__main__":
          main()
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript twoslash lines theme={"system"}
      // @noErrors
      function example_3_complex_nested_structure() {
        console.log("\n=== Example 3: Complex Nested Structure ===");

        const threadId = "thread_complex_456";

        // Tour de conversation avec plusieurs niveaux d'imbrication
        tracer.startActiveSpan("handle_complex_request", (turnSpan) => {
          turnSpan.setAttribute("wandb.thread_id", threadId);
          turnSpan.setAttribute("wandb.is_turn", true);
          turnSpan.setAttribute(
            "input.value",
            "Analyze this code and suggest improvements"
          );

          // Opération imbriquée de niveau 1
          tracer.startActiveSpan("code_analysis", (analysisSpan) => {
            analysisSpan.setAttribute("wandb.thread_id", threadId);
            // Pas de is_turn pour les opérations imbriquées

            // Opération imbriquée de niveau 2
            tracer.startActiveSpan("syntax_check", (syntaxSpan) => {
              syntaxSpan.setAttribute("wandb.thread_id", threadId);
              syntaxSpan.setAttribute("result", "No syntax errors found");
              syntaxSpan.end();
            });

            // Autre opération imbriquée de niveau 2
            tracer.startActiveSpan("performance_check", (perfSpan) => {
              perfSpan.setAttribute("wandb.thread_id", threadId);
              perfSpan.setAttribute("result", "Found 2 optimization opportunities");
              perfSpan.end();
            });

            analysisSpan.end();
          });

          // Autre opération imbriquée de niveau 1
          tracer.startActiveSpan("generate_suggestions", (suggestSpan) => {
            suggestSpan.setAttribute("wandb.thread_id", threadId);
            const suggestions = ["Use list comprehension", "Consider caching results"];
            suggestSpan.setAttribute("suggestions", JSON.stringify(suggestions));
            suggestSpan.end();
          });

          turnSpan.setAttribute(
            "output.value",
            "Analysis complete with 2 improvement suggestions"
          );
          console.log(`Complex turn completed in thread: ${threadId}`);
          turnSpan.end();
        });
      }

      function main() {
        example_3_complex_nested_structure();
      }

      main();
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="Tracer les opérations en arrière-plan qui appartiennent à un thread sans être des tours de conversation">
  <Tabs>
    <Tab title="Python">
      ```python lines theme={"system"}
      def example_4_non_turn_operations():
          """Exemple 4 : opérations qui font partie d'un thread sans être des tours de conversation"""
          print("\n=== Example 4: Non-Turn Thread Operations ===")

          thread_id = "thread_background_789"

          # Opération en arrière-plan qui fait partie du thread sans être un tour de conversation
          with tracer.start_as_current_span("background_indexing") as bg_span:
              bg_span.set_attribute("wandb.thread_id", thread_id)
              # wandb.is_turn n'est pas défini ou vaut false : il ne s'agit pas d'un tour de conversation
              bg_span.set_attribute("wandb.is_turn", False)
              bg_span.set_attribute("operation", "Indexing conversation history")
              print(f"Background operation in thread: {thread_id}")

          # Véritable tour de conversation dans le même thread
          with tracer.start_as_current_span("user_query") as turn_span:
              turn_span.set_attribute("wandb.thread_id", thread_id)
              turn_span.set_attribute("wandb.is_turn", True)
              turn_span.set_attribute("input.value", "Search my previous conversations")
              turn_span.set_attribute("output.value", "Found 5 relevant conversations")
              print(f"Turn completed in thread: {thread_id}")

      def main():
          example_4_non_turn_operations()

      if __name__ == "__main__":
          main()
      ```
    </Tab>

    <Tab title="TypeScript">
      ```typescript twoslash lines theme={"system"}
      // @noErrors
      function example_4_non_turn_operations() {
        console.log("\n=== Example 4: Non-Turn Thread Operations ===");

        const threadId = "thread_background_789";

        // Opération en arrière-plan qui fait partie du thread sans être un tour de conversation
        tracer.startActiveSpan("background_indexing", (bgSpan) => {
          bgSpan.setAttribute("wandb.thread_id", threadId);
          // wandb.is_turn n'est pas défini ou vaut false : il ne s'agit pas d'un tour de conversation
          bgSpan.setAttribute("wandb.is_turn", false);
          bgSpan.setAttribute("operation", "Indexing conversation history");
          console.log(`Background operation in thread: ${threadId}`);
          bgSpan.end();
        });

        // Véritable tour de conversation dans le même thread
        tracer.startActiveSpan("user_query", (turnSpan) => {
          turnSpan.setAttribute("wandb.thread_id", threadId);
          turnSpan.setAttribute("wandb.is_turn", true);
          turnSpan.setAttribute("input.value", "Search my previous conversations");
          turnSpan.setAttribute("output.value", "Found 5 relevant conversations");
          console.log(`Turn completed in thread: ${threadId}`);
          turnSpan.end();
        });
      }

      function main() {
        example_4_non_turn_operations();
      }

      main();
      ```
    </Tab>
  </Tabs>
</Accordion>

Une fois ces traces envoyées, vous pouvez les consulter dans l’interface de Weights & Biases, sous l’onglet **Threads**, où elles sont regroupées par `thread_id` et où chaque tour de conversation apparaît sur une ligne distincte.

<h2 id="attribute-mappings">
  Mappages d’attributs
</h2>

Weave mappe les attributs de span OpenTelemetry issus de divers frameworks d’instrumentation vers son modèle de données interne. Grâce à ce mappage, vous n’avez pas besoin de renommer ni de transformer les attributs de votre instrumentation existante pour bénéficier d’une vue détaillée dans Weave. Lorsque plusieurs noms d’attributs correspondent au même champ, Weave les applique par ordre de priorité, ce qui permet à plusieurs frameworks de coexister au sein des mêmes traces.

<h3 id="supported-frameworks">
  Frameworks pris en charge
</h3>

Weave prend en charge les conventions d’attributs des frameworks et SDK d’observabilité suivants :

* **OpenTelemetry GenAI** : conventions sémantiques standard pour l’IA générative (`gen_ai.*`).
* **OpenInference** : bibliothèque d’instrumentation d’Arize AI (`input.value`, `output.value`, `llm.*`, `openinference.*`).
* **Vercel AI SDK** : attributs de l’AI SDK de Vercel (`ai.prompt`, `ai.response`, `ai.model.*`, `ai.usage.*`).
* **MLflow** : attributs de suivi MLflow (`mlflow.spanInputs`, `mlflow.spanOutputs`).
* **Traceloop** : instrumentation OpenLLMetry (`traceloop.entity.*`, `traceloop.span.kind`).
* **Google Vertex AI** : attributs d’agent Vertex AI (`gcp.vertex.agent.*`).
* **OpenLit** : attributs d’observabilité OpenLit (`gen_ai.content.completion`).
* **Logfire / Pydantic AI** : instrumentation Pydantic AI de Logfire (`gen_ai.input.messages`, `gen_ai.output.messages`, `pydantic_ai.all_messages`, `final_result`).
* **Langfuse** : attributs de traçage Langfuse (`langfuse.startTime`, `langfuse.endTime`).

<h3 id="attribute-reference">
  Référence des attributs
</h3>

| Nom du champ d’attribut | Mappage W\&B | Description | Type | Exemple |
| :- | :- | :- | :- | :- |
| `ai.prompt` | `inputs` | Texte ou messages du prompt utilisateur. | String, list, dict | `"Write a short haiku about summer."` |
| `gen_ai.prompt` | `inputs` | Prompt ou tableau de messages destiné au modèle d’IA. | List, dict, string | `[{"role":"user","content":"abc"}]` |
| `input.value` | `inputs` | Valeur d’entrée pour l’invocation du modèle. | String, list, dict | `{"text":"Tell a joke"}` |
| `mlflow.spanInputs` | `inputs` | Données d’entrée du span. | String, list, dict | `["prompt text"]` |
| `gen_ai.input.messages` | `inputs` | Messages d’entrée émis par Logfire. | List, dict, string | `[{"role":"user","parts":[...]}]` |
| `pydantic_ai.all_messages` | `inputs` | Ensemble des messages d’un run d’agent Pydantic AI. | List | `[{"role":"user","content":"abc"}]` |
| `traceloop.entity.input` | `inputs` | Données d’entrée de l’entité. | String, list, dict | `"Translate this to French"` |
| `gcp.vertex.agent.tool_call_args` | `inputs` | Arguments de l’appel d’outil. | Dict | `{"args":{"query":"weather in SF"}}` |
| `gcp.vertex.agent.llm_request` | `inputs` | Charge utile de la requête LLM. | Dict | `{"contents":[{"role":"user","parts":[...]}]}` |
| `input` | `inputs` | Valeur d’entrée générique. | String, list, dict | `"Summarize this text"` |
| `inputs` | `inputs` | Tableau d’entrées générique. | List, dict, string | `["Summarize this text"]` |
| `ai.response` | `outputs` | Texte ou données de la réponse du modèle. | String, list, dict | `"Here is a haiku..."` |
| `gen_ai.completion` | `outputs` | Résultat de la complétion de l’IA. | String, list, dict | `"Completion text"` |
| `output.value` | `outputs` | Valeur de sortie du modèle. | String, list, dict | `{"text":"Answer text"}` |
| `gen_ai.output.messages` | `outputs` | Messages de sortie émis par Logfire. | List, dict, string | `[{"role":"assistant","parts":[...]}]` |
| `mlflow.spanOutputs` | `outputs` | Données de sortie du span. | String, list, dict | `["answer"]` |
| `final_result` | `outputs` | Sortie finale d’un run d’agent Pydantic AI. | String, dict | `"Answer text"` |
| `gen_ai.content.completion` | `outputs` | Résultat de la complétion du contenu. | String | `"Answer text"` |
| `traceloop.entity.output` | `outputs` | Données de sortie de l’entité. | String, list, dict | `"Answer text"` |
| `gcp.vertex.agent.tool_response` | `outputs` | Réponse issue de l’exécution de l’outil. | Dict, string | `{"toolResponse":"ok"}` |
| `gcp.vertex.agent.llm_response` | `outputs` | Charge utile de la réponse LLM. | Dict, string | `{"candidates":[...]}` |
| `output` | `outputs` | Valeur de sortie générique. | String, list, dict | `"Answer text"` |
| `outputs` | `outputs` | Tableau de sorties générique. | List, dict, string | `["Answer text"]` |
| `gen_ai.usage.input_tokens` | `usage.input_tokens` | Nombre de jetons d’entrée consommés. | Int | `42` |
| `gen_ai.usage.prompt_tokens` | `usage.prompt_tokens` | Nombre de jetons de prompt consommés. | Int | `30` |
| `llm.token_count.prompt` | `usage.prompt_tokens` | Nombre de jetons de prompt. | Int | `30` |
| `ai.usage.promptTokens` | `usage.prompt_tokens` | Jetons de prompt consommés. | Int | `30` |
| `gen_ai.usage.completion_tokens` | `usage.completion_tokens` | Nombre de jetons de complétion générés. | Int | `40` |
| `llm.token_count.completion` | `usage.completion_tokens` | Nombre de jetons de complétion. | Int | `40` |
| `ai.usage.completionTokens` | `usage.completion_tokens` | Jetons de complétion générés. | Int | `40` |
| `llm.usage.total_tokens` | `usage.total_tokens` | Nombre total de jetons utilisés dans la requête. | Int | `70` |
| `llm.token_count.total` | `usage.total_tokens` | Nombre total de jetons. | Int | `70` |
| `gen_ai.system` | `attributes.system` | Prompt système ou instructions. | String | `"You are a helpful assistant."` |
| `llm.system` | `attributes.system` | Prompt système ou instructions. | String | `"You are a helpful assistant."` |
| `gen_ai.system_instructions` | `attributes.system` | Prompt système ou instructions. | String | `"You are a helpful assistant."` |
| `weave.span.kind` | `attributes.kind` | Type ou catégorie du span. | String | `"llm"` |
| `traceloop.span.kind` | `attributes.kind` | Type ou catégorie du span. | String | `"llm"` |
| `openinference.span.kind` | `attributes.kind` | Type ou catégorie du span. | String | `"llm"` |
| `gen_ai.response.model` | `attributes.model` | Identifiant du modèle. | String | `"gpt-4o"` |
| `llm.model_name` | `attributes.model` | Identifiant du modèle. | String | `"gpt-4o-mini"` |
| `ai.model.id` | `attributes.model` | Identifiant du modèle. | String | `"gpt-4o"` |
| `llm.provider` | `attributes.provider` | Nom du fournisseur du modèle. | String | `"openai"` |
| `ai.model.provider` | `attributes.provider` | Nom du fournisseur du modèle. | String | `"openai"` |
| `gen_ai.request` | `attributes.model_parameters` | Paramètres de génération du modèle. | Dict | `{"temperature":0.7,"max_tokens":256}` |
| `llm.invocation_parameters` | `attributes.model_parameters` | Paramètres d’invocation du modèle. | Dict | `{"temperature":0.2}` |
| `wandb.display_name` | `display_name` | Nom d’affichage personnalisé dans l’interface. | String | `"User Message"` |
| `gcp.vertex.agent.session_id` | `thread_id` | Identifiant de session ou de thread. | String | `"thread_123"` |
| `wandb.thread_id` | `thread_id` | Identifiant de thread des conversations. | String | `"thread_123"` |
| `wb_run_id` | `wb_run_id` | Identifiant du run W\&B associé. | String | `"abc123"` |
| `wandb.wb_run_id` | `wb_run_id` | Identifiant du run W\&B associé. | String | `"abc123"` |
| `gcp.vertex.agent.session_id` | `is_turn` | Marque le span comme tour de conversation. | Boolean | `true` |
| `wandb.is_turn` | `is_turn` | Marque le span comme tour de conversation. | Boolean | `true` |
| `langfuse.startTime` | `start_time` (override) | Redéfinit l’horodatage de début du span. | Horodatage (ISO8601/unix ns) | `"2024-01-01T12:00:00Z"` |
| `langfuse.endTime` | `end_time` (override) | Redéfinit l’horodatage de fin du span. | Horodatage (ISO8601/unix ns) | `"2024-01-01T12:00:01Z"` |

<h2 id="limitations">
  Limitations
</h2>

L’interface utilisateur de Weights & Biases ne prend pas en charge le rendu des appels d’outil des traces OTel dans la vue Chat. Ceux-ci s’affichent à la place sous forme de JSON brut.
