Log Gemini chat request failures

This commit is contained in:
2026-10-09 14:16:11 +09:00
parent 82ef95fcea
commit fea0323d5e
2 changed files with 30 additions and 2 deletions
+26 -1
View File
@@ -1,10 +1,13 @@
"""지식DB Markdown 검색과 Gemini 설명. 제외 폴더는 읽지 않는다."""
import asyncio
import logging
import os
import re
import time
from functools import lru_cache
from pathlib import Path
from uuid import uuid4
import httpx
from fastapi import APIRouter, Depends, HTTPException
@@ -16,6 +19,7 @@ router = APIRouter(prefix="/knowledge-chat", tags=["B01_KnowledgeChat"])
KNOWLEDGE = Path(__file__).resolve().parents[1] / "resources" / "knowledge"
EXCLUDED = ("실무문서", "stmate", "다산소프트", "technical_info")
MODEL = "gemini-3.8-flash"
logger = logging.getLogger(__name__)
class ChatRequest(BaseModel):
@@ -149,6 +153,8 @@ async def ask_knowledge_chat(payload: ChatRequest, _session=Depends(verify_sessi
f"질문: {payload.question}"
)
)
request_id = uuid4().hex[:8]
started = time.monotonic()
try:
async with httpx.AsyncClient(timeout=30) as client:
for attempt in range(3):
@@ -157,13 +163,23 @@ async def ask_knowledge_chat(payload: ChatRequest, _session=Depends(verify_sessi
headers={"x-goog-api-key": key},
json={"contents": [{"parts": [{"text": prompt}]}]},
)
if response.status_code >= 400:
logger.warning(
"Gemini request failed id=%s model=%s attempt=%d status=%d elapsed=%.1fs prompt_chars=%d",
request_id,
MODEL,
attempt + 1,
response.status_code,
time.monotonic() - started,
len(prompt),
)
if response.status_code != 503 or attempt == 2:
break
await asyncio.sleep(attempt + 1)
if response.status_code == 503:
raise HTTPException(
status_code=503,
detail="Gemini 모델에 요청이 몰려 있습니다. 잠시 후 다시 질문해 주세요.",
detail=f"Gemini 모델에 요청이 몰려 있습니다. 잠시 후 다시 질문해 주세요. (문의번호 {request_id})",
)
response.raise_for_status()
answer = "".join(
@@ -180,12 +196,21 @@ async def ask_knowledge_chat(payload: ChatRequest, _session=Depends(verify_sessi
}.get(exc.response.status_code, "Gemini 요청에 실패했습니다. 잠시 후 다시 시도해 주세요.")
raise HTTPException(status_code=502, detail=detail) from exc
except httpx.TimeoutException as exc:
logger.warning(
"Gemini request timed out id=%s elapsed=%.1fs", request_id, time.monotonic() - started
)
raise HTTPException(
status_code=504,
detail="Gemini 3.8 Flash 응답이 지연되어 시간 초과됐습니다. "
"잠시 후 다시 시도해 주세요.",
) from exc
except (httpx.RequestError, ValueError) as exc:
logger.warning(
"Gemini request error id=%s type=%s elapsed=%.1fs",
request_id,
type(exc).__name__,
time.monotonic() - started,
)
raise HTTPException(
status_code=502,
detail="Gemini 서버에 연결하지 못했습니다. 잠시 후 다시 시도해 주세요.",
+4 -1
View File
@@ -36,7 +36,7 @@ def test_missing_api_key_is_reported_before_search(monkeypatch):
assert error.value.status_code == 503
def test_busy_gemini_is_retried_and_reported(monkeypatch):
def test_busy_gemini_is_retried_and_reported(monkeypatch, caplog):
monkeypatch.setenv("GEMINI_API_KEY", "test-key")
monkeypatch.setattr(chat, "search", lambda _question: [])
calls = []
@@ -64,6 +64,9 @@ def test_busy_gemini_is_retried_and_reported(monkeypatch):
asyncio.run(chat.ask_knowledge_chat(chat.ChatRequest(question="일위대가가 뭐야"), {}))
assert len(calls) == 3
assert "요청이 몰려" in error.value.detail
assert [f"attempt={n} status=503" in caplog.text for n in (1, 2, 3)] == [True] * 3
assert "test-key" not in caplog.text
assert "일위대가가 뭐야" not in caplog.text
def test_gemini_timeout_is_reported(monkeypatch):