/** * 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/VC-HB3-Accelerator */ const logger = require('./logger'); const ollamaConfig = require('../services/ollamaConfig'); /** * Проверяет, загружена ли модель в память через /api/ps * НЕ триггерит загрузку модели (в отличие от /api/generate) * @param {string} modelName - название модели (например: qwen2.5:7b) * @returns {Promise} true если модель в памяти */ async function isModelLoaded(modelName) { try { const axios = require('axios'); const baseUrl = ollamaConfig.getBaseUrl(); // Используем /api/ps - показывает какие модели сейчас в памяти // Этот endpoint НЕ триггерит загрузку модели! const response = await axios.get(`${baseUrl}/api/ps`, { timeout: 3000 }); // Проверяем, есть ли наша модель в списке загруженных if (response.data && response.data.models) { return response.data.models.some(m => { // Сравниваем без тега (qwen2.5 == qwen2.5:7b) const modelBaseName = modelName.split(':')[0]; const loadedBaseName = (m.name || m.model || '').split(':')[0]; return loadedBaseName === modelBaseName; }); } return false; } catch (error) { // API ps может не существовать в старых версиях Ollama // Или модель не загружена return false; } } /** * Ожидание готовности Ollama и загрузки модели в память * Ollama может загружаться до 4 минут при старте Docker контейнера * entrypoint.sh загружает модель qwen2.5:7b в память с keep_alive=24h * * @param {Object} options - Опции ожидания * @param {number} options.maxWaitTime - Максимальное время ожидания в мс (по умолчанию 4 минуты) * @param {number} options.retryInterval - Интервал между попытками в мс (по умолчанию 5 секунд) * @param {boolean} options.required - Обязательно ли ждать Ollama (по умолчанию false) * @returns {Promise} true если Ollama готов, false если таймаут (и required=false) */ async function waitForOllama(options = {}) { const { maxWaitTime = parseInt(process.env.OLLAMA_WAIT_TIME) || 4 * 60 * 1000, retryInterval = parseInt(process.env.OLLAMA_RETRY_INTERVAL) || 5000, required = process.env.OLLAMA_REQUIRED === 'true' || false } = options; const startTime = Date.now(); let attempt = 0; const maxAttempts = Math.ceil(maxWaitTime / retryInterval); logger.info(`[waitForOllama] ⏳ Ожидание готовности Ollama и загрузки модели в память (макс. ${maxWaitTime/1000}с, интервал ${retryInterval/1000}с)...`); while (Date.now() - startTime < maxWaitTime) { attempt++; try { // Шаг 1: Проверяем доступность Ollama API const healthStatus = await ollamaConfig.checkHealth(); if (healthStatus.status === 'ok') { const model = healthStatus.model; // Шаг 2: Проверяем, загружена ли модель в память const modelReady = await isModelLoaded(model); if (modelReady) { const waitedTime = ((Date.now() - startTime) / 1000).toFixed(1); logger.info(`[waitForOllama] ✅ Ollama готов! Модель ${model} загружена в память (ожидание ${waitedTime}с, попытка ${attempt}/${maxAttempts})`); logger.info(`[waitForOllama] 📊 Ollama: ${healthStatus.baseUrl}, доступно моделей: ${healthStatus.availableModels}`); return true; } else { logger.info(`[waitForOllama] ⏳ Ollama API готов, но модель ${model} ещё грузится в память... (попытка ${attempt}/${maxAttempts})`); } } else { logger.warn(`[waitForOllama] ⚠️ Ollama API не готов (попытка ${attempt}/${maxAttempts}): ${healthStatus.error}`); } } catch (error) { logger.warn(`[waitForOllama] ⚠️ Ошибка проверки Ollama (попытка ${attempt}/${maxAttempts}): ${error.message}`); } // Если не последняя попытка - ждем перед следующей if (Date.now() - startTime < maxWaitTime) { const remainingTime = Math.max(0, maxWaitTime - (Date.now() - startTime)); const nextRetry = Math.min(retryInterval, remainingTime); if (nextRetry > 0) { await new Promise(resolve => setTimeout(resolve, nextRetry)); } } } // Таймаут истек const totalWaitTime = ((Date.now() - startTime) / 1000).toFixed(1); if (required) { const error = `Ollama не готов после ${totalWaitTime}с ожидания (${attempt} попыток)`; logger.error(`[waitForOllama] ❌ ${error}`); throw new Error(error); } else { logger.warn(`[waitForOllama] ⚠️ Ollama не готов после ${totalWaitTime}с ожидания (${attempt} попыток). Продолжаем без AI.`); return false; } } /** * Проверка готовности Ollama (одна попытка, без ожидания) * @returns {Promise} true если Ollama готов */ async function isOllamaReady() { try { const healthStatus = await ollamaConfig.checkHealth(); if (healthStatus.status !== 'ok') return false; // Проверяем модель return await isModelLoaded(healthStatus.model); } catch (error) { return false; } } module.exports = { waitForOllama, isOllamaReady };