diff --git a/admin/app/services/ollama_service.ts b/admin/app/services/ollama_service.ts index 8f805be..526407c 100644 --- a/admin/app/services/ollama_service.ts +++ b/admin/app/services/ollama_service.ts @@ -3,7 +3,7 @@ import OpenAI from 'openai' import type { ChatCompletionChunk, ChatCompletionMessageParam } from 'openai/resources/chat/completions.js' import type { Stream } from 'openai/streaming.js' import { NomadOllamaModel } from '../../types/ollama.js' -import { FALLBACK_RECOMMENDED_OLLAMA_MODELS } from '../../constants/ollama.js' +import { EMBEDDING_MODEL_NAME, FALLBACK_RECOMMENDED_OLLAMA_MODELS } from '../../constants/ollama.js' import fs from 'node:fs/promises' import path from 'node:path' import logger from '@adonisjs/core/services/logger' @@ -594,13 +594,6 @@ export class OllamaService { } } - /** - * Embedding model name exempt from the chat-model unload sweep. Hardcoded - * to avoid a circular import with `RagService.EMBEDDING_MODEL`; keep in - * sync with that constant if it ever changes. - */ - private static readonly EMBEDDING_MODEL_EXEMPT = 'nomic-embed-text:v1.5' - /** * Enforces the "at most one chat model resident in VRAM" invariant by firing * `keep_alive: 0` against every currently-loaded model except (a) the @@ -641,7 +634,7 @@ export class OllamaService { } const toUnload = loadedModels.filter( - (name) => name !== OllamaService.EMBEDDING_MODEL_EXEMPT && name !== targetModel + (name) => name !== EMBEDDING_MODEL_NAME && name !== targetModel ) await Promise.all( diff --git a/admin/app/services/rag_service.ts b/admin/app/services/rag_service.ts index 3d09f70..dcbbd25 100644 --- a/admin/app/services/rag_service.ts +++ b/admin/app/services/rag_service.ts @@ -24,6 +24,7 @@ import type { FileWarning, FileWarningsResult, StoredFileInfo } from '../../type import type { KbIngestStateValue } from '../../types/kb_ingest_state.js' import { ZIMExtractionService } from './zim_extraction_service.js' import { ZIM_BATCH_SIZE } from '../../constants/zim_extraction.js' +import { EMBEDDING_MODEL_NAME } from '../../constants/ollama.js' import { ProcessAndEmbedFileResponse, ProcessZIMFileResponse, RAGResult, RerankedRAGResult } from '../../types/rag.js' export type EmbedSingleFileFailureCode = @@ -44,7 +45,6 @@ export class RagService { private resolvedEmbeddingModel: string | null = null public static UPLOADS_STORAGE_PATH = 'storage/kb_uploads' public static CONTENT_COLLECTION_NAME = 'nomad_knowledge_base' - public static EMBEDDING_MODEL = 'nomic-embed-text:v1.5' public static EMBEDDING_DIMENSION = 768 // Nomic Embed Text v1.5 dimension is 768 public static MODEL_CONTEXT_LENGTH = 2048 // nomic-embed-text has 2K token context public static MAX_SAFE_TOKENS = 1600 // Leave buffer for prefix and tokenization variance @@ -286,25 +286,25 @@ export class RagService { if (!this.embeddingModelVerified) { const allModels = await this.ollamaService.getModels(true) const embeddingModel = - allModels.find((model) => model.name === RagService.EMBEDDING_MODEL) ?? + allModels.find((model) => model.name === EMBEDDING_MODEL_NAME) ?? allModels.find((model) => model.name.toLowerCase().includes('nomic-embed-text')) if (!embeddingModel) { try { - const downloadResult = await this.ollamaService.downloadModel(RagService.EMBEDDING_MODEL) + const downloadResult = await this.ollamaService.downloadModel(EMBEDDING_MODEL_NAME) if (!downloadResult.success) { throw new Error(downloadResult.message || 'Unknown error during model download') } } catch (modelError) { logger.error( - `[RAG] Embedding model ${RagService.EMBEDDING_MODEL} not found locally and failed to download:`, + `[RAG] Embedding model ${EMBEDDING_MODEL_NAME} not found locally and failed to download:`, modelError ) this.embeddingModelVerified = false return null } } - this.resolvedEmbeddingModel = embeddingModel?.name ?? RagService.EMBEDDING_MODEL + this.resolvedEmbeddingModel = embeddingModel?.name ?? EMBEDDING_MODEL_NAME this.embeddingModelVerified = true } @@ -361,7 +361,7 @@ export class RagService { logger.debug(`[RAG] Embedding batch ${batchIdx + 1}/${totalBatches} (${batch.length} chunks)`) - const response = await this.ollamaService.embed(this.resolvedEmbeddingModel ?? RagService.EMBEDDING_MODEL, batch) + const response = await this.ollamaService.embed(this.resolvedEmbeddingModel ?? EMBEDDING_MODEL_NAME, batch) embeddings.push(...response.embeddings) @@ -818,12 +818,12 @@ export class RagService { if (!this.embeddingModelVerified) { const allModels = await this.ollamaService.getModels(true) const embeddingModel = - allModels.find((model) => model.name === RagService.EMBEDDING_MODEL) ?? + allModels.find((model) => model.name === EMBEDDING_MODEL_NAME) ?? allModels.find((model) => model.name.toLowerCase().includes('nomic-embed-text')) if (!embeddingModel) { logger.warn( - `[RAG] ${RagService.EMBEDDING_MODEL} not found. Cannot perform similarity search.` + `[RAG] ${EMBEDDING_MODEL_NAME} not found. Cannot perform similarity search.` ) this.embeddingModelVerified = false return [] @@ -855,7 +855,7 @@ export class RagService { return [] } - const response = await this.ollamaService.embed(this.resolvedEmbeddingModel ?? RagService.EMBEDDING_MODEL, [prefixedQuery]) + const response = await this.ollamaService.embed(this.resolvedEmbeddingModel ?? EMBEDDING_MODEL_NAME, [prefixedQuery]) // Perform semantic search with a higher limit to enable reranking const searchLimit = limit * 3 // Get more results for reranking diff --git a/admin/constants/ollama.ts b/admin/constants/ollama.ts index 5581832..23df8f0 100644 --- a/admin/constants/ollama.ts +++ b/admin/constants/ollama.ts @@ -64,6 +64,8 @@ export const FALLBACK_RECOMMENDED_OLLAMA_MODELS: NomadOllamaModel[] = [ export const DEFAULT_QUERY_REWRITE_MODEL = 'qwen2.5:3b' // default to qwen2.5 for query rewriting with good balance of text task performance and resource usage +export const EMBEDDING_MODEL_NAME = 'nomic-embed-text:v1.5' + /** * Adaptive RAG context limits based on model size. * Smaller models get overwhelmed with too much context, so we cap it.