Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/visible/cruel/llms.txt

Use this file to discover all available pages before exploring further.

cruelModel() wraps individual AI SDK v6 model instances to inject chaos into both generation and streaming operations.

API Reference

function cruelModel<T extends LanguageModelV3>(
  model: T,
  options?: CruelModelOptions
): T
model
LanguageModelV3
required
The AI SDK model instance to wrap. Works with any provider:
  • @ai-sdk/openai
  • @ai-sdk/anthropic
  • @ai-sdk/google
  • @ai-sdk/mistral
  • @ai-sdk/gateway
  • Any AI SDK v6 compatible provider
options
CruelModelOptions
Chaos configuration options. See Chaos Options below.
return
T
Returns a wrapped model with the same type signature as the input model.

Basic Usage

import { openai } from '@ai-sdk/openai'
import { generateText } from 'ai'
import { cruelModel } from 'cruel/ai-sdk'

const model = cruelModel(openai('gpt-4'), {
  rateLimit: 0.2,
  overloaded: 0.1,
  delay: [100, 500],
})

const result = await generateText({
  model,
  prompt: 'Explain distributed systems',
})

Chaos Options

API Errors

Simulate common API failures:
rateLimit
number | { rate: number; retryAfter?: number }
Probability of rate limit errors (0-1).
// Simple: 20% chance of rate limit
rateLimit: 0.2

// Advanced: Custom retry-after header
rateLimit: { rate: 0.2, retryAfter: 30 }
Throws: CruelAPIError with statusCode: 429
overloaded
number
Probability of model overload errors (0-1).
overloaded: 0.1 // 10% chance
Throws: CruelAPIError with statusCode: 529
invalidApiKey
number
Probability of invalid API key errors (0-1).
invalidApiKey: 0.05 // 5% chance
Throws: CruelAPIError with statusCode: 401, not retryable
quotaExceeded
number
Probability of quota exceeded errors (0-1).
quotaExceeded: 0.03 // 3% chance
Throws: CruelAPIError with statusCode: 402, not retryable
modelUnavailable
number
Probability of model unavailable errors (0-1).
modelUnavailable: 0.08 // 8% chance
Throws: CruelAPIError with statusCode: 503

Content Issues

contextLength
number
Probability of context length exceeded errors (0-1).
contextLength: 0.05 // 5% chance
Throws: CruelAPIError with statusCode: 400, not retryable
contentFilter
number
Probability of content filter errors (0-1).
contentFilter: 0.02 // 2% chance
Throws: CruelAPIError with statusCode: 400, not retryable
emptyResponse
number
Probability of empty response errors (0-1).
emptyResponse: 0.01 // 1% chance
Throws: CruelAPIError with statusCode: 200, not retryable
partialResponse
number
Probability of truncated text responses (0-1). Only applies to generateText().
partialResponse: 0.1 // 10% chance
Returns truncated content (10-70% of original length)

Latency

delay
number | [number, number]
Add latency before responses.
// Fixed delay
delay: 500 // Always 500ms

// Random delay range
delay: [100, 1000] // Between 100-1000ms
timeout
number
Probability of request timeouts (0-1). Request never completes.
timeout: 0.05 // 5% chance of timeout

Streaming Chaos

Options that only affect streamText() and streaming operations:
streamCut
number
Probability of stream interruption during token streaming (0-1).
streamCut: 0.1 // 10% chance of stream cut
Throws: CruelAPIError mid-stream
slowTokens
number | [number, number]
Add delay between streamed tokens (milliseconds).
// Fixed delay per token
slowTokens: 50 // 50ms per token

// Random delay per token
slowTokens: [20, 100] // 20-100ms per token
corruptChunks
number
Probability of corrupted text chunks (0-1). Injects replacement character (U+FFFD).
corruptChunks: 0.05 // 5% chance per chunk

Response Modification

finishReason
'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'
Override the finish reason in responses.
finishReason: 'length' // Always report max tokens reached
tokenUsage
{ inputTokens?: number; outputTokens?: number }
Override token usage reporting.
tokenUsage: {
  inputTokens: 999,
  outputTokens: 999,
}

Generic Failures

fail
number
Probability of generic generation failure (0-1).
fail: 0.1 // 10% chance
Throws: CruelAPIError with statusCode: 500

Event Monitoring

onChaos
(event: ChaosEvent) => void
Callback fired when chaos is injected.
onChaos: (event) => {
  console.log(`[${event.modelId}] ${event.type}`, event)
}
See ChaosEvent types

Streaming Example

import { openai } from '@ai-sdk/openai'
import { streamText } from 'ai'
import { cruelModel } from 'cruel/ai-sdk'

