Source code for hypotestx.core.llm.backends.gemini
"""
Google Gemini backend.
Free tier: Gemini 2.0 Flash — 1 million token context, 1500 requests/day free.
Get a key: https://aistudio.google.com/app/apikey (no credit card needed)
Usage:
result = hx.analyze(df, "...", backend="gemini", api_key="AIza...")
# Choose a specific model
result = hx.analyze(df, "...",
backend=GeminiBackend(api_key="AIza...",
model="gemini-2.0-flash-lite"))
Available free models (as of 2026):
gemini-2.0-flash fast, high quality — recommended default
gemini-2.0-flash-lite smallest / fastest / lowest quota cost
gemini-1.5-pro most capable (lower free quota)
"""
from __future__ import annotations
import json
import urllib.error
import urllib.request
from typing import Dict, List
from ..base import LLMBackend
_DEFAULT_MODEL = "gemini-2.0-flash"
_BASE_URL = "https://generativelanguage.googleapis.com/v1/models"
[docs]
class GeminiBackend(LLMBackend):
"""
Google Gemini backend via the Generative Language REST API.
No SDK required — uses only the Python standard library.
Args:
api_key: Google AI Studio API key.
model: Model name (default: ``gemini-2.0-flash``).
timeout: HTTP timeout seconds (default: 60).
temperature: Sampling temperature (default: 0).
max_tokens: Maximum output tokens (default: 512).
"""
name = "gemini"
def __init__(
self,
api_key: str,
model: str = _DEFAULT_MODEL,
timeout: int = 60,
temperature: float = 0.0,
max_tokens: int = 512,
):
self.api_key = api_key
self.model = model
self.timeout = timeout
self.temperature = temperature
self.max_tokens = max_tokens
# ------------------------------------------------------------------ #
# LLMBackend interface #
# ------------------------------------------------------------------ #
[docs]
def chat(self, messages: List[Dict[str, str]]) -> str:
"""
Call the Gemini ``generateContent`` endpoint.
The OpenAI message list is converted to Gemini's ``contents`` format:
``system`` roles are prepended to the first user message text.
"""
system_parts = []
gemini_contents = []
for msg in messages:
role = msg["role"]
content = msg["content"]
if role == "system":
system_parts.append(content)
elif role == "user":
text = "\n\n".join(system_parts) + "\n\n" + content if system_parts else content
system_parts = [] # consumed
gemini_contents.append(
{
"role": "user",
"parts": [{"text": text}],
}
)
elif role == "assistant":
gemini_contents.append(
{
"role": "model",
"parts": [{"text": content}],
}
)
payload = json.dumps(
{
"contents": gemini_contents,
"generationConfig": {
"temperature": self.temperature,
"maxOutputTokens": self.max_tokens,
},
}
).encode("utf-8")
url = f"{_BASE_URL}/{self.model}:generateContent" f"?key={self.api_key}"
req = urllib.request.Request(
url,
data=payload,
headers={"Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
data = json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"[Gemini] HTTP {exc.code}: {body}") from exc
except urllib.error.URLError as exc:
raise RuntimeError(f"[Gemini] Connection error: {exc.reason}") from exc
# Extract text from response
try:
return data["candidates"][0]["content"]["parts"][0]["text"]
except (KeyError, IndexError) as exc:
raise RuntimeError(f"[Gemini] Unexpected response format: {data}") from exc
def __repr__(self) -> str:
return f"<GeminiBackend model='{self.model}'>"