diff --git a/solution/backend/common/enums.py b/solution/backend/common/enums.py index 2be71f0..af41736 100644 --- a/solution/backend/common/enums.py +++ b/solution/backend/common/enums.py @@ -85,7 +85,7 @@ class ErrorType(Enum): COLLECT_ALREADY_RUNNING = auto() # 생성(generator) 관련 에러 - GENERATOR_NOT_CONFIGURED = 1500 # GEMINI_API_KEY 미설정 + GENERATOR_NOT_CONFIGURED = 1500 # 활성 LLM 공급자의 키 미설정(llm/provider.missing_key) GENERATOR_CALL_FAILED = auto() GENERATOR_INVALID_OUTPUT = auto() # 구조화 출력 파싱 실패 GENERATOR_LOW_CONFIDENCE = auto() # 신뢰도 낮음 — 자동 반영 금지, 사람 확인 큐로 diff --git a/solution/backend/services/collect_service.py b/solution/backend/services/collect_service.py index 71bf285..b250ee4 100644 --- a/solution/backend/services/collect_service.py +++ b/solution/backend/services/collect_service.py @@ -18,6 +18,7 @@ from common.category_schema import get_schema from services.collector import AdapterDisabled, AdapterNotFound, REGISTRY from services.collector import yanolja_adapter from services.external import naver_place_lookup, perplexity, tour_lookup +from services.llm import provider from services.fact_service import FactService from router.v1.fact.protocol import Req_UpsertFact from common.job_errors import PermanentJobError @@ -752,7 +753,7 @@ async def _enqueue_vision(place_id: str, owner_user_id: str) -> str | None: from services.job_service import enqueue_job if not gemini.is_configured(): - LOG.i("[collect] GEMINI_API_KEY 미설정 — 사진 분석 건너뜀(사진은 확인 큐에 남는다)") + LOG.i(f"[collect] {provider.missing_key()} 미설정 — 사진 분석 건너뜀(사진은 확인 큐에 남는다)") return None job_id, _created = await enqueue_job( JobQueue(), JobType.VISION, diff --git a/solution/backend/services/copy_service.py b/solution/backend/services/copy_service.py index 976a1fd..3f8f0ae 100644 --- a/solution/backend/services/copy_service.py +++ b/solution/backend/services/copy_service.py @@ -2,6 +2,7 @@ from common.logger import LOG from services.copy_steps import CopyAborted, prepare_copy, generate_copy, save_copy, fill_faqs from services.external import gemini_text +from services.llm import provider from services.job_progress import JobProgress COPY_STEPS = ("prepare", "generate", "save", "faq_fill") @@ -16,7 +17,7 @@ async def run_copy(job: dict) -> dict: copy = None note = None if inputs.ungrounded or not gemini_text.is_configured(): - note = "근거로 쓸 확인된 fact 가 없다" if inputs.ungrounded else "GEMINI_API_KEY 미설정" + note = "근거로 쓸 확인된 fact 가 없다" if inputs.ungrounded else f"{provider.missing_key()} 미설정" await progress.skip("generate", "no_facts" if inputs.ungrounded else "not_configured") if inputs.catalog is None and not inputs.ungrounded: raise CopyAborted(note) diff --git a/solution/backend/services/llm/provider.py b/solution/backend/services/llm/provider.py index 4dcea41..3e9b23c 100644 --- a/solution/backend/services/llm/provider.py +++ b/solution/backend/services/llm/provider.py @@ -8,3 +8,13 @@ from services.llm import gemini, openai def active(): return openai if external_api_config.llm_provider == "openai" else gemini + + +def missing_key() -> str: + """지금 활성인 공급자에게 필요한 env 이름. 키가 없을 때 **그 공급자를** 가리키려고 쓴다. + + ★ 예전에는 호출측이 "GEMINI_API_KEY 미설정" 을 문자열로 박아 뒀다. 공급자를 openai 로 + 바꾼 뒤에도 그 문구가 그대로 나가서, **없는 것은 OPENAI_API_KEY 인데 화면은 Gemini 를 + 탓했다**(실측 2026-09-21: 로컬에서 소개문이 안 나와 Gemini 키를 한참 들여다봤다). + 원인을 정확히 반대로 가리키는 종류라, 문구를 공급자에서 끌어오게 바꿨다.""" + return "OPENAI_API_KEY" if active() is openai else "GEMINI_API_KEY" diff --git a/solution/backend/services/song_service.py b/solution/backend/services/song_service.py index 52291ad..d84a829 100644 --- a/solution/backend/services/song_service.py +++ b/solution/backend/services/song_service.py @@ -53,6 +53,7 @@ from crud.place_crud import PlaceCRUD from crud.song_crud import SongCRUD from services import place_research, site_payload from services.external import gemini_text, suno +from services.llm import provider from services.llm.gemini import GeminiError, GeminiInvalidOutput, GeminiNotConfigured from common.job_errors import PermanentJobError @@ -187,8 +188,8 @@ async def ensure_song(place_id: str, owner_user_id: str, *, force: bool = False) region=region, grounding=lines, intro=intro, client=client, ) except GeminiNotConfigured: - LOG.i(f"[song] place={place_id} 건너뜀 — GEMINI_API_KEY 미설정") - return {"place_id": place_id, "skipped": "GEMINI_API_KEY 미설정"} + LOG.i(f"[song] place={place_id} 건너뜀 — {provider.missing_key()} 미설정") + return {"place_id": place_id, "skipped": f"{provider.missing_key()} 미설정"} except GeminiInvalidOutput as ex: LOG.i(f"[song] place={place_id} 건너뜀 — {ex}") return {"place_id": place_id, "skipped": str(ex)}