const model = cruelModel(openai('gpt-4'), {
  slowTokens: [20, 80],
  streamCut: 0.1,
  corruptChunks: 0.02,
  onChaos: (event) => {
    if (event.type === 'slowTokens') {
      console.log(`Token delay: ${event.ms}ms`)
    }
  },
})

const result = streamText({
  model,
  prompt: 'Write a haiku about testing',
})

try {
  for await (const chunk of result.textStream) {
    process.stdout.write(chunk)
  }
} catch (error) {
  console.error('Stream error:', error.message)
}

Other Model Types

Cruel provides specialized wrappers for all AI SDK model types:

Embedding Models

import { openai } from '@ai-sdk/openai'
import { embed } from 'ai'
import { cruelEmbeddingModel } from 'cruel/ai-sdk'

const model = cruelEmbeddingModel(openai.embedding('text-embedding-3-small'), {
  rateLimit: 0.1,
  delay: [50, 150],
})

const result = await embed({
  model,
  value: 'Hello world',
})

Image Models

import { openai } from '@ai-sdk/openai'
import { experimental_generateImage } from 'ai'
import { cruelImageModel } from 'cruel/ai-sdk'

const model = cruelImageModel(openai.image('dall-e-3'), {
  overloaded: 0.2,
  delay: [1000, 3000],
})

const result = await experimental_generateImage({
  model,
  prompt: 'A sunset over mountains',
})

Speech Models

import { openai } from '@ai-sdk/openai'
import { experimental_generateSpeech } from 'ai'
import { cruelSpeechModel } from 'cruel/ai-sdk'

const model = cruelSpeechModel(openai.speech('tts-1'), {
  rateLimit: 0.15,
  delay: [500, 1500],
})

const result = await experimental_generateSpeech({
  model,
  text: 'Hello from Cruel',
})

Transcription Models

import { openai } from '@ai-sdk/openai'
import { experimental_transcribe } from 'ai'
import { cruelTranscriptionModel } from 'cruel/ai-sdk'

const model = cruelTranscriptionModel(openai.transcription('whisper-1'), {
  overloaded: 0.1,
  delay: [200, 800],
})

const result = await experimental_transcribe({
  model,
  audio: audioBuffer,
})

Video Models

import { cruelVideoModel } from 'cruel/ai-sdk'

const model = cruelVideoModel(someProvider.video('video-model'), {
  delay: [2000, 5000],
  overloaded: 0.3,
})
Embedding, Image, Speech, Transcription, and Video models support a subset of chaos options: rateLimit, overloaded, invalidApiKey, quotaExceeded, delay, timeout, and fail.Streaming-specific options like streamCut and slowTokens only apply to language models.

Model ID Override

Cruel respects the MODEL environment variable for testing different models:
# Override model in wrapped instances
MODEL=gpt-3.5-turbo npm test
// Original: openai('gpt-4')
// With MODEL=gpt-3.5-turbo: openai('gpt-3.5-turbo')
const model = cruelModel(openai('gpt-4'), { rateLimit: 0.1 })
For provider syntax:
# Override just the model name, keep provider
MODEL=gpt-3.5-turbo npm test
// Original: gateway('openai/gpt-4')
// With MODEL=gpt-3.5-turbo: gateway('openai/gpt-3.5-turbo')
const model = cruelModel(gateway('openai/gpt-4'), { rateLimit: 0.1 })

Error Types

All errors thrown by cruelModel are instances of CruelAPIError:
import { CruelAPIError } from 'cruel/ai-sdk'

try {
  const result = await generateText({ model, prompt: 'Hello' })
} catch (error) {
  if (error instanceof CruelAPIError) {
    console.log('Status:', error.statusCode)      // HTTP status code
    console.log('Retryable:', error.isRetryable)  // Can AI SDK retry?
    console.log('Message:', error.message)        // Error description
    console.log('Data:', error.data)              // Additional data (e.g., retryAfter)
  }
}
See CruelAPIError source for implementation details.

TypeScript

cruelModel preserves the exact type of the wrapped model:
import { openai } from '@ai-sdk/openai'
import type { LanguageModelV3 } from 'cruel/ai-sdk'

const original = openai('gpt-4')
const wrapped = cruelModel(original, { rateLimit: 0.1 })

// Both have identical types
type Original = typeof original  // LanguageModelV3
type Wrapped = typeof wrapped    // LanguageModelV3

Next Steps

Provider Wrapping

Apply chaos at the provider level

Middleware

Use AI SDK middleware for chaos

Tool Chaos

Inject failures into tools

Presets

Use built-in presets

Build docs developers (and LLMs) love