fix(phase2): 타로 interpret 견고성 — maxOutputTokens 8192 + wall-clock 가드로 호출 상한 축소

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 21:01:43 +09:00
parent 3acc1dbbe6
commit 10a60300ae

View File

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