Error Handling
Meloqui provides typed errors that help you handle failures gracefully and build resilient applications.
Error Hierarchy
All Meloqui errors extend ChatError, which extends the standard Error:
Error
└── ChatError
├── RateLimitError (retryable)
├── TimeoutError (retryable)
├── ServerError (retryable)
├── RagRetrievalError (retryable)
├── AuthenticationError
├── InvalidRequestError
├── ModelNotFoundError
├── CapabilityError
├── PluginError
├── StorageError
└── InvalidConversationIdErrorQuick Start
typescript
import { ChatClient, ChatError, RateLimitError } from 'meloqui';
try {
const response = await client.chat('Hello');
} catch (error) {
if (error instanceof RateLimitError) {
console.log('Rate limited, wait before retrying');
} else if (error instanceof ChatError) {
console.log(`Chat error: ${error.message}`);
} else {
throw error; // Re-throw unknown errors
}
}Retryable vs Non-Retryable
Every ChatError has an isRetryable property:
typescript
if (error instanceof ChatError && error.isRetryable) {
// Safe to retry this request
}Retryable Errors
These errors may succeed if you retry:
| Error | Cause | Action |
|---|---|---|
RateLimitError | Too many requests (429) | Wait and retry |
TimeoutError | Request took too long | Retry or increase timeout |
ServerError | Provider server error (5xx) | Wait and retry |
RagRetrievalError | Embedding service failed | Retry or continue without context |
Non-Retryable Errors
These errors require fixing the request or configuration:
| Error | Cause | Action |
|---|---|---|
AuthenticationError | Invalid API key (401/403) | Check credentials |
InvalidRequestError | Bad parameters (400) | Fix request |
ModelNotFoundError | Model doesn't exist | Correct model name |
CapabilityError | Feature not supported | Use different approach |
PluginError | Plugin misconfigured | Fix plugin config |
StorageError | Storage operation failed | Check storage |
InvalidConversationIdError | Invalid conversation ID | Fix ID format |
Error Properties
ChatError (Base)
typescript
interface ChatError extends Error {
readonly isRetryable: boolean; // Can this be retried?
readonly provider?: string; // Which provider threw it?
readonly originalError?: Error; // Underlying SDK error
}CapabilityError
typescript
interface CapabilityError extends ChatError {
readonly capability: string; // e.g., "vision", "streaming"
}StorageError
typescript
interface StorageError extends ChatError {
readonly operation: string; // "save", "load", "delete", "list"
readonly conversationId?: string; // Affected conversation
}Handling Patterns
Basic Try-Catch
typescript
import { ChatError } from 'meloqui';
try {
await client.chat('Hello');
} catch (error) {
if (error instanceof ChatError) {
console.error(`Provider: ${error.provider}`);
console.error(`Retryable: ${error.isRetryable}`);
console.error(`Message: ${error.message}`);
}
}Specific Error Handling
Handle different errors differently:
typescript
import {
RateLimitError,
AuthenticationError,
TimeoutError,
CapabilityError,
ChatError
} from 'meloqui';
async function chat(message: string) {
try {
return await client.chat(message);
} catch (error) {
if (error instanceof RateLimitError) {
// Exponential backoff (sleep = ms => new Promise(r => setTimeout(r, ms)))
await sleep(5000);
return await client.chat(message);
}
if (error instanceof AuthenticationError) {
throw new Error('Invalid API key - check your configuration');
}
if (error instanceof TimeoutError) {
// Try with longer timeout
return await client.chat(message, { timeout: 60000 });
}
if (error instanceof CapabilityError) {
console.warn(`${error.capability} not supported, using fallback`);
// Fall back to text-only
}
if (error instanceof ChatError) {
// Generic handling for other chat errors
console.error(`Chat failed: ${error.message}`);
}
throw error;
}
}Using isRetryable
typescript
import { ChatError } from 'meloqui';
async function chatWithRetry(message: string, maxRetries = 3) {
let lastError: Error | undefined;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await client.chat(message);
} catch (error) {
lastError = error as Error;
if (error instanceof ChatError && error.isRetryable) {
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
console.log(`Retry ${attempt + 1} after ${delay}ms`);
await sleep(delay);
continue;
}
// Non-retryable error, throw immediately
throw error;
}
}
throw lastError;
}Automatic Retry
Let Meloqui handle retries automatically:
typescript
const client = new ChatClient({
provider: 'openai',
model: 'gpt-4o',
retryConfig: {
maxAttempts: 3,
initialBackoffMs: 1000,
maxBackoffMs: 10000,
backoffMultiplier: 2
}
});
// Retryable errors are automatically retried
const response = await client.chat('Hello');Retry Callback
Monitor retry attempts:
typescript
import { RetryManager } from 'meloqui';
const retryManager = new RetryManager(
{
maxAttempts: 3,
initialBackoffMs: 1000,
maxBackoffMs: 10000,
backoffMultiplier: 2
},
(attempt, error, delayMs) => {
console.log(`Retry ${attempt}: ${error.message} (waiting ${delayMs}ms)`);
}
);Common Scenarios
Vision Fallback
Handle providers that don't support vision:
typescript
import { CapabilityError } from 'meloqui';
async function analyzeImage(imagePath: string, prompt: string) {
try {
return await client.chat({
role: 'user',
content: [
{ type: 'image', image: imagePath },
{ type: 'text', text: prompt }
]
});
} catch (error) {
if (error instanceof CapabilityError) {
// Provider doesn't support vision, use text description
return await client.chat(`[Image: ${imagePath}] ${prompt}`);
}
throw error;
}
}Storage Error Recovery
typescript
import { StorageError, IStorage } from 'meloqui';
async function loadConversation(storage: IStorage, id: string) {
try {
return await storage.load(id);
} catch (error) {
if (error instanceof StorageError) {
console.warn(`Storage error: ${error.operation} failed for ${error.conversationId}`);
// Return empty history as fallback
return [];
}
throw error;
}
}RAG Graceful Degradation
typescript
const client = new ChatClient({
provider: 'openai',
model: 'gpt-4o',
documentStore: store,
ragOptions: {
failOnError: false, // Don't throw on RAG failures
onError: (error) => {
console.warn('RAG failed, continuing without context:', error.message);
}
}
});Best Practices
1. Always Catch ChatError
typescript
try {
await client.chat(message);
} catch (error) {
if (error instanceof ChatError) {
// Handle Meloqui errors
} else {
// Handle other errors (network, etc.)
throw error;
}
}2. Log Original Errors
typescript
if (error instanceof ChatError && error.originalError) {
console.error('Original error:', error.originalError);
}3. Use Automatic Retry for Production
typescript
// Let the SDK handle transient failures
const client = new ChatClient({
provider: 'openai',
model: 'gpt-4o',
retryConfig: {
maxAttempts: 3,
initialBackoffMs: 1000,
maxBackoffMs: 30000,
backoffMultiplier: 2
}
});4. Handle Provider-Specific Errors
typescript
if (error instanceof ChatError) {
switch (error.provider) {
case 'openai':
// OpenAI-specific handling
break;
case 'anthropic':
// Anthropic-specific handling
break;
}
}5. User-Friendly Messages
typescript
function getUserMessage(error: ChatError): string {
if (error instanceof RateLimitError) {
return 'Too many requests. Please wait a moment.';
}
if (error instanceof AuthenticationError) {
return 'Authentication failed. Please check your API key.';
}
if (error instanceof TimeoutError) {
return 'Request timed out. Please try again.';
}
return 'An error occurred. Please try again later.';
}Next Steps
- API Reference: Errors - Complete error type definitions
- Retry & Backoff - Automatic retry configuration
- Vision & Multimodal - Working with images
- RAG - Retrieval-augmented generation
- API Reference: Infrastructure - RetryManager details
