Skip to content

fix: Gemini deprecated 모델 제거 및 2.5-flash 단일 모델 재시도 로직 적용 - #131

Merged
evenif99 merged 4 commits into
devfrom
fix/gemini-fallback-model-update
Jun 23, 2026
Merged

fix: Gemini deprecated 모델 제거 및 2.5-flash 단일 모델 재시도 로직 적용#131
evenif99 merged 4 commits into
devfrom
fix/gemini-fallback-model-update

Conversation

@evenif99

@evenif99 evenif99 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • deprecated 모델 전부 제거 (gemini-2.0-flash-lite, gemini-2.0-flash, gemini-1.5-flash)
  • gemini-2.5-flash 단일 모델 사용 + 503/429 시 최대 3회 재시도 (1.5초 → 3초 → 4.5초 백오프)
  • chatbot, moderation, judgment 3개 파일 동일 패턴 적용
  • 에러 로그에 상세 메시지 포함
  • 챗봇 시스템 프롬프트에 마크다운 금지 규칙 추가

Test plan

  • 챗봇 정상 응답 확인 (마크다운 없이 순수 텍스트 응답)
  • 진술 저장 시 moderation 정상 동작 확인
  • 판결 생성 정상 동작 확인
  • 503/429 발생 시 재시도 로그 확인

🤖 Generated with Claude Code

Summary by CodeRabbit

릴리스 노트

  • Bug Fixes / Improvements
    • 채팅·판결 추출·콘텐츠 조정에서 모델 호출 오류 대응을 “다중 폴백”에서 “단일 모델 재시도”로 전환해 일시적 장애에 더 안정적으로 대응합니다.
    • 혼잡/서비스 불가(503) 및 과부하(429) 등 재시도 가능한 경우에만 제한된 횟수 내에서 재시도하며, 그 외 오류는 빠르게 실패 처리합니다.
    • 답변 형식 규칙을 강화해 마크다운 대신 순수 텍스트로 응답하도록 조정했습니다.

fix: Replace deprecated gemini-1.5-flash with gemini-2.0-flash

gemini-1.5-flash is no longer available in the v1beta API, causing
404 errors when all prior fallback models also fail. Update the last
resort model to gemini-2.0-flash across chatbot, moderation, and
judgment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@
@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
talky-owl Ready Ready Preview, Comment Jun 23, 2026 7:42am

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: d50fda32-8677-4c7c-a0bb-b7ae37ad5069

📥 Commits

Reviewing files that changed from the base of the PR and between c8dd4d0 and 769d9f8.

📒 Files selected for processing (1)
  • src/lib/ai/chatbot.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/ai/chatbot.ts

📝 Walkthrough

Walkthrough

Chatbot, Judgment, Moderation 세 AI 모듈에서 다중 폴백 모델 순회 패턴을 제거하고, 고정 모델(gemini-2.5-flash)에 대한 재시도 기반 오류 처리로 통일합니다. 각 모듈의 Gemini 호출 상수를 재정의하고, 재시도 가능 조건을 503/429 오류로 단순화하며, 모든 함수에서 attempt 루프와 지연된 재시도 패턴을 적용합니다.

Changes

AI 모듈 폴백 반복에서 재시도 로직으로 전환

