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_usdCalculated cost — emitted only when the response model is in the pricing table; an unrecognized model name yields no cost attribute

The JS SDK does not capture prompt or completion content

This is the parity gap that matters. The Python SDK records the request and response bodies (gen_ai.prompt.N.role, gen_ai.prompt.N.content, gen_ai.completion.content, gen_ai.response.choices); the JavaScript SDK records none of them, and setting traceContent: true does not change this. If you rely on reading prompts and completions back out of a trace, the JS SDK will not give you that today.

Beyond content, the only other Python-only attribute is gen_ai.latency_ms (plus gen_ai.request.stream). Temperature, max_tokens, has_tools, finish reason and response ID are captured by the JS SDK — see the table above.

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,
  })
);
 
// Spans will show provider as "together" (detected from baseURL)
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

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

Disable Content Capture

init({
  traceContent: false, // Don't capture prompts/completions
});

Custom Metadata

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

Next Steps