Skip to main content
The TypeScript SDK wraps your existing LLM client instances and automatically captures latency, token counts, costs, and errors for every call — with no changes to your application logic beyond initialization.

Installation

1

Install the package

npm install @zespan/sdk
All provider integrations are optional peer dependencies. Install only what you use:
npm install openai                    # OpenAI
npm install @anthropic-ai/sdk         # Anthropic
npm install @google/generative-ai     # Google Generative AI
npm install @aws-sdk/client-bedrock-runtime  # AWS Bedrock
npm install @mistralai/mistralai      # Mistral
npm install groq-sdk                  # Groq
npm install @langchain/core           # LangChain
2

Initialize Zespan

Call zespan.init() once at application startup, before any LLM calls are made.
import { zespan } from "@zespan/sdk";

zespan.init({
  apiKey: process.env.ZESPAN_API_KEY!,
  environment: "production",
});
3

Wrap your LLM client

Pass your client instance through the appropriate wrapper function. The wrapper returns the same client — your existing code continues to work unchanged.
import OpenAI from "openai";
import { zespan } from "@zespan/sdk";

const openai = zespan.wrapOpenAI(new OpenAI());

const response = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello!" }],
});

Init options

zespan.init(options) accepts the following configuration:
apiKey
string
required
Your Zespan API key. Must start with zsp_. Find this in your project settings.
environment
string
default:"production"
Environment label attached to every event. Use "staging" or "development" to separate traces by environment.
storePrompts
boolean
default:"true"
When true (default), prompt and completion text are stored alongside traces with PII redaction applied before transmission. Set to false to disable prompt storage entirely.
sampleRate
number
default:"1.0"
Fraction of events to send, between 0.0 and 1.0. Set to 0.1 to trace 10% of calls.
redactKeys
string[]
Keys whose values are redacted before any data is stored. Applied regardless of storePrompts.
batchSize
number
default:"50"
Number of events to accumulate before flushing.
flushInterval
number
default:"2000"
Milliseconds between automatic flushes. The SDK also flushes on process exit.
baseURL
string
default:"https://api.zespan.com"
Override the API base URL. Use this only for self-hosted deployments.
enableOTel
boolean
default:"false"
When true, also exports spans to an OpenTelemetry-compatible backend. Requires otelEndpoint.
debug
boolean
default:"false"
When true, logs internal errors to the console. Enable during integration testing.

Provider wrappers

OpenAI

import OpenAI from "openai";
import { zespan } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const openai = zespan.wrapOpenAI(new OpenAI());

// Non-streaming
const response = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Summarize this article." }],
});

// Streaming — TTFT captured automatically
const stream = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Write a poem." }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}

Anthropic

import Anthropic from "@anthropic-ai/sdk";
import { zespan } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const anthropic = zespan.wrapAnthropic(new Anthropic());

const message = await anthropic.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Explain monads in plain English." }],
});

Google Generative AI

import { GoogleGenerativeAI } from "@google/generative-ai";
import { zespan } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const google = zespan.wrapGoogle(new GoogleGenerativeAI(process.env.GOOGLE_API_KEY!));
const model = google.getGenerativeModel({ model: "gemini-2.5-flash" });
const result = await model.generateContent("What is quantum entanglement?");

AWS Bedrock

import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime";
import { wrapBedrock } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const bedrock = wrapBedrock(new BedrockRuntimeClient({ region: "us-east-1" }));

const response = await bedrock.converse({
  modelId: "amazon.nova-lite-v1:0",
  messages: [{ role: "user", content: [{ text: "Summarize this document." }] }],
});
wrapBedrock patches client.converse. The converseStream method is not currently wrapped — streaming Bedrock calls are not traced.

Mistral

import { Mistral } from "@mistralai/mistralai";
import { wrapMistral } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const mistral = wrapMistral(new Mistral({ apiKey: process.env.MISTRAL_API_KEY! }));

const response = await mistral.chat.complete({
  model: "mistral-small-latest",
  messages: [{ role: "user", content: "What is the capital of France?" }],
});

Groq

import Groq from "groq-sdk";
import { wrapGroq } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const groq = wrapGroq(new Groq({ apiKey: process.env.GROQ_API_KEY! }));

