Documentation

OpenAI integration

Two first-class ways to trace OpenAI calls, and they compose:

PathHow it worksBest for
SDK wrapper (TypeScript)wrapOpenAI proxies the client object; every call emits a span over HTTPJS/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 requestPython or anything else with an OpenAI-compatible client — zero code change

SDK wrapper (TypeScript)

ts
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.

go
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.

rust
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.

bash
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 key
python
from 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 at GET/POST /api/v1/gateway/keys) in the Authorization header — 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.