Kit supports four AI providers through a unified interface. Each provider implements the same
BaseProvider abstract class — switching providers requires only changing an environment variable. This page covers provider setup, model configuration, auto-detection, fallback chains, and capabilities.Quick Setup
With a single API key in
apps/boilerplate/.env.local, both LLM Chat and RAG Chat work immediately. No AI_PROVIDER or AI_MODEL configuration needed — Kit uses sensible defaults.Available Providers
API Key:
OPENAI_API_KEY Default Model: gpt-5-nano Base URL Override: OPENAI_BASE_URL| Model | Context | Input (per 1M) | Output (per 1M) | Best For |
|---|---|---|---|---|
gpt-5.2 | 400K | $1.75 | $14.00 | Latest flagship reasoning |
gpt-5 | 400K | $1.25 | $10.00 | Coding and agentic tasks |
gpt-5-mini | 400K | $0.25 | $2.00 | Well-defined tasks |
gpt-5-nano | 400K | $0.05 | $0.40 | Default — fast, cheapest |
gpt-4.1 | 1M | $2.00 | $8.00 | Complex tasks, coding |
gpt-4.1-mini | 1M | $0.40 | $1.60 | Efficient coding |
o3 | 200K | $2.00 | $8.00 | Deep reasoning |
o4-mini | 200K | $1.10 | $4.40 | Reasoning on a budget |
OpenAI is required for RAG embeddings (
text-embedding-3-small), even when using another provider for chat.Auto-Detection
When
AI_PROVIDER is not set, Kit automatically selects the provider based on which API keys are available. The detection order is: Anthropic → OpenAI → Google → xAI.src/lib/ai/config.ts — Provider Auto-Detection
export function getActiveProvider(): AIProvider | null {
// If provider is explicitly set, use it (AI_API_KEY can serve as its key)
if (aiConfig.AI_PROVIDER) {
return aiConfig.AI_PROVIDER
}
// Auto-detect based on available API keys (Anthropic preferred)
if (aiConfig.ANTHROPIC_API_KEY) return 'anthropic'
if (aiConfig.OPENAI_API_KEY) return 'openai'
if (aiConfig.GOOGLE_AI_API_KEY) return 'google'
if (aiConfig.XAI_API_KEY) return 'xai'
return null
}
To force a specific provider, set
AI_PROVIDER explicitly:bash
# Force Anthropic even if OpenAI key is also present
AI_PROVIDER=anthropic
Model Aliases
Kit provides human-friendly aliases for model names. Use
claude instead of claude-sonnet-4-5-20250929, or gpt4 instead of gpt-4.1:src/lib/ai/config.ts — Model Aliases
export const MODEL_ALIASES: Record<string, string> = {
// OpenAI aliases
gpt5: 'gpt-5',
'gpt5.2': 'gpt-5.2',
'gpt5-mini': 'gpt-5-mini',
'gpt5-nano': 'gpt-5-nano',
gpt4: 'gpt-4.1',
'gpt4-mini': 'gpt-4.1-mini',
'gpt-4': 'gpt-4.1',
o3: 'o3',
'o4-mini': 'o4-mini',
// Anthropic aliases
claude: 'claude-sonnet-4-5-20250929',
'claude-opus': 'claude-opus-4-6-20260205',
'claude-sonnet': 'claude-sonnet-4-5-20250929',
'claude-haiku': 'claude-haiku-4-5-20251001',
opus: 'claude-opus-4-6-20260205',
sonnet: 'claude-sonnet-4-5-20250929',
haiku: 'claude-haiku-4-5-20251001',
// Google aliases
gemini: 'gemini-2.5-flash',
'gemini-pro': 'gemini-2.5-pro',
'gemini-flash': 'gemini-2.5-flash',
'gemini-lite': 'gemini-2.5-flash-lite',
'gemini-3': 'gemini-3-flash-preview',
// xAI aliases
grok: 'grok-4-1-fast-reasoning',
'grok-code': 'grok-code-fast-1',
'grok-fast': 'grok-4-fast-reasoning',
'grok-4.1': 'grok-4-1-fast-reasoning',
}
Aliases work everywhere — API routes, hooks, and configuration. Pass
model: 'claude' or model: 'gemini-pro' and Kit resolves them to the full model ID automatically.Provider Factory
The provider factory creates and caches provider instances. It validates configuration with Zod, creates the correct provider class, and caches instances by a SHA-256 hash of the API key:
src/lib/ai/provider-factory.ts — Factory with Cache
export function createAIProvider(config: ProviderConfig): BaseProvider {
// Validate configuration
const validation = ProviderConfigSchema.safeParse(config)
if (!validation.success) {
const errors = validation.error.issues.map((err) => ({
field: err.path.join('.'),
message: err.message,
}))
throw new ValidationError('Invalid provider configuration', errors)
}
const validatedConfig = validation.data
// Check cache
const cacheKey = createCacheKey(validatedConfig)
const cached = providerCache.get(cacheKey)
if (cached) {
return cached
}
// Create new provider instance
let provider: BaseProvider
switch (validatedConfig.provider) {
case 'openai':
provider = new OpenAIProvider(validatedConfig)
break
case 'anthropic':
provider = new AnthropicProvider(validatedConfig)
break
case 'google':
provider = new GoogleProvider(validatedConfig)
break
case 'xai':
provider = new XAIProvider(validatedConfig)
break
default:
// This should never happen due to validation, but TypeScript needs it
throw new InvalidProviderError(validatedConfig.provider)
}
// Cache the provider instance
providerCache.set(cacheKey, provider)
return provider
}
Key behaviors:
- Validation first — Zod schema validates provider, API key, and model before creating an instance
- Cache by config hash — Same provider + API key + model returns the cached instance
- Switch statement — Maps provider string to concrete class (OpenAI, Anthropic, Google, xAI)
Fallback Chains
When the preferred provider is unavailable, Kit falls back to the best available alternative using a scoring algorithm. Each provider is scored based on the request's requirements:
src/lib/ai/provider-factory.ts — Provider Selection
export interface ProviderRequirements {
needsVision?: boolean
needsEmbeddings?: boolean
needsFunctions?: boolean
needsLargeContext?: boolean
preferCheap?: boolean
preferFast?: boolean
}
/**
* Select the best provider based on requirements
*/
export function selectProvider(
requirements: ProviderRequirements = {}
): AIProvider {
const available = getAvailableProviders()
if (available.length === 0) {
throw new ValidationError('No AI providers configured', [
{
field: 'API_KEYS',
message: 'At least one provider API key must be set',
},
])
}
// Score each provider based on requirements
const scores: Record<AIProvider, number> = {
openai: 0,
anthropic: 0,
google: 0,
xai: 0,
}
for (const provider of available) {
let score = 10 // Base score for being available
if (requirements.needsVision) {
if (
provider === 'openai' ||
provider === 'anthropic' ||
provider === 'google'
) {
score += 5
}
}
if (requirements.needsEmbeddings) {
if (provider === 'openai' || provider === 'google') {
score += 5
}
}
if (requirements.needsFunctions) {
// All providers support functions
score += 1
}
if (requirements.needsLargeContext) {
if (provider === 'anthropic' || provider === 'google') {
score += 5 // Both have 1M+ context windows
}
}
if (requirements.preferCheap) {
if (provider === 'google') {
score += 10 // Gemini Flash is very cheap
} else if (provider === 'openai') {
score += 5 // GPT-4o-mini is cheap
}
}
if (requirements.preferFast) {
if (provider === 'openai' || provider === 'google') {
score += 5
}
}
scores[provider] = score
}
// Find provider with highest score
let bestProvider: AIProvider = available[0]
let bestScore = scores[bestProvider]
for (const provider of available) {
if (scores[provider] > bestScore) {
bestProvider = provider
bestScore = scores[provider]
}
}
return bestProvider
}
The scoring system considers:
| Requirement | Top Scorers | Score Bonus |
|---|---|---|
needsVision | OpenAI, Anthropic, Google | +5 |
needsEmbeddings | OpenAI, Google | +5 |
needsLargeContext | Anthropic, Google (1M+) | +5 |
preferCheap | Google (+10), OpenAI (+5) | +5 to +10 |
preferFast | OpenAI, Google | +5 |
Every available provider gets a base score of 10. The provider with the highest total score is selected.
Provider Capabilities
Not all providers support all features. The capability matrix helps Kit make informed fallback decisions:
| Capability | OpenAI | Anthropic | xAI | |
|---|---|---|---|---|
| Streaming | Yes | Yes | Yes | Yes |
| Functions/Tools | Yes | Yes | Yes | Yes |
| Vision | Yes | Yes | Yes | No |
| Embeddings | Yes | No | Yes | No |
| System Messages | Yes | Yes | Yes | Yes |
| Max Context | 1M | 200K (1M beta) | 1M | 2M |
RAG search uses OpenAI's embedding model (configurable via
AI_EMBEDDING_MODEL, default: text-embedding-3-small), regardless of the active chat provider. Ensure OPENAI_API_KEY is set (or AI_API_KEY as fallback) if you use the RAG system, even with Anthropic or Google as the primary provider.Reasoning Model Handling
GPT-5 family and o-series models are reasoning models with special parameter constraints. The
isReasoningModel flag on ModelInfo controls runtime behavior:| Model | Reasoning | Temperature | Default maxTokens |
|---|---|---|---|
| GPT-5, GPT-5.2, GPT-5 Mini, GPT-5 Nano | Yes | Unsupported | 16,384 |
| o3, o4-mini | Yes | Unsupported | 16,384 |
| GPT-4.1 | No | 0.7 (default) | 1,000 |
Key constraints:
- Temperature is unsupported — passing it to reasoning models triggers an SDK warning and may degrade behavior. Kit omits
temperatureentirely for reasoning models. - maxTokens covers BOTH internal reasoning AND visible output — reasoning models use most of the token budget for internal chain-of-thought. The default of 1,000 is far too low; Kit uses 16,384 for reasoning models.
- Symptom of too-low maxTokens:
finishReason: 'length'with 0 output tokens → empty response to the user.
typescript
// Reasoning-aware parameter handling (openai.ts)
const isReasoning = modelInfo?.isReasoningModel === true
streamText({
...(isReasoning ? {} : { temperature: options.temperature ?? 0.7 }),
maxTokens: options.maxTokens ?? (isReasoning ? 16384 : 1000),
})
Streaming Response Diagnostics
streamText() from the Vercel AI SDK returns lazy Promises that resolve after the stream ends. Kit reads these for diagnostics:| Property | Type | Purpose |
|---|---|---|
result.finishReason | Promise<string> | Why the stream ended ('stop', 'length', 'content_filter') |
result.usage | Promise<object> | Token counts (promptTokens, completionTokens) |
result.warnings | Warning[] | Unsupported parameters, model deprecations |
finishReason values:
| Value | Meaning | Action |
|---|---|---|
'stop' | Normal completion | None |
'length' | Token budget exhausted | Increase maxTokens |
'content_filter' | Provider blocked the response | Review content policy |
Kit logs a warning when the stream completes with 0 content chunks or a non-
'stop' finish reason. This diagnostic data is essential for debugging empty AI responses.Custom Base URLs
Each provider supports a custom base URL for proxies, self-hosted models, or alternative endpoints:
| Variable | Default | Purpose |
|---|---|---|
OPENAI_BASE_URL | https://api.openai.com/v1 | OpenAI API proxy or compatible endpoint |
ANTHROPIC_BASE_URL | https://api.anthropic.com | Anthropic API proxy |
GOOGLE_AI_BASE_URL | https://generativelanguage.googleapis.com/v1 | Google AI proxy |
XAI_BASE_URL | https://api.x.ai/v1 | xAI API proxy |
OPENAI_ORG_ID | — | OpenAI organization ID for billing |
Error Handling and Retries
All providers share the same retry logic from
BaseProvider. Failed requests are retried with exponential backoff:src/lib/ai/providers/base-provider.ts — Retry Configuration
export abstract class BaseProvider {
protected readonly provider: AIProvider
protected readonly apiKey: string
protected readonly baseURL?: string
protected readonly timeout: number
protected readonly retryConfig: RetryConfig
protected defaultModel: string
protected defaultTemperature: number
protected defaultMaxTokens: number
constructor(config: ProviderConfig) {
this.provider = config.provider
this.apiKey = config.apiKey
this.baseURL = config.baseURL
this.timeout = config.timeout ?? 30000 // 30 seconds default
this.defaultModel = config.model ?? this.getDefaultModel()
this.defaultTemperature = config.defaultTemperature ?? 0.7
this.defaultMaxTokens = config.defaultMaxTokens ?? 1000
this.retryConfig = {
maxRetries: config.maxRetries ?? 3,
baseDelay: 1000,
maxDelay: 10000,
backoffFactor: 2,
}
}
The retry behavior:
- Max retries: 3 (configurable per provider)
- Base delay: 1,000ms
- Max delay: 10,000ms (cap)
- Backoff factor: 2x (1s → 2s → 4s)
- Jitter: 0-30% random variation to avoid thundering herd
- Timeout: 30 seconds per request (configurable)
Only retryable errors trigger retries — network timeouts and 5xx responses. Client errors (400, 401, 403) fail immediately.
The error class hierarchy provides structured error information:
| Error Class | When | Retryable |
|---|---|---|
AIProviderError | Base class for all provider errors | Varies |
NetworkError | Connection failures, DNS errors | Yes |
TimeoutError | Request exceeds timeout | Yes |
ValidationError | Invalid config or request | No |
InvalidProviderError | Unknown provider string | No |