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 | Calculated 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
| Provider | Base URL | Auto-detected |
|---|---|---|
| Together AI | api.together.xyz | Yes |
| Groq | api.groq.com | Yes |
| DeepSeek | api.deepseek.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
Disable Content Capture
init({
traceContent: false, // Don't capture prompts/completions
});Custom Metadata
init({
metadata: {
feature: 'chat',
team: 'platform',
},
});