SOY NV AI
Add Gemini API integration with REST API support, improve error handling, and add markdown bold formatting for messages
665bcdc
| """ | |
| Google Gemini API ν΄λΌμ΄μΈνΈ | |
| """ | |
| import os | |
| import time | |
| import requests | |
| import google.generativeai as genai | |
| from google.api_core import retry | |
| from typing import Optional, List, Dict | |
| import functools | |
| import json | |
| # Gemini API ν€ (νκ²½ λ³μ λλ λ°μ΄ν°λ² μ΄μ€μμ κ°μ Έμ€κΈ°) | |
| def get_gemini_api_key(): | |
| """Gemini API ν€ κ°μ Έμ€κΈ° (νκ²½ λ³μ μ°μ , μμΌλ©΄ DBμμ)""" | |
| # νκ²½ λ³μμμ λ¨Όμ νμΈ | |
| api_key = os.getenv('GEMINI_API_KEY', '').strip() | |
| if api_key: | |
| print(f"[Gemini] νκ²½ λ³μμμ API ν€ κ°μ Έμ΄ (κΈΈμ΄: {len(api_key)}μ)") | |
| return api_key | |
| # DBμμ κ°μ Έμ€κΈ° (μν μ°Έμ‘° λ°©μ§λ₯Ό μν΄ μ¬κΈ°μ μν¬νΈ) | |
| try: | |
| from app.database import SystemConfig | |
| api_key = SystemConfig.get_config('gemini_api_key', '').strip() | |
| if api_key: | |
| print(f"[Gemini] DBμμ API ν€ κ°μ Έμ΄ (κΈΈμ΄: {len(api_key)}μ)") | |
| else: | |
| print(f"[Gemini] DBμ API ν€κ° μκ±°λ λΉμ΄μμ") | |
| return api_key | |
| except Exception as e: | |
| print(f"[Gemini] DBμμ API ν€ μ‘°ν μ€ν¨: {e}") | |
| return '' | |
| GEMINI_API_KEY = get_gemini_api_key() | |
| # μ¬μ© κ°λ₯ν Gemini λͺ¨λΈ λͺ©λ‘ (μ΅μ λ²μ μ°μ ) | |
| AVAILABLE_GEMINI_MODELS = [ | |
| 'gemini-2.0-flash-exp', | |
| 'gemini-1.5-pro', | |
| 'gemini-1.5-flash', | |
| 'gemini-1.5-pro-latest', | |
| 'gemini-1.5-flash-latest', | |
| 'gemini-pro', | |
| 'gemini-pro-vision' | |
| ] | |
| class GeminiClient: | |
| """Google Gemini API ν΄λΌμ΄μΈνΈ ν΄λμ€""" | |
| def __init__(self, api_key: Optional[str] = None): | |
| """Gemini ν΄λΌμ΄μΈνΈ μ΄κΈ°ν""" | |
| if api_key: | |
| self.api_key = api_key | |
| else: | |
| # μ΅μ API ν€ κ°μ Έμ€κΈ° (DBμμ λμ μΌλ‘) | |
| self.api_key = get_gemini_api_key() | |
| if not self.api_key: | |
| print("[Gemini] κ²½κ³ : GEMINI_API_KEYκ° μ€μ λμ§ μμμ΅λλ€. νκ²½ λ³μλ κ΄λ¦¬ νμ΄μ§μμ μ€μ νμΈμ.") | |
| return | |
| try: | |
| # API ν€ μ€μ λ° νμμμ μ€μ (κΈ°λ³Έ 60μ΄ -> 300μ΄(5λΆ)λ‘ μ¦κ°) | |
| # μ μ νμμμ μ€μ | |
| self.request_timeout = 300 # 5λΆ(300μ΄) νμμμ | |
| # μ¬μλ μ μ± μ€μ | |
| self.retry_policy = retry.Retry( | |
| initial=10.0, # μ΄κΈ° λκΈ° μκ° (10μ΄) | |
| maximum=60.0, # μ΅λ λκΈ° μκ° (60μ΄) | |
| multiplier=2.0, # λκΈ° μκ° λ°°μ | |
| deadline=600.0 # μ 체 μ¬μλ κΈ°κ° (600μ΄ = 10λΆ) | |
| ) | |
| # REST API μ¬μ©μ μν μ€μ | |
| # νκ²½ λ³μλ₯Ό ν΅ν΄ HTTP ν΄λΌμ΄μΈνΈ νμμμ μ€μ | |
| os.environ.setdefault('HTTPX_TIMEOUT', str(self.request_timeout)) | |
| os.environ.setdefault('GOOGLE_API_TIMEOUT', str(self.request_timeout)) | |
| # REST API μλν¬μΈνΈ μ€μ (v1 μ¬μ©) | |
| self.rest_base_url = 'https://generativelanguage.googleapis.com/v1' | |
| self.use_rest_api = True # REST API κ°μ μ¬μ© | |
| # API ν€ μ€μ (fallbackμ©, REST APIκ° μ€ν¨ν κ²½μ°) | |
| try: | |
| genai.configure(api_key=self.api_key) | |
| print(f"[Gemini] API ν€ μ€μ μλ£ (REST API λͺ¨λ, νμμμ: {self.request_timeout}μ΄)") | |
| except Exception as e: | |
| print(f"[Gemini] API ν€ μ€μ μ€λ₯: {e}") | |
| # API ν€κ° μ€μ λ‘ μ€μ λμλμ§ νμΈ | |
| try: | |
| # genai λͺ¨λμ μ μ API ν€ νμΈ | |
| configured_key = getattr(genai, '_api_key', None) or getattr(genai, 'api_key', None) | |
| if configured_key: | |
| print(f"[Gemini] μ μ API ν€ νμΈ: μ€μ λ¨ (κΈΈμ΄: {len(str(configured_key))}μ)") | |
| else: | |
| print(f"[Gemini] κ²½κ³ : μ μ API ν€κ° νμΈλμ§ μμ΅λλ€. API νΈμΆμ΄ μ€ν¨ν μ μμ΅λλ€.") | |
| except Exception as e: | |
| print(f"[Gemini] API ν€ νμΈ μ€λ₯: {e}") | |
| print(f"[Gemini] μ¬μλ μ μ± μ μ©λ¨") | |
| except Exception as e: | |
| print(f"[Gemini] API ν€ μ€μ μ€λ₯: {e}") | |
| def reload_api_key(self): | |
| """API ν€λ₯Ό λ€μ λ‘λ (DBμμ μ΅μ κ° κ°μ Έμ€κΈ°)""" | |
| self.api_key = get_gemini_api_key() | |
| if self.api_key: | |
| try: | |
| genai.configure(api_key=self.api_key) | |
| print(f"[Gemini] API ν€ μ¬λ‘λ μλ£") | |
| return True | |
| except Exception as e: | |
| print(f"[Gemini] API ν€ μ¬λ‘λ μ€λ₯: {e}") | |
| return False | |
| return False | |
| def is_configured(self) -> bool: | |
| """Gemini APIκ° μ λλ‘ μ€μ λμλμ§ νμΈ""" | |
| return bool(self.api_key) | |
| def get_available_models(self) -> List[str]: | |
| """μ¬μ© κ°λ₯ν Gemini λͺ¨λΈ λͺ©λ‘ λ°ν""" | |
| if not self.is_configured(): | |
| return [] | |
| try: | |
| # μ€μ μ¬μ© κ°λ₯ν λͺ¨λΈ νμΈ | |
| available_models = [] | |
| for model_name in AVAILABLE_GEMINI_MODELS: | |
| try: | |
| model = genai.GenerativeModel(model_name) | |
| # λͺ¨λΈ μ κ·Ό κ°λ₯ μ¬λΆ νμΈ | |
| available_models.append(model_name) | |
| except Exception as e: | |
| # λͺ¨λΈμ μ°Ύμ μ μμΌλ©΄ 건λλ°κΈ° | |
| continue | |
| # λͺ¨λΈμ μ°Ύμ§ λͺ»ν κ²½μ° κΈ°λ³Έ λͺ¨λΈ μλ | |
| if not available_models: | |
| try: | |
| # κΈ°λ³Έμ μΌλ‘ gemini-1.5-flash μλ | |
| model = genai.GenerativeModel('gemini-1.5-flash') | |
| available_models.append('gemini-1.5-flash') | |
| except: | |
| pass | |
| print(f"[Gemini] μ¬μ© κ°λ₯ν λͺ¨λΈ: {available_models}") | |
| return available_models | |
| except Exception as e: | |
| print(f"[Gemini] λͺ¨λΈ λͺ©λ‘ μ‘°ν μ€λ₯: {e}") | |
| return [] | |
| def generate_response(self, prompt: str, model_name: str = 'gemini-1.5-flash', **kwargs) -> Dict: | |
| """ | |
| Gemini APIλ₯Ό μ¬μ©νμ¬ μλ΅ μμ± | |
| Args: | |
| prompt: μ λ ₯ ν둬ννΈ | |
| model_name: μ¬μ©ν λͺ¨λΈ μ΄λ¦ | |
| **kwargs: μΆκ° νλΌλ―Έν° (temperature, max_tokens λ±) | |
| Returns: | |
| Dict: {'response': str, 'error': str or None} | |
| """ | |
| if not self.is_configured(): | |
| return { | |
| 'response': None, | |
| 'error': 'Gemini API ν€κ° μ€μ λμ§ μμμ΅λλ€. GEMINI_API_KEY νκ²½ λ³μλ₯Ό μ€μ νμΈμ.' | |
| } | |
| try: | |
| # API ν€λ₯Ό νμ μ΅μ μΌλ‘ λ€μ κ°μ Έμμ μ€μ (DBμμ λ³κ²½λμμ μ μμ) | |
| current_api_key = get_gemini_api_key() | |
| if not current_api_key: | |
| return { | |
| 'response': None, | |
| 'error': 'Gemini API ν€κ° μ€μ λμ§ μμμ΅λλ€. κ΄λ¦¬ νμ΄μ§μμ API ν€λ₯Ό μ€μ νμΈμ.' | |
| } | |
| # API ν€κ° λ³κ²½λμκ±°λ μλ κ²½μ° μ¬μ€μ | |
| if not self.api_key or self.api_key != current_api_key: | |
| self.api_key = current_api_key | |
| genai.configure(api_key=self.api_key) | |
| print(f"[Gemini] API ν€ μ¬μ€μ μλ£ (κΈΈμ΄: {len(self.api_key)}μ)") | |
| else: | |
| # API ν€κ° μ΄λ―Έ μ€μ λμ΄ μμ΄λ λ§€λ² μ¬μ€μ νμ¬ νμ€ν ν¨ | |
| genai.configure(api_key=self.api_key) | |
| # λͺ¨λΈ μμ± | |
| model = genai.GenerativeModel(model_name) | |
| print(f"[Gemini] λͺ¨λΈ μμ± μλ£: {model_name}") | |
| # μμ± μ€μ | |
| generation_config = { | |
| 'temperature': kwargs.get('temperature', 0.7), | |
| 'top_p': kwargs.get('top_p', 0.95), | |
| 'top_k': kwargs.get('top_k', 40), | |
| 'max_output_tokens': kwargs.get('max_output_tokens', 8192), | |
| } | |
| # μλ΅ μμ± (νμμμ μ€μ ) | |
| timeout_seconds = getattr(self, 'request_timeout', 300) # 5λΆ νμμμ | |
| print(f"[Gemini] λͺ¨λΈ {model_name}λ‘ μλ΅ μμ± μ€... (νμμμ: {timeout_seconds}μ΄)") | |
| print(f"[Gemini] API ν€ νμΈ: μ€μ λ¨ (κΈΈμ΄: {len(self.api_key)}μ)") | |
| print(f"[Gemini] ν둬ννΈ κΈΈμ΄: {len(prompt)}μ") | |
| # νμμμμ μν μμ μκ° κΈ°λ‘ | |
| start_time = time.time() | |
| # google-generativeaiλ retry νλΌλ―Έν°λ₯Ό μ§μνμ§ μμΌλ―λ‘ | |
| # μ¬μλ μ μ± μ μλμΌλ‘ ꡬννμ¬ μ μ© | |
| retry_policy = getattr(self, 'retry_policy', None) | |
| print(f"[Gemini] API νΈμΆ μμ (νμμμ: {timeout_seconds}μ΄, μ¬μλ μ μ± μ μ©)") | |
| # μ¬μλ λ‘μ§ κ΅¬ν (μ¬μλ μ μ± κ°μ²΄μ μ€μ μ¬μ©) | |
| # retry.Retry κ°μ²΄λ₯Ό μμ±νμ§λ§, μ€μ λ‘λ κ·Έ μ€μ κ°λ€μ μ§μ μ¬μ© | |
| initial_wait = 10.0 # μ΄κΈ° λκΈ° μκ° | |
| max_wait = 60.0 # μ΅λ λκΈ° μκ° | |
| multiplier = 2.0 # λκΈ° μκ° λ°°μ | |
| deadline = 600.0 # μ 체 μ¬μλ κΈ°κ° (10λΆ) | |
| wait_time = initial_wait | |
| deadline_time = time.time() + deadline | |
| retry_count = 0 | |
| last_error = None | |
| while True: | |
| try: | |
| # API νΈμΆ μ API ν€ μ¬νμΈ λ° μ¬μ€μ | |
| current_key = get_gemini_api_key() | |
| if not current_key: | |
| raise Exception("API ν€κ° μ€μ λμ§ μμμ΅λλ€. κ΄λ¦¬ νμ΄μ§μμ API ν€λ₯Ό μ€μ νμΈμ.") | |
| # API ν€κ° λ³κ²½λμκ±°λ μ€μ λμ§ μμ κ²½μ° μ¬μ€μ | |
| if not self.api_key or self.api_key != current_key: | |
| self.api_key = current_key.strip() if current_key else None # 곡백 μ κ±° | |
| print(f"[Gemini] API ν€ μ¬μ€μ μλ£ (κΈΈμ΄: {len(self.api_key) if self.api_key else 0}μ)") | |
| # API ν€ μ ν¨μ± κ²μ¬ | |
| if not self.api_key or not self.api_key.strip(): | |
| raise Exception("API ν€κ° λΉμ΄μμ΅λλ€. κ΄λ¦¬ νμ΄μ§μμ API ν€λ₯Ό μ€μ νμΈμ.") | |
| # API ν€ μλ€ κ³΅λ°± μ κ±° | |
| api_key_clean = self.api_key.strip() | |
| if not api_key_clean: | |
| raise Exception("API ν€κ° μ ν¨νμ§ μμ΅λλ€ (κ³΅λ°±λ§ ν¬ν¨).") | |
| # API ν€ νμ νμΈ (Google API ν€λ λ³΄ν΅ AIzaλ‘ μμ) | |
| if not api_key_clean.startswith('AIza'): | |
| print(f"[Gemini] κ²½κ³ : API ν€κ° μΌλ°μ μΈ Google API ν€ νμμ΄ μλλλ€ (AIzaλ‘ μμνμ§ μμ)") | |
| # API ν€ κΈΈμ΄ νμΈ (μΌλ°μ μΌλ‘ 39μ μ΄μ) | |
| if len(api_key_clean) < 20: | |
| raise Exception(f"API ν€ κΈΈμ΄κ° λ무 μ§§μ΅λλ€ ({len(api_key_clean)}μ). μ¬λ°λ₯Έ API ν€μΈμ§ νμΈνμΈμ.") | |
| print(f"[Gemini] API ν€ κ²μ¦ μλ£ (κΈΈμ΄: {len(api_key_clean)}μ, μμ: {api_key_clean[:10]}..., λ: ...{api_key_clean[-5:]})") | |
| # REST APIλ₯Ό μ§μ μ¬μ©νμ¬ νΈμΆ | |
| use_rest = getattr(self, 'use_rest_api', True) | |
| if use_rest: | |
| print(f"[Gemini] REST API μ§μ νΈμΆ λͺ¨λ") | |
| # API λ²μ λ° λͺ¨λΈ μ΄λ¦ μ κ·ν | |
| # λͺ¨λΈ μ΄λ¦μμ 'gemini:' μ λμ¬ μ κ±° λ° μ κ·ν | |
| model_name_clean = model_name.strip() | |
| if ':' in model_name_clean: | |
| # "gemini:gemini-1.5-flash" νμμΈ κ²½μ° | |
| model_name_clean = model_name_clean.split(':', 1)[1].strip() | |
| elif model_name_clean.startswith('gemini-'): | |
| # "gemini-1.5-flash" νμμΈ κ²½μ° κ·Έλλ‘ μ¬μ© | |
| pass | |
| # REST API λ² μ΄μ€ URL (v1 μ¬μ©) | |
| rest_base_url = 'https://generativelanguage.googleapis.com/v1' | |
| url = f"{rest_base_url}/models/{model_name_clean}:generateContent" | |
| print(f"[Gemini] - API λ²μ : v1") | |
| print(f"[Gemini] - μλ³Έ λͺ¨λΈ μ΄λ¦: {model_name}") | |
| print(f"[Gemini] - μ κ·νλ λͺ¨λΈ μ΄λ¦: {model_name_clean}") | |
| print(f"[Gemini] - μ 체 URL: {url}") | |
| # REST API μμ² λ³Έλ¬Έ κ΅¬μ± | |
| request_body = { | |
| "contents": [{ | |
| "parts": [{ | |
| "text": prompt | |
| }] | |
| }], | |
| "generationConfig": generation_config | |
| } | |
| # REST API ν€λ (API ν€λ₯Ό ν€λλ‘ μ λ¬ μλ) | |
| headers = { | |
| "Content-Type": "application/json", | |
| "x-goog-api-key": api_key_clean | |
| } | |
| print(f"[Gemini] REST API νΈμΆ μ μ‘ μ€...") | |
| print(f"[Gemini] - URL: {url}") | |
| print(f"[Gemini] - λͺ¨λΈ: {model_name}") | |
| print(f"[Gemini] - API ν€: μ€μ λ¨ (κΈΈμ΄: {len(api_key_clean)}μ)") | |
| print(f"[Gemini] - API ν€ μμ: {api_key_clean[:15]}...") | |
| print(f"[Gemini] - API ν€ λ: ...{api_key_clean[-10:]}") | |
| print(f"[Gemini] - ν둬ννΈ κΈΈμ΄: {len(prompt)}μ") | |
| # REST API νΈμΆ (API ν€λ₯Ό ν€λμ params μμͺ½μΌλ‘ μ λ¬ μλ) | |
| # Google Gemini APIλ ν€λ λλ params μ€ νλλ§ νμνμ§λ§, μμͺ½ λͺ¨λ μλ | |
| api_params = {"key": api_key_clean} | |
| print(f"[Gemini] - API ν€ μ λ¬ λ°©μ: ν€λ (x-goog-api-key) λ° νλΌλ―Έν° (key)") | |
| rest_response = requests.post( | |
| url, | |
| headers=headers, | |
| json=request_body, | |
| params=api_params, | |
| timeout=timeout_seconds | |
| ) | |
| # μμ² URLμμ API ν€ λΆλΆλ§ μ κ±°νμ¬ λ‘κΉ (보μ) | |
| request_url = rest_response.request.url | |
| if 'key=' in request_url: | |
| # API ν€ λΆλΆμ λ§μ€νΉ | |
| from urllib.parse import urlparse, parse_qs, urlencode, urlunparse | |
| parsed = urlparse(request_url) | |
| params = parse_qs(parsed.query) | |
| if 'key' in params: | |
| masked_params = params.copy() | |
| masked_key = masked_params['key'][0][:10] + '...' + masked_params['key'][0][-5:] | |
| masked_params['key'] = [masked_key] | |
| masked_query = urlencode(masked_params, doseq=True) | |
| masked_url = urlunparse((parsed.scheme, parsed.netloc, parsed.path, parsed.params, masked_query, parsed.fragment)) | |
| print(f"[Gemini] - μμ² URL (λ§μ€νΉλ¨): {masked_url[:150]}...") | |
| else: | |
| print(f"[Gemini] - μμ² URL: {request_url[:150]}...") | |
| print(f"[Gemini] REST API μλ΅ μν μ½λ: {rest_response.status_code}") | |
| # μλ΅ λ³Έλ¬Έ νμΈ (μν μ½λκ° 200μ΄μ΄λ μλ¬κ° μμ μ μμ) | |
| response_has_error = False | |
| try: | |
| response_data_check = rest_response.json() | |
| # μλ΅μ error νλκ° μλμ§ νμΈ | |
| if 'error' in response_data_check: | |
| response_has_error = True | |
| error_info = response_data_check['error'] | |
| error_code = error_info.get('code', rest_response.status_code) | |
| error_message = error_info.get('message', 'μ μ μλ μ€λ₯') | |
| print(f"[Gemini] μλ΅ λ³Έλ¬Έμ μλ¬ κ°μ§: code={error_code}, message={error_message}") | |
| # μλ¬ μ½λμ λ°λΌ μ²λ¦¬ | |
| if error_code == 404: | |
| # 404 μ€λ₯μΈ κ²½μ° v1betaλ‘ μ¬μλ | |
| print(f"[Gemini] v1μμ λͺ¨λΈμ μ°Ύμ μ μμ, v1betaλ‘ μ¬μλ...") | |
| rest_base_url_v1beta = 'https://generativelanguage.googleapis.com/v1beta' | |
| url_v1beta = f"{rest_base_url_v1beta}/models/{model_name_clean}:generateContent" | |
| rest_response = requests.post( | |
| url_v1beta, | |
| headers=headers, | |
| json=request_body, | |
| params=api_params, | |
| timeout=timeout_seconds | |
| ) | |
| print(f"[Gemini] v1beta REST API μλ΅ μν μ½λ: {rest_response.status_code}") | |
| # v1beta μλ΅λ νμΈ | |
| if rest_response.status_code == 200: | |
| response_data_check_v1beta = rest_response.json() | |
| if 'error' in response_data_check_v1beta: | |
| # v1betaμμλ μλ¬ λ°μ | |
| error_info_v1beta = response_data_check_v1beta['error'] | |
| error_code_v1beta = error_info_v1beta.get('code', 404) | |
| error_message_v1beta = error_info_v1beta.get('message', 'μ μ μλ μ€λ₯') | |
| # λͺ¨λΈ λͺ©λ‘ μ‘°ν μλ | |
| available_models_str = "νμΈ λΆκ°" | |
| try: | |
| list_models_url_v1 = f"{rest_base_url}/models" | |
| list_response = requests.get( | |
| list_models_url_v1, | |
| headers={"x-goog-api-key": api_key_clean}, | |
| params={"key": api_key_clean}, | |
| timeout=10 | |
| ) | |
| if list_response.status_code == 200: | |
| models_data = list_response.json() | |
| available_models = [] | |
| for m in models_data.get('models', []): | |
| model_name_full = m.get('name', '') | |
| if '/' in model_name_full: | |
| model_name_short = model_name_full.split('/')[-1] | |
| else: | |
| model_name_short = model_name_full | |
| # generateContentλ₯Ό μ§μνλ λͺ¨λΈλ§ νν°λ§ | |
| supported_methods = m.get('supportedGenerationMethods', []) | |
| if 'generateContent' in supported_methods: | |
| available_models.append(model_name_short) | |
| available_models_str = ', '.join(available_models[:10]) if available_models else 'μμ' | |
| print(f"[Gemini] μ¬μ© κ°λ₯ν λͺ¨λΈ λͺ©λ‘ (v1): {available_models[:10]}") | |
| except Exception as list_error: | |
| print(f"[Gemini] λͺ¨λΈ λͺ©λ‘ μ‘°ν μ€ν¨: {list_error}") | |
| error_text_v1beta = json.dumps(error_info_v1beta) | |
| raise Exception(f"REST API μ€λ₯ {error_code_v1beta}: {error_text_v1beta}\nμ¬μ© κ°λ₯ν λͺ¨λΈ: {available_models_str}") | |
| else: | |
| # v1betaμμ μ±κ³΅ | |
| response_has_error = False | |
| print(f"[Gemini] v1betaμμ μ μ μλ΅ λ°μ") | |
| elif rest_response.status_code != 200: | |
| error_text_v1beta = rest_response.text[:1000] if rest_response.text else 'μμΈ μ 보 μμ' | |
| raise Exception(f"REST API μ€λ₯ {rest_response.status_code}: {error_text_v1beta}") | |
| else: | |
| # 404κ° μλ λ€λ₯Έ μλ¬ | |
| error_text = json.dumps(error_info) | |
| raise Exception(f"REST API μ€λ₯ {error_code}: {error_text}") | |
| except ValueError: | |
| # JSON νμ± μ€ν¨ | |
| pass | |
| # μ΄λ―Έ μλ¬κ° μ²λ¦¬λμ§ μμ κ²½μ°μλ§ μΆκ° μλ¬ μ²λ¦¬ | |
| # (response_has_errorκ° Trueλ©΄ μ΄λ―Έ μμμ μ²λ¦¬λμκ±°λ Exceptionμ΄ λ°μνμ κ²) | |
| if rest_response.status_code != 200 and not response_has_error: | |
| error_text = rest_response.text[:1000] if rest_response.text else 'μμΈ μ 보 μμ' | |
| # API ν€ μ€λ₯μΈ κ²½μ° λ μμΈν μλ΄ μ 곡 | |
| if rest_response.status_code == 400 and ('API key' in error_text or 'API_KEY' in error_text): | |
| print(f"[Gemini] β API ν€ μ€λ₯ κ°μ§") | |
| print(f"[Gemini] νμ¬ API ν€ μ 보:") | |
| print(f"[Gemini] - κΈΈμ΄: {len(api_key_clean)}μ") | |
| print(f"[Gemini] - μμ: {api_key_clean[:15]}...") | |
| print(f"[Gemini] - λ: ...{api_key_clean[-10:]}") | |
| print(f"[Gemini] - νμ νμΈ: {'AIzaλ‘ μμ' if api_key_clean.startswith('AIza') else 'AIzaλ‘ μμνμ§ μμ (λΉμ μ)'}") | |
| raise Exception(f"""REST API μ€λ₯ 400: API ν€κ° μ ν¨νμ§ μμ΅λλ€. | |
| νμΈ μ¬ν: | |
| 1. κ΄λ¦¬ νμ΄μ§μμ API ν€κ° μ¬λ°λ₯΄κ² μ€μ λμλμ§ νμΈνμΈμ. | |
| 2. Google AI Studio (https://aistudio.google.com/app/apikey)μμ API ν€κ° νμ±νλμ΄ μλμ§ νμΈνμΈμ. | |
| 3. API ν€ νμμ΄ μ¬λ°λ₯Έμ§ νμΈνμΈμ (μΌλ°μ μΌλ‘ 'AIza'λ‘ μμ). | |
| 4. API ν€μ λΆνμν 곡백μ΄λ μ€λ°κΏμ΄ ν¬ν¨λμ§ μμλμ§ νμΈνμΈμ. | |
| μ€λ₯ μμΈ: {error_text[:300]}""") | |
| raise Exception(f"REST API μ€λ₯ {rest_response.status_code}: {error_text}") | |
| # μλ¬κ° μμμ§λ§ μ²λ¦¬λμ§ μμ κ²½μ° (μ μ μλ΅μ΄ μλ) | |
| if response_has_error: | |
| # μ΄λ―Έ μμμ Exceptionμ΄ λ°μνμ΄μΌ νμ§λ§, νΉμ λͺ¨λ₯΄λ νμΈ | |
| error_text = rest_response.text[:1000] if rest_response.text else 'μμΈ μ 보 μμ' | |
| raise Exception(f"REST API μ€λ₯: μλ΅μ μλ¬κ° ν¬ν¨λμ΄ μμ΅λλ€. {error_text}") | |
| # REST API μλ΅ νμ± | |
| response_data = rest_response.json() | |
| # μλ΅μμ ν μ€νΈ μΆμΆ | |
| if 'candidates' in response_data and len(response_data['candidates']) > 0: | |
| candidate = response_data['candidates'][0] | |
| if 'content' in candidate and 'parts' in candidate['content']: | |
| parts = candidate['content']['parts'] | |
| if len(parts) > 0 and 'text' in parts[0]: | |
| response_text = parts[0]['text'] | |
| print(f"[Gemini] REST API μλ΅ μμ μ±κ³΅ (κΈΈμ΄: {len(response_text)}μ)") | |
| # genai λΌμ΄λΈλ¬λ¦¬ νμμΌλ‘ λ³ν (νΈνμ±μ μν΄) | |
| class MockResponse: | |
| def __init__(self, text): | |
| self.text = text | |
| response = MockResponse(response_text) | |
| break | |
| else: | |
| raise Exception("REST API μλ΅μ ν μ€νΈκ° μμ΅λλ€.") | |
| else: | |
| raise Exception("REST API μλ΅ νμμ΄ μ¬λ°λ₯΄μ§ μμ΅λλ€.") | |
| else: | |
| raise Exception("REST API μλ΅μ candidatesκ° μμ΅λλ€.") | |
| else: | |
| # κΈ°μ‘΄ genai λΌμ΄λΈλ¬λ¦¬ μ¬μ© (fallback) | |
| genai.configure(api_key=self.api_key) | |
| print(f"[Gemini] genai λΌμ΄λΈλ¬λ¦¬ μ¬μ© (fallback)") | |
| response = model.generate_content( | |
| prompt, | |
| generation_config=generation_config | |
| ) | |
| print(f"[Gemini] Gemini API μλ΅ μμ μ±κ³΅") | |
| break | |
| # μ±κ³΅ μ 루ν μ’ λ£ | |
| if retry_count > 0: | |
| print(f"[Gemini] μ¬μλ μ±κ³΅ (μ΄ {retry_count}ν μ¬μλ)") | |
| break | |
| except Exception as e: | |
| last_error = e | |
| error_str = str(e).lower() | |
| # μ¬μλ κ°λ₯ν μ€λ₯μΈμ§ νμΈ (νμμμ, λ€νΈμν¬ μ€λ₯ λ±) | |
| retryable_errors = ['timeout', '503', '502', '504', 'connection', 'network', 'illegal metadata'] | |
| is_retryable = any(err in error_str for err in retryable_errors) | |
| # deadline νμΈ | |
| if time.time() >= deadline_time: | |
| print(f"[Gemini] μ¬μλ deadline μ΄κ³Ό ({deadline}μ΄), λ§μ§λ§ μ€λ₯ λ°ν") | |
| raise | |
| if is_retryable: | |
| retry_count += 1 | |
| print(f"[Gemini] μ¬μλ {retry_count} - {wait_time:.1f}μ΄ ν μ¬μλ (μ€λ₯: {str(e)[:100]})") | |
| time.sleep(wait_time) | |
| wait_time = min(wait_time * multiplier, max_wait) # λ°°μλ‘ μ¦κ°, μ΅λκ° μ ν | |
| else: | |
| # μ¬μλ λΆκ°λ₯ν μ€λ₯ | |
| print(f"[Gemini] μ¬μλ λΆκ°λ₯ν μ€λ₯: {str(e)[:100]}") | |
| raise | |
| # μλ΅ μκ° νμΈ | |
| elapsed_time = time.time() - start_time | |
| print(f"[Gemini] μλ΅ μμ μλ£ (κ²½κ³Ό μκ°: {elapsed_time:.2f}μ΄)") | |
| # μλ΅ ν μ€νΈ μΆμΆ | |
| response_text = response.text if hasattr(response, 'text') else str(response) | |
| print(f"[Gemini] μλ΅ μμ± μλ£: {len(response_text)}μ") | |
| return { | |
| 'response': response_text, | |
| 'error': None | |
| } | |
| except Exception as e: | |
| error_msg = f'Gemini API μ€λ₯: {str(e)}' | |
| print(f"[Gemini] {error_msg}") | |
| return { | |
| 'response': None, | |
| 'error': error_msg | |
| } | |
| def generate_chat_response(self, messages: List[Dict], model_name: str = 'gemini-1.5-flash', **kwargs) -> Dict: | |
| """ | |
| Gemini APIλ₯Ό μ¬μ©νμ¬ μ±ν μλ΅ μμ± | |
| Args: | |
| messages: λ©μμ§ λ¦¬μ€νΈ [{'role': 'user', 'content': '...'}, ...] | |
| model_name: μ¬μ©ν λͺ¨λΈ μ΄λ¦ | |
| **kwargs: μΆκ° νλΌλ―Έν° | |
| Returns: | |
| Dict: {'response': str, 'error': str or None} | |
| """ | |
| if not self.is_configured(): | |
| return { | |
| 'response': None, | |
| 'error': 'Gemini API ν€κ° μ€μ λμ§ μμμ΅λλ€. GEMINI_API_KEY νκ²½ λ³μλ₯Ό μ€μ νμΈμ.' | |
| } | |
| try: | |
| # λͺ¨λΈ μμ± | |
| model = genai.GenerativeModel(model_name) | |
| # μ±ν μΈμ μμ | |
| chat = model.start_chat(history=[]) | |
| # μ΄μ λν λ΄μ μΆκ° (userμ assistant λ©μμ§) | |
| for msg in messages[:-1]: # λ§μ§λ§ λ©μμ§ μ μΈ | |
| if msg['role'] == 'user': | |
| chat.send_message(msg['content']) | |
| elif msg['role'] == 'assistant' or msg['role'] == 'ai': | |
| # Geminiλ μ¬μ©μ λ©μμ§λ§ μ§μ 보λ΄λ―λ‘, assistant λ©μμ§λ νμ€ν λ¦¬λ‘ μ²λ¦¬νμ§ μμ | |
| pass | |
| # λ§μ§λ§ μ¬μ©μ λ©μμ§λ‘ μλ΅ μμ± (νμμμ μ€μ ) | |
| last_message = messages[-1] if messages else {'content': ''} | |
| timeout_seconds = getattr(self, 'timeout', 600) # 5λΆ νμμμ | |
| print(f"[Gemini] μ±ν μλ΅ μμ± μ€... (νμμμ: {timeout_seconds}μ΄)") | |
| # νμμμμ μν μμ μκ° κΈ°λ‘ | |
| start_time = time.time() | |
| # google-generativeaiλ retry νλΌλ―Έν°λ₯Ό μ§μνμ§ μμΌλ―λ‘ | |
| # μ¬μλ μ μ± μ μλμΌλ‘ ꡬννμ¬ μ μ© | |
| print(f"[Gemini] μ±ν API νΈμΆ μμ (νμμμ: {timeout_seconds}μ΄, μ¬μλ μ μ± μ μ©)") | |
| # μ¬μλ λ‘μ§ κ΅¬ν (μ¬μλ μ μ± κ°μ²΄μ μ€μ μ¬μ©) | |
| initial_wait = 10.0 # μ΄κΈ° λκΈ° μκ° | |
| max_wait = 60.0 # μ΅λ λκΈ° μκ° | |
| multiplier = 2.0 # λκΈ° μκ° λ°°μ | |
| deadline = 600.0 # μ 체 μ¬μλ κΈ°κ° (10λΆ) | |
| wait_time = initial_wait | |
| deadline_time = time.time() + deadline | |
| retry_count = 0 | |
| last_error = None | |
| while True: | |
| try: | |
| response = chat.send_message(last_message['content']) | |
| # μ±κ³΅ μ 루ν μ’ λ£ | |
| if retry_count > 0: | |
| print(f"[Gemini] μ±ν μ¬μλ μ±κ³΅ (μ΄ {retry_count}ν μ¬μλ)") | |
| break | |
| except Exception as e: | |
| last_error = e | |
| error_str = str(e).lower() | |
| # μ¬μλ κ°λ₯ν μ€λ₯μΈμ§ νμΈ (νμμμ, λ€νΈμν¬ μ€λ₯ λ±) | |
| retryable_errors = ['timeout', '503', '502', '504', 'connection', 'network', 'illegal metadata'] | |
| is_retryable = any(err in error_str for err in retryable_errors) | |
| # deadline νμΈ | |
| if time.time() >= deadline_time: | |
| print(f"[Gemini] μ±ν μ¬μλ deadline μ΄κ³Ό ({deadline}μ΄), λ§μ§λ§ μ€λ₯ λ°ν") | |
| raise | |
| if is_retryable: | |
| retry_count += 1 | |
| print(f"[Gemini] μ±ν μ¬μλ {retry_count} - {wait_time:.1f}μ΄ ν μ¬μλ (μ€λ₯: {str(e)[:100]})") | |
| time.sleep(wait_time) | |
| wait_time = min(wait_time * multiplier, max_wait) # λ°°μλ‘ μ¦κ°, μ΅λκ° μ ν | |
| else: | |
| # μ¬μλ λΆκ°λ₯ν μ€λ₯ | |
| print(f"[Gemini] μ±ν μ¬μλ λΆκ°λ₯ν μ€λ₯: {str(e)[:100]}") | |
| raise | |
| elapsed_time = time.time() - start_time | |
| print(f"[Gemini] μ±ν μλ΅ μμ μλ£ (κ²½κ³Ό μκ°: {elapsed_time:.2f}μ΄)") | |
| response_text = response.text if hasattr(response, 'text') else str(response) | |
| print(f"[Gemini] μ±ν μλ΅ μμ± μλ£: {len(response_text)}μ") | |
| return { | |
| 'response': response_text, | |
| 'error': None | |
| } | |
| except Exception as e: | |
| error_msg = f'Gemini API μ€λ₯: {str(e)}' | |
| print(f"[Gemini] {error_msg}") | |
| return { | |
| 'response': None, | |
| 'error': error_msg | |
| } | |
| # μ μ Gemini ν΄λΌμ΄μΈνΈ μΈμ€ν΄μ€ | |
| _gemini_client = None | |
| def get_gemini_client() -> GeminiClient: | |
| """Gemini ν΄λΌμ΄μΈνΈ μ±κΈν€ μΈμ€ν΄μ€ λ°ν""" | |
| global _gemini_client | |
| if _gemini_client is None: | |
| _gemini_client = GeminiClient() | |
| else: | |
| # API ν€κ° λ³κ²½λμμ μ μμΌλ―λ‘ μ¬λ‘λ μλ | |
| _gemini_client.reload_api_key() | |
| return _gemini_client | |
| def reset_gemini_client(): | |
| """Gemini ν΄λΌμ΄μΈνΈ 리μ (API ν€ λ³κ²½ ν νΈμΆ)""" | |
| global _gemini_client | |
| _gemini_client = None | |
| return get_gemini_client() | |