Skip to content

Logging Conversations

Log conversations to track performance and generate insights.

Why Log Conversations?

Logging conversations enables:

  • Performance tracking - See how your agents perform with real users
  • Insights generation - AI analyzes patterns and issues
  • Optimization signal - Real data guides improvements

Structured Format

For best results, use the structured message format instead of raw text transcripts.

The SDK makes logging easy:

typescript
import { Converra } from 'converra';
import OpenAI from 'openai';

const converra = new Converra({ apiKey: process.env.CONVERRA_API_KEY });
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// 1. Get your agent
const agent = await converra.agents.get('agent_123');

// 2. Run your AI conversation
const messages = [
  { role: 'system', content: agent.content },
  { role: 'user', content: userMessage }
];

const response = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages
});

const assistantMessage = response.choices[0].message.content;

// 3. Log the conversation
await converra.conversations.create({
  agentId: 'agent_123',
  content: `User: ${userMessage}\nAssistant: ${assistantMessage}`,
  status: 'completed',
  llmModel: 'gpt-4o'
});

Multi-Turn Conversations

Two patterns. Pick based on when you have the data.

Option A — Batch (send the full transcript once)

If you already have the finished conversation in hand (webhook, async job, post-call processing), send it in a single call:

typescript
await converra.conversations.create({
  agentId: 'agent_123',
  content: [
    'User: Hi, I need help with my order',
    "Assistant: I'd be happy to help! What's your order number?",
    'User: #12345',
    'Assistant: Found it! Your order ships tomorrow.',
  ].join('\n'),
  status: 'completed', // triggers analysis
});

Option B — Incremental (send turns as they happen)

If you want the conversation visible in Converra mid-flight — long chats, voice, streaming agents — send each turn as it happens. Create once, append as you go, flip status to "completed" on the final call.

typescript
// SDK: one method, routes by whether you pass a conversationId
import { Converra } from 'converra';
const converra = new Converra({ apiKey: process.env.CONVERRA_API_KEY! });

// First turn — creates the conversation, returns an id
const { conversationId } = await converra.send({
  agent: 'Support Bot',
  messages: [{ role: 'user', content: 'Hi, I need help with my order' }],
  status: 'active',
});

// Each subsequent turn — pass the same conversationId
await converra.send({
  agent: 'Support Bot',
  conversationId,
  messages: [{ role: 'assistant', content: "I'd be happy to help! What's your order number?" }],
  status: 'active',
});

await converra.send({
  agent: 'Support Bot',
  conversationId,
  messages: [{ role: 'user', content: '#12345' }],
  status: 'active',
});

// Final turn — set status to 'completed' to trigger analysis
await converra.send({
  agent: 'Support Bot',
  conversationId,
  messages: [{ role: 'assistant', content: 'Found it! Your order ships tomorrow.' }],
  status: 'completed',
});

Three rules:

  1. No conversationId yet? Call creates a new conversation. Have one? Call appends to it.
  2. Keep the conversationId from the first response — it's the only state you need to carry.
  3. Send status: "completed" exactly once, on the final call. Without it the conversation stays active and is never analyzed.

Raw HTTP (no SDK)

Same two patterns against the REST API:

bash
# First turn — create
curl -X POST https://converra.ai/api/v1/conversations \
  -H "Authorization: Bearer $CONVERRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "agent_123",
    "content": "User: Hi",
    "status": "active",
    "messages": [{"role": "user", "content": "Hi"}]
  }'
# Response includes { "id": "...", "_links": { "appendMessages": { "href": "/api/v1/conversations/.../messages", "method": "POST", ... } } }

# Each subsequent turn — append
curl -X POST https://converra.ai/api/v1/conversations/{id}/messages \
  -H "Authorization: Bearer $CONVERRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{"role": "assistant", "content": "Hello!"}],
    "status": "active"
  }'

# Final turn — flip status
curl -X POST https://converra.ai/api/v1/conversations/{id}/messages \
  -H "Authorization: Bearer $CONVERRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{"role": "assistant", "content": "Goodbye!"}],
    "status": "completed"
  }'

Via API

bash
curl -X POST https://converra.ai/api/v1/conversations \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "agent_123",
    "content": "User: Hello\nAssistant: Hi there! How can I help?",
    "status": "completed",
    "llmModel": "gpt-4o"
  }'

Via MCP

Log this conversation for my support agent:

User: How do I cancel my subscription?
Assistant: I can help you cancel. Go to Settings > Subscription > Cancel.
User: Thanks!
Assistant: You're welcome! Your subscription will end on the billing date.

Conversation Format

Converra accepts two formats for conversation content.

Use a JSON messages array for best parsing and analysis:

typescript
await converra.conversations.create({
  agentId: 'agent_123',
  content: JSON.stringify({
    messages: [
      { role: 'user', content: 'I need help with my order' },
      { role: 'assistant', content: 'I\'d be happy to help! What\'s your order number?' },
      { role: 'user', content: '#12345' },
      { role: 'assistant', content: 'Found it! Your order ships tomorrow.' }
    ]
  }),
  status: 'completed',
  llmModel: 'gpt-4o'
});

Benefits:

  • Precise message boundaries
  • Role attribution is unambiguous
  • Metadata can be included per message
  • Direct compatibility with OpenAI/Anthropic formats

Extended format with metadata:

json
{
  "messages": [
    {
      "role": "user",
      "content": "I need help with my order",
      "timestamp": "2025-01-20T14:30:00Z"
    },
    {
      "role": "assistant",
      "content": "I'd be happy to help! What's your order number?",
      "timestamp": "2025-01-20T14:30:02Z",
      "model": "gpt-4o",
      "tokens": 15
    }
  ],
  "metadata": {
    "userId": "user_123",
    "sessionId": "sess_456",
    "channel": "web",
    "language": "en"
  }
}

Text Format

For simpler integrations, use alternating labeled messages:

User: First user message
Assistant: First assistant response
User: Second user message
Assistant: Second assistant response

Accepted labels:

  • User / Assistant
  • Human / AI
  • Customer / Agent

WARNING

Text format may have parsing issues with multi-line messages or messages containing colons. Use structured format for production.

Status Options

StatusUse When
completedConversation ended naturally
abandonedUser left without resolution
in_progressConversation still ongoing

Optional Metadata

Include additional context:

typescript
await converra.conversations.create({
  agentId: 'agent_123',
  content: conversationText,
  status: 'completed',
  llmModel: 'gpt-4o',
  // Optional
  companyName: 'Acme Corp',       // Customer company
  // Add custom fields as needed
});

Best Practices

Log All Conversations

Even "bad" conversations provide valuable data:

  • Abandoned sessions reveal pain points
  • Errors show edge cases to handle
  • Short conversations might indicate unclear system prompts

Log Promptly

Log conversations soon after they complete:

  • Data is fresh and accurate
  • Insights are timely
  • Performance tracking is current

Use Consistent Formatting

Keep the same format across all logged conversations:

  • Same labels (User/Assistant or Human/AI)
  • Same line breaks
  • Same structure

Handle Errors Gracefully

Don't let logging failures break your app:

typescript
try {
  await converra.conversations.create({ ... });
} catch (error) {
  console.error('Failed to log conversation:', error);
  // Continue - logging failure shouldn't break user experience
}

Viewing Logged Conversations

Dashboard

View all conversations at converra.ai/conversations

Via SDK

typescript
const { data: conversations } = await converra.conversations.list({
  agentId: 'agent_123',
  limit: 20
});

Next Steps