OpenAI (JS)
Instrument OpenAI in Node.js/TypeScript.
Auto-instrument the OpenAI Node.js SDK with a single function call.
Installation
npm install risicare openaiQuick 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(...); // TracedCaptured Attributes
| Attribute | Description |
|---|---|
gen_ai.system | openai (or detected provider for compatible APIs) |
gen_ai.request.model | Requested model name |
gen_ai.request.temperature | Temperature, when set on the request |
gen_ai.request.max_tokens | Max tokens, when set on the request |
gen_ai.request.has_tools | Whether the request carried tools |
gen_ai.request.tool_count | Number of tools on the request |
gen_ai.response.model | Model name returned by API |
gen_ai.response.id | Response ID returned by API |
gen_ai.response.finish_reasons | Finish reasons from the response |
gen_ai.usage.prompt_tokens | Input tokens |
gen_ai.usage.completion_tokens | Output tokens |
gen_ai.usage.total_tokens | Total tokens |
llm.cost.total_usd | The 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
| Provider | Base URL | Auto-detected |
|---|---|---|
| Together AI | api.together.xyz | Yes |
| Groq | api.groq.com | Yes |
| DeepSeek | api.deepseek.com | Yes |
| xAI (Grok) | api.x.ai | Yes |
| Fireworks | api.fireworks.ai | Yes |
| Baseten | inference.baseten.co | Yes |
| Novita | api.novita.ai | Yes |
| BytePlus | api.byteplus.com, ark.cn-beijing.byteplus.com | Yes |
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',
},
});