Spaces:
Running
Running
| import os | |
| import json | |
| from typing import Dict, Any, Optional | |
| import google.generativeai as genai | |
| from dotenv import load_dotenv | |
| from app.observability.metrics import LLM_TOKENS | |
| load_dotenv() | |
| # Load and validate key | |
| api_key = os.getenv("GEMINI_API_KEY") | |
| if not api_key: | |
| raise ValueError("GEMINI_API_KEY environment variable is missing from .env") | |
| # Configure Google GenAI | |
| genai.configure(api_key=api_key) | |
| # Load configured model name (default to gemini-2.5-flash) | |
| DEFAULT_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.5-flash") | |
| class LLMService: | |
| def call_gemini( | |
| prompt: str, | |
| system_instruction: Optional[str] = None, | |
| model_name: Optional[str] = None, | |
| temperature: float = 0.2, | |
| json_output: bool = False | |
| ) -> str: | |
| """ | |
| Invokes Gemini LLM model and returns the text response. | |
| If json_output is True, configures the request to return application/json. | |
| """ | |
| # Fallback to default model if none specified | |
| active_model = model_name or DEFAULT_MODEL | |
| # Define generation config | |
| generation_config = { | |
| "temperature": temperature, | |
| } | |
| if json_output: | |
| generation_config["response_mime_type"] = "application/json" | |
| # Initialize the model | |
| model = genai.GenerativeModel( | |
| model_name=active_model, | |
| generation_config=generation_config, | |
| system_instruction=system_instruction | |
| ) | |
| # Generate response | |
| response = model.generate_content(prompt) | |
| if not response.text: | |
| raise RuntimeError("Gemini model returned an empty response.") | |
| try: | |
| usage = response.usage_metadata | |
| if usage: | |
| # Track Input Tokens (Prompt) | |
| LLM_TOKENS.labels( | |
| model_name=active_model, | |
| token_type="input" | |
| ).inc(usage.prompt_token_count) | |
| # Track Output Tokens (Response) | |
| LLM_TOKENS.labels( | |
| model_name=active_model, | |
| token_type="output" | |
| ).inc(usage.candidates_token_count) | |
| except Exception as e: | |
| # Prevent token tracking errors from breaking the core execution | |
| pass | |
| return response.text | |
| def call_gemini_json( | |
| prompt: str, | |
| system_instruction: Optional[str] = None, | |
| model_name: Optional[str] = None, | |
| temperature: float = 0.2 | |
| ) -> Dict[str, Any]: | |
| """ | |
| Calls Gemini forcing a JSON structure and parses the result into a python dictionary. | |
| """ | |
| raw_response = LLMService.call_gemini( | |
| prompt=prompt, | |
| system_instruction=system_instruction, | |
| model_name=model_name, | |
| temperature=temperature, | |
| json_output=True | |
| ) | |
| try: | |
| res = json.loads(raw_response) | |
| if not isinstance(res, dict): | |
| raise ValueError(f"Expected JSON dictionary from Gemini, got {type(res).__name__}") | |
| return res | |
| except (json.JSONDecodeError, ValueError) as e: | |
| # Simple fallback if JSON parsing fails | |
| clean_str = raw_response.strip() | |
| if clean_str.startswith("```json"): | |
| clean_str = clean_str.split("```json")[1].split("```")[0].strip() | |
| elif clean_str.startswith("```"): | |
| clean_str = clean_str.split("```")[1].split("```")[0].strip() | |
| try: | |
| res2 = json.loads(clean_str) | |
| if not isinstance(res2, dict): | |
| raise ValueError(f"Expected JSON dictionary from Gemini on fallback, got {type(res2).__name__}") | |
| return res2 | |
| except Exception: | |
| raise ValueError(f"Failed to parse JSON response from Gemini. Raw output: {raw_response}") from e |