Skip to main content

Overview

Core AI provides a hierarchy of error types that help you handle failures gracefully and provide meaningful feedback to users.

Error Hierarchy

Error
└── LLMError
    └── ProviderError
        └── StructuredOutputError
            ├── StructuredOutputNoObjectGeneratedError
            ├── StructuredOutputParseError
            └── StructuredOutputValidationError

LLMError

Base error class for all Core AI errors.
class LLMError extends Error {
  public readonly cause?: unknown;

  constructor(message: string, cause?: unknown);
}
Usage:
import { generate, LLMError } from '@core-ai/core-ai';

try {
  const result = await generate({
    model,
    messages: [], // Empty messages array
  });
} catch (error) {
  if (error instanceof LLMError) {
    console.error('LLM error:', error.message);
    if (error.cause) {
      console.error('Caused by:', error.cause);
    }
  }
}
When thrown:
  • Empty messages array
  • Empty input for embeddings
  • Empty prompt for image generation
  • Invalid configuration or parameters
Always check for LLMError first to catch all Core AI errors, then check for more specific error types.

ProviderError

Error specific to provider API failures (network errors, API errors, rate limits, etc.).
class ProviderError extends LLMError {
  public readonly provider: string;
  public readonly statusCode?: number;

  constructor(
    message: string,
    provider: string,
    statusCode?: number,
    cause?: unknown
  );
}
Usage:
import { generate, ProviderError } from '@core-ai/core-ai';

try {
  const result = await generate({ model, messages });
} catch (error) {
  if (error instanceof ProviderError) {
    console.error(`Provider: ${error.provider}`);
    console.error(`Status: ${error.statusCode}`);
    console.error(`Message: ${error.message}`);
    
    // Handle specific status codes
    if (error.statusCode === 429) {
      console.log('Rate limited - retry with backoff');
    } else if (error.statusCode === 401) {
      console.log('Authentication failed - check API key');
    }
  }
}
Common Status Codes:
CodeMeaningRecommended Action
401Authentication failedCheck API key
403ForbiddenCheck API permissions
429Rate limit exceededImplement retry with exponential backoff
500Server errorRetry request
503Service unavailableWait and retry
When thrown:
  • Invalid API key
  • Rate limits exceeded
  • Network failures
  • Provider API errors
  • Timeout errors

Structured Output Errors

Errors specific to structured output generation (generateObject and streamObject).

StructuredOutputError

Base class for all structured output errors.
class StructuredOutputError extends ProviderError {
  public readonly rawOutput?: string;

  constructor(
    message: string,
    provider: string,
    options: {
      statusCode?: number;
      cause?: unknown;
      rawOutput?: string;
    }
  );
}
Properties:
  • rawOutput: The raw text output from the model (if available)
  • provider: Which provider was used
  • statusCode: HTTP status code (if applicable)
  • cause: Underlying error that caused this error

StructuredOutputNoObjectGeneratedError

Thrown when the model doesn’t generate any structured output.
import { generateObject, StructuredOutputNoObjectGeneratedError } from '@core-ai/core-ai';
import { z } from 'zod';

const schema = z.object({
  name: z.string(),
  age: z.number(),
});

try {
  const result = await model.generateObject({
    messages: [{ role: 'user', content: 'Hello' }],
    schema,
  });
} catch (error) {
  if (error instanceof StructuredOutputNoObjectGeneratedError) {
    console.error('Model did not generate structured output');
    console.error('Raw output:', error.rawOutput);
  }
}
When thrown:
  • Model responds with regular text instead of structured output
  • Tool call for structured output was not made
  • Finish reason is not ‘stop’ or ‘tool-calls’

StructuredOutputParseError

Thrown when the model’s output cannot be parsed as JSON.
import { generateObject, StructuredOutputParseError } from '@core-ai/core-ai';

try {
  const result = await model.generateObject({
    messages: [{ role: 'user', content: 'Generate user data' }],
    schema,
  });
} catch (error) {
  if (error instanceof StructuredOutputParseError) {
    console.error('Failed to parse JSON output');
    console.error('Raw output:', error.rawOutput);
    console.error('Parse error:', error.cause);
  }
}
When thrown:
  • Model output is not valid JSON
  • JSON syntax errors in model output
  • Malformed structured output

StructuredOutputValidationError

Thrown when the model’s output doesn’t match the Zod schema.
import { generateObject, StructuredOutputValidationError } from '@core-ai/core-ai';

const schema = z.object({
  name: z.string().min(1),
  age: z.number().int().positive(),
  email: z.string().email(),
});

try {
  const result = await model.generateObject({
    messages: [{ role: 'user', content: 'Generate user data' }],
    schema,
  });
} catch (error) {
  if (error instanceof StructuredOutputValidationError) {
    console.error('Schema validation failed');
    console.error('Issues:', error.issues);
    console.error('Raw output:', error.rawOutput);
    
    // Display each validation issue
    error.issues.forEach((issue, i) => {
      console.log(`${i + 1}. ${issue}`);
    });
  }
}
Properties:
  • issues: Array of human-readable validation error messages
