Добавление интеграции OpenRouter AI
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2026-01-14 14:17
|
||||||
|
|
||||||
|
import integrations.fields
|
||||||
|
import integrations.models.ai_services.glm
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('integrations', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GLMIntegration',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_active', models.BooleanField(db_index=True, default=False, help_text='Глобальный тумблер включения интеграции', verbose_name='Активна')),
|
||||||
|
('name', models.CharField(blank=True, default='', help_text='Произвольное название для удобства (опционально)', max_length=100, verbose_name='Название')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||||
|
('extra_config', models.JSONField(blank=True, default=dict, verbose_name='Доп. настройки')),
|
||||||
|
('api_key', integrations.fields.EncryptedCharField(blank=True, help_text='Ключ для доступа к API GLM от Z.AI (шифруется в БД)', max_length=500, verbose_name='API ключ')),
|
||||||
|
('api_url', models.URLField(default='https://api.z.ai/api/paas/v4', help_text='URL для обращения к API GLM (обычно https://api.z.ai/api/paas/v4)', max_length=500, verbose_name='URL API')),
|
||||||
|
('model_name', models.CharField(default='glm-4.7', help_text='Название используемой модели GLM (например, glm-4.7)', max_length=100, verbose_name='Название модели')),
|
||||||
|
('temperature', models.FloatField(default=0.7, help_text='Параметр температуры для генерации (0.0-1.0)', validators=[integrations.models.ai_services.glm.validate_temperature], verbose_name='Температура')),
|
||||||
|
('is_coding_endpoint', models.BooleanField(default=False, help_text='Отметьте, если используете специальный эндпоинт для задач программирования', verbose_name='Использовать эндпоинт для кодинга')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Интеграция GLM',
|
||||||
|
'verbose_name_plural': 'Интеграции GLM',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='recommerceintegration',
|
||||||
|
name='integration_type',
|
||||||
|
field=models.CharField(choices=[('marketplace', 'Маркетплейс'), ('payment', 'Платёжная система'), ('shipping', 'Служба доставки'), ('ai_service', 'Сервис ИИ')], default='marketplace', editable=False, max_length=20),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='woocommerceintegration',
|
||||||
|
name='integration_type',
|
||||||
|
field=models.CharField(choices=[('marketplace', 'Маркетплейс'), ('payment', 'Платёжная система'), ('shipping', 'Служба доставки'), ('ai_service', 'Сервис ИИ')], default='marketplace', editable=False, max_length=20),
|
||||||
|
),
|
||||||
|
]
|
||||||
35
myproject/integrations/migrations/0007_openrouter.py
Normal file
35
myproject/integrations/migrations/0007_openrouter.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Generated migration for OpenRouter integration
|
||||||
|
|
||||||
|
import integrations.fields
|
||||||
|
import integrations.models.ai_services.openrouter
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('integrations', '0006_update_glm_model_to_glm4'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OpenRouterIntegration',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_active', models.BooleanField(db_index=True, default=False, help_text='Глобальный тумблер включения интеграции', verbose_name='Активна')),
|
||||||
|
('name', models.CharField(blank=True, default='', help_text='Произвольное название для удобства (опционально)', max_length=100, verbose_name='Название')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||||
|
('extra_config', models.JSONField(blank=True, default=dict, verbose_name='Доп. настройки')),
|
||||||
|
('api_key', integrations.fields.EncryptedCharField(blank=True, help_text='Ключ для доступа к API OpenRouter (шифруется в БД)', max_length=500, verbose_name='API ключ')),
|
||||||
|
('api_url', models.URLField(default='https://openrouter.ai/api/v1', help_text='URL для обращения к API OpenRouter (обычно https://openrouter.ai/api/v1)', max_length=500, verbose_name='URL API')),
|
||||||
|
('model_name', models.CharField(default='xiaomi/mimo-v2-flash:free', help_text='Название используемой модели OpenRouter', max_length=100, verbose_name='Название модели')),
|
||||||
|
('temperature', models.FloatField(default=0.7, help_text='Параметр температуры для генерации (0.0-2.0)', validators=[integrations.models.ai_services.openrouter.validate_temperature], verbose_name='Температура')),
|
||||||
|
('max_tokens', models.IntegerField(default=1000, help_text='Максимальное количество токенов в ответе', verbose_name='Макс. токенов')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Интеграция OpenRouter',
|
||||||
|
'verbose_name_plural': 'Интеграции OpenRouter',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Generated migration for OpenRouter temperature choices
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('integrations', '0007_openrouter'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='openrouterintegration',
|
||||||
|
name='temperature',
|
||||||
|
field=models.FloatField(
|
||||||
|
default=0.7,
|
||||||
|
choices=[
|
||||||
|
(0.1, '0.1 - Очень консервативно'),
|
||||||
|
(0.3, '0.3 - Консервативно'),
|
||||||
|
(0.5, '0.5 - Умеренно'),
|
||||||
|
(0.7, '0.7 - Баланс (по умолчанию)'),
|
||||||
|
(1.0, '1.0 - Креативно'),
|
||||||
|
(1.5, '1.5 - Очень креативно'),
|
||||||
|
(2.0, '2.0 - Максимальная креативность'),
|
||||||
|
],
|
||||||
|
help_text='Параметр температуры для генерации (0.0-2.0)',
|
||||||
|
validators=[],
|
||||||
|
verbose_name='Температура'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
94
myproject/integrations/models/ai_services/openrouter.py
Normal file
94
myproject/integrations/models/ai_services/openrouter.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
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 до 2"""
|
||||||
|
if value < 0 or value > 2:
|
||||||
|
raise ValidationError('Температура должна быть в диапазоне 0.0-2.0')
|
||||||
|
|
||||||
|
|
||||||
|
# Список доступных моделей OpenRouter (бесплатные)
|
||||||
|
OPENROUTER_MODEL_CHOICES = [
|
||||||
|
('xiaomi/mimo-v2-flash:free', 'Xiaomi MIMO v2 Flash (Бесплатная)'),
|
||||||
|
('mistralai/devstral-2512:free', 'Mistral Devstral 2512 (Бесплатная)'),
|
||||||
|
('z-ai/glm-4.5-air:free', 'Z.AI GLM-4.5 Air (Бесплатная)'),
|
||||||
|
('qwen/qwen3-coder:free', 'Qwen 3 Coder (Бесплатная)'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Предустановленные значения температуры
|
||||||
|
OPENROUTER_TEMPERATURE_CHOICES = [
|
||||||
|
(0.1, '0.1 - Очень консервативно'),
|
||||||
|
(0.3, '0.3 - Консервативно'),
|
||||||
|
(0.5, '0.5 - Умеренно'),
|
||||||
|
(0.7, '0.7 - Баланс (по умолчанию)'),
|
||||||
|
(1.0, '1.0 - Креативно'),
|
||||||
|
(1.5, '1.5 - Очень креативно'),
|
||||||
|
(2.0, '2.0 - Максимальная креативность'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AIIntegration(BaseIntegration):
|
||||||
|
"""
|
||||||
|
Базовая модель для интеграций с ИИ-сервисами
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class OpenRouterIntegration(AIIntegration):
|
||||||
|
"""
|
||||||
|
Интеграция с OpenRouter.ai
|
||||||
|
"""
|
||||||
|
integration_type = IntegrationType.AI_SERVICE
|
||||||
|
|
||||||
|
api_key = EncryptedCharField(
|
||||||
|
max_length=500,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="API ключ",
|
||||||
|
help_text="Ключ для доступа к API OpenRouter (шифруется в БД)"
|
||||||
|
)
|
||||||
|
|
||||||
|
api_url = models.URLField(
|
||||||
|
max_length=500,
|
||||||
|
default="https://openrouter.ai/api/v1",
|
||||||
|
verbose_name="URL API",
|
||||||
|
help_text="URL для обращения к API OpenRouter (обычно https://openrouter.ai/api/v1)"
|
||||||
|
)
|
||||||
|
|
||||||
|
model_name = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
default="xiaomi/mimo-v2-flash:free",
|
||||||
|
choices=OPENROUTER_MODEL_CHOICES,
|
||||||
|
verbose_name="Название модели",
|
||||||
|
help_text="Название используемой модели OpenRouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
temperature = models.FloatField(
|
||||||
|
default=0.7,
|
||||||
|
choices=OPENROUTER_TEMPERATURE_CHOICES,
|
||||||
|
validators=[validate_temperature],
|
||||||
|
verbose_name="Температура",
|
||||||
|
help_text="Параметр температуры для генерации (0.0-2.0)"
|
||||||
|
)
|
||||||
|
|
||||||
|
max_tokens = models.IntegerField(
|
||||||
|
default=1000,
|
||||||
|
verbose_name="Макс. токенов",
|
||||||
|
help_text="Максимальное количество токенов в ответе"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Интеграция OpenRouter"
|
||||||
|
verbose_name_plural = "Интеграции OpenRouter"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_configured(self) -> bool:
|
||||||
|
return bool(self.api_key)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
if self.temperature < 0 or self.temperature > 2:
|
||||||
|
raise ValidationError({'temperature': 'Температура должна быть в диапазоне 0.0-2.0'})
|
||||||
165
myproject/integrations/services/ai_services/config.py
Normal file
165
myproject/integrations/services/ai_services/config.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
from django.conf import settings
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AIConfig:
|
||||||
|
"""
|
||||||
|
Конфигурация для интеграции с ИИ-сервисами
|
||||||
|
"""
|
||||||
|
api_key: str
|
||||||
|
api_url: str
|
||||||
|
model_name: str
|
||||||
|
temperature: float
|
||||||
|
is_coding_endpoint: bool = False
|
||||||
|
max_tokens: int = 1000
|
||||||
|
timeout: int = 30
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_integration_model(cls, integration_model):
|
||||||
|
"""
|
||||||
|
Создать конфигурацию из модели интеграции
|
||||||
|
"""
|
||||||
|
logger.info(f"=== Создание конфигурации из модели интеграции ===")
|
||||||
|
logger.info(f"Тип модели: {type(integration_model)}")
|
||||||
|
logger.info(f"Атрибуты модели: {dir(integration_model)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
api_key = integration_model.api_key
|
||||||
|
logger.info(f"API key (первые 10 символов): {api_key[:10] if api_key else 'None'}...")
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.error(f"Ошибка при получении api_key: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Всегда используем общий эндпоинт
|
||||||
|
api_url = "https://api.z.ai/api/paas/v4"
|
||||||
|
logger.info(f"Используем общий endpoint: {api_url}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
model_name = integration_model.model_name
|
||||||
|
logger.info(f"Model name: {model_name}")
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.error(f"Ошибка при получении model_name: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
temperature = float(integration_model.temperature)
|
||||||
|
logger.info(f"Temperature: {temperature}")
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.error(f"Ошибка при получении temperature: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
api_key=api_key,
|
||||||
|
api_url=api_url,
|
||||||
|
model_name=model_name,
|
||||||
|
temperature=temperature,
|
||||||
|
is_coding_endpoint=False, # Всегда False, так как используем общий endpoint
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_env(cls):
|
||||||
|
"""
|
||||||
|
Создать конфигурацию из переменных окружения
|
||||||
|
"""
|
||||||
|
return cls(
|
||||||
|
api_key=os.getenv('ZAI_API_KEY', ''),
|
||||||
|
api_url=os.getenv('ZAI_API_URL', 'https://api.z.ai/api/paas/v4'),
|
||||||
|
model_name=os.getenv('ZAI_MODEL_NAME', 'glm-4.7'),
|
||||||
|
temperature=float(os.getenv('ZAI_TEMPERATURE', '0.7')),
|
||||||
|
is_coding_endpoint=os.getenv('ZAI_CODING_ENDPOINT', 'false').lower() == 'true',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_glm_config(integration_model=None):
|
||||||
|
"""
|
||||||
|
Получить конфигурацию GLM из модели интеграции или из .env
|
||||||
|
"""
|
||||||
|
if integration_model:
|
||||||
|
return AIConfig.from_integration_model(integration_model)
|
||||||
|
else:
|
||||||
|
return AIConfig.from_env()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class OpenRouterConfig:
|
||||||
|
"""
|
||||||
|
Конфигурация для интеграции с OpenRouter
|
||||||
|
"""
|
||||||
|
api_key: str
|
||||||
|
api_url: str
|
||||||
|
model_name: str
|
||||||
|
temperature: float
|
||||||
|
max_tokens: int = 1000
|
||||||
|
timeout: int = 30
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_integration_model(cls, integration_model):
|
||||||
|
"""
|
||||||
|
Создать конфигурацию из модели интеграции
|
||||||
|
"""
|
||||||
|
logger.info(f"=== Создание конфигурации OpenRouter из модели интеграции ===")
|
||||||
|
logger.info(f"Тип модели: {type(integration_model)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
api_key = integration_model.api_key
|
||||||
|
logger.info(f"API key (первые 10 символов): {api_key[:10] if api_key else 'None'}...")
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.error(f"Ошибка при получении api_key: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
api_url = getattr(integration_model, 'api_url', 'https://openrouter.ai/api/v1')
|
||||||
|
logger.info(f"API URL: {api_url}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
model_name = integration_model.model_name
|
||||||
|
logger.info(f"Model name: {model_name}")
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.error(f"Ошибка при получении model_name: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
temperature = float(integration_model.temperature)
|
||||||
|
logger.info(f"Temperature: {temperature}")
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.error(f"Ошибка при получении temperature: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
max_tokens = getattr(integration_model, 'max_tokens', 1000)
|
||||||
|
logger.info(f"Max tokens: {max_tokens}")
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
api_key=api_key,
|
||||||
|
api_url=api_url,
|
||||||
|
model_name=model_name,
|
||||||
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_env(cls):
|
||||||
|
"""
|
||||||
|
Создать конфигурацию из переменных окружения
|
||||||
|
"""
|
||||||
|
return cls(
|
||||||
|
api_key=os.getenv('OPENROUTER_API_KEY', ''),
|
||||||
|
api_url=os.getenv('OPENROUTER_API_URL', 'https://openrouter.ai/api/v1'),
|
||||||
|
model_name=os.getenv('OPENROUTER_MODEL_NAME', 'xiaomi/mimo-v2-flash:free'),
|
||||||
|
temperature=float(os.getenv('OPENROUTER_TEMPERATURE', '0.7')),
|
||||||
|
max_tokens=int(os.getenv('OPENROUTER_MAX_TOKENS', '1000')),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_openrouter_config(integration_model=None):
|
||||||
|
"""
|
||||||
|
Получить конфигурацию OpenRouter из модели интеграции или из .env
|
||||||
|
"""
|
||||||
|
if integration_model:
|
||||||
|
return OpenRouterConfig.from_integration_model(integration_model)
|
||||||
|
else:
|
||||||
|
return OpenRouterConfig.from_env()
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
from typing import Dict, Any, Tuple, Optional
|
||||||
|
from ..base import BaseIntegrationService
|
||||||
|
from .config import get_openrouter_config
|
||||||
|
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 openai import OpenAI
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenRouterIntegrationService(BaseIntegrationService):
|
||||||
|
"""
|
||||||
|
Сервис интеграции с OpenRouter.ai
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
super().__init__(config)
|
||||||
|
logger.info(f"=== Инициализация OpenRouterIntegrationService ===")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cfg = get_openrouter_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
|
||||||
|
|
||||||
|
# Импортируем клиент OpenAI (OpenRouter совместим с OpenAI API)
|
||||||
|
try:
|
||||||
|
self.client = OpenAI(
|
||||||
|
api_key=self.cfg.api_key,
|
||||||
|
base_url=self.cfg.api_url,
|
||||||
|
)
|
||||||
|
logger.info(f"OpenAI клиент для OpenRouter успешно создан")
|
||||||
|
except ImportError:
|
||||||
|
logger.error("Библиотека openai не установлена")
|
||||||
|
raise ImportError(
|
||||||
|
"Необходимо установить библиотеку openai: pip install openai"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при создании клиента: {str(e)}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def test_connection(self) -> Tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Проверить соединение с API OpenRouter
|
||||||
|
"""
|
||||||
|
logger.info(f"=== Начало тестирования соединения с OpenRouter ===")
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"Отправка запроса к API OpenRouter...")
|
||||||
|
logger.info(f"Model: {self.cfg.model_name}, Temperature: {self.cfg.temperature}")
|
||||||
|
|
||||||
|
messages = [{"role": "user", "content": "ping"}]
|
||||||
|
|
||||||
|
response = self.client.chat.completions.create(
|
||||||
|
model=self.cfg.model_name,
|
||||||
|
messages=messages,
|
||||||
|
temperature=self.cfg.temperature,
|
||||||
|
max_tokens=10
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Получен ответ от API OpenRouter")
|
||||||
|
|
||||||
|
if response.choices and len(response.choices) > 0:
|
||||||
|
logger.info(f"Успешное подключение к OpenRouter")
|
||||||
|
return True, "Connection to OpenRouter successful"
|
||||||
|
else:
|
||||||
|
logger.error(f"Некорректный ответ от API OpenRouter: нет choices")
|
||||||
|
return False, "Invalid response from OpenRouter API"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка подключения к OpenRouter: {str(e)}", exc_info=True)
|
||||||
|
return False, f"Connection error: {str(e)}"
|
||||||
|
|
||||||
|
def sync(self) -> Tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Основная операция синхронизации (для OpenRouter не требуется)
|
||||||
|
"""
|
||||||
|
return True, "Синхронизация OpenRouter не требуется"
|
||||||
|
|
||||||
|
def generate_text(self,
|
||||||
|
prompt: str,
|
||||||
|
system_prompt: Optional[str] = None,
|
||||||
|
max_tokens: Optional[int] = None) -> Tuple[bool, str, Optional[Dict]]:
|
||||||
|
"""
|
||||||
|
Генерация текста с помощью OpenRouter
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt: Входной текст для генерации
|
||||||
|
system_prompt: Системный промпт (опционально)
|
||||||
|
max_tokens: Максимальное количество токенов в ответе
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (success: bool, message: str, response_data: dict or None)
|
||||||
|
"""
|
||||||
|
if not self.is_available():
|
||||||
|
return False, "Интеграция OpenRouter не активна", 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 or self.cfg.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"Ошибка генерации текста с помощью OpenRouter: {str(e)}")
|
||||||
|
return False, f"Ошибка генерации: {str(e)}", None
|
||||||
|
|
||||||
|
def generate_code(self,
|
||||||
|
prompt: str,
|
||||||
|
language: Optional[str] = None,
|
||||||
|
max_tokens: Optional[int] = None) -> Tuple[bool, str, Optional[Dict]]:
|
||||||
|
"""
|
||||||
|
Генерация кода с помощью OpenRouter
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt: Описание задачи для генерации кода
|
||||||
|
language: Язык программирования (опционально)
|
||||||
|
max_tokens: Максимальное количество токенов в ответе
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (success: bool, message: str, response_data: dict or None)
|
||||||
|
"""
|
||||||
|
if not self.is_available():
|
||||||
|
return False, "Интеграция OpenRouter не активна", 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 or self.cfg.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"Ошибка генерации кода с помощью OpenRouter: {str(e)}")
|
||||||
|
return False, f"Ошибка генерации кода: {str(e)}", None
|
||||||
Reference in New Issue
Block a user