UNPKG

@alvinveroy/codecompass

Version:

AI-powered MCP server for codebase navigation and LLM prompt optimization

146 lines (145 loc) 8.23 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkOllama = checkOllama; exports.checkOllamaModel = checkOllamaModel; exports.generateEmbedding = generateEmbedding; const axios_1 = __importDefault(require("axios")); // axios will be used by OllamaProvider.generateText const config_service_1 = require("./config-service"); const text_utils_1 = require("../utils/text-utils"); const retry_utils_1 = require("../utils/retry-utils"); /** * Check if Ollama server is running and accessible * @returns Promise<boolean> - True if Ollama is accessible, false otherwise */ async function checkOllama() { const host = config_service_1.configService.OLLAMA_HOST; config_service_1.logger.info(`Checking Ollama at ${host}`); try { await (0, retry_utils_1.withRetry)(async () => { const response = await axios_1.default.get(host, { timeout: 10000 }); // Specific timeout for check config_service_1.logger.info(`Ollama status: ${response.status}`); }); return true; } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); config_service_1.logger.error(`Failed to connect to Ollama: ${err.message}`); return false; } } /** * Check if a specific Ollama model is available * @param model - The model name to check * @param isEmbeddingModel - Whether this is an embedding model * @returns Promise<boolean> - True if model is available, false otherwise */ async function checkOllamaModel(model, isEmbeddingModel) { const host = config_service_1.configService.OLLAMA_HOST; config_service_1.logger.info(`Checking Ollama model: ${model}`); try { if (isEmbeddingModel) { const response = await axios_1.default.post(`${host}/api/embeddings`, { model, prompt: "test" }, { timeout: 10000 }); if (response.status === 200 && response.data.embedding) { config_service_1.logger.info(`Ollama model ${model} is available`); return true; } // If not returned true, it means it's not functional for this path config_service_1.logger.warn(`Ollama embedding model ${model} did not return expected data structure.`); return false; // Explicitly return false } else { const response = await axios_1.default.post(// Add type argument here `${host}/api/generate`, { model, prompt: "test", stream: false }, { timeout: 10000 }); if (response.status === 200 && response.data && typeof response.data.response === 'string') { // Check response.data and its type config_service_1.logger.info(`Ollama model ${model} is available`); return true; } // If not returned true, it means it's not functional for this path config_service_1.logger.warn(`Ollama generation model ${model} did not return expected data structure.`); return false; // Explicitly return false } // The logic above now ensures a boolean is always returned from the try block's success paths. // The original fall-through to throw an error is no longer needed here, // as we explicitly return false if the checks fail. } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); const errorDetails = { message: err.message }; if (axios_1.default.isAxiosError(error)) { const axiosError = error; errorDetails.response = axiosError.response ? { status: axiosError.response.status, data: axiosError.response.data, } : undefined; } config_service_1.logger.error(`Ollama model check error for ${model}`, errorDetails); // Instead of throwing, we should return false as per the function's Promise<boolean> signature // The caller can decide if this constitutes a critical failure. return false; } } /** * Generate embeddings for text using Ollama * @param text - The text to generate embeddings for * @returns Promise<number[]> - The embedding vector */ async function generateEmbedding(text) { const processedText = (0, text_utils_1.preprocessText)(text); const truncatedText = processedText.length > config_service_1.configService.MAX_INPUT_LENGTH ? processedText.slice(0, config_service_1.configService.MAX_INPUT_LENGTH) : processedText; try { const host = config_service_1.configService.OLLAMA_HOST; const model = config_service_1.configService.EMBEDDING_MODEL; // Use configured embedding model const response = await (0, retry_utils_1.withRetry)(async () => { config_service_1.logger.info(`Generating embedding for text (length: ${truncatedText.length}, snippet: "${truncatedText.slice(0, 100)}...")`); const res = await axios_1.default.post(`${host}/api/embeddings`, { model: model, prompt: truncatedText }, { timeout: config_service_1.configService.REQUEST_TIMEOUT }); if (!res.data.embedding || !Array.isArray(res.data.embedding)) { throw new Error("Invalid embedding response from Ollama API"); } // Ensure the embedding is an array of numbers and has the expected dimension if (!Array.isArray(res.data.embedding) || res.data.embedding.some(isNaN)) { config_service_1.logger.error(`Ollama API returned an invalid embedding vector (not an array of numbers or contains NaN) for model ${model}. Length: ${res.data.embedding?.length}`); throw new Error(`Ollama API returned an invalid embedding vector (contains NaN or not an array of numbers) for model ${model}.`); } // ADD THIS CHECK: const expectedDimension = config_service_1.configService.EMBEDDING_DIMENSION; if (res.data.embedding.some(v => !isFinite(v))) { config_service_1.logger.error(`Ollama API returned an embedding vector with non-finite values (Infinity or -Infinity) for model ${model}.`); throw new Error(`Ollama API returned an embedding vector with non-finite values for model ${model}.`); } if (res.data.embedding.length !== expectedDimension) { config_service_1.logger.error(`Ollama API returned an embedding vector with unexpected dimension for model ${model}. Expected: ${expectedDimension}, Actual: ${res.data.embedding.length}.`); throw new Error(`Ollama API returned an embedding vector with unexpected dimension. Expected: ${expectedDimension}, Actual: ${res.data.embedding.length}`); } return res.data; // Return the whole OllamaEmbeddingResponse object }); return response.embedding; } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); const errorLogDetails = { message: err.message, inputLength: truncatedText.length, inputSnippet: truncatedText.slice(0, 100), }; if (axios_1.default.isAxiosError(error)) { const axiosError = error; errorLogDetails.code = axiosError.code; errorLogDetails.response = axiosError.response ? { status: axiosError.response.status, data: axiosError.response.data, } : undefined; } config_service_1.logger.error("Ollama embedding error", errorLogDetails); // It's critical to re-throw here so that the calling function (e.g., in repository.ts) // knows that embedding failed and can skip adding a point with a bad/missing vector. // Or, return a specific marker like null/undefined if the caller is designed to handle it. throw new Error(`Failed to generate embedding with Ollama model ${config_service_1.configService.EMBEDDING_MODEL}: ${err.message}`); } }