Skip to content
telemetry.dev
Esc
↑↓navigate↵open⌘Jpreview
On this page

OpenAI

Trace OpenAI chat, Responses API, embedding, and stream calls from TypeScript or Python.

The integration records OpenAI calls without changes to the OpenAI calls. Make a project at telemetry.dev. Copy an API key from the project setup page. Keys use the td_live_... format.

What it captures

The integration records:

  • Chat messages, Responses API input, system instructions, model parameters, and embedding input
  • Response IDs, response models, finish reasons, output messages, errors, and time to first chunk
  • Input, output, total, cache-read, and reasoning token usage when OpenAI supplies these values
  • Text, image, and audio token counts from OpenAI usage details, as gen_ai.usage.{text,image,audio}.{input_tokens,output_tokens,cache_read.input_tokens}
  • Tool-call deltas and arguments from chat streams
  • Image, speech, transcription, and translation calls, and batch create, retrieve, and cancel calls
  • Video create and retrieve calls and Realtime WebSocket responses in TypeScript only
  • The provider from the client: azure.ai.openai for Azure OpenAI, a matching provider for known OpenAI-compatible hosts, and openai for other calls.

Embedding vectors are not part of the span output. Image, audio, and video bytes are never captured, including binary fields nested in Responses API input and output.

Install

npm i @telemetry-dev/sdk @telemetry-dev/openai openai
pip install telemetry-dev-openai

TypeScript

Requirement Version
@telemetry-dev/sdk peer ^0.1.0
openai peer >=6 <8
Node.js from @telemetry-dev/sdk >=20.19.0

Python

Requirement Version
Python >=3.10
telemetry-dev >=0.2.6
openai >=2,<3

Quickstart

import OpenAI from "openai";
import { flush, init, shutdown } from "@telemetry-dev/sdk";
import { wrapOpenAI } from "@telemetry-dev/openai";

init({ serviceName: "openai-app" });
const client = wrapOpenAI(new OpenAI());

try {
  const completion = await client.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      { role: "system", content: "You are a helpful bot" },
      { role: "user", content: "Tell me a joke about OpenTelemetry" },
    ],
    max_completion_tokens: 200,
  });
  console.log(completion.choices[0]?.message.content ?? "(no content)");
} finally {
  await flush();
  await shutdown();
}
import os

import telemetry_dev
from openai import OpenAI
from telemetry_dev_openai import wrap_openai

telemetry_dev.init(service_name="openai-app")
client = wrap_openai(OpenAI())

try:
    completion = client.chat.completions.create(
        model=os.getenv("MODEL", "gpt-4o-mini"),
        messages=[
            {"role": "system", "content": "You are a helpful bot"},
            {"role": "user", "content": "Tell me a joke about OpenTelemetry"},
        ],
        max_completion_tokens=200,
    )
    print(completion.choices[0].message.content or "(no content)")
finally:
    telemetry_dev.flush()
    telemetry_dev.shutdown()

Global instrumentation

Use global instrumentation when client wrappers are not practical:

import OpenAI from "openai";
import { flush, init, shutdown } from "@telemetry-dev/sdk";
import {
  instrumentOpenAI,
  uninstrumentOpenAI,
} from "@telemetry-dev/openai";

init({ serviceName: "openai-app" });
instrumentOpenAI();
const client = new OpenAI();

try {
  await client.embeddings.create({
    model: "text-embedding-3-small",
    input: "OpenTelemetry",
  });
} finally {
  uninstrumentOpenAI();
  await flush();
  await shutdown();
}

API

TypeScript

Function Purpose
wrapOpenAI(client, options?) Instruments one client. More calls for the same client have no effect.
instrumentOpenAI(options?) Instruments clients with the OpenAI resource prototypes.
uninstrumentOpenAI() Restores the resource prototypes. Client wrappers stay active.
wrapOpenAIRealtime(connection, options?) Instruments one Realtime WebSocket connection. Refer to Realtime.

Python

Function Purpose
wrap_openai(client, *, inject_stream_usage=False) Instruments one synchronous or asynchronous client.
instrument_openai(*, inject_stream_usage=False) Instruments the OpenAI resource classes.
uninstrument_openai() Restores the resource methods.

Options

TypeScript

PropType
injectStreamUsage?boolean

Add stream_options.include_usage when the caller does not set it. Hide the final usage-only chunk.

Typeboolean
Defaultfalse

injectStreamUsage is opt-in. Azure OpenAI requests with data_sources reject stream_options.

Python

PropType
inject_stream_usage?bool

Add stream_options.include_usage and hide the final usage-only chunk.

Typebool
DefaultFalse

