From b8ccb2f250275efa7ced3621f91c63f4b12dd088 Mon Sep 17 00:00:00 2001 From: sylyx Date: Thu, 7 May 2026 15:09:43 +0200 Subject: [PATCH] feat(gemini): migrate to google-genai SDK (no more deprecation warning) --- pyproject.toml | 2 +- src/aitrader/ai/gemini.py | 76 +++++++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 86ff324..f4f7a69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dependencies = [ "pydantic>=2.6.0", "pydantic-settings>=2.2.0", "pyyaml>=6.0.1", - "google-generativeai>=0.8.0", + "google-genai>=0.8.0", "anthropic>=0.40.0", "openai>=1.50.0", "requests>=2.32.0", diff --git a/src/aitrader/ai/gemini.py b/src/aitrader/ai/gemini.py index 5bed728..bb126a2 100644 --- a/src/aitrader/ai/gemini.py +++ b/src/aitrader/ai/gemini.py @@ -1,12 +1,13 @@ -"""Google-Gemini-Client mit JSON-Output.""" +"""Google-Gemini-Client mit JSON-Output (neues google-genai SDK).""" from __future__ import annotations import json import re import time -import google.generativeai as genai -from google.api_core.exceptions import ResourceExhausted +from google import genai +from google.genai import errors as genai_errors +from google.genai import types from ..config import Settings from ..logging_setup import get_logger @@ -17,10 +18,17 @@ log = get_logger(__name__) def _extract_retry_seconds(err: Exception, fallback: float) -> float: - m = re.search(r"retry in (\d+(?:\.\d+)?)", str(err)) + m = re.search(r"retry in (\d+(?:\.\d+)?)", str(err), re.IGNORECASE) return float(m.group(1)) if m else fallback +def _is_rate_limit(err: Exception) -> bool: + code = getattr(err, "code", None) or getattr(err, "status_code", None) + if code == 429: + return True + return "429" in str(err) or "RESOURCE_EXHAUSTED" in str(err).upper() + + class GeminiClient: provider = "gemini" @@ -29,45 +37,53 @@ class GeminiClient: ) -> None: if not settings.gemini_api_key: raise RuntimeError("GEMINI_API_KEY nicht gesetzt") - genai.configure(api_key=settings.gemini_api_key) - self.model = model or "gemini-2.0-flash" + self.model = model or "gemini-2.5-flash-lite" self.temperature = temperature - self._model = genai.GenerativeModel( - self.model, + self.client = genai.Client(api_key=settings.gemini_api_key) + self._config = types.GenerateContentConfig( system_instruction=SYSTEM_PROMPT, + temperature=self.temperature, + response_mime_type="application/json", + response_schema=JSON_SCHEMA, ) - self.timeout = settings.ai.timeout_seconds def decide(self, user_prompt: str) -> TradeDecision: attempts = 0 max_attempts = 3 while True: try: - resp = self._model.generate_content( - user_prompt, - generation_config={ - "response_mime_type": "application/json", - "response_schema": JSON_SCHEMA, - "temperature": self.temperature, - }, - request_options={"timeout": self.timeout}, + resp = self.client.models.generate_content( + model=self.model, + contents=user_prompt, + config=self._config, ) break - except ResourceExhausted as e: - attempts += 1 - if attempts >= max_attempts: - log.warning("gemini.rate_limit_giveup", attempts=attempts) - return TradeDecision( - action="HOLD", confidence=0.0, suggested_size_pct=0.0, - reasoning="rate_limit_exhausted", - ) - wait = min(_extract_retry_seconds(e, 30.0) + 2, 60.0) - log.warning("gemini.rate_limit", attempt=attempts, wait_s=wait) - time.sleep(wait) - text = resp.text or "{}" + except genai_errors.APIError as e: + if _is_rate_limit(e): + attempts += 1 + if attempts >= max_attempts: + log.warning("gemini.rate_limit_giveup", attempts=attempts) + return TradeDecision( + action="HOLD", confidence=0.0, suggested_size_pct=0.0, + reasoning="rate_limit_exhausted", + ) + wait = min(_extract_retry_seconds(e, 30.0) + 2, 60.0) + log.warning("gemini.rate_limit", attempt=attempts, wait_s=wait) + time.sleep(wait) + continue + log.warning("gemini.api_error", error=str(e)[:300]) + return TradeDecision( + action="HOLD", confidence=0.0, suggested_size_pct=0.0, + reasoning=f"api_error: {e}", + ) + + text = (resp.text or "{}").strip() try: data = json.loads(text) return TradeDecision.model_validate(data) except Exception as e: log.warning("gemini.parse_failed", error=str(e), raw=text[:300]) - return TradeDecision(action="HOLD", confidence=0.0, suggested_size_pct=0.0, reasoning=f"parse_error: {e}") + return TradeDecision( + action="HOLD", confidence=0.0, suggested_size_pct=0.0, + reasoning=f"parse_error: {e}", + )