Skip to main content
GitHub

OpenAI (JS)

Instrument OpenAI in Node.js/TypeScript.

Auto-instrument the OpenAI Node.js SDK with a single function call.

Installation

npm install risicare openai

Quick Start

import { init } from 'risicare';
import { patchOpenAI } from 'risicare/openai';
import OpenAI from 'openai';
 
// Initialize Risicare
init();
 
// Wrap the OpenAI client
const openai = patchOpenAI(new OpenAI());
 
// All calls are now traced
const response = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Hello!' }],
});

How It Works

patchOpenAI() returns an ES Proxy that wraps all OpenAI methods:

import { patchOpenAI } from 'risicare/openai';
 
// The original client is wrapped, not modified
const original = new OpenAI();
const traced = patchOpenAI(original);
 
// Both work, but only `traced` creates spans
await original.chat.completions.create(...); // Not traced
await traced.chat.completions.create(...);   // Traced

Captured Attributes

AttributeDescription
gen_ai.systemopenai (or detected provider for compatible APIs)
gen_ai.request.modelRequested model name
gen_ai.request.temperatureTemperature, when set on the request
gen_ai.request.max_tokensMax tokens, when set on the request
gen_ai.request.has_toolsWhether the request carried tools
gen_ai.request.tool_countNumber of tools on the request
gen_ai.response.modelModel name returned by API
gen_ai.response.idResponse ID returned by API
gen_ai.response.finish_reasonsFinish reasons from the response
gen_ai.usage.prompt_tokensInput tokens
gen_ai.usage.completion_tokensOutput tokens
gen_ai.usage.total_tokensTotal tokens
llm.cost.total_usdThe cost from the SDK's own table — emitted only when the response model is in that table; an unrecognized model name yields no cost attribute. The dashboard shows the server's cost for a model that the server's price table knows, and this cost for any other model

Attributes that differ between the SDKs

The two SDKs do not set the same attributes on an OpenAI span. For example, only Python sets gen_ai.latency_ms, and only JavaScript sets gen_ai.request.top_p. Temperature, max_tokens, has_tools and response ID are captured by both — see the table above. For prompt and completion text, see Content Capture.

Streaming Support

Streaming calls create a span, but response attributes (token counts, cost) are not captured during streaming. Use non-streaming calls for complete telemetry.

const stream = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Write a story' }],
  stream: true,
});
 
// Span is created with model info; token counts are NOT available for streaming
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || '');
}

OpenAI-Compatible Providers

The proxy automatically detects OpenAI-compatible endpoints:

import { patchOpenAI } from 'risicare/openai';
import OpenAI from 'openai';
 
// Works with any OpenAI-compatible provider
const together = patchOpenAI(
  new OpenAI({
    baseURL: 'https://api.together.xyz/v1',
    apiKey: process.env.TOGETHER_API_KEY,
  })
);
 
// gen_ai.system is "together" (detected from baseURL); the span name stays
// openai.chat.completions.create
await together.chat.completions.create({
  model: 'meta-llama/Llama-3-70b-chat-hf',
  messages: [{ role: 'user', content: 'Hello!' }],
});

Supported Compatible Providers

ProviderBase URLAuto-detected
Together AIapi.together.xyzYes
Groqapi.groq.comYes
DeepSeekapi.deepseek.comYes
xAI (Grok)api.x.aiYes
Fireworksapi.fireworks.aiYes
Baseteninference.baseten.coYes
Novitaapi.novita.aiYes
BytePlusapi.byteplus.com, ark.cn-beijing.byteplus.comYes

Tool Calls

When a request includes tools, the LLM span records tool-usage counts as attributes. The JS OpenAI patch does not emit separate tool child-spans (no tool.name / tool.arguments) — it annotates the parent LLM span:

const response = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'What is the weather?' }],
  tools: [
    {
      type: 'function',
      function: {
        name: 'get_weather',
        parameters: { type: 'object', properties: { location: { type: 'string' } } },
      },
    },
  ],
});
 
// The LLM span carries:
// - gen_ai.request.has_tools: true
// - gen_ai.request.tool_count: 1
// - gen_ai.response.tool_call_count: <n>   (when the model returns tool_calls)

Configuration

Content Capture

With traceContent: true (and the project's content setting on), patchOpenAI records the prompt and completion text (gen_ai.prompt.<n>.role, gen_ai.prompt.<n>.content, gen_ai.completion.content). A streamed call records the prompt only.

Custom Metadata

init({
  metadata: {
    feature: 'chat',
    team: 'platform',
  },
});

Next Steps