converra
v0.5.3
Published
Official Node.js SDK for Converra - AI agent optimization platform
Maintainers
Readme
converra
Official Node.js SDK for Converra - the AI agent optimization platform.
Installation
npm install converraKey Features
converra.send()— one method to log conversations (complete or turn-by-turn)- LLM client wrapping — one-line integration for OpenAI, Anthropic, Vercel AI SDK
- Multi-agent tracing — link related LLM calls with AsyncLocalStorage
- Prompt management — fetch, create, update prompts with built-in caching
- Optimization triggering — run A/B tests and apply winning variants
- Webhook handling — real-time notifications with type-safe handlers
Quick Start
import { Converra } from 'converra';
const converra = new Converra({ apiKey: process.env.CONVERRA_API_KEY });
// Log a conversation — one method, handles everything
await converra.send({
agent: 'Support Bot',
messages: [
{ role: 'system', content: 'You are a helpful support agent...' },
{ role: 'user', content: 'I need help with my order' },
{ role: 'assistant', content: 'I\'d be happy to help! What\'s your order number?' },
],
});converra.send()
The simplest way to get conversations into Converra. Pass messages from a complete conversation or send turns incrementally.
// Complete conversation — fire and forget
await converra.send({
agent: 'Support Bot',
messages: [
{ role: 'user', content: 'Hello' },
{ role: 'assistant', content: 'Hi! How can I help?' },
],
});
// Incremental — send turns as they happen
const { conversationId } = await converra.send({
agent: 'Chat Bot',
messages: [
{ role: 'user', content: 'Hi' },
{ role: 'assistant', content: 'Hello!' },
],
status: 'active', // keep the conversation open
});
// Append more turns
await converra.send({
agent: 'Chat Bot',
conversationId,
messages: [
{ role: 'user', content: 'Thanks!' },
{ role: 'assistant', content: "You're welcome!" },
],
status: 'completed', // triggers analysis
});Enriched messages
Each message can optionally carry per-turn context — model, tool calls, token usage, and latency:
await converra.send({
agent: 'Support Bot',
messages: [
{ role: 'user', content: 'What is your return policy?' },
{
role: 'assistant',
content: 'Our return policy allows...',
model: 'gpt-4o',
toolCalls: [{ name: 'lookup_policy', arguments: { topic: 'returns' }, result: '...' }],
usage: { promptTokens: 200, completionTokens: 85 },
latencyMs: 1200,
},
],
});All enrichment fields are optional — a plain { role, content } message works fine.
Organizing conversations
Use agent to group by project/workflow and userId to group by end customer:
await converra.send({
agent: 'Churn Research Q1', // groups conversations by research/project
userId: 'customer_acme', // groups conversations by customer
messages: [...],
});
// Filter later via API — list endpoints return { items, pagination }
const { items: acmeConversations, pagination } = await converra.conversations.list({
agentId: 'agent_123',
userId: 'customer_acme',
});LLM Client Wrapping (v0.2.0)
Wrap your LLM client with one line to automatically capture conversations and enable A/B testing.
OpenAI
import { Converra } from 'converra';
import OpenAI from 'openai';
const converra = new Converra({ apiKey: 'sk_...' });
const openai = converra.wrap(new OpenAI());
// Use openai normally — all calls are intercepted
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Hello' }],
});
// Conversation automatically captured in ConverraAnthropic
import Anthropic from '@anthropic-ai/sdk';
const anthropic = converra.wrap(new Anthropic());
const response = await anthropic.messages.create({
model: 'claude-3-5-sonnet',
system: 'You are a helpful assistant.',
messages: [{ role: 'user', content: 'Hello' }],
max_tokens: 100,
});Vercel AI SDK
import { createConverraMiddleware } from 'converra/ai-sdk';
const middleware = createConverraMiddleware(converra.createInterceptor());
const result = await streamText({
model: openai('gpt-4o'),
messages,
experimental_middleware: middleware,
});Multi-Agent Tracing
const result = await converra.trace('session-123').run(async () => {
// All LLM calls inside are linked into one trace
const r1 = await openai.chat.completions.create({...}); // orchestrator
const r2 = await openai.chat.completions.create({...}); // sub-agent
return r2;
});
// Agent boundaries auto-detected by system prompt changesExplicit Prompt ID (for dynamic prompts)
// When your system prompt includes dynamic content (RAG, user context)
const openai = converra.wrap(new OpenAI(), { promptId: 'abc123' });Subpath Imports
import { wrapOpenAI } from 'converra/openai';
import { wrapAnthropic } from 'converra/anthropic';
import { createConverraMiddleware } from 'converra/ai-sdk';Integration Guide
Minimal Integration
The simplest way to integrate — log conversations with send():
import { Converra } from 'converra';
const converra = new Converra({ apiKey: process.env.CONVERRA_API_KEY });
// After your LLM call, send the conversation
await converra.send({
agent: 'My Agent',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userMessage },
{ role: 'assistant', content: response.choices[0].message.content },
],
});Full Integration (Recommended)
For instant variant updates when optimizations complete, add webhook handling:
import { Converra, createWebhookHandler } from 'converra';
const converra = new Converra({
apiKey: process.env.CONVERRA_API_KEY,
cache: {
strategy: 'memory', // 'memory' (default) or 'none'
ttl: 5 * 60 * 1000 // 5 minutes (default)
}
});
// Create webhook handler with automatic cache invalidation
const webhookHandler = createWebhookHandler({
secret: process.env.CONVERRA_WEBHOOK_SECRET,
// Invalidate cache when a variant is applied
onPromptUpdated: (data) => {
converra.cache.invalidate(data.promptId);
console.log(`Prompt ${data.promptId} updated - cache invalidated`);
},
// Get notified when optimization completes
onOptimizationCompleted: (data) => {
if (data.results.winningVariantId) {
console.log(`Optimization complete! ${data.results.improvementPercentage}% improvement`);
}
},
});
// Register the webhook endpoint (Express example)
app.post('/webhooks/converra', express.raw({ type: 'application/json' }), async (req, res) => {
const result = await webhookHandler(req.body, req.headers['x-converra-signature']);
res.status(result.success ? 200 : 400).json(result);
});Next.js App Router Example
// app/api/webhooks/converra/route.ts
import { createWebhookHandler } from 'converra';
import { converra } from '@/lib/converra'; // your singleton instance
const handler = createWebhookHandler({
secret: process.env.CONVERRA_WEBHOOK_SECRET!,
onPromptUpdated: (data) => converra.cache.invalidate(data.promptId),
});
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get('x-converra-signature') || '';
const result = await handler(body, signature);
return Response.json(result, { status: result.success ? 200 : 400 });
}Caching
The SDK includes built-in caching for prompts to minimize API calls:
const converra = new Converra({
apiKey: process.env.CONVERRA_API_KEY,
cache: {
strategy: 'memory', // Default: in-memory cache
ttl: 5 * 60 * 1000 // Default: 5 minutes
}
});
// Cached automatically
const prompt = await converra.prompts.get('prompt_123');
// Bypass cache when needed
const fresh = await converra.prompts.get('prompt_123', { bypassCache: true });
// Manual cache operations
converra.cache.invalidate('prompt_123'); // Invalidate specific prompt
converra.cache.invalidateAll(); // Clear all cached prompts
converra.cache.stats(); // { size: 5, enabled: true, ttl: 300000 }
// Disable caching entirely
const noCache = new Converra({
apiKey: process.env.CONVERRA_API_KEY,
cache: { strategy: 'none' }
});Webhook Handler
The createWebhookHandler utility provides type-safe webhook handling:
import { createWebhookHandler } from 'converra';
const handler = createWebhookHandler({
secret: process.env.CONVERRA_WEBHOOK_SECRET!,
// Prompt events
onPromptUpdated: (data) => {
// data: { promptId, promptName, updateType, variantId?, appliedAt }
},
onPromptDeleted: (data) => {
// data: { promptId, deletedAt }
},
// Optimization events
onOptimizationStarted: (data) => {
// data: { processId, promptId, status, settings, startedAt }
},
onOptimizationCompleted: (data) => {
// data: { processId, promptId, results, duration, completedAt }
},
onOptimizationStopped: (data) => {
// data: { processId, promptId, reason, stoppedAt }
},
// Insight events
onInsightsGenerated: (data) => {
// data: { conversationId, promptId, insightsId, summary, sentiment }
},
// Conversation events
onConversationCreated: (data) => {
// data: { conversationId, promptId, status, createdAt }
},
// Catch-all handler
onEvent: (event, data, metadata) => {
console.log(`Received ${event}`, data);
},
// Error handler
onError: (error, event) => {
console.error(`Webhook error for ${event}:`, error);
},
});Prompts
// List all prompts
const { items: prompts } = await converra.prompts.list();
// Get a specific prompt (cached)
const prompt = await converra.prompts.get('prompt_123');
// Create a new prompt
const newPrompt = await converra.prompts.create({
name: 'Customer Support',
content: 'You are a helpful customer support agent...',
description: 'Main support chatbot prompt',
tags: ['support', 'production']
});
// Update a prompt
await converra.prompts.update('prompt_123', {
content: 'Updated prompt content...'
});Conversations
Use converra.send() (above) for the simplest path. The lower-level API is also available:
// Get insights for a conversation
const insights = await converra.conversations.getInsights('conv_456');
console.log(insights.sentiment); // 'positive'
console.log(insights.topics); // ['order tracking', 'delivery']Optimizations
Automatically optimize your prompts:
// Trigger an optimization
const optimization = await converra.optimizations.trigger({
promptId: 'prompt_123',
mode: 'exploratory', // 'validation' for statistical rigor
variantCount: 3,
intent: {
targetImprovements: ['clarity', 'task completion'],
hypothesis: 'Adding examples will improve understanding'
}
});
console.log(`Optimization started: ${optimization.id}`);
// Check optimization status
const status = await converra.optimizations.get(optimization.id);
console.log(status.progress);
// Get the variants being tested
const variants = await converra.optimizations.getVariants(optimization.id);
variants.forEach(v => {
console.log(`${v.name}: ${v.metrics?.lift}% lift`);
});
// Apply the winning variant
await converra.optimizations.applyVariant(optimization.id);Webhooks
Register webhooks to receive real-time notifications:
// Register a webhook
const webhook = await converra.webhooks.create({
url: 'https://your-app.com/webhooks/converra',
events: ['optimization.completed', 'prompt.updated', 'insights.generated'],
description: 'Production webhook'
});
// Save the secret - only shown once!
console.log('Webhook secret:', webhook.secret);Available Events
| Event | Description |
|-------|-------------|
| prompt.updated | Prompt content changed (e.g., variant applied) |
| prompt.deleted | Prompt was deleted |
| optimization.started | Optimization process began |
| optimization.completed | Optimization finished with results |
| optimization.stopped | Optimization was manually stopped |
| insights.generated | New insights available for a conversation |
| conversation.created | New conversation logged |
| batch.completed | Batch operation completed |
| batch.failed | Batch operation failed |
Personas
Manage simulation personas:
// List available personas
const { items: personas } = await converra.personas.list({
tags: ['enterprise', 'frustrated']
});
// Create a custom persona
await converra.personas.create({
name: 'Impatient Executive',
description: 'A busy C-level executive who values brevity...',
tags: ['enterprise', 'impatient', 'executive']
});Insights
Get aggregated performance data:
// Get insights for a specific prompt
const insights = await converra.insights.forPrompt('prompt_123', {
days: 30
});
console.log(`Task completion: ${insights.metrics.taskCompletionRate}%`);
console.log(`Avg sentiment: ${insights.metrics.avgSentiment}`);
// Get overall insights across all prompts
const overall = await converra.insights.overall({ days: 7 });Error Handling
import { Converra, ConverraError } from 'converra';
try {
await converra.prompts.get('nonexistent');
} catch (error) {
if (error instanceof ConverraError) {
console.error(`Error ${error.code}: ${error.message}`);
console.error(`Status: ${error.statusCode}`);
console.error(`Details:`, error.details);
}
}Configuration
const converra = new Converra({
apiKey: 'cvr_live_...', // Required
baseUrl: 'https://converra.ai/api/v1', // Optional, for self-hosted
timeout: 30000, // Optional, request timeout in ms
variantLookupTimeoutMs: 500, // Optional, hot-path fail-open timeout in ms
cache: {
strategy: 'memory', // Optional: 'memory' or 'none'
ttl: 300000, // Optional: cache TTL in ms
},
});TypeScript
Full TypeScript support with exported types:
import type {
Prompt,
Conversation,
Optimization,
WebhookPayload,
WebhookEvent,
PromptUpdatedPayload,
OptimizationCompletedPayload,
} from 'converra';
// Type-safe webhook handling
import { createWebhookHandler } from 'converra';
const handler = createWebhookHandler({
secret: '...',
onPromptUpdated: (data: PromptUpdatedPayload) => {
console.log(data.promptId, data.updateType);
},
});Requirements
- Node.js 18+
- A Converra API key (get one here)
Support
License
MIT
