v0.5.2 release - Contributors, Sponsors and Enquiries are most welcome 😌

Conversation Schemas

Build structured conversational experiences with custom schemas, validation, and dynamic routing.

What are Conversation Schemas?

Conversation Schemas allow you to define structured conversation flows with validation, state management, and conditional branching. Perfect for building:

  • Multi-step forms and onboarding flows
  • Booking and reservation systems
  • Customer support workflows
  • Survey and questionnaire bots
  • Interactive tutorials

Basic Example

Create a simple hotel booking conversation:

typescript
import {
  ConversationSchema,
  ConversationSchemaBuilder,
  Agent,
  AnthropicProvider,
  ToolRegistry,
} from '@lov3kaizen/agentsea-core';
import { z } from 'zod';

// Define conversation schema
const bookingSchema = new ConversationSchema({
  name: 'hotel-booking',
  description: 'Hotel reservation conversation',
  startStep: 'destination',
  steps: [
    {
      id: 'destination',
      prompt: 'Where would you like to stay?',
      schema: z.object({
        city: z.string().min(2),
      }),
      next: 'dates',
    },
    {
      id: 'dates',
      prompt: 'What are your check-in and check-out dates?',
      schema: z.object({
        checkIn: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
        checkOut: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
      }),
      validation: (response) => {
        const checkIn = new Date(response.checkIn);
        const checkOut = new Date(response.checkOut);
        return checkOut > checkIn;
      },
      errorMessage: 'Check-out must be after check-in',
      next: 'guests',
    },
    {
      id: 'guests',
      prompt: 'How many guests?',
      schema: z.object({
        adults: z.number().min(1).max(10),
        children: z.number().min(0).max(5),
      }),
      next: 'confirm',
    },
    {
      id: 'confirm',
      prompt: 'Please review your booking details and confirm.',
      onComplete: async (response, state) => {
        // Process booking
        console.log('Booking confirmed:', state.data);
      },
      next: null, // End conversation
    },
  ],
  onComplete: async (state) => {
    console.log('Conversation completed!', state);
  },
});

// Process user responses
const result1 = await bookingSchema.processResponse(
  JSON.stringify({ city: 'San Francisco' })
);
console.log(result1.nextPrompt); // "What are your check-in and check-out dates?"

const result2 = await bookingSchema.processResponse(
  JSON.stringify({ checkIn: '2025-12-01', checkOut: '2025-12-05' })
);
console.log(result2.nextPrompt); // "How many guests?"

Using with Agents

Integrate conversation schemas with AI agents for intelligent, structured conversations:

typescript
import {
  ConversationManager,
  Agent,
  AnthropicProvider,
  ToolRegistry,
} from '@lov3kaizen/agentsea-core';

// Create agent
const agent = new Agent(
  {
    name: 'booking-agent',
    model: 'claude-sonnet-4-20250514',
    provider: 'anthropic',
    systemPrompt: `You are a helpful hotel booking assistant.
Extract information from user messages and format it appropriately.`,
  },
  new AnthropicProvider(),
  new ToolRegistry(),
);

// Create conversation manager
const manager = new ConversationManager(
  agent,
  bookingSchema,
  'user-123',
);

// Start conversation
const start = await manager.start();
console.log(start.prompt); // "Where would you like to stay?"

// Process natural language
const response = await manager.processMessage(
  'I want to book a hotel in San Francisco'
);
console.log(response.response); // Next prompt
console.log(response.aiResponse); // AI-enhanced response

Builder Pattern

Use the fluent builder API for cleaner schema definitions:

typescript
const schema = new ConversationSchemaBuilder()
  .name('support-flow')
  .description('Customer support conversation')
  .startAt('category')
  .addStep({
    id: 'category',
    prompt: 'What type of issue are you experiencing?',
    next: (response) => {
      if (response.includes('billing')) return 'billing';
      if (response.includes('technical')) return 'technical';
      return 'general';
    },
  })
  .addStep({
    id: 'billing',
    prompt: 'Please describe your billing issue.',
    next: 'resolve',
  })
  .addStep({
    id: 'technical',
    prompt: 'Please describe your technical issue.',
    next: 'resolve',
  })
  .addStep({
    id: 'general',
    prompt: 'How can I help you today?',
    next: 'resolve',
  })
  .addStep({
    id: 'resolve',
    prompt: 'Is there anything else I can help with?',
    next: null,
  })
  .onComplete(async (state) => {
    console.log('Support ticket created:', state.data);
  })
  .build();

Dynamic Routing

Route users to different conversation paths based on their responses:

typescript
const schema = new ConversationSchema({
  name: 'product-quiz',
  startStep: 'budget',
  steps: [
    {
      id: 'budget',
      prompt: 'What is your budget?',
      next: (response, state) => {
        const budget = parseInt(response);
        if (budget < 1000) return 'basic-options';
        if (budget < 3000) return 'mid-options';
        return 'premium-options';
      },
    },
    {
      id: 'basic-options',
      prompt: 'Here are our budget-friendly options...',
      next: 'finalize',
    },
    {
      id: 'mid-options',
      prompt: 'Here are our mid-range options...',
      next: 'finalize',
    },
    {
      id: 'premium-options',
      prompt: 'Here are our premium options...',
      next: 'finalize',
    },
    {
      id: 'finalize',
      prompt: 'Would you like to proceed?',
      next: null,
    },
  ],
});

