fix: add retry + fallback for Gemini 503 UNAVAILABLE errors
- Retry up to 3 times with exponential backoff on 503/UNAVAILABLE - Fall back to gemini-2.0-flash on final attempt - Replace raw JSON error with user-friendly Japanese message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d1a41b4d3b
commit
0d741c4cb0
|
|
@ -261,14 +261,9 @@ $extractedText
|
|||
throw Exception('Gemini API Key is missing. Please set GEMINI_API_KEY.');
|
||||
}
|
||||
|
||||
final model = GenerativeModel(
|
||||
model: 'gemini-2.5-flash', // ⚠️ FIXED MODEL NAME - DO NOT CHANGE without explicit user approval (confirmed working on 2026-01-17)
|
||||
apiKey: apiKey,
|
||||
generationConfig: GenerationConfig(
|
||||
responseMimeType: 'application/json',
|
||||
temperature: 0.2, // チャート一貫性向上のため 0.4→0.2 に変更 (2026-02-09)
|
||||
),
|
||||
);
|
||||
// モデル候補: 503/UNAVAILABLE 時にフォールバック
|
||||
const primaryModel = 'gemini-2.5-flash'; // ⚠️ FIXED - confirmed 2026-01-17
|
||||
const fallbackModel = 'gemini-2.0-flash'; // 503 連続時のフォールバック
|
||||
|
||||
// Prepare Prompt
|
||||
final promptText = customPrompt ?? '''
|
||||
|
|
@ -296,15 +291,37 @@ $extractedText
|
|||
値が不明な場合は null または 適切な推測値を入れてください。
|
||||
''';
|
||||
|
||||
// Prepare Content
|
||||
// Prepare Content parts (画像バイト読み込みは一度だけ)
|
||||
final contentParts = <Part>[TextPart(promptText)];
|
||||
for (var path in imagePaths) {
|
||||
// 撮影時に圧縮済み
|
||||
final bytes = await File(path).readAsBytes();
|
||||
contentParts.add(DataPart('image/jpeg', bytes));
|
||||
}
|
||||
|
||||
// 503 時: リトライ(指数バックオフ)→ フォールバックモデル
|
||||
const maxRetries = 3;
|
||||
final modelsToTry = [primaryModel, primaryModel, primaryModel, fallbackModel];
|
||||
|
||||
for (int attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
final modelName = modelsToTry[attempt];
|
||||
final isLastAttempt = attempt == maxRetries;
|
||||
|
||||
try {
|
||||
if (attempt > 0) {
|
||||
final waitSec = attempt == maxRetries ? 2 : (attempt * 3);
|
||||
debugPrint('Retry $attempt/$maxRetries (model: $modelName, wait: ${waitSec}s)...');
|
||||
await Future.delayed(Duration(seconds: waitSec));
|
||||
}
|
||||
|
||||
final model = GenerativeModel(
|
||||
model: modelName,
|
||||
apiKey: apiKey,
|
||||
generationConfig: GenerationConfig(
|
||||
responseMimeType: 'application/json',
|
||||
temperature: 0.2,
|
||||
),
|
||||
);
|
||||
|
||||
final response = await model.generateContent([Content.multi(contentParts)]);
|
||||
|
||||
final jsonString = response.text;
|
||||
|
|
@ -321,12 +338,27 @@ $extractedText
|
|||
await AnalysisCacheService.registerBrandIndex(result.name, imageHash);
|
||||
}
|
||||
|
||||
if (attempt > 0) debugPrint('Succeeded on attempt $attempt (model: $modelName)');
|
||||
return result;
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('Direct API Error: $e');
|
||||
final errStr = e.toString();
|
||||
final is503 = errStr.contains('503') || errStr.contains('UNAVAILABLE') || errStr.contains('high demand');
|
||||
debugPrint('Direct API Error (attempt $attempt, model: $modelName): $e');
|
||||
|
||||
if (isLastAttempt || !is503) {
|
||||
// 最終試行 or 503以外のエラーはそのまま投げる
|
||||
if (is503) {
|
||||
throw Exception('AIサーバーが混雑しています。しばらく待ってから再試行してください。');
|
||||
}
|
||||
throw Exception('AI解析エラー(Direct): $e');
|
||||
}
|
||||
// 503 → 次のリトライへ
|
||||
}
|
||||
}
|
||||
|
||||
// ここには到達しない
|
||||
throw Exception('AI解析に失敗しました。再試行してください。');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue