218 lines
10 KiB
Python
218 lines
10 KiB
Python
from typing import Dict, Any, Tuple, Optional
|
||
from ..base import BaseIntegrationService
|
||
from django.conf import settings
|
||
import logging
|
||
import sys
|
||
import locale
|
||
|
||
# Патч для исправления проблемы с кодировкой в httpx на Windows
|
||
# Устанавливаем кодировку по умолчанию для Python
|
||
if sys.platform == 'win32':
|
||
try:
|
||
import httpx._models
|
||
original_normalize_header_value = httpx._models._normalize_header_value
|
||
|
||
def patched_normalize_header_value(value, encoding):
|
||
"""Патч для использования UTF-8 вместо ASCII для заголовков"""
|
||
# Если значение уже bytes, возвращаем его как есть
|
||
if isinstance(value, bytes):
|
||
return value
|
||
# Всегда используем UTF-8 вместо ASCII
|
||
encoding = encoding or 'utf-8'
|
||
if encoding.lower() == 'ascii':
|
||
encoding = 'utf-8'
|
||
return value.encode(encoding)
|
||
|
||
httpx._models._normalize_header_value = patched_normalize_header_value
|
||
logging.getLogger(__name__).info("Applied patch for httpx header encoding on Windows")
|
||
except Exception as e:
|
||
logging.getLogger(__name__).warning(f"Failed to apply httpx patch: {e}")
|
||
|
||
from zai import ZaiClient
|
||
from .config import get_glm_config
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class GLMIntegrationService(BaseIntegrationService):
|
||
"""
|
||
Сервис интеграции с GLM от Z.AI
|
||
"""
|
||
|
||
def __init__(self, config):
|
||
super().__init__(config)
|
||
logger.info(f"=== Инициализация GLMIntegrationService ===")
|
||
logger.info(f"Тип config: {type(config)}")
|
||
|
||
# Получаем конфигурацию из модели интеграции
|
||
try:
|
||
self.cfg = get_glm_config(config)
|
||
logger.info(f"Конфигурация успешно получена")
|
||
logger.info(f"API URL: {self.cfg.api_url}")
|
||
logger.info(f"Model name: {self.cfg.model_name}")
|
||
logger.info(f"Temperature: {self.cfg.temperature}")
|
||
logger.info(f"API key (первые 10 символов): {self.cfg.api_key[:10] if self.cfg.api_key else 'None'}...")
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при получении конфигурации: {str(e)}", exc_info=True)
|
||
raise
|
||
|
||
# Создаем клиент ZaiClient
|
||
try:
|
||
logger.info(f"Попытка создать ZaiClient...")
|
||
logger.info(f"API key type: {type(self.cfg.api_key)}")
|
||
logger.info(f"API key length: {len(self.cfg.api_key) if self.cfg.api_key else 0}")
|
||
logger.info(f"API key (первые 10 символов): {self.cfg.api_key[:10] if self.cfg.api_key else 'None'}...")
|
||
logger.info(f"API key (последние 10 символов): {self.cfg.api_key[-10:] if self.cfg.api_key and len(self.cfg.api_key) > 10 else 'None'}...")
|
||
|
||
self.client = ZaiClient(api_key=self.cfg.api_key)
|
||
logger.info(f"ZaiClient успешно создан")
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при создании ZaiClient: {str(e)}", exc_info=True)
|
||
raise
|
||
|
||
def test_connection(self) -> Tuple[bool, str]:
|
||
"""
|
||
Проверить соединение с API GLM
|
||
"""
|
||
import sys
|
||
import locale
|
||
|
||
logger.info(f"=== Начало тестирования соединения с GLM ===")
|
||
logger.info(f"Python default encoding: {sys.getdefaultencoding()}")
|
||
logger.info(f"Python filesystem encoding: {sys.getfilesystemencoding()}")
|
||
logger.info(f"Locale preferred encoding: {locale.getpreferredencoding()}")
|
||
|
||
try:
|
||
# Отправляем простой запрос для проверки подключения
|
||
logger.info(f"Отправка запроса к API GLM...")
|
||
logger.info(f"Model: {self.cfg.model_name}, Temperature: {self.cfg.temperature}")
|
||
|
||
# Используем английский текст для теста, чтобы избежать проблем с кодировкой
|
||
messages = [{"role": "user", "content": "ping"}]
|
||
logger.info(f"Messages: {messages}")
|
||
|
||
response = self.client.chat.completions.create(
|
||
model=self.cfg.model_name,
|
||
messages=messages,
|
||
temperature=self.cfg.temperature,
|
||
max_tokens=10
|
||
)
|
||
|
||
logger.info(f"Получен ответ от API GLM")
|
||
logger.info(f"Тип ответа: {type(response)}")
|
||
logger.info(f"Атрибуты ответа: {dir(response)}")
|
||
|
||
# Проверяем, что получили ответ
|
||
if hasattr(response, 'choices') and len(response.choices) > 0:
|
||
logger.info(f"Успешное подключение к GLM")
|
||
return True, "Connection to GLM successful"
|
||
else:
|
||
logger.error(f"Некорректный ответ от API GLM: нет choices")
|
||
return False, "Invalid response from GLM API"
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка подключения к GLM: {str(e)}", exc_info=True)
|
||
return False, f"Connection error: {str(e)}"
|
||
|
||
def sync(self) -> Tuple[bool, str]:
|
||
"""
|
||
Основная операция синхронизации (в случае GLM - может быть вызовом API для обработки данных)
|
||
"""
|
||
# Реализация будет зависеть от конкретных требований
|
||
return True, "Синхронизация GLM не требуется"
|
||
|
||
def generate_text(self,
|
||
prompt: str,
|
||
system_prompt: Optional[str] = None,
|
||
max_tokens: int = 1000) -> Tuple[bool, str, Optional[Dict]]:
|
||
"""
|
||
Генерация текста с помощью GLM
|
||
|
||
Args:
|
||
prompt: Входной текст для генерации
|
||
system_prompt: Системный промпт (опционально)
|
||
max_tokens: Максимальное количество токенов в ответе
|
||
|
||
Returns:
|
||
tuple: (success: bool, message: str, response_data: dict or None)
|
||
"""
|
||
if not self.is_available():
|
||
return False, "Интеграция GLM не активна", None
|
||
|
||
try:
|
||
# Подготовим сообщения
|
||
messages = []
|
||
if system_prompt:
|
||
messages.append({"role": "system", "content": system_prompt})
|
||
messages.append({"role": "user", "content": prompt})
|
||
|
||
response = self.client.chat.completions.create(
|
||
model=self.cfg.model_name,
|
||
messages=messages,
|
||
temperature=self.cfg.temperature,
|
||
max_tokens=max_tokens
|
||
)
|
||
|
||
# Извлекаем сгенерированный текст
|
||
generated_text = response.choices[0].message.content
|
||
usage_info = getattr(response, 'usage', {})
|
||
|
||
return True, "Текст успешно сгенерирован", {
|
||
'generated_text': generated_text,
|
||
'usage': usage_info,
|
||
'model': self.cfg.model_name
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка генерации текста с помощью GLM: {str(e)}")
|
||
return False, f"Ошибка генерации: {str(e)}", None
|
||
|
||
def generate_code(self,
|
||
prompt: str,
|
||
language: Optional[str] = None,
|
||
max_tokens: int = 1000) -> Tuple[bool, str, Optional[Dict]]:
|
||
"""
|
||
Генерация кода с помощью GLM (использует специальный эндпоинт, если указан)
|
||
|
||
Args:
|
||
prompt: Описание задачи для генерации кода
|
||
language: Язык программирования (опционально)
|
||
max_tokens: Максимальное количество токенов в ответе
|
||
|
||
Returns:
|
||
tuple: (success: bool, message: str, response_data: dict or None)
|
||
"""
|
||
if not self.is_available():
|
||
return False, "Интеграция GLM не активна", None
|
||
|
||
try:
|
||
# Подготовим системный промпт для генерации кода
|
||
system_prompt = "You are a helpful AI coding assistant. Generate clean, efficient, and well-documented code."
|
||
if language:
|
||
system_prompt += f" The code should be in {language}."
|
||
|
||
messages = [
|
||
{"role": "system", "content": system_prompt},
|
||
{"role": "user", "content": prompt}
|
||
]
|
||
|
||
response = self.client.chat.completions.create(
|
||
model=self.cfg.model_name,
|
||
messages=messages,
|
||
temperature=self.cfg.temperature,
|
||
max_tokens=max_tokens
|
||
)
|
||
|
||
generated_code = response.choices[0].message.content
|
||
usage_info = getattr(response, 'usage', {})
|
||
|
||
return True, "Код успешно сгенерирован", {
|
||
'generated_code': generated_code,
|
||
'usage': usage_info,
|
||
'model': self.cfg.model_name
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка генерации кода с помощью GLM: {str(e)}")
|
||
return False, f"Ошибка генерации кода: {str(e)}", None |