soyailabs / app /gemini_client.py
SOY NV AI
Add Gemini API integration with REST API support, improve error handling, and add markdown bold formatting for messages
665bcdc
raw
history blame
35.9 kB
"""
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()