From 5b0c2434bdb00d45b805cf39e4ba21db695da9d6 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 6 Oct 2026 15:43:34 +0900 Subject: [PATCH 1/4] fix: report Gemini model overload --- B01_Dashboard/B01_Dashboard_Knowledge_Chat.py | 32 +++++++++++++++---- resources/tester/test_b01_knowledge_chat.py | 32 +++++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/B01_Dashboard/B01_Dashboard_Knowledge_Chat.py b/B01_Dashboard/B01_Dashboard_Knowledge_Chat.py index ee0ce55bc..653e64637 100644 --- a/B01_Dashboard/B01_Dashboard_Knowledge_Chat.py +++ b/B01_Dashboard/B01_Dashboard_Knowledge_Chat.py @@ -1,5 +1,6 @@ """지식DB Markdown 검색과 Gemini 설명. 제외 폴더는 읽지 않는다.""" +import asyncio import os import re from functools import lru_cache @@ -150,21 +151,38 @@ async def ask_knowledge_chat(payload: ChatRequest, _session=Depends(verify_sessi ) try: async with httpx.AsyncClient(timeout=30) as client: - response = await client.post( - f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent", - headers={"x-goog-api-key": key}, - json={"contents": [{"parts": [{"text": prompt}]}]}, - ) + for attempt in range(3): + response = await client.post( + f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent", + headers={"x-goog-api-key": key}, + json={"contents": [{"parts": [{"text": 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 모델에 요청이 몰려 있습니다. 잠시 후 다시 질문해 주세요.", + ) response.raise_for_status() answer = "".join( part.get("text", "") for candidate in response.json().get("candidates", []) for part in candidate.get("content", {}).get("parts", []) ).strip() - except (httpx.HTTPError, ValueError) as exc: + except httpx.HTTPStatusError as exc: + detail = { + 400: "Gemini 요청 형식이 올바르지 않습니다.", + 401: "Gemini API 키를 확인해 주세요.", + 403: "Gemini API 키의 사용 권한을 확인해 주세요.", + 429: "Gemini API 사용 한도에 도달했습니다. 잠시 후 다시 시도해 주세요.", + }.get(exc.response.status_code, "Gemini 요청에 실패했습니다. 잠시 후 다시 시도해 주세요.") + raise HTTPException(status_code=502, detail=detail) from exc + except (httpx.RequestError, ValueError) as exc: raise HTTPException( status_code=502, - detail="Gemini 응답을 받지 못했습니다. API 키와 사용 한도를 확인해 주세요.", + detail="Gemini 서버에 연결하지 못했습니다. 잠시 후 다시 시도해 주세요.", ) from exc if not answer: raise HTTPException(status_code=502, detail="Gemini가 답변을 생성하지 못했습니다.") diff --git a/resources/tester/test_b01_knowledge_chat.py b/resources/tester/test_b01_knowledge_chat.py index 1860ba666..81ef1c8be 100644 --- a/resources/tester/test_b01_knowledge_chat.py +++ b/resources/tester/test_b01_knowledge_chat.py @@ -35,6 +35,36 @@ 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): + monkeypatch.setenv("GEMINI_API_KEY", "test-key") + monkeypatch.setattr(chat, "search", lambda _question: []) + calls = [] + + class Client: + def __init__(self, timeout): + pass + + async def __aenter__(self): + return self + + async def __aexit__(self, *_args): + pass + + async def post(self, *_args, **_kwargs): + calls.append(1) + return type("Response", (), {"status_code": 503})() + + async def no_wait(_seconds): + pass + + monkeypatch.setattr(chat.httpx, "AsyncClient", Client) + monkeypatch.setattr(chat.asyncio, "sleep", no_wait) + with pytest.raises(HTTPException) as error: + asyncio.run(chat.ask_knowledge_chat(chat.ChatRequest(question="일위대가가 뭐야"), {})) + assert len(calls) == 3 + assert "요청이 몰려" in error.value.detail + + def test_chat_searches_all_knowledge_markdown_except_excluded_folders(monkeypatch, tmp_path): public = tmp_path / "original" / "법률" guide = tmp_path / "technical_info" @@ -57,6 +87,8 @@ def test_chat_searches_all_knowledge_markdown_except_excluded_folders(monkeypatc sent = {} class Response: + status_code = 200 + def raise_for_status(self): pass From 803599ef347d767d3262743fc0573579f5745d3e Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 6 Oct 2026 15:54:34 +0900 Subject: [PATCH 2/4] =?UTF-8?q?chore(plan):=2010-6=20=EC=95=8C=EC=95=BD=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=C2=B7=20=EC=9A=B0=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EB=A6=AC=EC=85=8B=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=9B=90=EB=AC=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FJqWQpoB1RE7hiaQ2wH8qV --- docs/raw/사용자요구/2026-10-05_수정요구.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/raw/사용자요구/2026-10-05_수정요구.md b/docs/raw/사용자요구/2026-10-05_수정요구.md index 40cb0ec38..fb1497120 100644 --- a/docs/raw/사용자요구/2026-10-05_수정요구.md +++ b/docs/raw/사용자요구/2026-10-05_수정요구.md @@ -310,3 +310,10 @@ B 방향이 맞겠네. 새 프로젝트를 만드는건 문제없어보여. 이 (10-6 사용자) 다음으로 좌측 패널에서 서브 컨테이너의 항목을 선택/변경/생성 할 경우에 횡단도에서 선택한것처럼 이동되어야할것같아. + +(10-6 사용자) +추가로 배관의 도수로/돌붙임의 경우에는 복수 값이 아니야. 생성이 되었다면 추가 버튼이 없어져야함. + +(10-6 사용자) +1. 종단도의 구조물 알약의 이름이 구조물군의 종류 값과 매칭이 안됨. 같은 값을 보게 개선. +2. 종단도에서 배관이 선택한 상태에서 다른 지점에 옹벽을 추가하기 위해 마우스 오른쪽 버튼을 클릭후 사면안정을 눌렀더니 하위 리스트는 안보이고 리셋된 상태가 됨. 확인 From 7e6aebeb76666477e153a2ca33f457c4f68af94d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 6 Oct 2026 15:55:01 +0900 Subject: [PATCH 3/4] fix: distinguish Gemini timeout --- B01_Dashboard/B01_Dashboard_Knowledge_Chat.py | 6 +++++ resources/tester/test_b01_knowledge_chat.py | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/B01_Dashboard/B01_Dashboard_Knowledge_Chat.py b/B01_Dashboard/B01_Dashboard_Knowledge_Chat.py index 653e64637..6ada34528 100644 --- a/B01_Dashboard/B01_Dashboard_Knowledge_Chat.py +++ b/B01_Dashboard/B01_Dashboard_Knowledge_Chat.py @@ -179,6 +179,12 @@ async def ask_knowledge_chat(payload: ChatRequest, _session=Depends(verify_sessi 429: "Gemini API 사용 한도에 도달했습니다. 잠시 후 다시 시도해 주세요.", }.get(exc.response.status_code, "Gemini 요청에 실패했습니다. 잠시 후 다시 시도해 주세요.") raise HTTPException(status_code=502, detail=detail) from exc + except httpx.TimeoutException as exc: + raise HTTPException( + status_code=504, + detail="Gemini 3.8 Flash 응답이 지연되어 시간 초과됐습니다. " + "잠시 후 다시 시도해 주세요.", + ) from exc except (httpx.RequestError, ValueError) as exc: raise HTTPException( status_code=502, diff --git a/resources/tester/test_b01_knowledge_chat.py b/resources/tester/test_b01_knowledge_chat.py index 81ef1c8be..fb647702e 100644 --- a/resources/tester/test_b01_knowledge_chat.py +++ b/resources/tester/test_b01_knowledge_chat.py @@ -1,5 +1,6 @@ import asyncio +import httpx import pytest from fastapi import HTTPException @@ -65,6 +66,30 @@ def test_busy_gemini_is_retried_and_reported(monkeypatch): assert "요청이 몰려" in error.value.detail +def test_gemini_timeout_is_reported(monkeypatch): + monkeypatch.setenv("GEMINI_API_KEY", "test-key") + monkeypatch.setattr(chat, "search", lambda _question: []) + + class Client: + def __init__(self, timeout): + pass + + async def __aenter__(self): + return self + + async def __aexit__(self, *_args): + pass + + async def post(self, *_args, **_kwargs): + raise httpx.ReadTimeout("timed out") + + monkeypatch.setattr(chat.httpx, "AsyncClient", Client) + with pytest.raises(HTTPException) as error: + asyncio.run(chat.ask_knowledge_chat(chat.ChatRequest(question="일위대가가 뭐야"), {})) + assert error.value.status_code == 504 + assert "시간 초과" in error.value.detail + + def test_chat_searches_all_knowledge_markdown_except_excluded_folders(monkeypatch, tmp_path): public = tmp_path / "original" / "법률" guide = tmp_path / "technical_info" From 7c0e94bdb43dfa948a5d5671a1dd1404118b31bf Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 6 Oct 2026 16:00:07 +0900 Subject: [PATCH 4/4] =?UTF-8?q?chore(plan):=2010-6=20=EB=8F=84=EC=88=98?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=9C=EB=AA=A9=20=EB=B2=88=ED=98=B8=20=C2=B7=20?= =?UTF-8?q?=EB=86=92=EC=9D=B4=20=EC=A0=9C=EC=95=BD=20=ED=92=80=EA=B8=B0=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=9B=90=EB=AC=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FJqWQpoB1RE7hiaQ2wH8qV --- docs/raw/사용자요구/2026-10-05_수정요구.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/raw/사용자요구/2026-10-05_수정요구.md b/docs/raw/사용자요구/2026-10-05_수정요구.md index fb1497120..bb496d819 100644 --- a/docs/raw/사용자요구/2026-10-05_수정요구.md +++ b/docs/raw/사용자요구/2026-10-05_수정요구.md @@ -317,3 +317,7 @@ B 방향이 맞겠네. 새 프로젝트를 만드는건 문제없어보여. 이 (10-6 사용자) 1. 종단도의 구조물 알약의 이름이 구조물군의 종류 값과 매칭이 안됨. 같은 값을 보게 개선. 2. 종단도에서 배관이 선택한 상태에서 다른 지점에 옹벽을 추가하기 위해 마우스 오른쪽 버튼을 클릭후 사면안정을 눌렀더니 하위 리스트는 안보이고 리셋된 상태가 됨. 확인 + +(10-6 사용자) +1. 도수로 돌붙임에는 컨테이너 제목에 1과 2 같은 정보는 필요없어보임. +2. 구조물의 높이 선택에 따라 제약이 발생한 경우 토스트 메세지 발생. 이것도 흙막이, 집수정, 옹벽, 관 보호공에서 높이 제약을 풀어줘. 사용자가 선택하게 할거야. 이건 이전에 요청했던 내용이야.