Layer / File(s) Summary
재시도 상수 및 헬퍼 함수 일괄 정의
src/lib/ai/chatbot.ts, src/lib/ai/judgment.ts, src/lib/ai/moderation.ts
각 모듈에서 MODEL_FALLBACKS 배열과 isFallbackable 함수를 제거하고, 고정 모델명(MODEL_NAME), 최대 재시도 횟수(MAX_RETRIES = 3), 재시도 지연(RETRY_DELAY_MS = 1500), 재시도 판정 함수(isRetryable: 503/Service Unavailable/high demand/429 중심), 그리고 비동기 sleep 유틸을 추가합니다.
Chatbot 모듈 - 재시도 루프 및 프롬프트 적용
src/lib/ai/chatbot.ts
getChatbotResponse의 모델 순회 루프를 attempt 기반 재시도 루프로 변경합니다. ChatbotTimeoutError는 즉시 throw, isRetryable 조건과 재시도 한도 내에서만 지연 후 재시도(continue)하고, 그 외 오류는 경고 로그 후 루프 종료(break)합니다. SYSTEM_PROMPT에 마크다운 문법 금지 및 순수 텍스트 답변 지침을 추가합니다.
Judgment 모듈 - 재시도 루프 적용
src/lib/ai/judgment.ts
extractDisputeMetacallJudgmentAi 함수가 모두 모델 순회에서 attempt 루프로 변경되며, 성공 시 반환값에 modelName: MODEL_NAME을 고정 추가합니다. 실패 catch에서 isRetryable 조건 및 재시도 횟수 검사를 통해 지연 후 재시도 또는 throw lastErr 중 하나를 선택합니다.
Moderation 모듈 - 재시도 루프 적용 및 인터페이스 확장
src/lib/ai/moderation.ts
moderateContent의 모델 순회를 attempt 루프로 변경하고, 성공 시 ModerationResultmodelName: MODEL_NAME 필드를 추가합니다. 실패 시 lastErr를 갱신하고 isRetryable 조건 및 재시도 한도를 검사하여 지연 재시도 또는 즉시 throw를 수행합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • I5-Project/TALKY-OWL#70: extractDisputeMetacallJudgmentAi 함수의 Gemini 호출 로직이 도입되었던 PR로, 본 PR에서 이들 함수의 모델 폴백 구조를 재시도 기반으로 전환합니다.
  • I5-Project/TALKY-OWL#125: src/lib/ai/chatbot.tsgetChatbotResponse 폴백 모델 처리 로직이 도입되었던 PR로, 본 PR에서 해당 함수의 오류 처리를 재시도 기반으로 재설계합니다.
  • I5-Project/TALKY-OWL#129: Judgment과 Moderation 모듈의 Gemini 오류 처리 기반이 마련되었던 PR로, 본 PR에서 모델 선택 및 오류 분류 전략을 근본적으로 변경합니다.

Suggested reviewers

  • wjdalss21

Poem

