ваше сообщение коммита

This commit is contained in:
2025-07-27 03:30:13 +03:00
parent 057fe6254c
commit 1835632be9
141 changed files with 32514 additions and 6661 deletions

View File

@@ -18,26 +18,84 @@ const { OpenAIEmbeddings } = require('@langchain/openai');
const logger = require('../utils/logger');
const fetch = require('node-fetch');
// Простой кэш для ответов
const responseCache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 минут
class AIAssistant {
constructor() {
this.baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
this.defaultModel = process.env.OLLAMA_MODEL || 'qwen2.5';
this.defaultModel = process.env.OLLAMA_MODEL || 'qwen2.5:7b';
this.isModelLoaded = false;
this.lastHealthCheck = 0;
this.healthCheckInterval = 30000; // 30 секунд
}
// Проверка здоровья модели
async checkModelHealth() {
const now = Date.now();
if (now - this.lastHealthCheck < this.healthCheckInterval) {
return this.isModelLoaded;
}
try {
const response = await fetch(`${this.baseUrl}/api/tags`, {
timeout: 5000
});
if (response.ok) {
const data = await response.json();
this.isModelLoaded = data.models?.some(m => m.name === this.defaultModel) || false;
} else {
this.isModelLoaded = false;
}
} catch (error) {
console.error('Model health check failed:', error);
this.isModelLoaded = false;
}
this.lastHealthCheck = now;
return this.isModelLoaded;
}
// Очистка старых записей кэша
cleanupCache() {
const now = Date.now();
for (const [key, value] of responseCache.entries()) {
if (now - value.timestamp > CACHE_TTL) {
responseCache.delete(key);
}
}
}
// Создание экземпляра ChatOllama с нужными параметрами
createChat(language = 'ru') {
const systemPrompt =
language === 'ru'
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
: 'You are a helpful assistant. Respond in English.';
createChat(language = 'ru', customSystemPrompt = '') {
// Используем кастомный системный промпт, если он передан, иначе используем дефолтный
let systemPrompt = customSystemPrompt;
if (!systemPrompt) {
systemPrompt = language === 'ru'
? 'Вы - полезный ассистент. Отвечайте на русском языке кратко и по делу.'
: 'You are a helpful assistant. Respond in English briefly and to the point.';
}
return new ChatOllama({
baseUrl: this.baseUrl,
model: this.defaultModel,
system: systemPrompt,
temperature: 0.7,
maxTokens: 1000,
timeout: 30000, // 30 секунд таймаут
temperature: 0.3, // Уменьшаем для более предсказуемых ответов
maxTokens: 100, // Еще больше уменьшаем для быстрого ответа
timeout: 60000, // Увеличиваем таймаут до 60 секунд
options: {
num_ctx: 512, // Еще больше уменьшаем контекст для экономии памяти
num_thread: 12, // Увеличиваем количество потоков еще больше
num_gpu: 1,
num_gqa: 8,
rope_freq_base: 1000000,
rope_freq_scale: 0.5,
repeat_penalty: 1.1, // Добавляем штраф за повторения
top_k: 20, // Еще больше ограничиваем выбор токенов
top_p: 0.8, // Уменьшаем nucleus sampling
temperature: 0.1, // Еще больше уменьшаем для более предсказуемых ответов
}
});
}
@@ -52,6 +110,24 @@ class AIAssistant {
try {
console.log('getResponse called with:', { message, language, history, systemPrompt, rules });
// Очищаем старый кэш
this.cleanupCache();
// Проверяем здоровье модели
const isHealthy = await this.checkModelHealth();
if (!isHealthy) {
console.warn('Model is not healthy, returning fallback response');
return 'Извините, модель временно недоступна. Пожалуйста, попробуйте позже.';
}
// Создаем ключ кэша
const cacheKey = JSON.stringify({ message, language, systemPrompt, rules });
const cached = responseCache.get(cacheKey);
if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) {
console.log('Returning cached response');
return cached.response;
}
// Определяем язык, если не указан явно
const detectedLanguage = language === 'auto' ? this.detectLanguage(message) : language;
console.log('Detected language:', detectedLanguage);
@@ -77,28 +153,39 @@ class AIAssistant {
// Добавляем текущее сообщение пользователя
messages.push({ role: 'user', content: message });
let response = null;
// Пробуем прямой API запрос (OpenAI-совместимый endpoint)
try {
console.log('Trying direct API request...');
const response = await this.fallbackRequestOpenAI(messages, detectedLanguage);
response = await this.fallbackRequestOpenAI(messages, detectedLanguage, fullSystemPrompt);
console.log('Direct API response received:', response);
return response;
} catch (error) {
console.error('Error in direct API request:', error);
// Если прямой запрос не удался, пробуем через ChatOllama (склеиваем сообщения в текст)
const chat = this.createChat(detectedLanguage, fullSystemPrompt);
try {
const prompt = messages.map(m => `${m.role === 'user' ? 'Пользователь' : m.role === 'assistant' ? 'Ассистент' : 'Система'}: ${m.content}`).join('\n');
console.log('Sending request to ChatOllama...');
const chatResponse = await chat.invoke(prompt);
console.log('ChatOllama response:', chatResponse);
response = chatResponse.content;
} catch (chatError) {
console.error('Error using ChatOllama:', chatError);
throw chatError;
}
}
// Если прямой запрос не удался, пробуем через ChatOllama (склеиваем сообщения в текст)
const chat = this.createChat(detectedLanguage);
try {
const prompt = messages.map(m => `${m.role === 'user' ? 'Пользователь' : m.role === 'assistant' ? 'Ассистент' : 'Система'}: ${m.content}`).join('\n');
console.log('Sending request to ChatOllama...');
const response = await chat.invoke(prompt);
console.log('ChatOllama response:', response);
return response.content;
} catch (error) {
console.error('Error using ChatOllama:', error);
throw error;
// Кэшируем ответ
if (response) {
responseCache.set(cacheKey, {
response,
timestamp: Date.now()
});
}
return response;
} catch (error) {
console.error('Error in getResponse:', error);
return 'Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже.';
@@ -106,10 +193,15 @@ class AIAssistant {
}
// Новый метод для OpenAI/Qwen2.5 совместимого endpoint
async fallbackRequestOpenAI(messages, language) {
async fallbackRequestOpenAI(messages, language, systemPrompt = '') {
try {
console.log('Using fallbackRequestOpenAI with:', { messages, language });
console.log('Using fallbackRequestOpenAI with:', { messages, language, systemPrompt });
const model = this.defaultModel;
// Создаем AbortController для таймаута
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 60000); // Увеличиваем до 60 секунд
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -118,11 +210,24 @@ class AIAssistant {
messages,
stream: false,
options: {
temperature: 0.7,
num_predict: 1000,
temperature: 0.3,
num_predict: 200, // Уменьшаем максимальную длину ответа
num_ctx: 1024, // Уменьшаем контекст для экономии памяти
num_thread: 8, // Увеличиваем количество потоков
num_gpu: 1, // Используем GPU если доступен
num_gqa: 8, // Оптимизация для qwen2.5
rope_freq_base: 1000000, // Оптимизация для qwen2.5
rope_freq_scale: 0.5, // Оптимизация для qwen2.5
repeat_penalty: 1.1, // Добавляем штраф за повторения
top_k: 40, // Ограничиваем выбор токенов
top_p: 0.9, // Используем nucleus sampling
},
}),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@@ -134,6 +239,9 @@ class AIAssistant {
return data.response || '';
} catch (error) {
console.error('Error in fallbackRequestOpenAI:', error);
if (error.name === 'AbortError') {
throw new Error('Request timeout - модель не ответила в течение 60 секунд');
}
throw error;
}
}