Spaces:
Sleeping
Sleeping
| """ | |
| A robust, resilient analyzer using the Google Gemini Pro API. | |
| This module prompts Gemini for a simple key-value format and then constructs | |
| the final JSON object in Python, making it resilient to LLM syntax errors. | |
| """ | |
| import os | |
| import logging | |
| import httpx | |
| from typing import Optional, TypedDict, List, Union | |
| logger = logging.getLogger(__name__) | |
| class AnalysisResult(TypedDict): | |
| sentiment: str | |
| sentiment_score: float | |
| reason: str | |
| entities: List[str] | |
| topic: str | |
| impact: str | |
| summary: str | |
| error: Optional[str] | |
| class GeminiAnalyzer: | |
| API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent" | |
| def __init__(self, client: httpx.AsyncClient, api_key: Optional[str] = None): | |
| self.client = client | |
| self.api_key = api_key or os.getenv("GEMINI_API_KEY") | |
| if not self.api_key: | |
| raise ValueError("GEMINI_API_KEY is not set.") | |
| self.params = {"key": self.api_key} | |
| self.headers = {"Content-Type": "application/json"} | |
| def _build_prompt(self, text: str) -> dict: | |
| """Creates a prompt asking for a simple, parsable text format.""" | |
| return { | |
| "contents": [{ | |
| "parts": [{ | |
| "text": f""" | |
| Analyze the following financial text from the cryptocurrency world. | |
| Respond using a simple key::value format, with each key-value pair on a new line. Do NOT use JSON. | |
| KEYS: | |
| sentiment:: [POSITIVE, NEGATIVE, or NEUTRAL] | |
| sentiment_score:: [A float between -1.0 and 1.0] | |
| reason:: [A brief, one-sentence explanation for the sentiment.] | |
| entities:: [A comma-separated list of cryptocurrencies mentioned, e.g., Bitcoin, ETH] | |
| topic:: [One of: Regulation, Partnership, Technical Update, Market Hype, Security, General News] | |
| impact:: [One of: LOW, MEDIUM, HIGH] | |
| summary:: [A concise, one-sentence summary of the text.] | |
| TEXT TO ANALYZE: "{text}" | |
| """ | |
| }] | |
| }] | |
| } | |
| def _parse_structured_text(self, text: str) -> AnalysisResult: | |
| """Parses the key::value text response from Gemini into a structured dict.""" | |
| data = {} | |
| for line in text.splitlines(): | |
| if '::' in line: | |
| key, value = line.split('::', 1) | |
| data[key.strip()] = value.strip() | |
| # Build the final, validated object | |
| return { | |
| "sentiment": data.get("sentiment", "NEUTRAL").upper(), | |
| "sentiment_score": float(data.get("sentiment_score", 0.0)), | |
| "reason": data.get("reason", "N/A"), | |
| "entities": [e.strip() for e in data.get("entities", "").split(',') if e.strip()], | |
| "topic": data.get("topic", "General News"), | |
| "impact": data.get("impact", "LOW").upper(), | |
| "summary": data.get("summary", "Summary not available."), | |
| "error": None | |
| } | |
| async def analyze_text(self, text: str) -> AnalysisResult: | |
| """Sends text to Gemini and returns a structured analysis.""" | |
| prompt = self._build_prompt(text) | |
| try: | |
| response = await self.client.post(self.API_URL, headers=self.headers, params=self.params, json=prompt, timeout=60.0) | |
| response.raise_for_status() | |
| full_response = response.json() | |
| response_text = full_response["candidates"][0]["content"]["parts"][0]["text"] | |
| # Use our new, robust parser | |
| return self._parse_structured_text(response_text) | |
| except Exception as e: | |
| logger.error(f"β Gemini API or Parsing Error: {e}") | |
| return { | |
| "sentiment": "ERROR", "sentiment_score": 0.0, "reason": str(e), | |
| "entities": [], "topic": "Unknown", "impact": "Unknown", | |
| "summary": "Failed to perform analysis.", "error": str(e) | |
| } |