OpenAI integration
Two first-class ways to trace OpenAI calls, and they compose:
| Path | How it works | Best for |
|---|---|---|
| SDK wrapper (TypeScript) | wrapOpenAI proxies the client object; every call emits a span over HTTP | JS/TS apps that already hold an openai client |
| Gateway proxy (any language) | Point OPENAI_BASE_URL at AuditTrail; the server forwards upstream and traces every request | Python or anything else with an OpenAI-compatible client — zero code change |
SDK wrapper (TypeScript)
import OpenAI from "openai";
import { AuditTrailClient, wrapOpenAI } from "@audittrail/sdk";
const audit = new AuditTrailClient({
baseUrl: "https://your-audittrail-host",
apiKey: process.env.AUDITTRAIL_API_KEY,
});
const openai = wrapOpenAI(new OpenAI(), audit);
// Every call is traced — model, tokens, cost, gen_ai.* attributes.
const res = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "hello" }],
});The wrapper covers chat.completions.create and embeddings.create, is
structurally typed (the SDK has no runtime dependency on the openai
package), and joins whatever trace context is active — wrap your request
handler with the SDK's traceable() and the OpenAI spans nest under it.
SDK wrapper (Go)
The Go wrapper is an http.RoundTripper you install on the official
openai-go client — no monkey-patching, no provider dependency in the
SDK. It reads the active trace context off the outgoing request, so a
call nested inside WithSpan parents automatically.
import (
"github.com/openai/openai-go"
"github.com/openai/openai-go/option"
audittrail "github.com/Partha-dev01/AuditTrail/packages/sdk-go"
)
audit, _ := audittrail.New(audittrail.Config{APIKey: apiKey})
client := openai.NewClient(
option.WithHTTPClient(audittrail.OpenAIHTTPClient(audit)),
)
// Every chat/embedding call is now traced — model, tokens, gen_ai.*.SDK wrapper (Rust)
The Rust wrapper is generic over any Serialize request/response pair,
so it works with async-openai's create_byot (which returns
serde_json::Value) or any typed client — the SDK carries no
provider-crate dependency.
let request = serde_json::json!({ "model": "gpt-4o", "messages": [] });
let response: serde_json::Value = audit
.wrap_openai_chat(&request, || async { client.chat().create_byot(request.clone()).await })
.await?;Gateway proxy (any language)
No SDK at all: repoint the base URL and keep your existing client code.
export OPENAI_BASE_URL="https://your-audittrail-host/api/v1/gateway/proxy/v1"
export OPENAI_API_KEY="at-gw-..." # an AuditTrail VIRTUAL key, not your OpenAI keyfrom openai import OpenAI
client = OpenAI() # reads the env vars above — nothing else changes
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "hello"}],
)The gateway validates the virtual key, forwards to the real OpenAI API
using the upstream key configured on the server — the
AUDITTRAIL_GATEWAY_OPENAI_KEY env var (see
Gateway proxy) or a per-request stored secret via
the X-AuditTrail-Secret header (agent secrets) —
and records a fully-attributed span for every request — streaming
included. (BYOK keys added at /settings?tab=ai-assistant back the
Operations Assistant only; the gateway never reads them.) Requests for models with no configured provider key are
rejected loudly with 400 unsupported_model rather than silently mocked.
Full envelope details, virtual-key management and error semantics: Gateway proxy.
Which one?
- Want spans nested inside your app's own trace tree (tools, chains, business logic)? Use the wrapper (or a callback handler if you're on LangChain).
- Want zero code change and central key custody? Use the gateway.
- Both at once is fine — wrapper spans and gateway spans land in the same dashboard, and the gateway adds server-side governance (rules run on every proxied call).
Gotchas
- The gateway path expects an AuditTrail virtual key (
at-gw-…, created atGET/POST /api/v1/gateway/keys) in theAuthorizationheader — sending your raw OpenAI key there will be rejected. Your OpenAI key lives server-side, stored via BYOK. - Cost accounting resolves through the four-layer pricing system (agent-reported > operator overrides > bundled catalog > default), so new OpenAI models price correctly as soon as the catalog refreshes.