@retrivora-ai/rag-engine 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ChromaDBProvider-QNI7UCX4.mjs +8 -0
- package/dist/DocumentChunker-cfaMidtA.d.mts +93 -0
- package/dist/DocumentChunker-cfaMidtA.d.ts +93 -0
- package/dist/LLMFactory-JFOY2V4X.mjs +8 -0
- package/dist/MilvusProvider-OO6QGZDZ.mjs +8 -0
- package/dist/MongoDBProvider-WWVJG3WT.mjs +8 -0
- package/dist/PineconeProvider-NJ675H7U.mjs +8 -0
- package/dist/PostgreSQLProvider-ISNMD3BE.mjs +8 -0
- package/dist/QdrantProvider-LJWOIGES.mjs +8 -0
- package/dist/RagConfig-DG_0f8ka.d.mts +145 -0
- package/dist/RagConfig-DG_0f8ka.d.ts +145 -0
- package/dist/RedisProvider-ASONNYBI.mjs +8 -0
- package/dist/WeaviateProvider-PSDCUGC7.mjs +8 -0
- package/dist/chunk-6FODXNUF.mjs +91 -0
- package/dist/chunk-7NXI6ZWX.mjs +89 -0
- package/dist/chunk-AALIF3AL.mjs +91 -0
- package/dist/chunk-BP4U4TT5.mjs +548 -0
- package/dist/chunk-HUGLYKD6.mjs +84 -0
- package/dist/chunk-I4E63NIC.mjs +24 -0
- package/dist/chunk-JI6VD5TJ.mjs +387 -0
- package/dist/chunk-QEYVWVT5.mjs +102 -0
- package/dist/chunk-S5DRHETN.mjs +110 -0
- package/dist/{chunk-ZPXLQR5Q.mjs → chunk-UKDXCXW7.mjs} +5 -23
- package/dist/chunk-V75V7BT2.mjs +117 -0
- package/dist/chunk-VOIWNO5O.mjs +11 -0
- package/dist/chunk-VPNRDXIA.mjs +74 -0
- package/dist/handlers/index.d.mts +9 -33
- package/dist/handlers/index.d.ts +9 -33
- package/dist/handlers/index.js +1477 -962
- package/dist/handlers/index.mjs +4 -2
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/index.mjs +4 -2
- package/dist/server.d.mts +220 -53
- package/dist/server.d.ts +220 -53
- package/dist/server.js +1531 -1099
- package/dist/server.mjs +55 -131
- package/package.json +19 -2
- package/src/app/page.tsx +1 -1
- package/src/components/MessageBubble.tsx +1 -1
- package/src/components/SourceCard.tsx +1 -1
- package/src/config/RagConfig.ts +1 -1
- package/src/config/serverConfig.ts +2 -2
- package/src/core/ConfigResolver.ts +69 -0
- package/src/core/Pipeline.ts +101 -0
- package/src/core/ProviderRegistry.ts +71 -0
- package/src/core/VectorPlugin.ts +52 -0
- package/src/handlers/index.ts +21 -100
- package/src/hooks/useRagChat.ts +1 -1
- package/src/index.ts +2 -6
- package/src/providers/vectordb/BaseVectorProvider.ts +61 -0
- package/src/providers/vectordb/ChromaDBProvider.ts +94 -0
- package/src/providers/vectordb/MilvusProvider.ts +100 -0
- package/src/providers/vectordb/MongoDBProvider.ts +118 -0
- package/src/{vectordb/adapters/PineconeAdapter.ts → providers/vectordb/PineconeProvider.ts} +21 -56
- package/src/{vectordb/adapters/PgVectorAdapter.ts → providers/vectordb/PostgreSQLProvider.ts} +23 -58
- package/src/providers/vectordb/QdrantProvider.ts +95 -0
- package/src/providers/vectordb/RedisProvider.ts +84 -0
- package/src/providers/vectordb/WeaviateProvider.ts +124 -0
- package/src/server.ts +16 -8
- package/src/test-refactor.ts +59 -0
- package/src/types/index.ts +24 -0
- package/src/utils/templateUtils.ts +6 -6
- package/dist/DocumentChunker-BUrIrcPk.d.mts +0 -43
- package/dist/DocumentChunker-BUrIrcPk.d.ts +0 -43
- package/dist/RAGPipeline-BmkIv1HD.d.mts +0 -298
- package/dist/RAGPipeline-BmkIv1HD.d.ts +0 -298
- package/dist/chunk-NVOMLHXW.mjs +0 -1259
- package/src/rag/RAGPipeline.ts +0 -200
- package/src/vectordb/IVectorDB.ts +0 -75
- package/src/vectordb/VectorDBFactory.ts +0 -41
- package/src/vectordb/adapters/MongoDbAdapter.ts +0 -175
- package/src/vectordb/adapters/UniversalVectorDBAdapter.ts +0 -177
package/src/rag/RAGPipeline.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RAGPipeline — the central orchestrator for the RAG workflow.
|
|
3
|
-
*
|
|
4
|
-
* Responsibilities:
|
|
5
|
-
* 1. INGEST: chunk → embed → upsert into vector DB
|
|
6
|
-
* 2. ASK: embed query → retrieve top-K chunks → call LLM with context
|
|
7
|
-
*
|
|
8
|
-
* It is LLM-agnostic and vector-DB-agnostic; concrete implementations are
|
|
9
|
-
* injected via ILLMProvider and IVectorDB.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { IVectorDB, VectorMatch } from '../vectordb/IVectorDB';
|
|
13
|
-
import { ILLMProvider, ChatMessage } from '../llm/ILLMProvider';
|
|
14
|
-
import { VectorDBFactory } from '../vectordb/VectorDBFactory';
|
|
15
|
-
import { LLMFactory } from '../llm/LLMFactory';
|
|
16
|
-
import { DocumentChunker } from './DocumentChunker';
|
|
17
|
-
import { RagConfig } from '../config/RagConfig';
|
|
18
|
-
|
|
19
|
-
export interface IngestDocument {
|
|
20
|
-
/** Unique document ID (used as chunk ID prefix) */
|
|
21
|
-
docId: string;
|
|
22
|
-
/** Full text content of the document */
|
|
23
|
-
content: string;
|
|
24
|
-
/** Optional metadata stored alongside each chunk */
|
|
25
|
-
metadata?: Record<string, unknown>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ChatResponse {
|
|
29
|
-
/** The LLM's answer */
|
|
30
|
-
reply: string;
|
|
31
|
-
/** Retrieved source chunks used to generate the answer */
|
|
32
|
-
sources: VectorMatch[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class RAGPipeline {
|
|
36
|
-
private readonly vectorDB: IVectorDB;
|
|
37
|
-
private readonly llmProvider: ILLMProvider;
|
|
38
|
-
private readonly embeddingProvider: ILLMProvider;
|
|
39
|
-
private readonly chunker: DocumentChunker;
|
|
40
|
-
private readonly config: RagConfig;
|
|
41
|
-
private initialised = false;
|
|
42
|
-
|
|
43
|
-
constructor(
|
|
44
|
-
config: RagConfig,
|
|
45
|
-
adapters?: {
|
|
46
|
-
vectorDB?: IVectorDB;
|
|
47
|
-
llmProvider?: ILLMProvider;
|
|
48
|
-
embeddingProvider?: ILLMProvider;
|
|
49
|
-
}
|
|
50
|
-
) {
|
|
51
|
-
this.config = config;
|
|
52
|
-
this.vectorDB = adapters?.vectorDB ?? VectorDBFactory.create(config.vectorDb);
|
|
53
|
-
this.llmProvider = adapters?.llmProvider ?? LLMFactory.create(config.llm, config.embedding);
|
|
54
|
-
|
|
55
|
-
// Use custom embedding provider if provided
|
|
56
|
-
if (adapters?.embeddingProvider) {
|
|
57
|
-
this.embeddingProvider = adapters.embeddingProvider;
|
|
58
|
-
} else if (config.llm.provider === 'anthropic') {
|
|
59
|
-
// If the LLM provider is Anthropic and no custom embedding is provided, spin up a dedicated embedding provider
|
|
60
|
-
this.embeddingProvider = LLMFactory.createEmbeddingProvider(config.embedding);
|
|
61
|
-
} else {
|
|
62
|
-
this.embeddingProvider = this.llmProvider;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
this.chunker = new DocumentChunker(
|
|
66
|
-
config.rag?.chunkSize ?? 1000,
|
|
67
|
-
config.rag?.chunkOverlap ?? 200
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Lazily initialise the vector DB connection */
|
|
72
|
-
async init(): Promise<void> {
|
|
73
|
-
if (!this.initialised) {
|
|
74
|
-
await this.vectorDB.initialize();
|
|
75
|
-
this.initialised = true;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
// INGEST
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Ingest one or more documents into the vector DB.
|
|
85
|
-
* Automatically chunks, embeds, and upserts each chunk.
|
|
86
|
-
*/
|
|
87
|
-
async ingest(
|
|
88
|
-
documents: IngestDocument[],
|
|
89
|
-
namespace?: string
|
|
90
|
-
): Promise<{ docId: string; chunksIngested: number }[]> {
|
|
91
|
-
await this.init();
|
|
92
|
-
|
|
93
|
-
const ns = namespace ?? this.config.projectId;
|
|
94
|
-
const results: { docId: string; chunksIngested: number }[] = [];
|
|
95
|
-
|
|
96
|
-
for (const doc of documents) {
|
|
97
|
-
const chunks = this.chunker.chunk(doc.content, {
|
|
98
|
-
docId: doc.docId,
|
|
99
|
-
metadata: doc.metadata,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const chunkTexts = chunks.map((c) => c.content);
|
|
103
|
-
const vectors = await this.embeddingProvider.batchEmbed(chunkTexts);
|
|
104
|
-
|
|
105
|
-
const upsertDocs = chunks.map((chunk, i) => ({
|
|
106
|
-
id: chunk.id,
|
|
107
|
-
vector: vectors[i],
|
|
108
|
-
content: chunk.content,
|
|
109
|
-
metadata: chunk.metadata,
|
|
110
|
-
}));
|
|
111
|
-
|
|
112
|
-
await this.vectorDB.batchUpsert(upsertDocs, ns);
|
|
113
|
-
results.push({ docId: doc.docId, chunksIngested: chunks.length });
|
|
114
|
-
console.log(`[RAGPipeline] Ingested ${chunks.length} chunks for doc: ${doc.docId}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return results;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
// ASK
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Run a full RAG query:
|
|
126
|
-
* 1. Embed the user question
|
|
127
|
-
* 2. Retrieve top-K relevant chunks from the vector DB
|
|
128
|
-
* 3. Filter by score threshold
|
|
129
|
-
* 4. Build context string
|
|
130
|
-
* 5. Call the LLM with chat history + context
|
|
131
|
-
*/
|
|
132
|
-
async ask(
|
|
133
|
-
question: string,
|
|
134
|
-
history: ChatMessage[] = [],
|
|
135
|
-
namespace?: string
|
|
136
|
-
): Promise<ChatResponse> {
|
|
137
|
-
await this.init();
|
|
138
|
-
|
|
139
|
-
const ns = namespace ?? this.config.projectId;
|
|
140
|
-
const topK = this.config.rag?.topK ?? 5;
|
|
141
|
-
const scoreThreshold = this.config.rag?.scoreThreshold ?? 0.0;
|
|
142
|
-
|
|
143
|
-
console.log(`[RAGPipeline] Searching Pinecone (Namespace: "${ns}", TopK: ${topK})...`);
|
|
144
|
-
|
|
145
|
-
// 1. Embed the question
|
|
146
|
-
const queryVector = await this.embeddingProvider.embed(question);
|
|
147
|
-
|
|
148
|
-
// 2. Retrieve relevant chunks
|
|
149
|
-
const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
|
|
150
|
-
console.log(`[RAGPipeline] Found ${rawMatches.length} raw matches in Pinecone.`);
|
|
151
|
-
|
|
152
|
-
// 3. Apply score threshold filter
|
|
153
|
-
const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
|
|
154
|
-
console.log(`[RAGPipeline] ${sources.length} sources remaining after threshold filter (min: ${scoreThreshold}).`);
|
|
155
|
-
|
|
156
|
-
// 4. Build context
|
|
157
|
-
const context = sources.length
|
|
158
|
-
? sources.map((m, i) => `[Source ${i + 1}]\n${m.content}`).join('\n\n---\n\n')
|
|
159
|
-
: 'No relevant context found.';
|
|
160
|
-
|
|
161
|
-
// 5. Build message list (append current question)
|
|
162
|
-
const messages: ChatMessage[] = [
|
|
163
|
-
...history,
|
|
164
|
-
{ role: 'user', content: question },
|
|
165
|
-
];
|
|
166
|
-
|
|
167
|
-
// 6. Call the LLM
|
|
168
|
-
const reply = await this.llmProvider.chat(messages, context);
|
|
169
|
-
|
|
170
|
-
return { reply, sources };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ---------------------------------------------------------------------------
|
|
174
|
-
// HEALTH
|
|
175
|
-
// ---------------------------------------------------------------------------
|
|
176
|
-
|
|
177
|
-
async health(): Promise<{ vectorDB: boolean; llm: boolean }> {
|
|
178
|
-
const [vectorDB, llm] = await Promise.all([
|
|
179
|
-
this.vectorDB.ping().catch(() => false),
|
|
180
|
-
this.llmProvider.ping().catch(() => false),
|
|
181
|
-
]);
|
|
182
|
-
return { vectorDB, llm };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ---------------------------------------------------------------------------
|
|
186
|
-
// DATA MANAGEMENT
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
|
-
|
|
189
|
-
async deleteDocument(docId: string, namespace?: string): Promise<void> {
|
|
190
|
-
await this.init();
|
|
191
|
-
const ns = namespace ?? this.config.projectId;
|
|
192
|
-
await this.vectorDB.delete(docId, ns);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async deleteProject(namespace?: string): Promise<void> {
|
|
196
|
-
await this.init();
|
|
197
|
-
const ns = namespace ?? this.config.projectId;
|
|
198
|
-
await this.vectorDB.deleteNamespace(ns);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic Vector Database interface.
|
|
3
|
-
* Any adapter (Pinecone, pgVector, REST, Chroma, Qdrant, …) must implement this.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface VectorMatch {
|
|
7
|
-
/** Unique identifier of the stored chunk */
|
|
8
|
-
id: string;
|
|
9
|
-
/** Cosine or dot-product similarity score (0–1) */
|
|
10
|
-
score: number;
|
|
11
|
-
/** The original text content of the chunk */
|
|
12
|
-
content: string;
|
|
13
|
-
/** Arbitrary metadata attached at upsert time */
|
|
14
|
-
metadata?: Record<string, unknown>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface UpsertDocument {
|
|
18
|
-
id: string;
|
|
19
|
-
vector: number[];
|
|
20
|
-
content: string;
|
|
21
|
-
metadata?: Record<string, unknown>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface IVectorDB {
|
|
25
|
-
/**
|
|
26
|
-
* Initialise the connection (create tables, verify index, etc.)
|
|
27
|
-
* Called once during RAGPipeline construction.
|
|
28
|
-
*/
|
|
29
|
-
initialize(): Promise<void>;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Insert or update a single vector + content pair.
|
|
33
|
-
* @param namespace – optional project/tenant namespace
|
|
34
|
-
*/
|
|
35
|
-
upsert(doc: UpsertDocument, namespace?: string): Promise<void>;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Batch upsert for efficient ingestion of many documents.
|
|
39
|
-
*/
|
|
40
|
-
batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void>;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Find the top-K most similar vectors to the query vector.
|
|
44
|
-
* @param vector – embedding of the user query
|
|
45
|
-
* @param topK – number of results to return
|
|
46
|
-
* @param namespace – optional project/tenant namespace
|
|
47
|
-
* @param filter – optional metadata filter (provider-specific shape)
|
|
48
|
-
*/
|
|
49
|
-
query(
|
|
50
|
-
vector: number[],
|
|
51
|
-
topK: number,
|
|
52
|
-
namespace?: string,
|
|
53
|
-
filter?: Record<string, unknown>
|
|
54
|
-
): Promise<VectorMatch[]>;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Delete a stored vector by ID.
|
|
58
|
-
*/
|
|
59
|
-
delete(id: string, namespace?: string): Promise<void>;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Delete all vectors in a namespace (full data reset for a project).
|
|
63
|
-
*/
|
|
64
|
-
deleteNamespace(namespace: string): Promise<void>;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Check if the underlying DB is reachable.
|
|
68
|
-
*/
|
|
69
|
-
ping(): Promise<boolean>;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Gracefully close the connection.
|
|
73
|
-
*/
|
|
74
|
-
disconnect(): Promise<void>;
|
|
75
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VectorDBFactory — instantiates the correct IVectorDB adapter
|
|
3
|
-
* based on the VectorDBConfig.provider value.
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* const db = VectorDBFactory.create(config.vectorDb);
|
|
7
|
-
* await db.initialize();
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { IVectorDB } from './IVectorDB';
|
|
11
|
-
import { VectorDBConfig } from '../config/RagConfig';
|
|
12
|
-
import { PineconeAdapter } from './adapters/PineconeAdapter';
|
|
13
|
-
import { PgVectorAdapter } from './adapters/PgVectorAdapter';
|
|
14
|
-
import { UniversalVectorDBAdapter } from './adapters/UniversalVectorDBAdapter';
|
|
15
|
-
import { MongoDbAdapter } from './adapters/MongoDbAdapter';
|
|
16
|
-
|
|
17
|
-
export class VectorDBFactory {
|
|
18
|
-
static create(config: VectorDBConfig): IVectorDB {
|
|
19
|
-
switch (config.provider) {
|
|
20
|
-
case 'pinecone':
|
|
21
|
-
return new PineconeAdapter(config);
|
|
22
|
-
case 'pgvector':
|
|
23
|
-
return new PgVectorAdapter(config);
|
|
24
|
-
case 'mongodb':
|
|
25
|
-
return new MongoDbAdapter(config.options);
|
|
26
|
-
case 'rest':
|
|
27
|
-
case 'universal_rest':
|
|
28
|
-
case 'custom':
|
|
29
|
-
return new UniversalVectorDBAdapter(config);
|
|
30
|
-
default:
|
|
31
|
-
// Fallback to Universal Adapter if baseUrl is present in options
|
|
32
|
-
if ((config.options as Record<string, unknown>)?.baseUrl) {
|
|
33
|
-
return new UniversalVectorDBAdapter(config);
|
|
34
|
-
}
|
|
35
|
-
throw new Error(
|
|
36
|
-
`[VectorDBFactory] Unknown provider "${(config as VectorDBConfig).provider}". ` +
|
|
37
|
-
`Supported: pinecone | pgvector | mongodb | rest`
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { MongoClient, Collection, Db } from 'mongodb';
|
|
2
|
-
import { IVectorDB, UpsertDocument, VectorMatch } from '../IVectorDB';
|
|
3
|
-
|
|
4
|
-
interface MongoDoc {
|
|
5
|
-
_id: string;
|
|
6
|
-
[key: string]: unknown;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* MongoDB Adapter for Atlas Vector Search.
|
|
11
|
-
*
|
|
12
|
-
* Requirements:
|
|
13
|
-
* 1. A MongoDB Atlas cluster (v6.0.11+, v7.0.2+).
|
|
14
|
-
* 2. A Vector Search Index created on the collection.
|
|
15
|
-
*
|
|
16
|
-
* Config Options:
|
|
17
|
-
* - uri: string (Required)
|
|
18
|
-
* - database: string (Required)
|
|
19
|
-
* - collection: string (Required)
|
|
20
|
-
* - indexName: string (Default: 'vector_index')
|
|
21
|
-
* - embeddingKey: string (Default: 'embedding')
|
|
22
|
-
* - contentKey: string (Default: 'content')
|
|
23
|
-
* - metadataKey: string (Default: 'metadata')
|
|
24
|
-
*/
|
|
25
|
-
export class MongoDbAdapter implements IVectorDB {
|
|
26
|
-
private client: MongoClient;
|
|
27
|
-
private db?: Db;
|
|
28
|
-
private collection?: Collection<MongoDoc>;
|
|
29
|
-
private uri: string;
|
|
30
|
-
private dbName: string;
|
|
31
|
-
private collectionName: string;
|
|
32
|
-
private indexName: string;
|
|
33
|
-
private embeddingKey: string;
|
|
34
|
-
private contentKey: string;
|
|
35
|
-
private metadataKey: string;
|
|
36
|
-
|
|
37
|
-
constructor(options: Record<string, unknown>) {
|
|
38
|
-
this.uri = options.uri as string;
|
|
39
|
-
this.dbName = options.database as string;
|
|
40
|
-
this.collectionName = options.collection as string;
|
|
41
|
-
this.indexName = (options.indexName as string) || 'vector_index';
|
|
42
|
-
this.embeddingKey = (options.embeddingKey as string) || 'embedding';
|
|
43
|
-
this.contentKey = (options.contentKey as string) || 'content';
|
|
44
|
-
this.metadataKey = (options.metadataKey as string) || 'metadata';
|
|
45
|
-
|
|
46
|
-
if (!this.uri || !this.dbName || !this.collectionName) {
|
|
47
|
-
throw new Error('MongoDbAdapter requires uri, database, and collection options.');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
this.client = new MongoClient(this.uri);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async initialize(): Promise<void> {
|
|
54
|
-
await this.client.connect();
|
|
55
|
-
this.db = this.client.db(this.dbName);
|
|
56
|
-
this.collection = this.db.collection(this.collectionName);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async upsert(doc: UpsertDocument, namespace?: string): Promise<void> {
|
|
60
|
-
if (!this.collection) await this.initialize();
|
|
61
|
-
|
|
62
|
-
const document = {
|
|
63
|
-
_id: doc.id as string,
|
|
64
|
-
[this.embeddingKey]: doc.vector,
|
|
65
|
-
[this.contentKey]: doc.content,
|
|
66
|
-
[this.metadataKey]: doc.metadata || {},
|
|
67
|
-
...(namespace ? { namespace } : {}),
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
await this.collection!.updateOne(
|
|
71
|
-
{ _id: doc.id as string },
|
|
72
|
-
{ $set: document },
|
|
73
|
-
{ upsert: true }
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
78
|
-
if (!this.collection) await this.initialize();
|
|
79
|
-
|
|
80
|
-
const operations = docs.map((doc) => ({
|
|
81
|
-
updateOne: {
|
|
82
|
-
filter: { _id: doc.id as string },
|
|
83
|
-
update: {
|
|
84
|
-
$set: {
|
|
85
|
-
_id: doc.id as string,
|
|
86
|
-
[this.embeddingKey]: doc.vector,
|
|
87
|
-
[this.contentKey]: doc.content,
|
|
88
|
-
[this.metadataKey]: doc.metadata || {},
|
|
89
|
-
...(namespace ? { namespace } : {}),
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
upsert: true,
|
|
93
|
-
},
|
|
94
|
-
}));
|
|
95
|
-
|
|
96
|
-
await this.collection!.bulkWrite(operations);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async query(
|
|
100
|
-
vector: number[],
|
|
101
|
-
topK: number,
|
|
102
|
-
namespace?: string,
|
|
103
|
-
filter?: Record<string, unknown>
|
|
104
|
-
): Promise<VectorMatch[]> {
|
|
105
|
-
if (!this.collection) await this.initialize();
|
|
106
|
-
|
|
107
|
-
const pipeline: Record<string, unknown>[] = [
|
|
108
|
-
{
|
|
109
|
-
$vectorSearch: {
|
|
110
|
-
index: this.indexName,
|
|
111
|
-
path: this.embeddingKey,
|
|
112
|
-
queryVector: vector,
|
|
113
|
-
numCandidates: Math.max(topK * 10, 100),
|
|
114
|
-
limit: topK,
|
|
115
|
-
...(filter || namespace
|
|
116
|
-
? {
|
|
117
|
-
filter: {
|
|
118
|
-
...(filter || {}),
|
|
119
|
-
...(namespace ? { namespace } : {}),
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
: {}),
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
$project: {
|
|
127
|
-
score: { $meta: 'vectorSearchScore' },
|
|
128
|
-
content: `$${this.contentKey}`,
|
|
129
|
-
metadata: `$${this.metadataKey}`,
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
];
|
|
133
|
-
|
|
134
|
-
const results = await this.collection!.aggregate(pipeline).toArray();
|
|
135
|
-
|
|
136
|
-
return (results as unknown as MongoDoc[]).map((res) => ({
|
|
137
|
-
id: res._id.toString(),
|
|
138
|
-
score: res.score as number,
|
|
139
|
-
content: res.content as string,
|
|
140
|
-
metadata: res.metadata as Record<string, unknown>,
|
|
141
|
-
}));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async delete(id: string, namespace?: string): Promise<void> {
|
|
145
|
-
if (!this.collection) await this.initialize();
|
|
146
|
-
const query = { _id: id, ...(namespace ? { namespace } : {}) };
|
|
147
|
-
await this.collection!.deleteOne(query as unknown as MongoDoc);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async deleteNamespace(namespace: string): Promise<void> {
|
|
151
|
-
if (!this.collection) await this.initialize();
|
|
152
|
-
await this.collection!.deleteMany({ namespace });
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async ping(): Promise<boolean> {
|
|
156
|
-
try {
|
|
157
|
-
if (!this.collection) await this.initialize();
|
|
158
|
-
await this.db!.command({ ping: 1 });
|
|
159
|
-
return true;
|
|
160
|
-
} catch (err) {
|
|
161
|
-
console.error('[MongoDbAdapter] Ping failed:', err);
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async disconnect(): Promise<void> {
|
|
167
|
-
try {
|
|
168
|
-
await this.client.close();
|
|
169
|
-
this.db = undefined;
|
|
170
|
-
this.collection = undefined;
|
|
171
|
-
} catch (err) {
|
|
172
|
-
console.error('[MongoDbAdapter] Failed to disconnect:', err);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import axios, { AxiosInstance } from 'axios';
|
|
2
|
-
import { IVectorDB, VectorMatch, UpsertDocument } from '../IVectorDB';
|
|
3
|
-
import { VectorDBConfig } from '../../config/RagConfig';
|
|
4
|
-
import { resolvePath, buildPayload } from '../../utils/templateUtils';
|
|
5
|
-
import { VECTOR_PROFILES } from '../../config/UniversalProfiles';
|
|
6
|
-
|
|
7
|
-
interface UniversalVectorDBOptions {
|
|
8
|
-
baseUrl?: string;
|
|
9
|
-
headers?: Record<string, string>;
|
|
10
|
-
queryPath?: string;
|
|
11
|
-
upsertPath?: string;
|
|
12
|
-
deletePath?: string;
|
|
13
|
-
deleteNamespacePath?: string;
|
|
14
|
-
pingPath?: string;
|
|
15
|
-
queryPayloadTemplate?: string;
|
|
16
|
-
upsertPayloadTemplate?: string;
|
|
17
|
-
batchUpsertPayloadTemplate?: string;
|
|
18
|
-
responseExtractPath?: string;
|
|
19
|
-
idPath?: string;
|
|
20
|
-
scorePath?: string;
|
|
21
|
-
contentPath?: string;
|
|
22
|
-
metadataPath?: string;
|
|
23
|
-
timeout?: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class UniversalVectorDBAdapter implements IVectorDB {
|
|
27
|
-
private readonly http: AxiosInstance;
|
|
28
|
-
private readonly indexName: string;
|
|
29
|
-
private readonly opts: UniversalVectorDBOptions;
|
|
30
|
-
|
|
31
|
-
constructor(config: VectorDBConfig) {
|
|
32
|
-
this.indexName = config.indexName;
|
|
33
|
-
|
|
34
|
-
// Merge profile defaults if specified
|
|
35
|
-
const options = (config.options as Record<string, unknown>) ?? {};
|
|
36
|
-
let profile: Record<string, unknown> = {};
|
|
37
|
-
|
|
38
|
-
if (typeof options.profile === 'string') {
|
|
39
|
-
profile = (VECTOR_PROFILES as Record<string, Record<string, unknown>>)[options.profile] || {};
|
|
40
|
-
} else if (typeof options.profile === 'object' && options.profile !== null) {
|
|
41
|
-
profile = options.profile as Record<string, unknown>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
this.opts = { ...profile, ...(config.options ?? {}) } as UniversalVectorDBOptions;
|
|
45
|
-
|
|
46
|
-
if (!this.opts.baseUrl) {
|
|
47
|
-
throw new Error('[UniversalVectorDBAdapter] options.baseUrl is required');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
this.http = axios.create({
|
|
51
|
-
baseURL: this.opts.baseUrl,
|
|
52
|
-
headers: {
|
|
53
|
-
'Content-Type': 'application/json',
|
|
54
|
-
...(this.opts.headers || {}),
|
|
55
|
-
},
|
|
56
|
-
timeout: this.opts.timeout ?? 30_000,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async initialize(): Promise<void> {
|
|
61
|
-
if (this.opts.pingPath) {
|
|
62
|
-
const ok = await this.ping();
|
|
63
|
-
if (!ok) {
|
|
64
|
-
throw new Error(`[UniversalVectorDBAdapter] Could not reach health endpoint: ${this.opts.pingPath}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async upsert(doc: UpsertDocument, namespace?: string): Promise<void> {
|
|
70
|
-
const path = this.opts.upsertPath ?? '/upsert';
|
|
71
|
-
|
|
72
|
-
if (this.opts.upsertPayloadTemplate) {
|
|
73
|
-
const payload = buildPayload(this.opts.upsertPayloadTemplate, {
|
|
74
|
-
id: doc.id,
|
|
75
|
-
vector: doc.vector,
|
|
76
|
-
content: doc.content,
|
|
77
|
-
metadata: doc.metadata ?? {},
|
|
78
|
-
namespace: namespace ?? '',
|
|
79
|
-
index: this.indexName,
|
|
80
|
-
});
|
|
81
|
-
await this.http.post(path, payload);
|
|
82
|
-
} else {
|
|
83
|
-
// Fallback to standard generic shape
|
|
84
|
-
await this.http.post(path, {
|
|
85
|
-
index: this.indexName,
|
|
86
|
-
namespace,
|
|
87
|
-
...doc,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
93
|
-
const path = this.opts.upsertPath ?? '/upsert';
|
|
94
|
-
|
|
95
|
-
// For batch upsert with templating, if the user provided batchUpsertPayloadTemplate
|
|
96
|
-
if (this.opts.batchUpsertPayloadTemplate) {
|
|
97
|
-
const payload = buildPayload(this.opts.batchUpsertPayloadTemplate, {
|
|
98
|
-
vectors: docs,
|
|
99
|
-
namespace: namespace ?? '',
|
|
100
|
-
index: this.indexName,
|
|
101
|
-
});
|
|
102
|
-
await this.http.post(path, payload);
|
|
103
|
-
} else {
|
|
104
|
-
// Fallback to standard loop or generic shape
|
|
105
|
-
await Promise.all(docs.map((doc) => this.upsert(doc, namespace)));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async query(
|
|
110
|
-
vector: number[],
|
|
111
|
-
topK: number,
|
|
112
|
-
namespace?: string,
|
|
113
|
-
filter?: Record<string, unknown>
|
|
114
|
-
): Promise<VectorMatch[]> {
|
|
115
|
-
const path = this.opts.queryPath ?? '/query';
|
|
116
|
-
|
|
117
|
-
let payload: unknown;
|
|
118
|
-
if (this.opts.queryPayloadTemplate) {
|
|
119
|
-
payload = buildPayload(this.opts.queryPayloadTemplate, {
|
|
120
|
-
vector,
|
|
121
|
-
topK,
|
|
122
|
-
namespace: namespace ?? '',
|
|
123
|
-
index: this.indexName,
|
|
124
|
-
filter: filter ?? {},
|
|
125
|
-
});
|
|
126
|
-
} else {
|
|
127
|
-
payload = { index: this.indexName, namespace, vector, topK, filter };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const { data } = await this.http.post(path, payload);
|
|
131
|
-
|
|
132
|
-
const extractPath = this.opts.responseExtractPath ?? 'matches';
|
|
133
|
-
const matchesRaw = resolvePath(data, extractPath);
|
|
134
|
-
|
|
135
|
-
if (!Array.isArray(matchesRaw)) {
|
|
136
|
-
throw new Error(`[UniversalVectorDBAdapter] Expected an array at responseExtractPath '${extractPath}', but got ${typeof matchesRaw}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const idPath = this.opts.idPath ?? 'id';
|
|
140
|
-
const scorePath = this.opts.scorePath ?? 'score';
|
|
141
|
-
const contentPath = this.opts.contentPath ?? 'content';
|
|
142
|
-
const metadataPath = this.opts.metadataPath ?? 'metadata';
|
|
143
|
-
|
|
144
|
-
return matchesRaw.map((match) => ({
|
|
145
|
-
id: String(resolvePath(match, idPath) || ''),
|
|
146
|
-
score: Number(resolvePath(match, scorePath) || 0),
|
|
147
|
-
content: String(resolvePath(match, contentPath) || ''),
|
|
148
|
-
metadata: (resolvePath(match, metadataPath) as Record<string, unknown> | undefined) || {},
|
|
149
|
-
}));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async delete(id: string, namespace?: string): Promise<void> {
|
|
153
|
-
const path = this.opts.deletePath ?? '/delete';
|
|
154
|
-
await this.http.post(path, { index: this.indexName, namespace, id });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async deleteNamespace(namespace: string): Promise<void> {
|
|
158
|
-
const path = this.opts.deleteNamespacePath ?? '/delete-namespace';
|
|
159
|
-
await this.http.post(path, { index: this.indexName, namespace });
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async ping(): Promise<boolean> {
|
|
163
|
-
try {
|
|
164
|
-
if (this.opts.pingPath) {
|
|
165
|
-
await this.http.get(this.opts.pingPath);
|
|
166
|
-
}
|
|
167
|
-
return true;
|
|
168
|
-
} catch (err) {
|
|
169
|
-
console.error('[UniversalVectorDBAdapter] Ping failed:', err);
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async disconnect(): Promise<void> {
|
|
175
|
-
// Axios based adapter is stateless per request.
|
|
176
|
-
}
|
|
177
|
-
}
|