import { createEmbeddingClientFromEnvironment, type EmbeddingEnvironment } from './embedding-client'; import { createQdrantRegulatoryIndexFromEnvironment, type QdrantEnvironment } from './qdrant-regulatory-index'; import { RegulatoryFindingRetrievalService } from './regulatory-finding-retrieval'; import { DenseRegulatoryCandidateRetriever, HybridRegulatoryCandidateRetriever, RegulatoryIngestionService, RegulatoryRetrievalPipeline, SparseRegulatoryCandidateRetriever } from './regulatory-rag-service'; export interface RegulatoryRagEnvironment extends EmbeddingEnvironment, QdrantEnvironment { ASSESSOR_RAG_CHUNK_CHARACTERS?: string; ASSESSOR_RAG_EMBEDDING_BATCH_SIZE?: string; } export function createRegulatoryRagRuntime( environment: RegulatoryRagEnvironment = { ASSESSOR_QDRANT_URL: process.env.ASSESSOR_QDRANT_URL, ASSESSOR_QDRANT_API_KEY: process.env.ASSESSOR_QDRANT_API_KEY, ASSESSOR_QDRANT_TIMEOUT_MS: process.env.ASSESSOR_QDRANT_TIMEOUT_MS, ASSESSOR_RAG_EMBEDDING_BASE_URL: process.env.ASSESSOR_RAG_EMBEDDING_BASE_URL, ASSESSOR_RAG_EMBEDDING_MODEL: process.env.ASSESSOR_RAG_EMBEDDING_MODEL, ASSESSOR_RAG_EMBEDDING_API_KEY: process.env.ASSESSOR_RAG_EMBEDDING_API_KEY, ASSESSOR_RAG_EMBEDDING_DIMENSIONS: process.env.ASSESSOR_RAG_EMBEDDING_DIMENSIONS, ASSESSOR_RAG_EMBEDDING_REQUEST_DIMENSIONS: process.env.ASSESSOR_RAG_EMBEDDING_REQUEST_DIMENSIONS, ASSESSOR_RAG_EMBEDDING_TIMEOUT_MS: process.env.ASSESSOR_RAG_EMBEDDING_TIMEOUT_MS, ASSESSOR_RAG_CHUNK_CHARACTERS: process.env.ASSESSOR_RAG_CHUNK_CHARACTERS, ASSESSOR_RAG_EMBEDDING_BATCH_SIZE: process.env.ASSESSOR_RAG_EMBEDDING_BATCH_SIZE } ): { ingestion: RegulatoryIngestionService; retrieval: RegulatoryRetrievalPipeline; findingRetrieval: RegulatoryFindingRetrievalService; index: ReturnType; embeddingModel: string; } { const embeddings = createEmbeddingClientFromEnvironment(environment); const index = createQdrantRegulatoryIndexFromEnvironment(embeddings.dimensions, embeddings.model, environment); const dense = new DenseRegulatoryCandidateRetriever(embeddings, index); const sparse = new SparseRegulatoryCandidateRetriever(index); const retrieval = new RegulatoryRetrievalPipeline(new HybridRegulatoryCandidateRetriever(dense, sparse)); return { ingestion: new RegulatoryIngestionService(embeddings, index, { maximumChunkCharacters: boundedInteger(environment.ASSESSOR_RAG_CHUNK_CHARACTERS, 1_600, 200, 20_000, 'ASSESSOR_RAG_CHUNK_CHARACTERS'), embeddingBatchSize: boundedInteger(environment.ASSESSOR_RAG_EMBEDDING_BATCH_SIZE, 32, 1, 128, 'ASSESSOR_RAG_EMBEDDING_BATCH_SIZE') }), retrieval, findingRetrieval: new RegulatoryFindingRetrievalService(retrieval), index, embeddingModel: embeddings.model }; } function boundedInteger(value: string | undefined, fallback: number, minimum: number, maximum: number, name: string): number { if (!value?.trim()) return fallback; const parsed = Number(value); if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) throw new Error(`${name} must be an integer between ${minimum} and ${maximum}.`); return parsed; }