Spaces:
Runtime error
Runtime error
| """ | |
| gemini_adapter.py — Concrete LLMAdapter for Google Gemini. | |
| V3 (Sidecar): adds stream_chat() for sentence-level streaming. | |
| """ | |
| import os | |
| from typing import AsyncGenerator, Optional | |
| import google.generativeai as genai | |
| from llm_adapter import LLMAdapter | |
| class GeminiAdapter(LLMAdapter): | |
| """ | |
| Wraps Google Gemini API as an LLMAdapter. | |
| Supports both blocking `chat()` and streaming `stream_chat()`. | |
| """ | |
| def __init__( | |
| self, | |
| api_key: Optional[str] = None, | |
| model_name: str = "gemini-3.1-flash-lite", | |
| system_prompt: Optional[str] = None, | |
| ): | |
| self.api_key = api_key or os.getenv("GEMINI_API_KEY") | |
| if not self.api_key: | |
| raise ValueError( | |
| "Gemini API key not found. Set GEMINI_API_KEY environment variable " | |
| "or pass api_key directly." | |
| ) | |
| self.model_name = model_name | |
| self.system_prompt = system_prompt or "You are a helpful AI assistant." | |
| genai.configure(api_key=self.api_key) | |
| self._model = genai.GenerativeModel( | |
| model_name=self.model_name, | |
| system_instruction=self.system_prompt, | |
| ) | |
| # ------------------------------------------------------------------ | |
| # Blocking interface (existing — unchanged) | |
| # ------------------------------------------------------------------ | |
| def chat(self, prompt: str, system_prompt: Optional[str] = None) -> str: | |
| """Send prompt, return full response text (blocking).""" | |
| model = self._get_model(system_prompt) | |
| response = model.generate_content(prompt) | |
| return response.text | |
| # ------------------------------------------------------------------ | |
| # Streaming interface (NEW) | |
| # ------------------------------------------------------------------ | |
| async def stream_chat( | |
| self, | |
| prompt: str, | |
| system_prompt: Optional[str] = None, | |
| ) -> AsyncGenerator[str, None]: | |
| """ | |
| Yield token chunks from Gemini as they arrive. | |
| This is a synchronous SDK call wrapped in an async generator — | |
| Gemini's Python SDK streams synchronously, so we iterate the | |
| response object directly and yield each text chunk. | |
| """ | |
| model = self._get_model(system_prompt) | |
| # generate_content with stream=True returns a synchronous iterator | |
| response = model.generate_content(prompt, stream=True) | |
| for chunk in response: | |
| text = getattr(chunk, "text", None) | |
| if text: | |
| yield text | |
| # ------------------------------------------------------------------ | |
| # Helpers | |
| # ------------------------------------------------------------------ | |
| def _get_model(self, system_prompt: Optional[str]): | |
| """Return model instance, recreating if system prompt differs.""" | |
| if system_prompt and system_prompt != self.system_prompt: | |
| return genai.GenerativeModel( | |
| model_name=self.model_name, | |
| system_instruction=system_prompt, | |
| ) | |
| return self._model | |
| def get_model_name(self) -> str: | |
| return self.model_name | |