LLM Observability SDK
AI observability SDK with cost tracking, trace analysis, and performance monitoring.
Installation
npm install @usetransactional/llm-nodeQuick Start
Initialize the SDK once at application startup, then instrument your LLM calls with traces and generations:
import { initLlmOps, getLlmOps } from '@usetransactional/llm-node';
// Initialize once at startup
initLlmOps({
dsn: 'https://pk_abc123@api.usetransactional.com/observability/42',
});
const llmOps = getLlmOps();
// Create a trace for a user interaction
const trace = llmOps.trace({
name: 'chat-completion',
input: { message: 'Explain quantum computing' },
userId: 'user-123',
tags: ['chat', 'physics'],
});
// Record an LLM generation within the trace
const generation = llmOps.generation({
name: 'openai-response',
traceId: trace.id,
modelName: 'gpt-4o',
input: [{ role: 'user', content: 'Explain quantum computing' }],
modelParameters: { temperature: 0.7, maxTokens: 1000 },
});
// After receiving the LLM response
generation.end({
output: 'Quantum computing uses quantum bits...',
promptTokens: 12,
completionTokens: 150,
totalTokens: 162,
});
trace.end({
output: { response: 'Quantum computing uses quantum bits...' },
status: 'success',
});Initialization
The SDK uses a singleton pattern. Initialize it once at application startup before making any observations.
initLlmOps(config)
Initializes the global LLM observability instance. Must be called before any other SDK methods.
import { initLlmOps } from '@usetransactional/llm-node';
initLlmOps({
dsn: 'https://pk_abc123@api.usetransactional.com/observability/42',
debug: true,
});getLlmOps()
Returns the initialized singleton instance. Throws an error if called before initLlmOps().
import { getLlmOps } from '@usetransactional/llm-node';
const llmOps = getLlmOps();isInitialized()
Returns true if the SDK has been initialized, false otherwise. Useful for conditional instrumentation.
import { isInitialized } from '@usetransactional/llm-node';
if (isInitialized()) {
const llmOps = getLlmOps();
// record observations
}resetLlmOps()
Resets the singleton instance. Primarily used for testing to ensure a clean state between test runs.
import { resetLlmOps } from '@usetransactional/llm-node';
afterEach(() => {
resetLlmOps();
});Configuration
All configuration options for initLlmOps():
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
dsn | string | Yes* | - | Full DSN URL. Format: https://<publicKey>@<host>/observability/<projectId> |
publicKey | string | Yes* | - | Public API key. Alternative to DSN |
projectId | string | Yes* | - | Project ID. Alternative to DSN |
baseUrl | string | Yes* | - | API base URL. Alternative to DSN |
enabled | boolean | No | true | Enable or disable the SDK. When false, all methods are no-ops |
batchSize | number | No | 100 | Number of events to batch before flushing |
flushInterval | number | No | 5000 | Milliseconds between automatic flushes |
debug | boolean | No | false | Enable debug logging to the console |
*Either provide dsn or all three of publicKey, projectId, and baseUrl.
Using a DSN (recommended):
initLlmOps({
dsn: 'https://pk_abc123@api.usetransactional.com/observability/42',
});Using explicit configuration:
initLlmOps({
publicKey: 'pk_abc123',
projectId: '42',
baseUrl: 'https://api.usetransactional.com',
});Tracing
Traces represent a complete unit of work, such as a user request or an agent loop. Every observation (generation, span, event) is associated with a trace.
Creating a Trace
const llmOps = getLlmOps();
const trace = llmOps.trace({
name: 'customer-support-agent',
input: { question: 'How do I reset my password?' },
userId: 'user-456',
metadata: {
environment: 'production',
version: '1.2.0',
},
tags: ['support', 'auth'],
});Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Descriptive name for the trace |
input | unknown | No | Input data that initiated the trace |
userId | string | No | ID of the user who triggered the trace |
metadata | Record<string, unknown> | No | Arbitrary metadata to attach to the trace |
tags | string[] | No | Tags for filtering and categorization |
TraceHandle
The object returned by llmOps.trace() with the following properties and methods:
| Property / Method | Type | Description |
|---|---|---|
id | string | Unique trace identifier |
end(data?) | (data?: { output?, status? }) => void | End the trace with optional output and status |
error(err) | (err: Error) => void | Record an error and end the trace with error status |
// Successful completion
trace.end({
output: { answer: 'Go to Settings > Security > Reset Password' },
status: 'success',
});
// Error completion
try {
// ... LLM call
} catch (err) {
trace.error(err);
}Updating a Trace
Update a trace after creation to add additional context:
llmOps.updateTrace(trace.id, {
metadata: { resolvedBy: 'agent-v2' },
tags: ['resolved'],
});Observations
Observations are the individual steps within a trace. There are three types: generations, spans, and events.
generation()
Records an LLM call with model details, token usage, and cost tracking.
const generation = llmOps.generation({
name: 'summarize-article',
traceId: trace.id,
modelName: 'gpt-4o',
input: [
{ role: 'system', content: 'Summarize the following article.' },
{ role: 'user', content: articleText },
],
modelParameters: {
temperature: 0.3,
maxTokens: 500,
},
});
// After receiving the response
generation.end({
output: 'The article discusses...',
promptTokens: 2048,
completionTokens: 120,
totalTokens: 2168,
});span()
Records a generic operation such as a tool call, retrieval step, or processing stage.
const span = llmOps.span({
name: 'vector-search',
traceId: trace.id,
input: { query: 'password reset instructions', topK: 5 },
});
// After the operation completes
span.end({
output: { results: documents, count: 5 },
});event()
Records a point-in-time event such as a user action or system occurrence. Events have no duration.
llmOps.event({
name: 'user-feedback',
traceId: trace.id,
input: { rating: 5, comment: 'Very helpful!' },
});ObservationHandle
The object returned by generation(), span(), and event():
| Property / Method | Type | Description |
|---|---|---|
id | string | Unique observation identifier |
end(data?) | (data?: { output?, promptTokens?, completionTokens?, totalTokens? }) => void | End the observation with optional output and token usage |
error(err) | (err: Error) => void | Record an error on the observation |
Updating an Observation
llmOps.updateObservation(generation.id, {
metadata: { cached: true },
});Vercel AI SDK Integration
The SDK provides first-class wrappers for the Vercel AI SDK to automatically capture traces and token usage.
wrapAiSdk(fn)
Wraps generateText or similar functions from the Vercel AI SDK:
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { getLlmOps } from '@usetransactional/llm-node';
const llmOps = getLlmOps();
const wrappedGenerateText = llmOps.wrapAiSdk(generateText);
const result = await wrappedGenerateText({
model: openai('gpt-4o'),
prompt: 'Explain the theory of relativity in simple terms.',
});
console.log(result.text);The wrapper automatically captures the model name, input, output, token usage, and latency as a generation observation within a trace.
wrapStreamText(fn)
Wraps streamText for streaming responses:
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { getLlmOps } from '@usetransactional/llm-node';
const llmOps = getLlmOps();
const wrappedStreamText = llmOps.wrapStreamText(streamText);
const result = await wrappedStreamText({
model: openai('gpt-4o'),
prompt: 'Write a poem about observability.',
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}Token usage and the full output are captured automatically when the stream completes.
LangChain Integration
Use the TransactionalCallbackHandler to automatically instrument all LangChain calls.
import { ChatOpenAI } from '@langchain/openai';
import { TransactionalCallbackHandler } from '@usetransactional/llm-node/langchain';
const handler = new TransactionalCallbackHandler({
sessionId: 'session-abc',
userId: 'user-123',
});
const model = new ChatOpenAI({
modelName: 'gpt-4o',
temperature: 0.7,
callbacks: [handler],
});
const response = await model.invoke('What is the capital of France?');
console.log(response.content);The callback handler automatically creates traces and generations for every LangChain LLM call, chain run, and tool invocation. Constructor options:
| Option | Type | Required | Description |
|---|---|---|---|
sessionId | string | No | Session identifier to group related traces |
userId | string | No | User identifier to associate with traces |
Lifecycle
Flushing Events
Force an immediate flush of all queued events:
await llmOps.flush();Graceful Shutdown
Flush all pending events and release resources. Call this before your application exits:
process.on('SIGTERM', async () => {
await llmOps.shutdown();
process.exit(0);
});In serverless environments (AWS Lambda, Vercel Functions), call flush() at the end of each invocation to ensure all events are sent before the function freezes:
export async function handler(event) {
const llmOps = getLlmOps();
// ... your logic
await llmOps.flush();
return response;
}Batching
The SDK automatically batches events for efficient network usage. Events are queued in memory and flushed to the Transactional API when either condition is met:
- The queue reaches
batchSizeevents (default: 100) - The
flushIntervaltimer elapses (default: 5000ms)
This means under normal load, events are sent in batches every 5 seconds. Under high throughput, batches are sent as soon as 100 events accumulate. You can tune these values based on your application's needs:
initLlmOps({
dsn: 'https://pk_abc123@api.usetransactional.com/observability/42',
batchSize: 50, // Flush sooner under high throughput
flushInterval: 2000, // Flush every 2 seconds
});Types
The SDK exports all types for full TypeScript support:
import type {
TraceHandle,
ObservationHandle,
ObservationType,
ObservationLevel,
TraceStatus,
} from '@usetransactional/llm-node';| Type | Description |
|---|---|
TraceHandle | Object returned by llmOps.trace() with id, end(), and error() |
ObservationHandle | Object returned by generation(), span(), and event() with id, end(), and error() |
ObservationType | Enum: GENERATION, SPAN, EVENT |
ObservationLevel | Severity level for observations: DEBUG, DEFAULT, WARNING, ERROR |
TraceStatus | Trace completion status: success, error |
On This Page
- Installation
- Quick Start
- Initialization
- initLlmOps(config)
- getLlmOps()
- isInitialized()
- resetLlmOps()
- Configuration
- Tracing
- Creating a Trace
- TraceHandle
- Updating a Trace
- Observations
- generation()
- span()
- event()
- ObservationHandle
- Updating an Observation
- Vercel AI SDK Integration
- wrapAiSdk(fn)
- wrapStreamText(fn)
- LangChain Integration
- Lifecycle
- Flushing Events
- Graceful Shutdown
- Batching
- Types