The integration instruments chat create and parse, Responses create, retrieve, and parse, embeddings create, images, audio, and batches. It instruments synchronous and asynchronous resources.

Calls

OpenAI call Span
chat.completions.create() chat {model} generation
responses.create() chat {model} generation
Streamed responses.retrieve() chat unknown generation
embeddings.create() embeddings {model} embedding

The integration keeps asResponse() and withResponse() on traced promises. These methods add the request ID and HTTP status to the span.

Media and batches

Image, audio, and video calls produce generation spans with operation generate_content. The span names differ by language:

OpenAI call TypeScript span Python span
images.generate(), images.edit(), image variations image {model} generate_content {model}
audio.speech.create() speech {model} generate_content {model}
audio.transcriptions.create() transcription {model} generate_content {model}
audio.translations.create() translation {model} generate_content {model}
videos.create() video {model} No span
videos.retrieve() video unknown No span
batches.create(), retrieve(), and cancel() openai.batch.{action} openai.batch.{action}

Image spans keep the prompt and token usage. In TypeScript, non-streamed image spans also keep the image count and revised prompts. Speech spans keep the text input but not the returned audio. Transcription and translation spans keep the returned text and token usage.

Batch spans use the custom operations openai.batch.create, openai.batch.retrieve, and openai.batch.cancel, because OpenTelemetry defines no standard batch operation. Each span records the batch ID and status. Batch list pagination has no instrumentation in Python.

TypeScript traces video calls when the installed OpenAI SDK has client.videos. The Python integration does not instrument client.videos, so video calls from Python produce no spans.

Realtime

The TypeScript integration traces Realtime WebSocket connections through an explicit wrapper. instrumentOpenAI() does not cover them.

Install the OpenAI Realtime client’s optional ws dependency and its TypeScript types:

npm i ws
npm i -D @types/ws
import { OpenAIRealtimeWS } from "openai/realtime/ws";
import { wrapOpenAIRealtime } from "@telemetry-dev/openai";

const realtime = wrapOpenAIRealtime(
  new OpenAIRealtimeWS({ model: "gpt-realtime" }),
  { model: "gpt-realtime" },
);

Each response.create event starts one generation span. The span finishes on response.done, a transport error, or an early close, and it records output timing and modality usage.

To match responses that complete out of order, the wrapper adds a private correlation field to response.metadata and keeps the caller’s fields. OpenAI allows at most 16 metadata fields. When the caller already sends 16, the request goes out unchanged and the span fails immediately, because the wrapper cannot match its events.

PropType
traceTimeoutMs?number

Fail an unfinished span after this many milliseconds. Must be a positive finite number.

Typenumber
Default300000
maxInFlight?number

Maximum open spans for one connection. Past this limit, the oldest span fails and is removed. Must be a positive integer.

Typenumber
Default100

The Python integration does not wrap Realtime connections.

Streaming

TypeScript

Chat streams combine content, refusal, and tool-call argument deltas. Responses streams close on completed, incomplete, failed, or error events.

responses.stream({ response_id }) has instrumentation. Its span starts as chat unknown because the first response event supplies the model. A non-streamed responses.retrieve() call has no span.

The integration records an iteration error and returns the error to the caller. Consume or close each stream to finish its span.

Python

chat.completions.stream() and responses.stream() use the instrumented methods. The integration does not instrument the stream helpers directly.

responses.stream(response_id=...) has instrumentation. A retrieve() call without stream=True has no span.

The default request does not change. Set stream_options={"include_usage": True} or use inject_stream_usage=True to record streamed chat usage.

Instrument the client before access to with_raw_response or with_streaming_response. The OpenAI SDK stores bound methods at the first access to each namespace.

Speech spans with a streamed or downloaded response cover the request, not the later byte reads. A transcription stream span stays open until you consume the stream.

Provider detection

AzureOpenAI and AsyncAzureOpenAI calls use provider azure.ai.openai.

A client with an OpenAI-compatible base URL on one of these domains, or a subdomain of one, uses the matching provider:

Base URL domain Provider
openrouter.ai openrouter
groq.com groq
x.ai x_ai
deepseek.com deepseek
together.xyz together_ai
fireworks.ai fireworks_ai

Other clients use openai.

Flush

TypeScript: For a short script, call await flush() and await shutdown() before exit. In serverless code, call await flush() before return. You can also pass the work to waitUntil.

Python: For a short script, call telemetry_dev.flush() and telemetry_dev.shutdown() before exit. In serverless code, call flush() before the runtime freezes.

Open the trace explorer to examine the spans. Refer to the quickstart for API-key setup.

Last updated on August 3, 2026

Was this page helpful?