ストーリー
LLM特有のエラー分類
エラーカテゴリ
| カテゴリ | エラー例 | リトライ | フォールバック |
|---|---|---|---|
| レート制限 | 429 Too Many Requests | あり(バックオフ) | 別プロバイダー |
| サーバーエラー | 500/502/503 | あり | 別プロバイダー |
| タイムアウト | 30秒超過 | あり(短縮) | 軽量モデル |
| コンテンツフィルター | 出力がフィルタリングされた | なし | プロンプト修正 |
| トークン超過 | コンテキスト長の制限 | なし | コンテキスト削減 |
| 認証エラー | 401 Unauthorized | なし | 即座にアラート |
| 不正なレスポンス | JSONパース失敗 | あり | リトライ + 修正指示 |
エラー識別と分類
type LLMErrorType =
| 'rate_limit'
| 'server_error'
| 'timeout'
| 'content_filter'
| 'token_exceeded'
| 'auth_error'
| 'invalid_response'
| 'unknown';
class LLMErrorClassifier {
classify(error: Error): {
type: LLMErrorType;
retryable: boolean;
fallbackable: boolean;
severity: 'low' | 'medium' | 'high' | 'critical';
} {
const message = error.message.toLowerCase();
if (message.includes('rate_limit') || message.includes('429')) {
return {
type: 'rate_limit',
retryable: true,
fallbackable: true,
severity: 'medium',
};
}
if (message.includes('500') || message.includes('502') || message.includes('503')) {
return {
type: 'server_error',
retryable: true,
fallbackable: true,
severity: 'high',
};
}
if (message.includes('timeout') || message.includes('ETIMEDOUT')) {
return {
type: 'timeout',
retryable: true,
fallbackable: true,
severity: 'medium',
};
}
if (message.includes('content_filter') || message.includes('content_policy')) {
return {
type: 'content_filter',
retryable: false,
fallbackable: false,
severity: 'low',
};
}
if (message.includes('context_length') || message.includes('token')) {
return {
type: 'token_exceeded',
retryable: false,
fallbackable: true,
severity: 'medium',
};
}
if (message.includes('401') || message.includes('auth') || message.includes('api_key')) {
return {
type: 'auth_error',
retryable: false,
fallbackable: false,
severity: 'critical',
};
}
return {
type: 'unknown',
retryable: true,
fallbackable: true,
severity: 'high',
};
}
}
モデルフォールバックチェーン
フォールバック戦略
graph TD
P["プライマリ:
GPT-4o
Azure Japan East"]
S["セカンダリ:
Claude 3.5 Sonnet
AWS Bedrock Tokyo"]
T["ターシャリ:
GPT-4o-mini
Azure Japan East"]
C["キャッシュ:
セマンティックキャッシュから類似回答"]
D["定型回答:
一時的にサービスが利用できません"]
P -->|"失敗"| S
S -->|"失敗"| T
T -->|"失敗"| C
C -->|"該当なし"| D
style P fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style S fill:#d1fae5,stroke:#059669,color:#065f46
style T fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e
style C fill:#f3e8ff,stroke:#7c3aed,color:#5b21b6
style D fill:#fee2e2,stroke:#dc2626,color:#991b1b
フォールバックチェーンの実装
interface FallbackChainConfig {
providers: {
provider: LLMProvider;
priority: number;
maxLatencyMs: number;
}[];
cache?: SemanticCache;
defaultResponse: string;
}
class FallbackChain {
private readonly sortedProviders: FallbackChainConfig['providers'];
constructor(
private readonly config: FallbackChainConfig,
private readonly errorClassifier: LLMErrorClassifier,
private readonly logger: Logger,
private readonly metrics: MetricsCollector,
) {
this.sortedProviders = [...config.providers].sort(
(a, b) => a.priority - b.priority,
);
}
async complete(
messages: ChatMessage[],
options?: CompletionOptions,
): Promise<CompletionResult & { provider: string; fallbackLevel: number }> {
const errors: Array<{ provider: string; error: Error }> = [];
// 各プロバイダーを順に試行
for (let i = 0; i < this.sortedProviders.length; i++) {
const { provider, maxLatencyMs } = this.sortedProviders[i];
try {
const result = await provider.complete(messages, {
...options,
timeout: maxLatencyMs,
});
// 成功時のメトリクス記録
this.metrics.record({
event: 'llm_completion',
provider: provider.name,
model: provider.modelId,
fallbackLevel: i,
latencyMs: result.latencyMs,
success: true,
});
return { ...result, provider: provider.name, fallbackLevel: i };
} catch (error) {
const classified = this.errorClassifier.classify(error as Error);
errors.push({ provider: provider.name, error: error as Error });
this.logger.warn(`Provider ${provider.name} failed`, {
errorType: classified.type,
fallbackable: classified.fallbackable,
attemptIndex: i,
});
// フォールバック不可のエラーは即座にスロー
if (!classified.fallbackable) {
throw error;
}
}
}
// 全プロバイダー失敗: キャッシュを試行
if (this.config.cache) {
const query = messages.find(m => m.role === 'user')?.content ?? '';
const cached = await this.config.cache.get(query);
if (cached) {
this.logger.info('Serving from cache after all providers failed');
this.metrics.record({
event: 'llm_completion',
provider: 'cache',
fallbackLevel: this.sortedProviders.length,
success: true,
});
return {
content: cached.answer,
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
model: 'cache',
finishReason: 'stop',
latencyMs: 0,
provider: 'cache',
fallbackLevel: this.sortedProviders.length,
};
}
}
// 最終手段: 定型回答
this.logger.error('All fallback options exhausted', {
errors: errors.map(e => ({
provider: e.provider,
message: e.error.message,
})),
});
this.metrics.record({
event: 'llm_completion',
provider: 'default',
fallbackLevel: this.sortedProviders.length + 1,
success: false,
});
return {
content: this.config.defaultResponse,
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
model: 'default',
finishReason: 'stop',
latencyMs: 0,
provider: 'default',
fallbackLevel: this.sortedProviders.length + 1,
};
}
}
Graceful Degradation
段階的な品質低下
interface DegradationLevel {
level: number;
description: string;
strategy: string;
}
const DEGRADATION_LEVELS: DegradationLevel[] = [
{
level: 0,
description: '通常運用',
strategy: 'フルRAGパイプライン + GPT-4o',
},
{
level: 1,
description: '軽量モード',
strategy: 'RAG + GPT-4o-mini(精度やや低下)',
},
{
level: 2,
description: 'キャッシュ優先モード',
strategy: 'セマンティックキャッシュ優先 + LLMは新規クエリのみ',
},
{
level: 3,
description: '検索のみモード',
strategy: 'ベクトル検索結果を直接表示(LLM不使用)',
},
{
level: 4,
description: '最小限モード',
strategy: '定型文 + サポート問い合わせリンク',
},
];
class GracefulDegradationManager {
private currentLevel = 0;
private readonly errorWindow: number[] = [];
private readonly windowSize = 60000; // 1分間
constructor(
private readonly thresholds: {
level1ErrorRate: number; // 例: 0.1 (10%)
level2ErrorRate: number; // 例: 0.3 (30%)
level3ErrorRate: number; // 例: 0.5 (50%)
level4ErrorRate: number; // 例: 0.8 (80%)
},
private readonly logger: Logger,
) {}
recordResult(success: boolean): void {
const now = Date.now();
this.errorWindow.push(success ? 0 : 1);
// 古いエントリを削除
while (
this.errorWindow.length > 0
&& this.errorWindow[0] < now - this.windowSize
) {
this.errorWindow.shift();
}
this.updateLevel();
}
getCurrentLevel(): DegradationLevel {
return DEGRADATION_LEVELS[this.currentLevel];
}
private updateLevel(): void {
if (this.errorWindow.length < 10) return;
const errorRate = this.errorWindow.reduce((sum, v) => sum + v, 0)
/ this.errorWindow.length;
let newLevel = 0;
if (errorRate >= this.thresholds.level4ErrorRate) newLevel = 4;
else if (errorRate >= this.thresholds.level3ErrorRate) newLevel = 3;
else if (errorRate >= this.thresholds.level2ErrorRate) newLevel = 2;
else if (errorRate >= this.thresholds.level1ErrorRate) newLevel = 1;
if (newLevel !== this.currentLevel) {
this.logger.warn(
`Degradation level changed: ${this.currentLevel} → ${newLevel}`,
{ errorRate, level: DEGRADATION_LEVELS[newLevel] },
);
this.currentLevel = newLevel;
}
}
}
タイムアウト戦略
レイヤー別タイムアウト
| レイヤー | タイムアウト | 理由 |
|---|---|---|
| HTTP接続 | 5秒 | APIへの接続確立 |
| LLM生成(通常) | 30秒 | 一般的なリクエスト |
| LLM生成(複雑) | 60秒 | 長文生成や複雑な推論 |
| ストリーミング初回チャンク | 10秒 | TTFB(Time to First Byte) |
| パイプライン全体 | 90秒 | 検索 + 生成 + 後処理の合計 |
class TimeoutManager {
private readonly timeouts: Map<string, number> = new Map([
['connection', 5000],
['completion_simple', 30000],
['completion_complex', 60000],
['stream_first_chunk', 10000],
['pipeline_total', 90000],
]);
getTimeout(operation: string, complexity: 'simple' | 'complex' = 'simple'): number {
if (operation === 'completion') {
return this.timeouts.get(`completion_${complexity}`) ?? 30000;
}
return this.timeouts.get(operation) ?? 30000;
}
// アダプティブタイムアウト: 過去のレイテンシから動的に調整
adaptiveTimeout(
recentLatencies: number[],
percentile: number = 95,
multiplier: number = 1.5,
): number {
if (recentLatencies.length === 0) return 30000;
const sorted = [...recentLatencies].sort((a, b) => a - b);
const index = Math.ceil(sorted.length * (percentile / 100)) - 1;
const p95 = sorted[index];
return Math.min(p95 * multiplier, 90000);
}
}
まとめ
| ポイント | 内容 |
|---|---|
| エラー分類 | LLM特有のエラーを分類し、リトライ可否とフォールバック可否を判定 |
| フォールバックチェーン | プライマリ → セカンダリ → キャッシュ → 定型文の段階的な切り替え |
| Graceful Degradation | エラー率に応じて運用レベルを動的に調整 |
| タイムアウト | レイヤー別の適切な設定とアダプティブな動的調整 |
チェックリスト
- LLM特有のエラーカテゴリと対応策を理解した
- モデルフォールバックチェーンの設計と実装を理解した
- Graceful Degradationの段階的な品質低下戦略を理解した
- レイヤー別タイムアウト戦略を理解した
次のステップへ
エラーハンドリングとフォールバック戦略を学びました。次のセクションでは、ストリーミングレスポンスとUXの最適化について学びます。
“最高品質 or 全停止”ではなく、“品質を落としてでもサービスを継続する”。それがプロダクション品質のAIシステムだ。
推定読了時間: 40分