ваше сообщение коммита
This commit is contained in:
@@ -17,6 +17,8 @@ const { HNSWLib } = require('@langchain/community/vectorstores/hnswlib');
|
||||
const { OpenAIEmbeddings } = require('@langchain/openai');
|
||||
const logger = require('../utils/logger');
|
||||
const fetch = require('node-fetch');
|
||||
const aiCache = require('./ai-cache');
|
||||
const aiQueue = require('./ai-queue');
|
||||
|
||||
// Простой кэш для ответов
|
||||
const responseCache = new Map();
|
||||
@@ -105,6 +107,38 @@ class AIAssistant {
|
||||
return cyrillicPattern.test(message) ? 'ru' : 'en';
|
||||
}
|
||||
|
||||
// Определение приоритета запроса
|
||||
getRequestPriority(message, history, rules) {
|
||||
let priority = 0;
|
||||
|
||||
// Высокий приоритет для коротких запросов
|
||||
if (message.length < 50) {
|
||||
priority += 10;
|
||||
}
|
||||
|
||||
// Приоритет по типу запроса
|
||||
const urgentKeywords = ['срочно', 'urgent', 'важно', 'important', 'помоги', 'help'];
|
||||
if (urgentKeywords.some(keyword => message.toLowerCase().includes(keyword))) {
|
||||
priority += 20;
|
||||
}
|
||||
|
||||
// Приоритет для администраторов
|
||||
if (rules && rules.isAdmin) {
|
||||
priority += 15;
|
||||
}
|
||||
|
||||
// Приоритет по времени ожидания (если есть история)
|
||||
if (history && history.length > 0) {
|
||||
const lastMessage = history[history.length - 1];
|
||||
const timeDiff = Date.now() - (lastMessage.timestamp || Date.now());
|
||||
if (timeDiff > 30000) { // Более 30 секунд ожидания
|
||||
priority += 5;
|
||||
}
|
||||
}
|
||||
|
||||
return priority;
|
||||
}
|
||||
|
||||
// Основной метод для получения ответа
|
||||
async getResponse(message, language = 'auto', history = null, systemPrompt = '', rules = null) {
|
||||
try {
|
||||
@@ -120,14 +154,57 @@ class AIAssistant {
|
||||
return 'Извините, модель временно недоступна. Пожалуйста, попробуйте позже.';
|
||||
}
|
||||
|
||||
// Создаем ключ кэша
|
||||
const cacheKey = JSON.stringify({ message, language, systemPrompt, rules });
|
||||
const cached = responseCache.get(cacheKey);
|
||||
if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) {
|
||||
// Проверяем кэш
|
||||
const cacheKey = aiCache.generateKey([{ role: 'user', content: message }], {
|
||||
temperature: 0.3,
|
||||
maxTokens: 150
|
||||
});
|
||||
const cachedResponse = aiCache.get(cacheKey);
|
||||
if (cachedResponse) {
|
||||
console.log('Returning cached response');
|
||||
return cached.response;
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
// Определяем приоритет запроса
|
||||
const priority = this.getRequestPriority(message, history, rules);
|
||||
|
||||
// Добавляем запрос в очередь
|
||||
const requestId = await aiQueue.addRequest({
|
||||
message,
|
||||
language,
|
||||
history,
|
||||
systemPrompt,
|
||||
rules
|
||||
}, priority);
|
||||
|
||||
// Ждем результат из очереди
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Request timeout - очередь перегружена'));
|
||||
}, 60000); // 60 секунд таймаут для очереди
|
||||
|
||||
const onCompleted = (item) => {
|
||||
if (item.id === requestId) {
|
||||
clearTimeout(timeout);
|
||||
aiQueue.off('completed', onCompleted);
|
||||
aiQueue.off('failed', onFailed);
|
||||
resolve(item.result);
|
||||
}
|
||||
};
|
||||
|
||||
const onFailed = (item) => {
|
||||
if (item.id === requestId) {
|
||||
clearTimeout(timeout);
|
||||
aiQueue.off('completed', onCompleted);
|
||||
aiQueue.off('failed', onFailed);
|
||||
reject(new Error(item.error));
|
||||
}
|
||||
};
|
||||
|
||||
aiQueue.on('completed', onCompleted);
|
||||
aiQueue.on('failed', onFailed);
|
||||
});
|
||||
|
||||
// Определяем язык, если не указан явно
|
||||
const detectedLanguage = language === 'auto' ? this.detectLanguage(message) : language;
|
||||
console.log('Detected language:', detectedLanguage);
|
||||
@@ -179,10 +256,7 @@ class AIAssistant {
|
||||
|
||||
// Кэшируем ответ
|
||||
if (response) {
|
||||
responseCache.set(cacheKey, {
|
||||
response,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
aiCache.set(cacheKey, response);
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -200,7 +274,7 @@ class AIAssistant {
|
||||
|
||||
// Создаем AbortController для таймаута
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 60000); // Увеличиваем до 60 секунд
|
||||
const timeoutId = setTimeout(() => controller.abort(), 120000); // Увеличиваем до 120 секунд
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
@@ -211,16 +285,19 @@ class AIAssistant {
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: 0.3,
|
||||
num_predict: 200, // Уменьшаем максимальную длину ответа
|
||||
num_ctx: 1024, // Уменьшаем контекст для экономии памяти
|
||||
num_thread: 8, // Увеличиваем количество потоков
|
||||
num_predict: 150, // Уменьшаем максимальную длину ответа для ускорения
|
||||
num_ctx: 512, // Уменьшаем контекст для экономии памяти и ускорения
|
||||
num_thread: 12, // Увеличиваем количество потоков для ускорения
|
||||
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
|
||||
top_k: 20, // Уменьшаем выбор токенов для ускорения
|
||||
top_p: 0.8, // Уменьшаем nucleus sampling для ускорения
|
||||
mirostat: 2, // Используем mirostat для стабильности
|
||||
mirostat_tau: 5.0, // Настройка mirostat
|
||||
mirostat_eta: 0.1, // Настройка mirostat
|
||||
},
|
||||
}),
|
||||
signal: controller.signal,
|
||||
@@ -240,7 +317,7 @@ class AIAssistant {
|
||||
} catch (error) {
|
||||
console.error('Error in fallbackRequestOpenAI:', error);
|
||||
if (error.name === 'AbortError') {
|
||||
throw new Error('Request timeout - модель не ответила в течение 60 секунд');
|
||||
throw new Error('Request timeout - модель не ответила в течение 120 секунд');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
77
backend/services/ai-cache.js
Normal file
77
backend/services/ai-cache.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Кэширование AI ответов для ускорения работы
|
||||
*/
|
||||
|
||||
const crypto = require('crypto');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
class AICache {
|
||||
constructor() {
|
||||
this.cache = new Map();
|
||||
this.maxSize = 1000; // Максимальное количество кэшированных ответов
|
||||
this.ttl = 24 * 60 * 60 * 1000; // 24 часа в миллисекундах
|
||||
}
|
||||
|
||||
// Генерация ключа кэша на основе запроса
|
||||
generateKey(messages, options = {}) {
|
||||
const content = JSON.stringify({
|
||||
messages: messages.map(m => ({ role: m.role, content: m.content })),
|
||||
temperature: options.temperature || 0.3,
|
||||
maxTokens: options.num_predict || 150
|
||||
});
|
||||
return crypto.createHash('md5').update(content).digest('hex');
|
||||
}
|
||||
|
||||
// Получение ответа из кэша
|
||||
get(key) {
|
||||
const cached = this.cache.get(key);
|
||||
if (!cached) return null;
|
||||
|
||||
// Проверяем TTL
|
||||
if (Date.now() - cached.timestamp > this.ttl) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.info(`[AICache] Cache hit for key: ${key.substring(0, 8)}...`);
|
||||
return cached.response;
|
||||
}
|
||||
|
||||
// Сохранение ответа в кэш
|
||||
set(key, response) {
|
||||
// Очищаем старые записи если кэш переполнен
|
||||
if (this.cache.size >= this.maxSize) {
|
||||
const oldestKey = this.cache.keys().next().value;
|
||||
this.cache.delete(oldestKey);
|
||||
}
|
||||
|
||||
this.cache.set(key, {
|
||||
response,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
logger.info(`[AICache] Cached response for key: ${key.substring(0, 8)}...`);
|
||||
}
|
||||
|
||||
// Очистка кэша
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
logger.info('[AICache] Cache cleared');
|
||||
}
|
||||
|
||||
// Статистика кэша
|
||||
getStats() {
|
||||
return {
|
||||
size: this.cache.size,
|
||||
maxSize: this.maxSize,
|
||||
hitRate: this.calculateHitRate()
|
||||
};
|
||||
}
|
||||
|
||||
calculateHitRate() {
|
||||
// Простая реализация - в реальности нужно отслеживать hits/misses
|
||||
return this.cache.size / this.maxSize;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AICache();
|
||||
@@ -1,377 +1,148 @@
|
||||
/**
|
||||
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is proprietary and confidential.
|
||||
* Unauthorized copying, modification, or distribution is prohibited.
|
||||
*
|
||||
* For licensing inquiries: info@hb3-accelerator.com
|
||||
* Website: https://hb3-accelerator.com
|
||||
* GitHub: https://github.com/HB3-ACCELERATOR
|
||||
* Очередь для AI запросов с приоритизацией
|
||||
*/
|
||||
|
||||
const Queue = require('better-queue');
|
||||
const EventEmitter = require('events');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
class AIQueueService {
|
||||
class AIQueue extends EventEmitter {
|
||||
constructor() {
|
||||
this.queue = null;
|
||||
this.isInitialized = false;
|
||||
this.userRequestTimes = new Map(); // Добавляем Map для отслеживания запросов пользователей
|
||||
super();
|
||||
this.queue = [];
|
||||
this.processing = false;
|
||||
this.maxConcurrent = 2; // Максимум 2 запроса одновременно
|
||||
this.activeRequests = 0;
|
||||
this.stats = {
|
||||
totalProcessed: 0,
|
||||
totalFailed: 0,
|
||||
averageProcessingTime: 0,
|
||||
currentQueueSize: 0,
|
||||
lastProcessedAt: null
|
||||
total: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
avgResponseTime: 0
|
||||
};
|
||||
|
||||
this.initQueue();
|
||||
}
|
||||
|
||||
initQueue() {
|
||||
try {
|
||||
this.queue = new Queue(this.processTask.bind(this), {
|
||||
// Ограничиваем количество одновременных запросов к Ollama
|
||||
concurrent: 2,
|
||||
|
||||
// Максимальное время выполнения задачи
|
||||
maxTimeout: 180000, // 3 минуты
|
||||
|
||||
// Задержка между задачами для предотвращения перегрузки
|
||||
afterProcessDelay: 1000, // 1 секунда
|
||||
|
||||
// Максимальное количество повторных попыток
|
||||
maxRetries: 2,
|
||||
|
||||
// Задержка между повторными попытками
|
||||
retryDelay: 5000, // 5 секунд
|
||||
|
||||
// Функция определения приоритета
|
||||
priority: this.getTaskPriority.bind(this),
|
||||
|
||||
// Функция фильтрации задач
|
||||
filter: this.filterTask.bind(this),
|
||||
|
||||
// Функция слияния одинаковых задач
|
||||
merge: this.mergeTasks.bind(this),
|
||||
|
||||
// ID задачи для предотвращения дублирования
|
||||
id: 'requestId'
|
||||
});
|
||||
|
||||
this.setupEventListeners();
|
||||
this.isInitialized = true;
|
||||
|
||||
logger.info('[AIQueue] Queue initialized successfully');
|
||||
} catch (error) {
|
||||
logger.error('[AIQueue] Failed to initialize queue:', error);
|
||||
this.isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Определение приоритета задачи
|
||||
getTaskPriority(task, cb) {
|
||||
try {
|
||||
let priority = 1; // Базовый приоритет
|
||||
|
||||
// Высокий приоритет для администраторов
|
||||
if (task.userRole === 'admin') {
|
||||
priority += 10;
|
||||
}
|
||||
|
||||
// Приоритет по типу запроса
|
||||
switch (task.type) {
|
||||
case 'urgent':
|
||||
priority += 20;
|
||||
break;
|
||||
case 'chat':
|
||||
priority += 5;
|
||||
break;
|
||||
case 'analysis':
|
||||
priority += 3;
|
||||
break;
|
||||
case 'generation':
|
||||
priority += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Приоритет по размеру запроса (короткие запросы имеют больший приоритет)
|
||||
if (task.message && task.message.length < 100) {
|
||||
priority += 2;
|
||||
}
|
||||
|
||||
// Приоритет по времени ожидания
|
||||
const waitTime = Date.now() - task.timestamp;
|
||||
if (waitTime > 30000) { // Более 30 секунд ожидания
|
||||
priority += 5;
|
||||
}
|
||||
|
||||
cb(null, priority);
|
||||
} catch (error) {
|
||||
cb(error, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Фильтрация задач
|
||||
filterTask(task, cb) {
|
||||
try {
|
||||
// Проверяем обязательные поля
|
||||
if (!task.message || typeof task.message !== 'string') {
|
||||
return cb('Invalid message format');
|
||||
}
|
||||
|
||||
if (!task.requestId) {
|
||||
return cb('Missing request ID');
|
||||
}
|
||||
|
||||
// Проверяем размер сообщения
|
||||
if (task.message.length > 10000) {
|
||||
return cb('Message too long (max 10000 characters)');
|
||||
}
|
||||
|
||||
// Проверяем частоту запросов от пользователя
|
||||
if (this.isUserRateLimited(task.userId)) {
|
||||
return cb('User rate limit exceeded');
|
||||
}
|
||||
|
||||
cb(null, task);
|
||||
} catch (error) {
|
||||
cb(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Слияние одинаковых задач
|
||||
mergeTasks(oldTask, newTask, cb) {
|
||||
try {
|
||||
// Если это тот же запрос от того же пользователя, обновляем метаданные
|
||||
if (oldTask.message === newTask.message && oldTask.userId === newTask.userId) {
|
||||
oldTask.timestamp = newTask.timestamp;
|
||||
oldTask.retryCount = (oldTask.retryCount || 0) + 1;
|
||||
cb(null, oldTask);
|
||||
} else {
|
||||
cb(null, newTask);
|
||||
}
|
||||
} catch (error) {
|
||||
cb(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка задачи
|
||||
async processTask(task, cb) {
|
||||
const startTime = Date.now();
|
||||
const taskId = task.requestId;
|
||||
|
||||
try {
|
||||
logger.info(`[AIQueue] Processing task ${taskId} for user ${task.userId}`);
|
||||
|
||||
// Импортируем AI сервис
|
||||
const aiAssistant = require('./ai-assistant');
|
||||
const encryptedDb = require('./encryptedDatabaseService');
|
||||
|
||||
// Выполняем AI запрос
|
||||
const result = await aiAssistant.getResponse(
|
||||
task.message,
|
||||
task.language || 'auto',
|
||||
task.history || null,
|
||||
task.systemPrompt || '',
|
||||
task.rules || null
|
||||
);
|
||||
|
||||
const processingTime = Date.now() - startTime;
|
||||
|
||||
// Сохраняем AI ответ в базу данных
|
||||
if (task.conversationId && result) {
|
||||
try {
|
||||
const aiMessage = await encryptedDb.saveData('messages', {
|
||||
conversation_id: task.conversationId,
|
||||
user_id: task.userId,
|
||||
content: result,
|
||||
sender_type: 'assistant',
|
||||
role: 'assistant',
|
||||
channel: 'web'
|
||||
});
|
||||
|
||||
// Получаем расшифрованные данные для WebSocket
|
||||
const decryptedAiMessage = await encryptedDb.getData('messages', { id: aiMessage.id }, 1);
|
||||
if (decryptedAiMessage && decryptedAiMessage[0]) {
|
||||
// Отправляем сообщение через WebSocket
|
||||
const { broadcastChatMessage } = require('../wsHub');
|
||||
broadcastChatMessage(decryptedAiMessage[0], task.userId);
|
||||
}
|
||||
|
||||
logger.info(`[AIQueue] AI response saved for conversation ${task.conversationId}`);
|
||||
} catch (dbError) {
|
||||
logger.error(`[AIQueue] Error saving AI response:`, dbError);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем статистику
|
||||
this.updateStats(true, processingTime);
|
||||
|
||||
logger.info(`[AIQueue] Task ${taskId} completed in ${processingTime}ms`);
|
||||
|
||||
cb(null, {
|
||||
success: true,
|
||||
result,
|
||||
processingTime,
|
||||
taskId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
const processingTime = Date.now() - startTime;
|
||||
|
||||
// Обновляем статистику
|
||||
this.updateStats(false, processingTime);
|
||||
|
||||
logger.error(`[AIQueue] Task ${taskId} failed:`, error);
|
||||
|
||||
cb(null, {
|
||||
success: false,
|
||||
error: error.message,
|
||||
processingTime,
|
||||
taskId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Добавление задачи в очередь
|
||||
addTask(taskData) {
|
||||
if (!this.isInitialized || !this.queue) {
|
||||
throw new Error('Queue is not initialized');
|
||||
}
|
||||
|
||||
const task = {
|
||||
...taskData,
|
||||
// Добавление запроса в очередь
|
||||
async addRequest(request, priority = 0) {
|
||||
const queueItem = {
|
||||
id: Date.now() + Math.random(),
|
||||
request,
|
||||
priority,
|
||||
timestamp: Date.now(),
|
||||
requestId: taskData.requestId || `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const ticket = this.queue.push(task, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
this.queue.push(queueItem);
|
||||
this.queue.sort((a, b) => b.priority - a.priority); // Сортировка по приоритету
|
||||
|
||||
this.stats.total++;
|
||||
logger.info(`[AIQueue] Added request ${queueItem.id} with priority ${priority}`);
|
||||
|
||||
// Запускаем обработку если не запущена
|
||||
if (!this.processing) {
|
||||
this.processQueue();
|
||||
}
|
||||
|
||||
return queueItem.id;
|
||||
}
|
||||
|
||||
// Обработка очереди
|
||||
async processQueue() {
|
||||
if (this.processing || this.activeRequests >= this.maxConcurrent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.processing = true;
|
||||
|
||||
while (this.queue.length > 0 && this.activeRequests < this.maxConcurrent) {
|
||||
const item = this.queue.shift();
|
||||
if (!item) continue;
|
||||
|
||||
this.activeRequests++;
|
||||
item.status = 'processing';
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const result = await this.processRequest(item.request);
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
item.status = 'completed';
|
||||
item.result = result;
|
||||
item.responseTime = responseTime;
|
||||
|
||||
this.stats.completed++;
|
||||
this.updateAvgResponseTime(responseTime);
|
||||
|
||||
logger.info(`[AIQueue] Request ${item.id} completed in ${responseTime}ms`);
|
||||
|
||||
// Эмитим событие о завершении
|
||||
this.emit('completed', item);
|
||||
|
||||
} catch (error) {
|
||||
item.status = 'failed';
|
||||
item.error = error.message;
|
||||
|
||||
this.stats.failed++;
|
||||
logger.error(`[AIQueue] Request ${item.id} failed:`, error.message);
|
||||
|
||||
// Эмитим событие об ошибке
|
||||
this.emit('failed', item);
|
||||
} finally {
|
||||
this.activeRequests--;
|
||||
}
|
||||
}
|
||||
|
||||
this.processing = false;
|
||||
|
||||
// Если в очереди еще есть запросы, продолжаем обработку
|
||||
if (this.queue.length > 0) {
|
||||
setTimeout(() => this.processQueue(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка одного запроса
|
||||
async processRequest(request) {
|
||||
// Прямой вызов AI без очереди
|
||||
const aiAssistant = require('./ai-assistant');
|
||||
|
||||
// Используем прямой метод без очереди
|
||||
const messages = [];
|
||||
if (request.systemPrompt) {
|
||||
messages.push({ role: 'system', content: request.systemPrompt });
|
||||
}
|
||||
if (request.history && Array.isArray(request.history)) {
|
||||
for (const msg of request.history) {
|
||||
if (msg.role && msg.content) {
|
||||
messages.push({ role: msg.role, content: msg.content });
|
||||
}
|
||||
});
|
||||
|
||||
// Добавляем обработчики событий для билета
|
||||
ticket.on('failed', (error) => {
|
||||
logger.error(`[AIQueue] Task ${task.requestId} failed:`, error);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
ticket.on('finish', (result) => {
|
||||
logger.info(`[AIQueue] Task ${task.requestId} finished`);
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Настройка обработчиков событий очереди
|
||||
setupEventListeners() {
|
||||
this.queue.on('task_queued', (taskId) => {
|
||||
logger.info(`[AIQueue] Task ${taskId} queued`);
|
||||
this.stats.currentQueueSize = this.queue.length;
|
||||
});
|
||||
|
||||
this.queue.on('task_started', (taskId) => {
|
||||
logger.info(`[AIQueue] Task ${taskId} started`);
|
||||
});
|
||||
|
||||
this.queue.on('task_finish', (taskId, result) => {
|
||||
logger.info(`[AIQueue] Task ${taskId} finished successfully`);
|
||||
this.stats.lastProcessedAt = new Date();
|
||||
this.stats.currentQueueSize = this.queue.length;
|
||||
});
|
||||
|
||||
this.queue.on('task_failed', (taskId, error) => {
|
||||
logger.error(`[AIQueue] Task ${taskId} failed:`, error);
|
||||
this.stats.currentQueueSize = this.queue.length;
|
||||
});
|
||||
|
||||
this.queue.on('empty', () => {
|
||||
logger.info('[AIQueue] Queue is empty');
|
||||
this.stats.currentQueueSize = 0;
|
||||
});
|
||||
|
||||
this.queue.on('drain', () => {
|
||||
logger.info('[AIQueue] Queue drained');
|
||||
this.stats.currentQueueSize = 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Обновление статистики
|
||||
updateStats(success, processingTime) {
|
||||
this.stats.totalProcessed++;
|
||||
if (!success) {
|
||||
this.stats.totalFailed++;
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем среднее время обработки
|
||||
const totalTime = this.stats.averageProcessingTime * (this.stats.totalProcessed - 1) + processingTime;
|
||||
this.stats.averageProcessingTime = totalTime / this.stats.totalProcessed;
|
||||
messages.push({ role: 'user', content: request.message });
|
||||
|
||||
// Прямой вызов API без очереди
|
||||
return await aiAssistant.fallbackRequestOpenAI(messages, request.language, request.systemPrompt);
|
||||
}
|
||||
|
||||
// Проверка ограничения частоты запросов пользователя
|
||||
isUserRateLimited(userId) {
|
||||
// Простая реализация - можно улучшить с использованием Redis
|
||||
const now = Date.now();
|
||||
const userRequests = this.userRequestTimes.get(userId) || [];
|
||||
|
||||
// Удаляем старые запросы (старше 1 минуты)
|
||||
const recentRequests = userRequests.filter(time => now - time < 60000);
|
||||
|
||||
// Ограничиваем до 10 запросов в минуту
|
||||
if (recentRequests.length >= 10) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Добавляем текущий запрос
|
||||
recentRequests.push(now);
|
||||
this.userRequestTimes.set(userId, recentRequests);
|
||||
|
||||
return false;
|
||||
// Обновление средней скорости ответа
|
||||
updateAvgResponseTime(responseTime) {
|
||||
const total = this.stats.completed;
|
||||
this.stats.avgResponseTime =
|
||||
(this.stats.avgResponseTime * (total - 1) + responseTime) / total;
|
||||
}
|
||||
|
||||
// Получение статистики очереди
|
||||
// Получение статистики
|
||||
getStats() {
|
||||
const queueStats = this.queue ? this.queue.getStats() : {};
|
||||
|
||||
return {
|
||||
...this.stats,
|
||||
queueStats,
|
||||
isInitialized: this.isInitialized,
|
||||
currentQueueSize: this.queue ? this.queue.length : 0,
|
||||
runningTasks: this.queue ? this.queue.running : 0
|
||||
queueLength: this.queue.length,
|
||||
activeRequests: this.activeRequests,
|
||||
processing: this.processing
|
||||
};
|
||||
}
|
||||
|
||||
// Очистка очереди
|
||||
clear() {
|
||||
if (this.queue) {
|
||||
this.queue.destroy();
|
||||
this.initQueue();
|
||||
}
|
||||
}
|
||||
|
||||
// Пауза/возобновление очереди
|
||||
pause() {
|
||||
if (this.queue) {
|
||||
this.queue.pause();
|
||||
logger.info('[AIQueue] Queue paused');
|
||||
}
|
||||
}
|
||||
|
||||
resume() {
|
||||
if (this.queue) {
|
||||
this.queue.resume();
|
||||
logger.info('[AIQueue] Queue resumed');
|
||||
}
|
||||
this.queue = [];
|
||||
logger.info('[AIQueue] Queue cleared');
|
||||
}
|
||||
}
|
||||
|
||||
// Создаем и экспортируем единственный экземпляр
|
||||
const aiQueueService = new AIQueueService();
|
||||
module.exports = aiQueueService;
|
||||
module.exports = new AIQueue();
|
||||
@@ -349,8 +349,8 @@ class EncryptedDataService {
|
||||
params.push(...Object.values(conditions));
|
||||
}
|
||||
|
||||
const { rows } = await db.getQuery()(query, params);
|
||||
return rows;
|
||||
const result = await db.getQuery()(query, params);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
console.error(`❌ Ошибка удаления данных из ${tableName}:`, error);
|
||||
throw error;
|
||||
|
||||
@@ -454,7 +454,7 @@ async function getBot() {
|
||||
const aiResponse = await Promise.race([
|
||||
aiResponsePromise,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('AI response timeout')), 60000)
|
||||
setTimeout(() => reject(new Error('AI response timeout')), 120000)
|
||||
)
|
||||
]);
|
||||
|
||||
@@ -494,7 +494,7 @@ async function getBot() {
|
||||
// Запускаем бота с таймаутом
|
||||
const launchPromise = botInstance.launch();
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error('Telegram bot launch timeout')), 10000); // 10 секунд таймаут
|
||||
setTimeout(() => reject(new Error('Telegram bot launch timeout')), 30000); // 30 секунд таймаут
|
||||
});
|
||||
|
||||
await Promise.race([launchPromise, timeoutPromise]);
|
||||
|
||||
Reference in New Issue
Block a user