const response = await groq.chat.completions.create({
  model: "llama-3.3-70b-versatile",
  messages: [{ role: "user", content: "Explain gradient descent." }],
});

OpenRouter

import OpenAI from "openai";
import { zespan } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const openrouter = zespan.wrapOpenRouter(
  new OpenAI({
    baseURL: "https://openrouter.ai/api/v1",
    apiKey: process.env.OPENROUTER_API_KEY!,
  })
);

const response = await openrouter.chat.completions.create({
  model: "anthropic/claude-sonnet-4-6",
  messages: [{ role: "user", content: "Hello from OpenRouter!" }],
});

LiteLLM

import { wrapLiteLLM } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const litellm = wrapLiteLLM({
  baseURL: "http://localhost:4000",
  apiKey: process.env.LITELLM_API_KEY,
});

const response = await litellm.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello from LiteLLM!" }],
});

Context enrichment

Use withLumiqtraceContext to attach a userId, sessionId, or custom tags to all traces generated within a function scope.
import { withLumiqtraceContext, zespan } from "@zespan/sdk";
import OpenAI from "openai";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());

export async function POST(req: Request) {
  const { userId, sessionId } = await getSession(req);

  return withLumiqtraceContext(
    { userId, sessionId, tags: { feature: "chat", plan: "pro" } },
    async () => {
      const response = await openai.chat.completions.create({
        model: "gpt-4o",
        messages: [{ role: "user", content: await req.text() }],
      });
      return Response.json(response);
    }
  );
}
Set userId and sessionId on every request that involves a logged-in user. This enables per-user cost breakdown and session replay in the Zespan dashboard.

Agent tracing

Use withAgent to trace a multi-step agent workflow. It creates an agent span and exposes methods to log plans, trace tool calls, and record handoffs.
import { withAgent, zespan } from "@zespan/sdk";
import OpenAI from "openai";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());

await withAgent(
  {
    name: "CustomerSupportAgent",
    role: "specialist",
    framework: "custom",
    tools: [{ name: "lookup_order", description: "Lookup order by id" }],
  },
  async (agent) => {
    agent.logPlan(["Lookup order", "Check refund policy", "Draft response"]);

    const order = await agent.traceTool(
      "lookup_order",
      { orderId: "123" },
      async () => ({ id: "123", status: "delivered", total: 49.99 })
    );

    agent.delegateTo("RefundPolicyAgent", "refund requested");

    const response = await openai.chat.completions.create({
      model: "gpt-4o",
      messages: [{ role: "user", content: `Order: ${JSON.stringify(order)}` }],
    });
    return response.choices[0].message.content;
  }
);
AgentContext methods:
  • agent.logPlan(steps: string[]) — records a planning span
  • agent.traceTool(name, args, fn) — wraps an async function, records args and result as a tool span
  • agent.delegateTo(targetName, reason) — records a handoff span

Manual spans

Use startSpan to instrument any function as a custom span and attach evaluation scores.
import { startSpan, zespan } from "@zespan/sdk";
import OpenAI from "openai";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());

const span = startSpan({ name: "rag-pipeline" });
try {
  const docs = await retrieveDocuments("user query");
  const response = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [
      { role: "system", content: `Use these docs: ${docs}` },
      { role: "user", content: "user query" },
    ],
  });
  span.setEvalScore("relevance", 0.92);
  return response.choices[0].message.content;
} finally {
  span.end();
}

Prompt management

The PromptClient fetches versioned prompts from the Zespan prompt library at runtime. Results are cached locally.
import { PromptClient, zespan } from "@zespan/sdk";
import OpenAI from "openai";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());
const prompts = new PromptClient();

const prompt = await prompts.get("support-reply", { label: "production" });
const text = prompts.compile(prompt, {
  customer_name: "Alex",
  order_id: "ORD-7821",
});

const response = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [
    { role: "system", content: text },
    { role: "user", content: "I need help with my order." },
  ],
});
See Prompt management for the full API reference — get, list, create, updateLabels, compile, clearCache.

PII redaction

