@voightxyz/openai
v0.1.7
Published
Voight observability for the OpenAI SDK. Wrap your OpenAI client and capture every model call — prompts, tokens, costs, latency, errors — surfaced live in the Voight dashboard.
Maintainers
Readme
@voightxyz/openai
Voight observability for the OpenAI SDK. Wrap your OpenAI client and capture every model call — prompts, tokens, cache reads, tool calls, costs, latency, errors — surfaced live in the Voight dashboard.
Same backend and dashboard as @voightxyz/anthropic. Drop in whichever provider your app uses; events from both land side-by-side under the same agent.
Quick setup with the wizard
If your app already imports openai, the lowest-friction install is the wizard from the main SDK:
cd your-app
npx -y @voightxyz/sdk initIt detects openai (and @anthropic-ai/sdk if present) in your package.json, prompts for a privacy level + Voight key + agent name, and writes a ready-to-import src/lib/voight.ts with the wrapped client. 30 seconds, zero copy-paste. Full walkthrough at docs.voight.xyz/ai-apps/wizard.
Continue below if you'd rather wire it manually.
Install
npm install openai @voightxyz/openaiQuick start
import OpenAI from 'openai'
import { wrapOpenAI } from '@voightxyz/openai'
const client = wrapOpenAI(new OpenAI(), {
voightApiKey: process.env.VOIGHT_KEY,
agent: 'my-prod-agent',
})
const response = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello' }],
})That's it — every call is captured automatically. Visit your Voight dashboard to see them in real time.
Tracing & per-user attribution
For production apps where you want to group every LLM call inside one request into one trace, and attribute cost per end-user with one line of code, wrap each request boundary with withTrace:
import OpenAI from 'openai'
import { wrapOpenAI, withTrace, log } from '@voightxyz/openai'
const openai = wrapOpenAI(new OpenAI(), {
agent: 'production-chat-api',
privacy: 'standard',
})
app.post('/api/chat', async (req, res) => {
await withTrace(
async () => {
log('chat request received')
const reply = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: req.body.prompt }],
})
res.json({ reply })
},
{
routeTag: 'POST /api/chat',
tags: { userId: req.user.id, plan: req.user.plan },
},
)
})Every wrapped LLM call inside the withTrace block automatically inherits the routeTag and tags. The tags.userId field drives per-user spend tracking — the dashboard's Users sub-tab populates with per-customer cost as soon as your first request lands.
The same withTrace exported here also works in @voightxyz/anthropic — they share an async-context store, so an app calling both providers inside one request gets one trace, not two.
What's captured
| Signal | Where it lands |
|---|---|
| Model id (with version suffix) | model |
| Prompt messages | input.messages (or input.input for Responses API) |
| Response text | metadata.responseText |
| Token counts (input / output / total) | metadata.tokens |
| Cache reads (prompt_tokens_details.cached_tokens) | metadata.tokens.cache_read |
| Reasoning tokens (o1, o3 — Responses API only) | metadata.tokens.reasoning |
| Tool / function calls | metadata.toolCalls + toolExecuted |
| Streaming flag | metadata.streaming |
| API surface used (chat.completions vs responses) | metadata.api |
| Trace grouping (auto UUID or explicit) | metadata.sessionId |
| Finish reason / response status | metadata.finishReason |
| Latency (ms) | durationMs |
| Errors (re-thrown to the caller) | errorMessage + outcome: 'failed' |
Supported endpoints
client.chat.completions.create— legacy chat completions (non-streaming + streaming)client.responses.create— Responses API (non-streaming + streaming, function calls, reasoning models)
The wrapper passes everything else through untouched. Embeddings, images, audio, and the Azure OpenAI client are on the 0.2.0 roadmap.
Options
| Option | Type | Default | Purpose |
| --- | --- | --- | --- |
| voightApiKey | string | process.env.VOIGHT_KEY | Your Voight key from the dashboard |
| agent | string | process.env.VOIGHT_AGENT → HOSTNAME → 'unknown-agent' | Stable identifier surfaced in the dashboard |
| apiBase | string | https://api.voight.xyz | Override for self-hosted deployments |
| privacy | 'minimal' \| 'standard' \| 'full' | 'standard' | Capture aggressiveness |
| sessionId | string | auto UUID v4 | Trace grouping. Stable across calls of one wrapper instance |
| enabled | boolean | true | Kill switch — returns the original client untouched |
Privacy
Three levels apply to prompts, response text, and tool-call arguments. The function name in toolExecuted always survives as a tag (not user content).
| Level | Prompts | Response text | Tool arguments | Tokens / timing / model |
| --- | --- | --- | --- | --- |
| minimal | dropped | dropped | dropped | kept |
| standard (default) | scrubbed | scrubbed | scrubbed | kept |
| full | verbatim | verbatim | verbatim | kept |
Standard scrubs 12 patterns: PEM private keys, JWTs, Anthropic / OpenAI / Stripe live / GitHub / AWS / Slack / Voight API keys, emails, E.164 phones, and Luhn-validated credit cards.
See CHANGELOG.md for release notes.
License
Apache 2.0
