Обновление GLM моделей до GLM-4
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated migration for updating GLM default model
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def update_glm_default_model(apps, schema_editor):
|
||||||
|
"""Обновляем модель по умолчанию для существующих записей GLM"""
|
||||||
|
GLMIntegration = apps.get_model('integrations', 'GLMIntegration')
|
||||||
|
|
||||||
|
# Обновляем все записи с glm-4.7 на glm-4-flash
|
||||||
|
GLMIntegration.objects.filter(model_name='glm-4.7').update(model_name='glm-4-flash')
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_update_glm_default_model(apps, schema_editor):
|
||||||
|
"""Откат миграции - возвращаем glm-4.7"""
|
||||||
|
GLMIntegration = apps.get_model('integrations', 'GLMIntegration')
|
||||||
|
|
||||||
|
# Возвращаем glm-4.7
|
||||||
|
GLMIntegration.objects.filter(model_name='glm-4-flash').update(model_name='glm-4.7')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('integrations', '0002_glmintegration_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(update_glm_default_model, reverse_update_glm_default_model),
|
||||||
|
]
|
||||||
29
myproject/integrations/migrations/0004_revert_glm_model.py
Normal file
29
myproject/integrations/migrations/0004_revert_glm_model.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated migration for reverting GLM model back to glm-4.7
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def revert_glm_model(apps, schema_editor):
|
||||||
|
"""Откатываем модель обратно на glm-4.7"""
|
||||||
|
GLMIntegration = apps.get_model('integrations', 'GLMIntegration')
|
||||||
|
|
||||||
|
# Обновляем все записи с glm-4-flash обратно на glm-4.7
|
||||||
|
GLMIntegration.objects.filter(model_name='glm-4-flash').update(model_name='glm-4.7')
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_revert_glm_model(apps, schema_editor):
|
||||||
|
"""Применяем миграцию - возвращаем glm-4-flash"""
|
||||||
|
GLMIntegration = apps.get_model('integrations', 'GLMIntegration')
|
||||||
|
|
||||||
|
# Возвращаем glm-4-flash
|
||||||
|
GLMIntegration.objects.filter(model_name='glm-4.7').update(model_name='glm-4-flash')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('integrations', '0003_update_glm_default_model'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(revert_glm_model, reverse_revert_glm_model),
|
||||||
|
]
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated migration for updating GLM model to glm-4
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def update_glm_model_to_glm4(apps, schema_editor):
|
||||||
|
"""Обновляем модель с glm-4-flash на glm-4"""
|
||||||
|
GLMIntegration = apps.get_model('integrations', 'GLMIntegration')
|
||||||
|
|
||||||
|
# Обновляем все записи с glm-4-flash на glm-4
|
||||||
|
GLMIntegration.objects.filter(model_name='glm-4-flash').update(model_name='glm-4')
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_update_glm_model_to_glm4(apps, schema_editor):
|
||||||
|
"""Откат миграции - возвращаем glm-4-flash"""
|
||||||
|
GLMIntegration = apps.get_model('integrations', 'GLMIntegration')
|
||||||
|
|
||||||
|
# Возвращаем glm-4-flash
|
||||||
|
GLMIntegration.objects.filter(model_name='glm-4').update(model_name='glm-4-flash')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('integrations', '0004_revert_glm_model'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(update_glm_model_to_glm4, reverse_update_glm_model_to_glm4),
|
||||||
|
]
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated migration for updating GLM model from glm-4-flash to glm-4
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def update_glm_model_to_glm4(apps, schema_editor):
|
||||||
|
"""Обновляем модель с glm-4-flash на glm-4"""
|
||||||
|
GLMIntegration = apps.get_model('integrations', 'GLMIntegration')
|
||||||
|
|
||||||
|
# Обновляем все записи с glm-4-flash на glm-4
|
||||||
|
GLMIntegration.objects.filter(model_name='glm-4-flash').update(model_name='glm-4')
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_update_glm_model_to_glm4(apps, schema_editor):
|
||||||
|
"""Откат миграции - возвращаем glm-4-flash"""
|
||||||
|
GLMIntegration = apps.get_model('integrations', 'GLMIntegration')
|
||||||
|
|
||||||
|
# Возвращаем glm-4-flash
|
||||||
|
GLMIntegration.objects.filter(model_name='glm-4').update(model_name='glm-4-flash')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('integrations', '0005_update_glm_model_to_glm4'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(update_glm_model_to_glm4, reverse_update_glm_model_to_glm4),
|
||||||
|
]
|
||||||
89
myproject/integrations/models/ai_services/glm.py
Normal file
89
myproject/integrations/models/ai_services/glm.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
from django.db import models
|
||||||
|
from integrations.models.base import BaseIntegration, IntegrationType
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from integrations.fields import EncryptedCharField
|
||||||
|
|
||||||
|
|
||||||
|
def validate_temperature(value):
|
||||||
|
"""Валидатор для температуры, принимает значения от 0 до 1"""
|
||||||
|
if value < 0 or value > 1:
|
||||||
|
raise ValidationError('Температура должна быть в диапазоне 0.0-1.0')
|
||||||
|
|
||||||
|
|
||||||
|
# Список доступных моделей GLM (включая бесплатные)
|
||||||
|
GLM_MODEL_CHOICES = [
|
||||||
|
('glm-4', 'GLM-4 (Платная, дешевле)'),
|
||||||
|
('glm-4.7', 'GLM-4.7 (Платная)'),
|
||||||
|
('charglm-3', 'ChargLM-3 (Платная)'),
|
||||||
|
('glm-4.6v', 'GLM-4.6V (Бесплатная)'),
|
||||||
|
('glm-4.5v', 'GLM-4.5V (Бесплатная)'),
|
||||||
|
('glm-4.5-air', 'GLM-4.5-Air (Бесплатная)'),
|
||||||
|
('glm-4.5-flash', 'GLM-4.5-Flash (Бесплатная)'),
|
||||||
|
('glm-4.5-flashx', 'GLM-4.5-FlashX (Бесплатная)'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AIIntegration(BaseIntegration):
|
||||||
|
"""
|
||||||
|
Базовая модель для интеграций с ИИ-сервисами
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class GLMIntegration(AIIntegration):
|
||||||
|
"""
|
||||||
|
Интеграция с GLM от Z.AI
|
||||||
|
"""
|
||||||
|
integration_type = IntegrationType.AI_SERVICE
|
||||||
|
|
||||||
|
api_key = EncryptedCharField(
|
||||||
|
max_length=500,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="API ключ",
|
||||||
|
help_text="Ключ для доступа к API GLM от Z.AI (шифруется в БД)"
|
||||||
|
)
|
||||||
|
|
||||||
|
api_url = models.URLField(
|
||||||
|
max_length=500,
|
||||||
|
default="https://api.z.ai/api/paas/v4",
|
||||||
|
verbose_name="URL API",
|
||||||
|
help_text="URL для обращения к API GLM (обычно https://api.z.ai/api/paas/v4)"
|
||||||
|
)
|
||||||
|
|
||||||
|
model_name = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
default="glm-4",
|
||||||
|
choices=GLM_MODEL_CHOICES,
|
||||||
|
verbose_name="Название модели",
|
||||||
|
help_text="Название используемой модели GLM (например, glm-4.7, glm-4)"
|
||||||
|
)
|
||||||
|
|
||||||
|
temperature = models.FloatField(
|
||||||
|
default=0.7,
|
||||||
|
validators=[validate_temperature],
|
||||||
|
verbose_name="Температура",
|
||||||
|
help_text="Параметр температуры для генерации (0.0-1.0)"
|
||||||
|
)
|
||||||
|
|
||||||
|
is_coding_endpoint = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name="Использовать эндпоинт для кодинга",
|
||||||
|
help_text="Отметьте, если используете специальный эндпоинт для задач программирования"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Интеграция GLM"
|
||||||
|
verbose_name_plural = "Интеграции GLM"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_configured(self) -> bool:
|
||||||
|
return bool(self.api_key)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
if self.temperature < 0 or self.temperature > 1:
|
||||||
|
raise ValidationError({'temperature': 'Температура должна быть в диапазоне 0.0-1.0'})
|
||||||
|
|
||||||
|
# Всегда используем общий эндпоинт
|
||||||
|
self.api_url = "https://api.z.ai/api/paas/v4"
|
||||||
218
myproject/integrations/services/ai_services/glm_service.py
Normal file
218
myproject/integrations/services/ai_services/glm_service.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
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
|
||||||
41
myproject/test_glm_models.py
Normal file
41
myproject/test_glm_models.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
"""
|
||||||
|
Скрипт для тестирования доступных моделей GLM
|
||||||
|
"""
|
||||||
|
from zai import ZaiClient
|
||||||
|
|
||||||
|
# Список моделей для тестирования
|
||||||
|
models_to_test = [
|
||||||
|
"glm-4.7",
|
||||||
|
"glm-4-flash",
|
||||||
|
"glm-4",
|
||||||
|
"glm-3",
|
||||||
|
"glm-4-air",
|
||||||
|
"glm-4-flashx",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Ваш API ключ (замените на свой)
|
||||||
|
api_key = "YOUR_API_KEY_HERE"
|
||||||
|
|
||||||
|
client = ZaiClient(api_key=api_key)
|
||||||
|
|
||||||
|
for model in models_to_test:
|
||||||
|
try:
|
||||||
|
print(f"Тестирование модели: {model}")
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model=model,
|
||||||
|
messages=[{"role": "user", "content": "ping"}],
|
||||||
|
temperature=0.7,
|
||||||
|
max_tokens=10
|
||||||
|
)
|
||||||
|
print(f"✅ Модель {model} работает!")
|
||||||
|
print(f" Ответ: {response.choices[0].message.content if response.choices else 'Нет ответа'}")
|
||||||
|
break # Если модель работает, останавливаем тест
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
if "Unknown Model" in error_msg or "1211" in error_msg:
|
||||||
|
print(f"❌ Модель {model} не существует")
|
||||||
|
elif "429" in error_msg or "1113" in error_msg:
|
||||||
|
print(f"✅ Модель {model} существует, но нет баланса")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(f"❌ Модель {model} вызвала ошибку: {error_msg}")
|
||||||
Reference in New Issue
Block a user