Zespan automatically redacts values from tags and metadata fields before they leave your application. The key is preserved; the value is replaced with "[REDACTED]". Default redacted keys (always applied): password, secret, token, api_key, apikey, auth, authorization, access_token, refresh_token, private_key, credential, ssn, credit_card, card_number, cvv. Add custom keys at initialization — they are merged with the defaults:
zespan.init({
  apiKey: process.env.ZESPAN_API_KEY!,
  redactKeys: ["email", "phone", "address", "ip_address", "dob"],
});
To replace the defaults entirely with your own list:
zespan.init({
  apiKey: process.env.ZESPAN_API_KEY!,
  redactKeys: ["ssn", "account_number"],
  replaceDefaultRedactKeys: true,
});
Replacing the defaults removes protection for common sensitive field names like password and token. Only do this if your custom list covers all sensitive fields your application may produce.
Redaction applies to tags and metadata fields, and to stored prompt and completion text. Prompt storage is on by default — set storePrompts: false to disable it entirely.

Guardrails

Guardrails run content safety checks before sending a prompt to the LLM (pre-check) and before returning the completion (post-check). Pass guardrails: true to any wrapper to enable both phases.
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

const openai = zespan.wrapOpenAI(new OpenAI(), { guardrails: true });
For fine-grained control:
const openai = zespan.wrapOpenAI(new OpenAI(), {
  guardrails: {
    pre: true,          // Check prompt before sending to LLM
    post: true,         // Check completion before returning to app
    failClosed: false,  // If guardrail service errors, allow call through
  },
});
Handle blocks with GuardrailBlockedError:
import { zespan, GuardrailBlockedError } from "@zespan/sdk";
import OpenAI from "openai";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI(), { guardrails: true });

async function generateResponse(userMessage: string): Promise<string> {
  try {
    const response = await openai.chat.completions.create({
      model: "gpt-4o",
      messages: [{ role: "user", content: userMessage }],
    });
    return response.choices[0].message.content ?? "";
  } catch (err) {
    if (err instanceof GuardrailBlockedError) {
      console.warn(`Guardrail blocked ${err.phase} content:`, err.results);
      return "I'm sorry, I can't help with that request.";
    }
    throw err;
  }
}
GuardrailBlockedError properties:
  • phase"pre" (input blocked) or "post" (output blocked)
  • results — array of per-policy results with passed, action, reason, modifiedText
Guardrails work on all provider wrappers: wrapAnthropic, wrapGoogle, wrapOpenRouter, and LumiqtraceCallbackHandler.
Configure guardrail policies in the Zespan dashboard under Settings → Guardrails. Policies are evaluated server-side — update them without redeploying your application.

Config propagation

Zespan can push configuration changes — model overrides, sample rate, guardrail toggles — to your running application without a redeployment. Changes made via ZespanPilot or the dashboard take effect within the next flush cycle (default 2 seconds). What can be propagated:
Rule typeEffect
model_overrideRedirect calls for a given model to a different model
sample_rateIncrease or decrease the fraction of events traced
guardrail_enableEnable or disable guardrails on a wrapper
pii_redact_keysAdd keys to the redaction list
log_levelToggle debug logging
disable_tracingStop all tracing immediately
Force an immediate config refresh:
import { zespan } from "@zespan/sdk";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });

// Force refresh — useful during startup
await zespan.getClient().refreshConfig();
To disable remote config propagation entirely:
zespan.init({
  apiKey: process.env.ZESPAN_API_KEY!,
  disableConfigSync: true,
});
With disableConfigSync: true, the SDK ignores all remote config changes. All behavior is determined solely by the options passed to init().

Flushing in serverless environments

In short-lived processes such as AWS Lambda, Vercel Functions, or Cloudflare Workers, call zespan.flush() explicitly before the handler returns to guarantee delivery.
import { zespan } from "@zespan/sdk";
import OpenAI from "openai";

zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());

export async function handler(event: any) {
  const response = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: event.prompt }],
  });
  const result = response.choices[0].message.content;

  await zespan.flush();
  return { statusCode: 200, body: result };
}
Omitting zespan.flush() in serverless environments is the most common cause of missing traces. Always call it before your handler returns.