When thrown:
  • Missing required fields
  • Wrong data types
  • Values that don’t match schema constraints
  • Invalid enum values

Complete Error Handling Example

import {
  generate,
  generateObject,
  LLMError,
  ProviderError,
  StructuredOutputNoObjectGeneratedError,
  StructuredOutputParseError,
  StructuredOutputValidationError,
} from '@core-ai/core-ai';
import { z } from 'zod';

async function generateUserProfile(prompt: string) {
  const schema = z.object({
    name: z.string(),
    age: z.number(),
    email: z.string().email(),
  });

  try {
    const result = await model.generateObject({
      messages: [{ role: 'user', content: prompt }],
      schema,
      schemaName: 'UserProfile',
    });
    
    return result.object;
  } catch (error) {
    // Handle validation errors - most specific
    if (error instanceof StructuredOutputValidationError) {
      console.error('Schema validation failed:');
      error.issues.forEach(issue => console.error(`- ${issue}`));
      throw new Error('Invalid user profile format');
    }
    
    // Handle parse errors
    if (error instanceof StructuredOutputParseError) {
      console.error('Failed to parse JSON output');
      console.error('Raw output:', error.rawOutput);
      throw new Error('Model returned invalid JSON');
    }
    
    // Handle missing output
    if (error instanceof StructuredOutputNoObjectGeneratedError) {
      console.error('Model did not generate structured output');
      throw new Error('Model did not follow instructions');
    }
    
    // Handle provider errors
    if (error instanceof ProviderError) {
      if (error.statusCode === 429) {
        console.log('Rate limited - implementing retry...');
        await new Promise(resolve => setTimeout(resolve, 5000));
        return generateUserProfile(prompt); // Retry
      }
      
      console.error(`Provider error (${error.provider}):`, error.message);
      throw new Error('AI service unavailable');
    }
    
    // Handle generic LLM errors
    if (error instanceof LLMError) {
      console.error('LLM error:', error.message);
      throw new Error('AI request failed');
    }
    
    // Handle unexpected errors
    console.error('Unexpected error:', error);
    throw error;
  }
}

Retry Strategies

Exponential Backoff

async function generateWithRetry(
  model: ChatModel,
  messages: Message[],
  maxRetries = 3
) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await generate({ model, messages });
    } catch (error) {
      if (error instanceof ProviderError && error.statusCode === 429) {
        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        console.log(`Rate limited. Retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error; // Don't retry other errors
    }
  }
  throw new Error('Max retries exceeded');
}

Circuit Breaker

class CircuitBreaker {
  private failures = 0;
  private lastFailureTime = 0;
  private readonly threshold = 5;
  private readonly timeout = 60000; // 1 minute

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.isOpen()) {
      throw new Error('Circuit breaker is open');
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private isOpen(): boolean {
    if (this.failures >= this.threshold) {
      const timeSinceLastFailure = Date.now() - this.lastFailureTime;
      return timeSinceLastFailure < this.timeout;
    }
    return false;
  }

  private onSuccess() {
    this.failures = 0;
  }

  private onFailure() {
    this.failures++;
    this.lastFailureTime = Date.now();
  }
}

const breaker = new CircuitBreaker();

const result = await breaker.execute(() =>
  generate({ model, messages })
);

Validation Best Practices

Validate inputs before sending: Check for empty messages, validate file sizes, and ensure proper content types before making API calls to avoid unnecessary errors.
function validateMessages(messages: Message[]): void {
  if (messages.length === 0) {
    throw new LLMError('messages must not be empty');
  }
  
  for (const message of messages) {
    if (message.role === 'user' && typeof message.content === 'string') {
      if (message.content.trim().length === 0) {
        throw new LLMError('user message content must not be empty');
      }
    }
  }
}

Logging Errors

Log error details for debugging: Include provider, model, status codes, and raw output when available to help diagnose issues.
import { ProviderError } from '@core-ai/core-ai';

function logError(error: unknown, context: Record<string, unknown>) {
  const errorInfo: Record<string, unknown> = {
    ...context,
    message: error instanceof Error ? error.message : String(error),
    name: error instanceof Error ? error.name : 'Unknown',
  };

  if (error instanceof ProviderError) {
    errorInfo.provider = error.provider;
    errorInfo.statusCode = error.statusCode;
  }

  if (error instanceof StructuredOutputError) {
    errorInfo.rawOutput = error.rawOutput;
  }

  if (error instanceof StructuredOutputValidationError) {
    errorInfo.validationIssues = error.issues;
  }

  console.error('AI Error:', JSON.stringify(errorInfo, null, 2));
}

// Usage
try {
  const result = await generate({ model, messages });
} catch (error) {
  logError(error, {
    operation: 'generate',
    model: model.modelId,
    provider: model.provider,
    messageCount: messages.length,
  });
  throw error;
}

Next Steps

  • Learn about Providers for provider-specific behavior
  • Explore Configuration for controlling generation
  • Understand Messages for building conversations