DSN / app /gemini_client.py
nexusbert's picture
push
4b0eec9
Raw
History Blame Contribute Delete
4.29 kB
from __future__ import annotations
from google.genai import types
import logging
import os
import threading
from typing import Any
logger = logging.getLogger(__name__)
_client: Any = None
_client_lock = threading.Lock()
def _api_key() -> str:
return (
os.environ.get("GEMINI_API_KEY", "").strip()
or os.environ.get("GOOGLE_API_KEY", "").strip()
)
def generation_backend() -> str:
"""Return ``gemini`` or ``local``. ``auto`` picks Gemini when an API key is set."""
raw = os.environ.get("GENERATION_BACKEND", "auto").strip().lower()
if raw in ("gemini", "google"):
if not _api_key():
raise RuntimeError(
"GENERATION_BACKEND=gemini but GEMINI_API_KEY / GOOGLE_API_KEY is not set."
)
return "gemini"
if raw in ("local", "hf", "huggingface"):
return "local"
return "gemini" if _api_key() else "local"
def use_gemini() -> bool:
return generation_backend() == "gemini"
def gemini_model() -> str:
return os.environ.get("GEMINI_MODEL", "gemini-2.0-flash").strip() or "gemini-2.0-flash"
def skip_local_llm_hub_download() -> bool:
if os.environ.get("SKIP_LOCAL_LLM_HUB_DOWNLOAD", "").strip().lower() in (
"1",
"true",
"yes",
):
return True
if os.environ.get("GENERATION_BACKEND", "auto").strip().lower() in ("gemini", "google"):
return True
return use_gemini()
def _get_client() -> Any:
global _client
if _client is not None:
return _client
with _client_lock:
if _client is not None:
return _client
key = _api_key()
if not key:
raise RuntimeError(
"Gemini API key missing. Set GEMINI_API_KEY or GOOGLE_API_KEY "
"(or GENERATION_BACKEND=local)."
)
from google import genai # type: ignore[import-untyped]
_client = genai.Client(api_key=key)
logger.info("Gemini client ready (model=%s)", gemini_model())
return _client
def gemini_generate_text(
*,
system_instruction: str,
user_text: str,
temperature: float = 0.0,
max_output_tokens: int = 4096,
response_schema: Any | None = None,
) -> str:
from google.genai import types # type: ignore[import-untyped]
client = _get_client()
resp = client.models.generate_content(
model=gemini_model(),
contents=user_text,
config=types.GenerateContentConfig(
system_instruction=system_instruction,
temperature=temperature,
max_output_tokens=max_output_tokens,
response_mime_type="application/json",
response_schema=response_schema,
thinking_config=types.ThinkingConfig(
thinking_level="minimal" # Options: 'minimal' or 'low' to free up response tokens
)
),
)
text = (resp.text or "").strip()
if not text:
raise RuntimeError("Gemini returned empty text")
return text
def gemini_generate_chat(
messages: list[dict[str, str]],
*,
temperature: float = 0.2,
max_output_tokens: int = 1024,
) -> str:
from google.genai import types # type: ignore[import-untyped]
system_parts: list[str] = []
contents: list[Any] = []
for m in messages:
role = m.get("role", "user")
text = m.get("content", "")
if role == "system":
system_parts.append(text)
continue
gem_role = "model" if role == "assistant" else "user"
contents.append(
types.Content(role=gem_role, parts=[types.Part.from_text(text=text)])
)
if not contents:
raise ValueError("No user/model messages for Gemini")
config_kw: dict[str, Any] = {
"temperature": temperature,
"max_output_tokens": max_output_tokens,
}
if system_parts:
config_kw["system_instruction"] = "\n\n".join(system_parts)
client = _get_client()
resp = client.models.generate_content(
model=gemini_model(),
contents=contents,
config=types.GenerateContentConfig(**config_kw),
)
text = (resp.text or "").strip()
if not text:
raise RuntimeError("Gemini returned empty text")
return text