@telemetry-dev/openai
v0.1.0
Published
OpenAI SDK instrumentation for telemetry.dev: generation and embedding spans for the official openai Node SDK.
Downloads
192
Readme
@telemetry-dev/openai
OpenAI SDK instrumentation for telemetry.dev. It wraps the official openai Node SDK and emits telemetry.dev generation and embedding spans through @telemetry-dev/sdk.
Install
pnpm add @telemetry-dev/sdk @telemetry-dev/openai openaiInitialize the core SDK first:
import { init, flush, shutdown } from "@telemetry-dev/sdk";
init({
apiKey: process.env.TELEMETRY_DEV_API_KEY,
baseUrl: process.env.TELEMETRY_DEV_BASE_URL,
serviceName: "my-service",
});
// app code...
await flush();
await shutdown();Per-client wrapping
import OpenAI from "openai";
import { wrapOpenAI } from "@telemetry-dev/openai";
const openai = wrapOpenAI(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Tell me a joke about OpenTelemetry" }],
});Use this when you want explicit control over which clients are instrumented.
Global instrumentation
import OpenAI from "openai";
import { instrumentOpenAI, uninstrumentOpenAI } from "@telemetry-dev/openai";
instrumentOpenAI();
const openai = new OpenAI();
try {
await openai.responses.create({
model: "gpt-4o-mini",
input: "Tell me a joke about OpenTelemetry",
});
} finally {
uninstrumentOpenAI();
}Use this as the app-wide one-liner at startup when all OpenAI clients should be instrumented.
Instrumented surfaces
client.chat.completions.create(...)client.chat.completions.parse(...)is covered by the OpenAI SDK because it routes throughcreate()client.chat.completions.stream(...)is covered for the same reasonclient.responses.create(...)client.responses.parse(...)is covered by the OpenAI SDK because it routes throughcreate()client.responses.stream({ input, model, ... })is covered for new responses because it routes throughcreate({ stream: true })client.embeddings.create(...)
The integration maps native OpenAI request/response shapes directly into telemetry.dev fields. It does not normalize messages into another schema.
Streaming
Chat completion streams are traced. Requests are sent unchanged by default, so token usage is only captured when the caller sets stream_options.include_usage themselves. Pass { injectStreamUsage: true } to wrapOpenAI or instrumentOpenAI to inject stream_options.include_usage automatically; the synthetic usage-only chunk is then hidden from the caller. Injection is opt-in because some providers reject stream_options — for example Azure OpenAI "on your data" (data_sources) returns 400 for it while plain stream: true works.
const openai = wrapOpenAI(new OpenAI(), { injectStreamUsage: true });Responses API streams are traced through responses.create({ stream: true }); terminal response.completed, response.failed, and response.incomplete events close the span.
Embeddings
Embedding calls emit gen_ai.operation.name = "embeddings", request model/input, response model, and token usage. Embedding vectors are intentionally not captured as output.
Azure OpenAI
wrapOpenAI(new AzureOpenAI(...)) records provider azure.ai.openai. Global prototype instrumentation defaults to provider detection from the resource's client when available.
Limitations
responses.stream({ response_id: ... })resumes an existing response throughretrieve(), which is not instrumented in this version.- Wrapped calls preserve
withResponse()andasResponse(), but the returned promise is not guaranteed to beinstanceofthe OpenAI SDK's internalAPIPromiseclass. - Unawaited OpenAI calls keep the SDK's lazy behavior: no request is made and no span is finished until the returned promise/stream is consumed.
