# backend/app/llm/gemini.py # Google Gemini (Generative Language API) 프로바이더. # 엔드포인트: {base}/models/{model}:generateContent?key=API_KEY (base 기본 v1beta). # host 오버레이가 ollama 용 값으로 남아 있을 수 있어, gemini 답지 않은 host 는 기본 base 로 무시. import httpx from ..runtime_config import effective_llm from .ollama import _coerce, _lenient_json from .prompts import CLASSIFY_SYSTEM, build_classify_prompt from .provider import Classification, LLMProvider DEFAULT_BASE = "https://generativelanguage.googleapis.com/v1beta" def _resolve_base(host: str | None) -> str: h = (host or "").rstrip("/") # 비어 있거나 ollama 류 host(11434/ollama/localhost)면 gemini 기본 base 사용. if not h or ":11434" in h or "ollama" in h or "localhost" in h or "127.0.0.1" in h: return DEFAULT_BASE return h class GeminiProvider(LLMProvider): name = "gemini" def __init__( self, host: str | None = None, model: str | None = None, api_key: str | None = None, timeout: float | None = None, ): eff = effective_llm() self.base = _resolve_base(host if host is not None else eff.host) self.model = model or eff.model self.api_key = api_key if api_key is not None else eff.api_key self.timeout = timeout if timeout is not None else eff.timeout def _generate(self, text: str) -> str: body = { "contents": [{"role": "user", "parts": [{"text": text}]}], "generationConfig": {"temperature": 0.2, "responseMimeType": "application/json"}, } r = httpx.post( f"{self.base}/models/{self.model}:generateContent", params={"key": self.api_key}, json=body, timeout=self.timeout, ) r.raise_for_status() cands = r.json().get("candidates", []) if not cands: return "{}" parts = cands[0].get("content", {}).get("parts", []) return "".join(p.get("text", "") for p in parts) def list_models(self) -> list[str]: try: r = httpx.get(f"{self.base}/models", params={"key": self.api_key}, timeout=5.0) if r.status_code != 200: return [] out = [] for m in r.json().get("models", []): if "generateContent" in (m.get("supportedGenerationMethods") or []): out.append((m.get("name") or "").removeprefix("models/")) return [m for m in out if m] except Exception: return [] def health(self) -> dict: try: r = httpx.get(f"{self.base}/models", params={"key": self.api_key}, timeout=5.0) ok = r.status_code == 200 return { "reachable": ok, "provider": self.name, "model": self.model, "host": self.base, "detail": "ok" if ok else f"HTTP {r.status_code}", } except Exception as e: return { "reachable": False, "provider": self.name, "model": self.model, "host": self.base, "detail": str(e), } def generate_json(self, prompt: str, schema: dict | None = None) -> dict: return _lenient_json(self._generate(prompt)) def classify_capture(self, raw: str, context: dict) -> Classification: prompt = f"{CLASSIFY_SYSTEM}\n\n{build_classify_prompt(raw, context)}" data = _lenient_json(self._generate(prompt)) return _coerce(data, raw, context, model=f"gemini:{self.model}")