diff --git a/app/api/tarot/interpret/route.ts b/app/api/tarot/interpret/route.ts index 3b42d24..35bc0ba 100644 --- a/app/api/tarot/interpret/route.ts +++ b/app/api/tarot/interpret/route.ts @@ -16,11 +16,16 @@ loadDotenv({ path: resolve(process.cwd(), '.env.local'), override: true }); // 모델 우선순위 — 강력한 순서 (이 API 키로 접근 가능한 모델만) — 사주 analyze와 동일 폴백 목록 const MODELS = [ - { id: 'gemini-2.5-pro', maxTokens: 4096 }, - { id: 'gemini-2.5-flash', maxTokens: 4096 }, - { id: 'gemini-2.0-flash', maxTokens: 4096 }, + { id: 'gemini-2.5-pro', maxTokens: 8192 }, + { id: 'gemini-2.5-flash', maxTokens: 8192 }, + { id: 'gemini-2.0-flash', maxTokens: 8192 }, ] as const; +// wall-clock 예산 — maxDuration(60s)보다 여유 있게 끊어 graceful 502를 반환 +const TIME_BUDGET_MS = 45_000; +// 최악 호출 수 상한 — 모델 폴백 × 검증 실패 reroll을 합쳐도 이 값을 넘지 않음 +const MAX_ATTEMPTS = 3; + export async function POST(request: Request) { // 1) 인증 — 로그인 사용자만 (Gemini API 무단 호출 방지) const supabase = await createClient(); @@ -71,10 +76,21 @@ export async function POST(request: Request) { context_meta, }); - // 5) 호출 — 모델 폴백 × 검증 실패 시 사유 주입 reroll(최대 2 시도) + // 5) 호출 — 모델 폴백 + 검증 실패 시 같은 모델로 1회 reroll + // wall-clock 45s 예산과 총 호출 3회 상한으로 최악 케이스를 조기 종료(→ 502) + const startedAt = Date.now(); let feedback = ''; - for (let attempt = 0; attempt < 2; attempt += 1) { - for (const { id: modelId, maxTokens } of MODELS) { + let attempts = 0; + + modelLoop: + for (const { id: modelId, maxTokens } of MODELS) { + // retry 0: 최초 시도, retry 1: 검증 실패 시에만 같은 모델로 1회 reroll + for (let retry = 0; retry < 2; retry += 1) { + if (attempts >= MAX_ATTEMPTS || Date.now() - startedAt > TIME_BUDGET_MS) { + break modelLoop; + } + attempts += 1; + try { const model = genAI.getGenerativeModel({ model: modelId, @@ -100,9 +116,12 @@ export async function POST(request: Request) { return NextResponse.json({ interpretation_json: parsed, model: modelId }); } + // 검증 실패 — 사유를 피드백으로 주입해 같은 모델로 1회 reroll(retry 루프 계속) feedback = invalid ?? 'JSON 파싱 실패'; } catch (modelError) { + // 호출 자체의 예외(레이트리밋 등)는 reroll하지 않고 바로 다음 모델로 폴백 feedback = modelError instanceof Error ? modelError.message : 'model error'; + break; } } }