Редактировать giga_client.py
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Простой клиент для GigaChat API.
|
Простой клиент для GigaChat API с поддержкой повторных попыток и circuit breaker.
|
||||||
Только вызовы chat и upload_file, без бизнес-логики.
|
Использует tenacity для retry и pybreaker для circuit breaker.
|
||||||
Используется AIService и другими модулями.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -11,9 +10,14 @@ from typing import List, Dict, Optional
|
|||||||
|
|
||||||
from gigachat import GigaChat
|
from gigachat import GigaChat
|
||||||
from gigachat.models import Chat, Messages, MessagesRole
|
from gigachat.models import Chat, Messages, MessagesRole
|
||||||
|
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
|
||||||
|
import pybreaker
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Circuit breaker: при 3 ошибках подряд открывается на 60 секунд
|
||||||
|
breaker = pybreaker.CircuitBreaker(fail_max=3, reset_timeout=60)
|
||||||
|
|
||||||
|
|
||||||
class GigaClient:
|
class GigaClient:
|
||||||
def __init__(self, api_key: str, model: str, temperature: float, timeout: int, verify_ssl: bool = False):
|
def __init__(self, api_key: str, model: str, temperature: float, timeout: int, verify_ssl: bool = False):
|
||||||
@@ -36,6 +40,12 @@ class GigaClient:
|
|||||||
logger.debug("GigaChat client initialized")
|
logger.debug("GigaChat client initialized")
|
||||||
return self._client
|
return self._client
|
||||||
|
|
||||||
|
@retry(
|
||||||
|
stop=stop_after_attempt(3),
|
||||||
|
wait=wait_exponential(multiplier=1, min=1, max=8),
|
||||||
|
retry=retry_if_exception_type((TimeoutError, ConnectionError))
|
||||||
|
)
|
||||||
|
@breaker
|
||||||
async def chat(
|
async def chat(
|
||||||
self,
|
self,
|
||||||
history: List[Dict[str, str]],
|
history: List[Dict[str, str]],
|
||||||
@@ -45,7 +55,7 @@ class GigaClient:
|
|||||||
temperature: Optional[float] = None,
|
temperature: Optional[float] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Отправляет запрос к GigaChat и возвращает ответ.
|
Отправляет запрос к GigaChat и возвращает ответ с повторными попытками.
|
||||||
|
|
||||||
Аргументы:
|
Аргументы:
|
||||||
history: список предыдущих сообщений (каждое с ключами role и content).
|
history: список предыдущих сообщений (каждое с ключами role и content).
|
||||||
@@ -65,7 +75,6 @@ class GigaClient:
|
|||||||
messages.append(Messages(role=MessagesRole.USER, content=query))
|
messages.append(Messages(role=MessagesRole.USER, content=query))
|
||||||
|
|
||||||
attachments = [file_id] if file_id else None
|
attachments = [file_id] if file_id else None
|
||||||
|
|
||||||
actual_temp = temperature if temperature is not None else self.temperature
|
actual_temp = temperature if temperature is not None else self.temperature
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -81,9 +90,12 @@ class GigaClient:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return response.choices[0].message.content
|
return response.choices[0].message.content
|
||||||
|
except pybreaker.CircuitBreakerError:
|
||||||
|
logger.error("Circuit breaker открыт, GigaChat временно недоступен")
|
||||||
|
return "Извините, сервис ИИ временно недоступен. Попробуйте позже."
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при вызове GigaChat: {e}", exc_info=True)
|
logger.error(f"Ошибка при вызове GigaChat: {e}", exc_info=True)
|
||||||
return "Извините, произошла ошибка при обращении к сервису ИИ."
|
raise
|
||||||
|
|
||||||
async def upload_file(self, file_path: str) -> Optional[str]:
|
async def upload_file(self, file_path: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
@@ -92,16 +104,13 @@ class GigaClient:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with open(file_path, "rb") as f:
|
with open(file_path, "rb") as f:
|
||||||
# Загружаем файл синхронно в потоке
|
|
||||||
uploaded = await asyncio.to_thread(self._get_client().upload_file, f, "general")
|
uploaded = await asyncio.to_thread(self._get_client().upload_file, f, "general")
|
||||||
# В разных версиях SDK поле может называться id_, id или file_id
|
|
||||||
if hasattr(uploaded, "id_"):
|
if hasattr(uploaded, "id_"):
|
||||||
return uploaded.id_
|
return uploaded.id_
|
||||||
if hasattr(uploaded, "id"):
|
if hasattr(uploaded, "id"):
|
||||||
return uploaded.id
|
return uploaded.id
|
||||||
if hasattr(uploaded, "file_id"):
|
if hasattr(uploaded, "file_id"):
|
||||||
return uploaded.file_id
|
return uploaded.file_id
|
||||||
# fallback: пробуем преобразовать в словарь
|
|
||||||
data = uploaded.dict() if hasattr(uploaded, "dict") else {}
|
data = uploaded.dict() if hasattr(uploaded, "dict") else {}
|
||||||
return data.get("id_") or data.get("id") or data.get("file_id")
|
return data.get("id_") or data.get("id") or data.get("file_id")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user