Karan6124's picture
fix(agent): handle non-dict LLM response gracefully and delay setting task to FAILED during Celery retries
6ae9355
Raw
History Blame Contribute Delete
4.01 kB
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:
@staticmethod
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
@staticmethod
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