🐇 폴백 순회는 안녕~ 안녕~
재시도 루프 단 하나만 집중!
503에 429, 지연하고 또 지연,
세 모듈 모두 한 패턴으로 통일,
토끼의 효율 네트워크 완성! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명이 저장소의 필수 템플릿 구조를 따르지 않았습니다. 작업 내용, 담당 영역, 관련 Issue, 변경사항, 테스트 결과, 작업 범위, 보안 확인 등 주요 섹션이 누락되었습니다. PR 템플릿의 모든 필수 섹션(1~12번)을 작성하여 포함시키고, 체크리스트 항목들을 확인하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 요약하고 있습니다. deprecated 모델 제거 및 gemini-2.5-flash 단일 모델로의 전환, 재시도 로직 적용이 정확히 반영되어 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gemini-fallback-model-update

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.23.0)
src/lib/ai/chatbot.ts

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.21][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist


Comment @coderabbitai help to get the list of available commands.

fix: Remove deprecated models and add per-model retry for 503/429

gemini-2.0-flash-lite, gemini-2.0-flash, gemini-1.5-flash are all
deprecated (404). Replace with gemini-2.5-flash + gemini-2.5-pro
fallback. Add per-model retry (up to 2 attempts with backoff) for
transient 503/429 errors before switching to next model. Apply to
chatbot, moderation, and judgment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@
fix: Use gemini-2.5-flash only with increased retry count

Remove multi-model fallback to avoid gemini-2.5-pro cost overhead.
Use gemini-2.5-flash as single model with up to 3 retries on
transient 503/429 errors across chatbot, moderation, and judgment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/ai/judgment.ts`:
- Around line 110-122: The generic Error('JSON') exceptions thrown during JSON
parsing validation (in the jsonMatch check, JSON.parse catch block, and type
validation for title and summary fields) are not being caught by the isRetryable
or shouldFallback logic, causing immediate failure instead of attempting
fallback models. Replace these three Error('JSON') throws with an error type
that will be recognized as retryable by the error handling logic, ensuring that
JSON format issues trigger fallback model attempts rather than immediate
termination.
- Around line 3-5: The retry delay strategy using MAX_RETRIES_PER_MODEL set to 2
with RETRY_DELAY_MS set to 1500 consumes too much of the 10-second timeout
budget (10000ms) allocated in the route handler, leaving insufficient time for
actual API calls. Reduce RETRY_DELAY_MS and/or MAX_RETRIES_PER_MODEL constants
to ensure the total retry wait time across all models in MODEL_FALLBACKS array
stays well below the 10-second timeout limit, preserving adequate time for the
actual API request execution. Aim for a retry strategy that uses no more than
3-4 seconds total across all retry attempts and model fallbacks combined.

In `@src/lib/ai/moderation.ts`:
- Around line 13-14: The moderation retry policy defined by
MAX_RETRIES_PER_MODEL and RETRY_DELAY_MS constants conflicts with the 8-second
timeout in src/app/api/disputes/route.ts. The retry backoff logic in lines
87-124 accumulates to approximately 9 seconds for multiple models, causing the
retry path to exceed the 8-second timeout constraint. Adjust the
MAX_RETRIES_PER_MODEL and RETRY_DELAY_MS constants to ensure the total retry
duration (including all backoff iterations across multiple models) stays well
within the 8-second timeout limit.
- Around line 94-102: The errors thrown at the JSON validation check and JSON
parsing catch block in the Gemini moderation response handling are not being
caught by the shouldFallback or isRetryable conditions, causing immediate
termination instead of triggering the fallback model chain. Instead of throwing
errors directly for invalid JSON and unparseable JSON cases, catch these errors
and handle them in a way that allows the fallback/retry mechanism to work, such
as marking them as retryable errors or wrapping them appropriately so the outer
error handling logic at line 127 can properly detect and route them to the
fallback chain.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 2def09e8-3959-4418-945a-be2c8797f6df

📥 Commits

Reviewing files that changed from the base of the PR and between 0851ff1 and 86a196c.

📒 Files selected for processing (3)
  • src/lib/ai/chatbot.ts
  • src/lib/ai/judgment.ts
  • src/lib/ai/moderation.ts

Comment thread src/lib/ai/judgment.ts Outdated
Comment on lines +3 to +5
const MODEL_FALLBACKS = ['gemini-2.5-flash', 'gemini-2.5-pro']
const MAX_RETRIES_PER_MODEL = 2
const RETRY_DELAY_MS = 1500

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

재시도 대기 시간이 라우트 타임아웃 예산을 사실상 소진합니다.

Line 104~Line 141 로직은 모델당 최대 4.5초(1.5s + 3s) 대기를 사용하고, 2개 모델이면 대기만 최대 9초입니다. src/app/api/disputes/route.tswithTimeout(extractDisputeMeta(content), 10000)과 결합되면 실제 API 호출 시간을 거의 남기지 못해 폴백 완료 전에 타임아웃으로 실패할 가능성이 큽니다.

제안 수정안
 const MAX_RETRIES_PER_MODEL = 2
 const RETRY_DELAY_MS = 1500
+const EXTRACT_META_BUDGET_MS = 9500

 export async function extractDisputeMeta(statement: string): Promise<DisputeMetaResult> {
+  const deadline = Date.now() + EXTRACT_META_BUDGET_MS
   const apiKey = process.env.GEMINI_API_KEY
   if (!apiKey) throw new Error('GEMINI_API_KEY is not configured')
@@
         if (isRetryable(err) && attempt < MAX_RETRIES_PER_MODEL) {
+          const remaining = deadline - Date.now()
+          if (remaining <= 0) throw err
           console.warn(`[extractDisputeMeta] ${modelName} retry ${attempt + 1}/${MAX_RETRIES_PER_MODEL}: ${errMsg}`)
-          await sleep(RETRY_DELAY_MS * (attempt + 1))
+          await sleep(Math.min(RETRY_DELAY_MS * (attempt + 1), remaining))
           continue
         }

Also applies to: 104-141

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/judgment.ts` around lines 3 - 5, The retry delay strategy using
MAX_RETRIES_PER_MODEL set to 2 with RETRY_DELAY_MS set to 1500 consumes too much
of the 10-second timeout budget (10000ms) allocated in the route handler,
leaving insufficient time for actual API calls. Reduce RETRY_DELAY_MS and/or
MAX_RETRIES_PER_MODEL constants to ensure the total retry wait time across all
models in MODEL_FALLBACKS array stays well below the 10-second timeout limit,
preserving adequate time for the actual API request execution. Aim for a retry
strategy that uses no more than 3-4 seconds total across all retry attempts and
model fallbacks combined.

Comment thread src/lib/ai/judgment.ts Outdated
Comment on lines +110 to +122
const jsonMatch = text.match(/\{[\s\S]*\}/)
if (!jsonMatch) throw new Error('JSON')

let parsed: { title?: unknown; summary?: unknown }
try {
parsed = JSON.parse(jsonMatch[0])
} catch {
throw new Error('JSON')
}

if (typeof parsed.title !== 'string' || typeof parsed.summary !== 'string') {
throw new Error('JSON')
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

JSON 포맷 오류에서 즉시 종료되어 다음 폴백 모델을 시도하지 못합니다.

Error('JSON')isRetryable/shouldFallback 어느 쪽에도 걸리지 않아 Line 144, Line 288에서 즉시 throw 됩니다. 생성형 응답의 형식 흔들림이 한 번만 나와도 체인이 끊겨, 폴백 모델 도입 효과가 크게 줄어듭니다.

제안 수정안
+function isJsonFormatError(err: unknown): boolean {
+  const msg = err instanceof Error ? err.message : String(err)
+  return msg === 'JSON'
+}
@@
-        if (isRetryable(err) && attempt < MAX_RETRIES_PER_MODEL) {
+        if ((isRetryable(err) || isJsonFormatError(err)) && attempt < MAX_RETRIES_PER_MODEL) {
           console.warn(`[extractDisputeMeta] ${modelName} retry ${attempt + 1}/${MAX_RETRIES_PER_MODEL}: ${errMsg}`)
           await sleep(RETRY_DELAY_MS * (attempt + 1))
           continue
         }
-
-        throw err
+        if (isJsonFormatError(err)) break
+        throw err
@@
-        if (isRetryable(err) && attempt < MAX_RETRIES_PER_MODEL) {
+        if ((isRetryable(err) || isJsonFormatError(err)) && attempt < MAX_RETRIES_PER_MODEL) {
           console.warn(`[callJudgmentAi] ${modelName} retry ${attempt + 1}/${MAX_RETRIES_PER_MODEL}: ${errMsg}`)
           await sleep(RETRY_DELAY_MS * (attempt + 1))
           continue
         }
-
-        throw err
+        if (isJsonFormatError(err)) break
+        throw err

Also applies to: 129-145, 257-270, 273-289

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/judgment.ts` around lines 110 - 122, The generic Error('JSON')
exceptions thrown during JSON parsing validation (in the jsonMatch check,
JSON.parse catch block, and type validation for title and summary fields) are
not being caught by the isRetryable or shouldFallback logic, causing immediate
failure instead of attempting fallback models. Replace these three Error('JSON')
throws with an error type that will be recognized as retryable by the error
handling logic, ensuring that JSON format issues trigger fallback model attempts
rather than immediate termination.

Comment thread src/lib/ai/moderation.ts Outdated
Comment on lines +13 to +14
const MAX_RETRIES_PER_MODEL = 2
const RETRY_DELAY_MS = 1500

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

모더레이션 재시도 정책이 상위 타임아웃(8초)과 충돌합니다.

Line 87~Line 124의 백오프 합은 모델당 4.5초, 2개 모델이면 9초입니다. src/app/api/disputes/route.ts에서 withTimeout(moderateContent(content), 8000)을 사용하므로, 재시도 경로는 타임아웃으로 조기 실패할 확률이 높습니다.

제안 수정안
-const MAX_RETRIES_PER_MODEL = 2
-const RETRY_DELAY_MS = 1500
+const MAX_RETRIES_PER_MODEL = 1
+const RETRY_DELAY_MS = 800

Also applies to: 87-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/moderation.ts` around lines 13 - 14, The moderation retry policy
defined by MAX_RETRIES_PER_MODEL and RETRY_DELAY_MS constants conflicts with the
8-second timeout in src/app/api/disputes/route.ts. The retry backoff logic in
lines 87-124 accumulates to approximately 9 seconds for multiple models, causing
the retry path to exceed the 8-second timeout constraint. Adjust the
MAX_RETRIES_PER_MODEL and RETRY_DELAY_MS constants to ensure the total retry
duration (including all backoff iterations across multiple models) stays well
within the 8-second timeout limit.

Comment thread src/lib/ai/moderation.ts Outdated
Comment on lines +94 to +102
const jsonMatch = text.match(/\{[\s\S]*\}/)
if (!jsonMatch) throw new Error('Gemini moderation returned invalid JSON')

let parsed: { isBlocked?: unknown; reason?: unknown; confidenceScore?: unknown; hasPersonalInfo?: unknown }
try {
parsed = JSON.parse(jsonMatch[0])
} catch {
throw new Error('Gemini moderation returned unparseable JSON')
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

JSON 파싱 실패에서 즉시 throw 되어 폴백 모델이 동작하지 않습니다.

Line 95, Line 101에서 던진 JSON 오류는 shouldFallback/isRetryable에 해당하지 않아 Line 127에서 즉시 종료됩니다. 모델 응답 포맷 흔들림에 취약해, 준비한 폴백 체인을 충분히 활용하지 못합니다.

제안 수정안
 function shouldFallback(err: unknown): boolean {
@@
 }
 
+function isJsonFormatError(err: unknown): boolean {
+  const msg = err instanceof Error ? err.message : String(err)
+  return (
+    msg.includes('invalid JSON') ||
+    msg.includes('unparseable JSON')
+  )
+}
+
@@
-        if (isRetryable(err) && attempt < MAX_RETRIES_PER_MODEL) {
+        if ((isRetryable(err) || isJsonFormatError(err)) && attempt < MAX_RETRIES_PER_MODEL) {
           console.warn(`[moderation] ${modelName} retry ${attempt + 1}/${MAX_RETRIES_PER_MODEL}: ${errMsg}`)
           await sleep(RETRY_DELAY_MS * (attempt + 1))
           continue
         }
 
+        if (isJsonFormatError(err)) break
         throw err

Also applies to: 112-128

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/moderation.ts` around lines 94 - 102, The errors thrown at the
JSON validation check and JSON parsing catch block in the Gemini moderation
response handling are not being caught by the shouldFallback or isRetryable
conditions, causing immediate termination instead of triggering the fallback
model chain. Instead of throwing errors directly for invalid JSON and
unparseable JSON cases, catch these errors and handle them in a way that allows
the fallback/retry mechanism to work, such as marking them as retryable errors
or wrapping them appropriately so the outer error handling logic at line 127 can
properly detect and route them to the fallback chain.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
src/lib/ai/moderation.ts (2)

102-112: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

JSON 파싱 실패에서 즉시 throw 되어 재시도가 동작하지 않습니다.

Line 85/91의 'invalid JSON'/'unparseable JSON' 오류는 isRetryable(503/Service Unavailable/high demand/429)에 해당하지 않아 Line 112에서 곧바로 throw 됩니다. 모델 응답 포맷 흔들림에 취약하므로, 포맷 오류를 재시도 대상에 포함하는 것을 권장합니다.

제안 수정안
+function isJsonFormatError(err: unknown): boolean {
+  const msg = err instanceof Error ? err.message : String(err)
+  return msg.includes('invalid JSON') || msg.includes('unparseable JSON')
+}
@@
-      if (isRetryable(err) && attempt < MAX_RETRIES) {
+      if ((isRetryable(err) || isJsonFormatError(err)) && attempt < MAX_RETRIES) {
         console.warn(`[moderation] ${MODEL_NAME} retry ${attempt + 1}/${MAX_RETRIES}: ${errMsg}`)
         await sleep(RETRY_DELAY_MS * (attempt + 1))
         continue
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/moderation.ts` around lines 102 - 112, JSON parsing errors
(invalid JSON/unparseable JSON from lines 85/91) are not being retried because
the isRetryable function does not recognize them as retryable conditions,
causing them to be immediately thrown at line 112. Modify the isRetryable
function to also return true for JSON parsing related errors in addition to the
existing retryable conditions (503/Service Unavailable/high demand/429), so that
format errors will trigger retries instead of failing immediately.

77-112: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

재시도 지연 합계(9초)가 모더레이션 라우트 타임아웃(8초)을 초과합니다.

MAX_RETRIES = 3 + RETRY_DELAY_MS * (attempt + 1)로 재시도 대기만 1.5s + 3s + 4.5s = 9초인데, src/app/api/disputes/route.tswithTimeout(moderateContent(content), 8000)을 사용합니다. 재시도 경로가 8초 예산을 넘기므로 503/429 발생 시 재시도가 끝나기도 전에 타임아웃으로 실패합니다. 백오프를 줄이거나 데드라인을 반영하세요.

#!/bin/bash
rg -nP 'RETRY_DELAY_MS|MAX_RETRIES' src/lib/ai/moderation.ts
rg -nP 'withTimeout\(\s*moderateContent' src/app/api/disputes/route.ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/moderation.ts` around lines 77 - 112, The total retry delay time
in the moderation retry loop exceeds the 8-second timeout configured in the
disputes route. With MAX_RETRIES set to 3 and RETRY_DELAY_MS multiplied by
(attempt + 1) in the sleep call, the cumulative backoff delays (1.5s + 3s + 4.5s
= 9s) alone exceed the timeout, leaving no time for actual API calls. Reduce
either the MAX_RETRIES constant or the RETRY_DELAY_MS constant (or both) so that
the total possible retry duration stays well under 8 seconds, accounting for the
exponential backoff formula used in the sleep call.
src/lib/ai/judgment.ts (2)

94-133: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

재시도 지연 합계(9초)가 라우트 타임아웃 예산(10초)을 거의 소진합니다.

MAX_RETRIES = 3 + RETRY_DELAY_MS * (attempt + 1) 조합으로 재시도 대기만 1.5s + 3s + 4.5s = 9초이며, 여기에 generateContent 4회 호출 시간이 더해집니다. src/app/api/disputes/route.tswithTimeout(extractDisputeMeta(content), 10000)과 결합되면 실제 호출 시간이 거의 남지 않아 재시도 완료 전에 타임아웃으로 실패할 가능성이 높습니다. 데드라인 인지 백오프(Math.min(delay, remaining))나 더 작은 RETRY_DELAY_MS/MAX_RETRIES를 권장합니다.

#!/bin/bash
rg -nP 'RETRY_DELAY_MS|MAX_RETRIES' src/lib/ai/judgment.ts
rg -nP 'withTimeout\(\s*(extractDisputeMeta|callJudgmentAi)' src/app/api/disputes/route.ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/judgment.ts` around lines 94 - 133, The exponential backoff delays
in the retry loop (RETRY_DELAY_MS multiplied by attempt + 1) accumulate to
approximately 9 seconds across 3 retries, leaving almost no time for actual
generateContent calls within the 10-second timeout budget of the parent API
route. Reduce either the RETRY_DELAY_MS constant or MAX_RETRIES constant (or
both) to allow sufficient time for the actual API calls, or implement
deadline-aware backoff logic that caps the sleep duration to
Math.min(calculatedDelay, remainingTimeUntilTimeout) to ensure retries fail fast
when approaching the timeout limit. This applies to the sleep call within the
catch block of the retry loop in the extractDisputeMeta function.

119-129: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

JSON 포맷 오류에서 즉시 throw 되어 단일 모델 재시도 이점을 활용하지 못합니다.

Line 101/107/111의 throw new Error('JSON')isRetryable(503/Service Unavailable/high demand/429)에 해당하지 않아 Line 129에서 즉시 throw 됩니다. 단일 모델 재시도 구조에서는 동일 모델 재호출로 포맷 흔들림이 해소될 수 있으므로, JSON 포맷 오류도 재시도 대상에 포함하는 것이 효과적입니다(callJudgmentAi의 Line 256~266에도 동일 적용).

제안 수정안
+function isJsonFormatError(err: unknown): boolean {
+  const msg = err instanceof Error ? err.message : String(err)
+  return msg === 'JSON'
+}
@@
-      if (isRetryable(err) && attempt < MAX_RETRIES) {
+      if ((isRetryable(err) || isJsonFormatError(err)) && attempt < MAX_RETRIES) {
         console.warn(`[extractDisputeMeta] ${MODEL_NAME} retry ${attempt + 1}/${MAX_RETRIES}: ${errMsg}`)
         await sleep(RETRY_DELAY_MS * (attempt + 1))
         continue
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/judgment.ts` around lines 119 - 129, JSON format errors thrown at
lines 101, 107, and 111 are not being retried because they do not match the
conditions checked by the isRetryable function, which only handles specific
error types like 503 or 429. Modify the isRetryable function or the error
handling logic in the catch block to treat JSON format errors as retryable
errors so they can be retried within the MAX_RETRIES loop instead of being
immediately thrown. Apply the same fix to the callJudgmentAi function around
lines 256-266 where similar JSON format error handling exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/ai/chatbot.ts`:
- Around line 106-125: The retry loop uses exponential backoff (RETRY_DELAY_MS *
(attempt + 1)) that, combined with REQUEST_TIMEOUT_MS per request, can
accumulate to over 2 minutes in worst case scenarios, exposing excessive delays
to users. To fix this, implement a maximum cumulative timeout for the entire
retry operation in the retry loop within the chatbot function. Track the total
elapsed time from when the loop starts, and break out of the loop if the
cumulative time exceeds a reasonable maximum (e.g., 60 seconds). Alternatively,
reduce either RETRY_DELAY_MS, MAX_RETRIES, or both to keep total potential delay
within acceptable bounds for user-facing API calls.

---

Duplicate comments:
In `@src/lib/ai/judgment.ts`:
- Around line 94-133: The exponential backoff delays in the retry loop
(RETRY_DELAY_MS multiplied by attempt + 1) accumulate to approximately 9 seconds
across 3 retries, leaving almost no time for actual generateContent calls within
the 10-second timeout budget of the parent API route. Reduce either the
RETRY_DELAY_MS constant or MAX_RETRIES constant (or both) to allow sufficient
time for the actual API calls, or implement deadline-aware backoff logic that
caps the sleep duration to Math.min(calculatedDelay, remainingTimeUntilTimeout)
to ensure retries fail fast when approaching the timeout limit. This applies to
the sleep call within the catch block of the retry loop in the
extractDisputeMeta function.
- Around line 119-129: JSON format errors thrown at lines 101, 107, and 111 are
not being retried because they do not match the conditions checked by the
isRetryable function, which only handles specific error types like 503 or 429.
Modify the isRetryable function or the error handling logic in the catch block
to treat JSON format errors as retryable errors so they can be retried within
the MAX_RETRIES loop instead of being immediately thrown. Apply the same fix to
the callJudgmentAi function around lines 256-266 where similar JSON format error
handling exists.

In `@src/lib/ai/moderation.ts`:
- Around line 102-112: JSON parsing errors (invalid JSON/unparseable JSON from
lines 85/91) are not being retried because the isRetryable function does not
recognize them as retryable conditions, causing them to be immediately thrown at
line 112. Modify the isRetryable function to also return true for JSON parsing
related errors in addition to the existing retryable conditions (503/Service
Unavailable/high demand/429), so that format errors will trigger retries instead
of failing immediately.
- Around line 77-112: The total retry delay time in the moderation retry loop
exceeds the 8-second timeout configured in the disputes route. With MAX_RETRIES
set to 3 and RETRY_DELAY_MS multiplied by (attempt + 1) in the sleep call, the
cumulative backoff delays (1.5s + 3s + 4.5s = 9s) alone exceed the timeout,
leaving no time for actual API calls. Reduce either the MAX_RETRIES constant or
the RETRY_DELAY_MS constant (or both) so that the total possible retry duration
stays well under 8 seconds, accounting for the exponential backoff formula used
in the sleep call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a8c45a6-894b-4816-a373-95e0f7a778a9

📥 Commits

Reviewing files that changed from the base of the PR and between 86a196c and c8dd4d0.

📒 Files selected for processing (3)
  • src/lib/ai/chatbot.ts
  • src/lib/ai/judgment.ts
  • src/lib/ai/moderation.ts

Comment thread src/lib/ai/chatbot.ts
Comment on lines +106 to 125
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
try {
const model = genAI.getGenerativeModel({ model: modelName })
const model = genAI.getGenerativeModel({ model: MODEL_NAME })
const chat = model.startChat({ history: chatHistory })
const result = await withTimeout(chat.sendMessage(userMessage), REQUEST_TIMEOUT_MS)
return result.response.text()
} catch (error) {
lastError = error
const errMsg = error instanceof Error ? error.message : String(error)

if (error instanceof ChatbotTimeoutError) throw error

if (isFallbackable(error)) {
console.warn(`[chatbot] ${modelName} unavailable, trying next model...`)
if (isRetryable(error) && attempt < MAX_RETRIES) {
console.warn(`[chatbot] ${MODEL_NAME} retry ${attempt + 1}/${MAX_RETRIES}: ${errMsg}`)
await sleep(RETRY_DELAY_MS * (attempt + 1))
continue
}

console.warn(`[chatbot] ${MODEL_NAME} failed: ${errMsg}`)
break

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm RETRY_DELAY_MS / REQUEST_TIMEOUT_MS in chatbot and any route-level timeout
rg -nP 'RETRY_DELAY_MS|REQUEST_TIMEOUT_MS|MAX_RETRIES' src/lib/ai/chatbot.ts
rg -nP 'withTimeout|getChatbotResponse' src/app/api/chatbot/route.ts

Repository: I5-Project/TALKY-OWL

Length of output: 790


🏁 Script executed:

cat -n src/app/api/chatbot/route.ts | head -80

Repository: I5-Project/TALKY-OWL

Length of output: 3098


🏁 Script executed:

ast-grep outline src/lib/ai/chatbot.ts --view expanded

Repository: I5-Project/TALKY-OWL

Length of output: 811


🏁 Script executed:

rg -nP 'category|writing|카테고리|글쓰기' src/lib/ai/chatbot.ts -A 2

Repository: I5-Project/TALKY-OWL

Length of output: 234


🏁 Script executed:

sed -n '8,44p' src/lib/ai/chatbot.ts

Repository: I5-Project/TALKY-OWL

Length of output: 1011


🏁 Script executed:

rg -nP 'SYSTEM_PROMPT' src/lib/ai/chatbot.ts -A 35

Repository: I5-Project/TALKY-OWL

Length of output: 2479


재시도 누적 지연으로 응답 시간이 과도해질 수 있습니다.

현재 설정에서 RETRY_DELAY_MS = 1500, MAX_RETRIES = 3, REQUEST_TIMEOUT_MS = 30000일 때, 재시도 대기만 1.5s + 3s + 4.5s = 9초이고, 여기에 최대 4회의 sendMessage 시도(각 최대 30초)가 더해져 최악의 경우 약 2분까지 누적됩니다. src/app/api/chatbot/route.ts에는 getChatbotResponse 호출을 감싸는 외부 타임아웃이 없으므로, 이 전체 지연이 사용자에게 노출됩니다. 최대 대기 시간을 제한하거나 더 짧은 백오프 전략을 검토하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/ai/chatbot.ts` around lines 106 - 125, The retry loop uses
exponential backoff (RETRY_DELAY_MS * (attempt + 1)) that, combined with
REQUEST_TIMEOUT_MS per request, can accumulate to over 2 minutes in worst case
scenarios, exposing excessive delays to users. To fix this, implement a maximum
cumulative timeout for the entire retry operation in the retry loop within the
chatbot function. Track the total elapsed time from when the loop starts, and
break out of the loop if the cumulative time exceeds a reasonable maximum (e.g.,
60 seconds). Alternatively, reduce either RETRY_DELAY_MS, MAX_RETRIES, or both
to keep total potential delay within acceptable bounds for user-facing API
calls.

fix(chatbot): Add plain text only instruction to system prompt

Gemini sometimes returns markdown syntax (**, ##) in responses
which renders as literal characters in the chat UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@
@evenif99 evenif99 changed the title fix: fallback 모델 gemini-1.5-flash를 gemini-2.0-flash로 교체 fix: Gemini deprecated 모델 제거 및 2.5-flash 단일 모델 재시도 로직 적용 Jun 23, 2026
@evenif99 evenif99 self-assigned this Jun 23, 2026
@evenif99
evenif99 merged commit e9083e1 into dev Jun 23, 2026
2 of 3 checks passed
@evenif99
evenif99 deleted the fix/gemini-fallback-model-update branch June 23, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant