|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
self.api_key = get_gemini_api_key() |
|
|
|
|
|
if not self.api_key: |
|
|
print("[Gemini] κ²½κ³ : GEMINI_API_KEYκ° μ€μ λμ§ μμμ΅λλ€. νκ²½ λ³μλ κ΄λ¦¬ νμ΄μ§μμ μ€μ νμΈμ.") |
|
|
return |
|
|
|
|
|
try: |
|
|
|
|
|
|
|
|
self.request_timeout = 300 |
|
|
|
|
|
|
|
|
self.retry_policy = retry.Retry( |
|
|
initial=10.0, |
|
|
maximum=60.0, |
|
|
multiplier=2.0, |
|
|
deadline=600.0 |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
os.environ.setdefault('HTTPX_TIMEOUT', str(self.request_timeout)) |
|
|
os.environ.setdefault('GOOGLE_API_TIMEOUT', str(self.request_timeout)) |
|
|
|
|
|
|
|
|
self.rest_base_url = 'https://generativelanguage.googleapis.com/v1' |
|
|
self.use_rest_api = True |
|
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
try: |
|
|
|
|
|
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: |
|
|
|
|
|
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: |
|
|
|
|
|
current_api_key = get_gemini_api_key() |
|
|
if not current_api_key: |
|
|
return { |
|
|
'response': None, |
|
|
'error': 'Gemini 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: |
|
|
|
|
|
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) |
|
|
print(f"[Gemini] λͺ¨λΈ {model_name}λ‘ μλ΅ μμ± μ€... (νμμμ: {timeout_seconds}μ΄)") |
|
|
print(f"[Gemini] API ν€ νμΈ: μ€μ λ¨ (κΈΈμ΄: {len(self.api_key)}μ)") |
|
|
print(f"[Gemini] ν둬ννΈ κΈΈμ΄: {len(prompt)}μ") |
|
|
|
|
|
|
|
|
start_time = time.time() |
|
|
|
|
|
|
|
|
|
|
|
retry_policy = getattr(self, 'retry_policy', None) |
|
|
print(f"[Gemini] API νΈμΆ μμ (νμμμ: {timeout_seconds}μ΄, μ¬μλ μ μ±
μ μ©)") |
|
|
|
|
|
|
|
|
|
|
|
initial_wait = 10.0 |
|
|
max_wait = 60.0 |
|
|
multiplier = 2.0 |
|
|
deadline = 600.0 |
|
|
|
|
|
wait_time = initial_wait |
|
|
deadline_time = time.time() + deadline |
|
|
retry_count = 0 |
|
|
last_error = None |
|
|
|
|
|
while True: |
|
|
try: |
|
|
|
|
|
current_key = get_gemini_api_key() |
|
|
if not current_key: |
|
|
raise Exception("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}μ)") |
|
|
|
|
|
|
|
|
if not self.api_key or not self.api_key.strip(): |
|
|
raise Exception("API ν€κ° λΉμ΄μμ΅λλ€. κ΄λ¦¬ νμ΄μ§μμ API ν€λ₯Ό μ€μ νμΈμ.") |
|
|
|
|
|
|
|
|
api_key_clean = self.api_key.strip() |
|
|
if not api_key_clean: |
|
|
raise Exception("API ν€κ° μ ν¨νμ§ μμ΅λλ€ (κ³΅λ°±λ§ ν¬ν¨).") |
|
|
|
|
|
|
|
|
if not api_key_clean.startswith('AIza'): |
|
|
print(f"[Gemini] κ²½κ³ : API ν€κ° μΌλ°μ μΈ Google API ν€ νμμ΄ μλλλ€ (AIzaλ‘ μμνμ§ μμ)") |
|
|
|
|
|
|
|
|
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:]})") |
|
|
|
|
|
|
|
|
use_rest = getattr(self, 'use_rest_api', True) |
|
|
if use_rest: |
|
|
print(f"[Gemini] REST API μ§μ νΈμΆ λͺ¨λ") |
|
|
|
|
|
|
|
|
|
|
|
model_name_clean = model_name.strip() |
|
|
if ':' in model_name_clean: |
|
|
|
|
|
model_name_clean = model_name_clean.split(':', 1)[1].strip() |
|
|
elif model_name_clean.startswith('gemini-'): |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
request_body = { |
|
|
"contents": [{ |
|
|
"parts": [{ |
|
|
"text": prompt |
|
|
}] |
|
|
}], |
|
|
"generationConfig": generation_config |
|
|
} |
|
|
|
|
|
|
|
|
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)}μ") |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
request_url = rest_response.request.url |
|
|
if 'key=' in request_url: |
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
response_has_error = False |
|
|
try: |
|
|
response_data_check = rest_response.json() |
|
|
|
|
|
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: |
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
if rest_response.status_code == 200: |
|
|
response_data_check_v1beta = rest_response.json() |
|
|
if 'error' in response_data_check_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 |
|
|
|
|
|
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: |
|
|
|
|
|
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}") |
|
|
elif error_code == 429: |
|
|
|
|
|
print(f"[Gemini] β ν λΉλ μ΄κ³Ό μ€λ₯ (429) κ°μ§") |
|
|
print(f"[Gemini] μ¬μλ λΆκ°λ₯ν μ€λ₯: REST API μ€λ₯ {error_code}: {json.dumps(error_info)}") |
|
|
|
|
|
|
|
|
recommended_model = None |
|
|
if 'gemini-2.0-flash' in error_message.lower() or 'gemini 2.0' in error_message.lower(): |
|
|
recommended_model = "gemini-2.0-flash-exp" |
|
|
|
|
|
|
|
|
quota_error_msg = f"""Gemini API ν λΉλ μ΄κ³Ό (429) |
|
|
|
|
|
νμ¬ μ¬μ© μ€μΈ λͺ¨λΈ '{model_name_clean}'μ μΌμΌ μμ² νλλ₯Ό μ΄κ³Όνμ΅λλ€. |
|
|
|
|
|
ν΄κ²° λ°©λ²: |
|
|
1. λ΄μΌκΉμ§ λκΈ° (ν λΉλμ΄ μλμΌλ‘ μ¬μ€μ λ©λλ€) |
|
|
2. λ€λ₯Έ Gemini λͺ¨λΈλ‘ λ³κ²½: |
|
|
- gemini-2.0-flash-exp (λ λμ ν λΉλ μ 곡) |
|
|
- gemini-1.5-pro |
|
|
- gemini-1.5-flash |
|
|
3. Google AI Studioμμ ν λΉλ νμΈ: |
|
|
https://aistudio.google.com/app/apikey |
|
|
|
|
|
μμΈ μ€λ₯: {error_message[:200]}""" |
|
|
|
|
|
if recommended_model: |
|
|
quota_error_msg += f"\n\nκΆμ₯: {recommended_model} λͺ¨λΈλ‘ λ³κ²½μ κ³ λ €ν΄λ³΄μΈμ." |
|
|
|
|
|
raise Exception(quota_error_msg) |
|
|
else: |
|
|
|
|
|
error_text = json.dumps(error_info) |
|
|
raise Exception(f"REST API μ€λ₯ {error_code}: {error_text}") |
|
|
except ValueError: |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
if rest_response.status_code != 200 and not response_has_error: |
|
|
error_text = rest_response.text[:1000] if rest_response.text else 'μμΈ μ 보 μμ' |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
error_text = rest_response.text[:1000] if rest_response.text else 'μμΈ μ 보 μμ' |
|
|
raise Exception(f"REST API μ€λ₯: μλ΅μ μλ¬κ° ν¬ν¨λμ΄ μμ΅λλ€. {error_text}") |
|
|
|
|
|
|
|
|
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)}μ)") |
|
|
|
|
|
|
|
|
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.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() |
|
|
|
|
|
|
|
|
|
|
|
non_retryable_errors = ['429', 'quota', 'exceeded', '400', '401', '403', 'api key', 'invalid', 'unauthorized', 'forbidden'] |
|
|
is_non_retryable = any(err in error_str for err in non_retryable_errors) |
|
|
|
|
|
retryable_errors = ['timeout', '503', '502', '504', 'connection', 'network', 'illegal metadata'] |
|
|
is_retryable = any(err in error_str for err in retryable_errors) and not is_non_retryable |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
if is_non_retryable: |
|
|
print(f"[Gemini] μ¬μλ λΆκ°λ₯ν μ€λ₯ (ν λΉλ/μΈμ¦ μ€λ₯): {str(e)[:200]}") |
|
|
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=[]) |
|
|
|
|
|
|
|
|
for msg in messages[:-1]: |
|
|
if msg['role'] == 'user': |
|
|
chat.send_message(msg['content']) |
|
|
elif msg['role'] == 'assistant' or msg['role'] == 'ai': |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
last_message = messages[-1] if messages else {'content': ''} |
|
|
timeout_seconds = getattr(self, 'timeout', 600) |
|
|
print(f"[Gemini] μ±ν
μλ΅ μμ± μ€... (νμμμ: {timeout_seconds}μ΄)") |
|
|
|
|
|
|
|
|
start_time = time.time() |
|
|
|
|
|
|
|
|
|
|
|
print(f"[Gemini] μ±ν
API νΈμΆ μμ (νμμμ: {timeout_seconds}μ΄, μ¬μλ μ μ±
μ μ©)") |
|
|
|
|
|
|
|
|
initial_wait = 10.0 |
|
|
max_wait = 60.0 |
|
|
multiplier = 2.0 |
|
|
deadline = 600.0 |
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
|
|
|
non_retryable_errors = ['429', 'quota', 'exceeded', '400', '401', '403', 'api key', 'invalid', 'unauthorized', 'forbidden'] |
|
|
is_non_retryable = any(err in error_str for err in non_retryable_errors) |
|
|
|
|
|
retryable_errors = ['timeout', '503', '502', '504', 'connection', 'network', 'illegal metadata'] |
|
|
is_retryable = any(err in error_str for err in retryable_errors) and not is_non_retryable |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
if is_non_retryable: |
|
|
print(f"[Gemini] μ±ν
μ¬μλ λΆκ°λ₯ν μ€λ₯ (ν λΉλ/μΈμ¦ μ€λ₯): {str(e)[:200]}") |
|
|
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_client = None |
|
|
|
|
|
def get_gemini_client() -> GeminiClient: |
|
|
"""Gemini ν΄λΌμ΄μΈνΈ μ±κΈν€ μΈμ€ν΄μ€ λ°ν""" |
|
|
global _gemini_client |
|
|
if _gemini_client is None: |
|
|
_gemini_client = GeminiClient() |
|
|
else: |
|
|
|
|
|
_gemini_client.reload_api_key() |
|
|
return _gemini_client |
|
|
|
|
|
def reset_gemini_client(): |
|
|
"""Gemini ν΄λΌμ΄μΈνΈ 리μ
(API ν€ λ³κ²½ ν νΈμΆ)""" |
|
|
global _gemini_client |
|
|
_gemini_client = None |
|
|
return get_gemini_client() |
|
|
|
|
|
|