fix(phase2): 타로 interpret 견고성 — maxOutputTokens 8192 + wall-clock 가드로 호출 상한 축소
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user