|
|
"""
|
|
|
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()
|
|
|
|
|
|
|
|
|
input_tokens = None
|
|
|
output_tokens = None
|
|
|
if 'usageMetadata' in response_data:
|
|
|
usage = response_data['usageMetadata']
|
|
|
input_tokens = usage.get('promptTokenCount')
|
|
|
output_tokens = usage.get('candidatesTokenCount')
|
|
|
total_tokens = usage.get('totalTokenCount')
|
|
|
print(f"[Gemini] ν ν° μ¬μ©λ: μ
λ ₯={input_tokens}, μΆλ ₯={output_tokens}, μ΄={total_tokens}")
|
|
|
|
|
|
|
|
|
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, input_tokens=None, output_tokens=None):
|
|
|
self.text = text
|
|
|
self.input_tokens = input_tokens
|
|
|
self.output_tokens = output_tokens
|
|
|
|
|
|
response = MockResponse(response_text, input_tokens, output_tokens)
|
|
|
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)
|
|
|
|
|
|
|
|
|
input_tokens = getattr(response, 'input_tokens', None)
|
|
|
output_tokens = getattr(response, 'output_tokens', None)
|
|
|
|
|
|
print(f"[Gemini] μλ΅ μμ± μλ£: {len(response_text)}μ, μ
λ ₯ ν ν°: {input_tokens}, μΆλ ₯ ν ν°: {output_tokens}")
|
|
|
return {
|
|
|
'response': response_text,
|
|
|
'error': None,
|
|
|
'input_tokens': input_tokens,
|
|
|
'output_tokens': output_tokens
|
|
|
}
|
|
|
|
|
|
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()
|
|
|
|
|
|
|