Multi-Turn Conversations
The Conversation class manages multi-turn interactions with the Chaos AI API. It automatically tracks message history, handles session IDs, and supports branching and persistence -- so you can focus on the dialogue itself rather than state management.
Creating a Conversation
A Conversation wraps a Chaos client and a set of options. At minimum you need a userId.
import { Chaos, Conversation } from '@chaoslabs/ai-sdk';
const chaos = new Chaos({ apiKey: process.env.CHAOS_API_KEY! });
const conversation = new Conversation(chaos, {
userId: 'user-123',
wallets: [
{ address: '0xYourWalletAddress', chain: 'ethereum' },
],
});ConversationOptions
| Option | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | Unique user identifier |
wallets | Array<{ address: string; chain: string }> | No | Wallet addresses across chains |
model | string | No | Model override (defaults to WALLET_MODEL) |
maxHistoryLength | number | No | Max messages kept in history (default 50) |
sessionId | string | No | Explicit session ID (auto-generated if omitted) |
walletContextOverride | WalletContextOverride | No | Override wallet context for deterministic testing |
Sending Messages
Call send(message) to send a user message and receive a ChatCreateResponse. The conversation automatically appends both the user message and the assistant reply to its internal history, so subsequent calls carry the full context.
import { Chaos, Conversation, extractText } from '@chaoslabs/ai-sdk';
const chaos = new Chaos({ apiKey: process.env.CHAOS_API_KEY! });
const conversation = new Conversation(chaos, {
userId: 'user-123',
wallets: [{ address: '0xABC...', chain: 'ethereum' }],
});
// First turn
const response1 = await conversation.send("What's my portfolio value?");
console.log(extractText(response1));
// Second turn -- the API sees full history automatically
const response2 = await conversation.send('Which asset has the highest allocation?');
console.log(extractText(response2));
// Third turn -- references both prior answers
const response3 = await conversation.send('Should I rebalance?');
console.log(extractText(response3));Conversation Stats
The stats getter returns a snapshot of the conversation state.
const stats = conversation.stats;
console.log(stats.sessionId); // "session-1719...-a3f2k1"
console.log(stats.userTurns); // 3
console.log(stats.assistantTurns); // 3
console.log(stats.totalMessages); // 6
console.log(stats.startedAt); // Date when conversation was created
console.log(stats.lastMessageAt); // Date of the most recent send()ConversationStats
| Property | Type | Description |
|---|---|---|
sessionId | string | Current session identifier |
userTurns | number | Count of user messages sent |
assistantTurns | number | Count of assistant replies received |
totalMessages | number | Total items in the history array |
startedAt | Date | When the conversation was created |
lastMessageAt | Date | null | When the last send() completed |
Branching with fork()
fork() creates a new Conversation that shares the same history up to that point but has its own session ID. The original conversation is unaffected. This is useful for exploring alternative paths -- for example, comparing different DeFi strategies from the same starting context.
import { Chaos, Conversation, extractText } from '@chaoslabs/ai-sdk';
const chaos = new Chaos({ apiKey: process.env.CHAOS_API_KEY! });
const conversation = new Conversation(chaos, {
userId: 'user-123',
wallets: [{ address: '0xABC...', chain: 'ethereum' }],
});
// Build up shared context
await conversation.send('I have 10 ETH and want to earn yield.');
await conversation.send('What are my options on Aave?');
// Fork to explore two strategies independently
const branchA = conversation.fork();
const branchB = conversation.fork();
// Each branch diverges from here
const responseA = await branchA.send('Show me the deposit APY for WETH.');
const responseB = await branchB.send('What about supplying USDC instead?');
console.log('Branch A:', extractText(responseA));
console.log('Branch B:', extractText(responseB));
// Original conversation is unchanged
console.log('Original turns:', conversation.stats.userTurns); // 2Persistence with toJSON() and fromJSON()
You can serialize a conversation to JSON for storage (database, file, localStorage) and restore it later. This is essential for server-side applications where conversations span multiple HTTP requests.
import { Chaos, Conversation, extractText } from '@chaoslabs/ai-sdk';
import { writeFileSync, readFileSync } from 'fs';
const chaos = new Chaos({ apiKey: process.env.CHAOS_API_KEY! });
// --- Save a conversation ---
const conversation = new Conversation(chaos, {
userId: 'user-123',
wallets: [{ address: '0xABC...', chain: 'ethereum' }],
});
await conversation.send('What is the current ETH price?');
await conversation.send('How does that compare to last week?');
// Serialize to JSON
const snapshot = conversation.toJSON();
writeFileSync('conversation.json', JSON.stringify(snapshot, null, 2));
// --- Restore later ---
const saved = JSON.parse(readFileSync('conversation.json', 'utf-8'));
const restored = Conversation.fromJSON(chaos, saved);
// Continue where you left off -- full history is available
const response = await restored.send('And what about BTC?');
console.log(extractText(response));
console.log(restored.stats.userTurns); // 3Resetting and Clearing
The Conversation class offers two ways to start over:
reset()-- Clears history, resets turn counters, generates a new session ID, and updates thestartedAttimestamp. Use this when you want a completely fresh conversation.clearHistory()-- Clears history and resets turn counters but keeps the same session ID. Use this when you want to keep the session identity but discard prior context.
// Full reset -- new session ID, fresh start
const oldSessionId = conversation.sessionId;
conversation.reset();
console.log(conversation.stats.totalMessages); // 0
console.log(conversation.sessionId !== oldSessionId); // true
// Clear history only -- same session ID
const currentSessionId = conversation.sessionId;
await conversation.send('Hello');
conversation.clearHistory();
console.log(conversation.stats.totalMessages); // 0
console.log(conversation.sessionId === currentSessionId); // trueAccessing the Message History
The messages getter returns a readonly copy of the internal history array. Each item is an InputItem with type, role, and content fields.
const messages = conversation.messages;
for (const msg of messages) {
console.log(`[${msg.role}]: ${msg.content}`);
}
// Example output:
// [user]: What's my portfolio value?
// [assistant]: Your portfolio is worth approximately $45,230...
// [user]: Which asset has the highest allocation?
// [assistant]: ETH represents 62% of your portfolio...Complete Example: DeFi Research Assistant
Here is an end-to-end example that creates a conversation, conducts a multi-turn research session, forks to explore alternatives, and persists the state.
import { Chaos, Conversation, extractText, extractTableBlocks, tableToObjects } from '@chaoslabs/ai-sdk';
import { writeFileSync } from 'fs';
async function main() {
const chaos = new Chaos({ apiKey: process.env.CHAOS_API_KEY! });
const conversation = new Conversation(chaos, {
userId: 'user-456',
wallets: [
{ address: '0xMyEthWallet', chain: 'ethereum' },
{ address: '0xMyArbWallet', chain: 'arbitrum' },
],
maxHistoryLength: 30,
});
// Turn 1: Ask about portfolio
const r1 = await conversation.send('Show me my portfolio across all chains.');
console.log(extractText(r1));
// Turn 2: Drill into a specific position
const r2 = await conversation.send('Tell me more about my Aave positions.');
const tables = extractTableBlocks(r2);
if (tables.length > 0) {
const rows = tableToObjects(tables[0]);
console.log('Aave positions:', rows);
}
// Turn 3: Fork to compare two strategies
const conservativeBranch = conversation.fork();
const aggressiveBranch = conversation.fork();
const conservative = await conservativeBranch.send(
'What low-risk yield options do I have for my idle USDC?'
);
const aggressive = await aggressiveBranch.send(
'What are the highest-yield opportunities, even if risky?'
);
console.log('Conservative:', extractText(conservative));
console.log('Aggressive:', extractText(aggressive));
// Persist the main conversation for later
writeFileSync('session.json', JSON.stringify(conversation.toJSON(), null, 2));
// Print final stats
const stats = conversation.stats;
console.log(`Session ${stats.sessionId}:`);
console.log(` ${stats.userTurns} user turns, ${stats.assistantTurns} assistant turns`);
console.log(` Started: ${stats.startedAt.toISOString()}`);
}
main().catch(console.error);