Schema Validation

Use Zod schemas for robust input validation:

typescript
import { z } from 'zod';

const step = {
  id: 'user-info',
  prompt: 'Please provide your information.',
  schema: z.object({
    email: z.string().email('Invalid email format'),
    age: z.number()
      .int()
      .min(18, 'Must be 18 or older')
      .max(120, 'Invalid age'),
    phone: z.string()
      .regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone format'),
  }),
  validation: async (response) => {
    // Additional async validation
    const emailExists = await checkEmail(response.email);
    return !emailExists;
  },
  errorMessage: 'Email already registered',
  next: 'confirm',
};

State Management

Access and manage conversation state throughout the flow:

typescript
// Get current state
const state = schema.getState();
console.log(state.currentStep); // Current step ID
console.log(state.data); // Collected data
console.log(state.history); // Conversation history

// Reset conversation
schema.reset();

// Export/import state for persistence
const exported = schema.exportState();
localStorage.setItem('conversation', exported);

// Later...
const saved = localStorage.getItem('conversation');
schema.importState(saved);

Callbacks

Execute custom logic at different stages:

typescript
const schema = new ConversationSchema({
  name: 'payment-flow',
  startStep: 'amount',
  steps: [
    {
      id: 'amount',
      prompt: 'How much would you like to pay?',
      onComplete: async (response, state) => {
        // Called after this step completes
        console.log('Amount entered:', response);
      },
      next: 'method',
    },
    {
      id: 'method',
      prompt: 'Choose payment method.',
      onComplete: async (response, state) => {
        // Validate payment method
        await validatePaymentMethod(response);
      },
      next: 'process',
    },
    {
      id: 'process',
      prompt: 'Processing payment...',
      onComplete: async (response, state) => {
        // Process payment
        await processPayment(
          state.data.amount,
          state.data.method
        );
      },
      next: null,
    },
  ],
  onComplete: async (state) => {
    // Called when entire conversation completes
    console.log('Payment completed:', state.data);
    await sendReceipt(state.data);
  },
  onError: async (error, state) => {
    // Handle errors
    console.error('Payment error:', error);
    await logError(error, state);
  },
});

Error Handling

Handle validation errors and retries gracefully:

typescript
const step = {
  id: 'verification-code',
  prompt: 'Enter the 6-digit verification code.',
  schema: z.object({
    code: z.string().length(6).regex(/^\d{6}$/),
  }),
  validation: async (response) => {
    const isValid = await verifyCode(response.code);
    return isValid;
  },
  errorMessage: 'Invalid verification code. Please try again.',
  maxRetries: 3,
  next: 'success',
};

// Process with error handling
try {
  const result = await schema.processResponse(userInput);

  if (!result.success) {
    console.log(result.message); // Error message
    // Show error to user and retry
  } else if (result.isComplete) {
    console.log('Conversation completed!');
  } else {
    console.log(result.nextPrompt); // Next question
  }
} catch (error) {
  console.error('Critical error:', error);
}

Best Practices

  • Clear Prompts: Write clear, concise prompts that explain what information is needed
  • Validation: Use Zod schemas for type safety and validation
  • Error Messages: Provide helpful error messages that guide users
  • State Persistence: Save conversation state to allow users to resume
  • Callbacks: Use callbacks for side effects (API calls, database updates)
  • Dynamic Routing: Create personalized flows based on user responses
  • Testing: Test all conversation paths and edge cases

Complete Example

A full restaurant reservation system:

typescript
import {
  ConversationSchema,
  ConversationManager,
  Agent,
  AnthropicProvider,
  ToolRegistry,
} from '@lov3kaizen/agentsea-core';
import { z } from 'zod';

const reservationSchema = new ConversationSchema({
  name: 'restaurant-reservation',
  startStep: 'date',
  steps: [
    {
      id: 'date',
      prompt: 'What date would you like to reserve?',
      schema: z.object({
        date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
      }),
      validation: (response) => {
        const date = new Date(response.date);
        return date >= new Date();
      },
      errorMessage: 'Please select a future date',
      next: 'time',
    },
    {
      id: 'time',
      prompt: 'What time would you prefer?',
      schema: z.object({
        time: z.enum(['11:00', '12:00', '13:00', '18:00', '19:00', '20:00']),
      }),
      next: 'party-size',
    },
    {
      id: 'party-size',
      prompt: 'How many people?',
      schema: z.object({
        size: z.number().min(1).max(12),
      }),
      next: 'special-requests',
    },
    {
      id: 'special-requests',
      prompt: 'Any special requests or dietary requirements?',
      next: 'confirm',
    },
    {
      id: 'confirm',
      prompt: 'Please confirm your reservation.',
      onComplete: async (response, state) => {
        await createReservation({
          date: state.data.date.date,
          time: state.data.time.time,
          size: state.data['party-size'].size,
          notes: response,
        });
      },
      next: null,
    },
  ],
  onComplete: async (state) => {
    await sendConfirmationEmail(state.data);
  },
});

// Use with agent
const agent = new Agent(/* config */);
const manager = new ConversationManager(agent, reservationSchema);

// Handle user messages
async function handleMessage(userMessage: string) {
  const result = await manager.processMessage(userMessage);
  return result.response;
}

Next Steps