@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/handlers/index.ts
CHANGED
|
@@ -1,44 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createChatHandler — factory that returns a Next.js App Router POST handler
|
|
3
|
-
* for the RAG chat endpoint.
|
|
4
|
-
*
|
|
5
|
-
* Use this when you want to mount the RAG chat API inside your OWN Next.js app
|
|
6
|
-
* (i.e. you installed @rag/ai-accelerator as a package and want the API route
|
|
7
|
-
* to live at your project's /api/chat).
|
|
8
|
-
*
|
|
9
|
-
* Usage — in your project's `src/app/api/chat/route.ts`:
|
|
10
|
-
*
|
|
11
|
-
* import { createChatHandler } from '@rag/ai-accelerator/handlers';
|
|
12
|
-
* import myConfig from '@/config/ragConfig';
|
|
13
|
-
* export const POST = createChatHandler(myConfig);
|
|
14
|
-
*
|
|
15
|
-
* Or with env-based config:
|
|
16
|
-
*
|
|
17
|
-
* import { createChatHandler } from '@rag/ai-accelerator/handlers';
|
|
18
|
-
* import { getRagConfig } from '@rag/ai-accelerator';
|
|
19
|
-
* export const POST = createChatHandler(getRagConfig());
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
23
|
-
import {
|
|
2
|
+
import { VectorPlugin } from '../core/VectorPlugin';
|
|
24
3
|
import { RagConfig } from '../config/RagConfig';
|
|
25
|
-
import { ChatMessage
|
|
26
|
-
import { IVectorDB } from '../vectordb/IVectorDB';
|
|
4
|
+
import { ChatMessage } from '../llm/ILLMProvider';
|
|
27
5
|
import { DocumentParser } from '../utils/DocumentParser';
|
|
28
6
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export function createChatHandler(config: RagConfig, adapters?: CustomAdapters) {
|
|
36
|
-
let pipeline: RAGPipeline | null = null;
|
|
37
|
-
|
|
38
|
-
function getPipeline(): RAGPipeline {
|
|
39
|
-
if (!pipeline) pipeline = new RAGPipeline(config, adapters);
|
|
40
|
-
return pipeline;
|
|
41
|
-
}
|
|
7
|
+
/**
|
|
8
|
+
* createChatHandler — factory that returns a Next.js App Router POST handler
|
|
9
|
+
*/
|
|
10
|
+
export function createChatHandler(config?: Partial<RagConfig>) {
|
|
11
|
+
const plugin = new VectorPlugin(config);
|
|
42
12
|
|
|
43
13
|
return async function POST(req: NextRequest) {
|
|
44
14
|
try {
|
|
@@ -53,8 +23,7 @@ export function createChatHandler(config: RagConfig, adapters?: CustomAdapters)
|
|
|
53
23
|
return NextResponse.json({ error: 'message is required' }, { status: 400 });
|
|
54
24
|
}
|
|
55
25
|
|
|
56
|
-
const
|
|
57
|
-
const result = await rag.ask(message, history, namespace);
|
|
26
|
+
const result = await plugin.chat(message, history, namespace);
|
|
58
27
|
return NextResponse.json(result);
|
|
59
28
|
} catch (err) {
|
|
60
29
|
const message = err instanceof Error ? err.message : 'Internal server error';
|
|
@@ -65,19 +34,9 @@ export function createChatHandler(config: RagConfig, adapters?: CustomAdapters)
|
|
|
65
34
|
|
|
66
35
|
/**
|
|
67
36
|
* createIngestHandler — factory for the document ingestion endpoint.
|
|
68
|
-
*
|
|
69
|
-
* Usage — in your project's `src/app/api/ingest/route.ts`:
|
|
70
|
-
*
|
|
71
|
-
* import { createIngestHandler } from '@rag/ai-accelerator/handlers';
|
|
72
|
-
* export const POST = createIngestHandler(myConfig, { vectorDB: new MyCustomDB() });
|
|
73
37
|
*/
|
|
74
|
-
export function createIngestHandler(config
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
function getPipeline(): RAGPipeline {
|
|
78
|
-
if (!pipeline) pipeline = new RAGPipeline(config, adapters);
|
|
79
|
-
return pipeline;
|
|
80
|
-
}
|
|
38
|
+
export function createIngestHandler(config?: Partial<RagConfig>) {
|
|
39
|
+
const plugin = new VectorPlugin(config);
|
|
81
40
|
|
|
82
41
|
return async function POST(req: NextRequest) {
|
|
83
42
|
try {
|
|
@@ -88,16 +47,11 @@ export function createIngestHandler(config: RagConfig, adapters?: CustomAdapters
|
|
|
88
47
|
};
|
|
89
48
|
|
|
90
49
|
if (!Array.isArray(documents) || documents.length === 0) {
|
|
91
|
-
return NextResponse.json(
|
|
92
|
-
{ error: 'documents array is required and must not be empty' },
|
|
93
|
-
{ status: 400 }
|
|
94
|
-
);
|
|
50
|
+
return NextResponse.json({ error: 'documents array is required' }, { status: 400 });
|
|
95
51
|
}
|
|
96
52
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const totalChunks = results.reduce((sum, r) => sum + r.chunksIngested, 0);
|
|
100
|
-
return NextResponse.json({ results, totalChunks });
|
|
53
|
+
const results = await plugin.ingest(documents, namespace);
|
|
54
|
+
return NextResponse.json({ results });
|
|
101
55
|
} catch (err) {
|
|
102
56
|
const message = err instanceof Error ? err.message : 'Internal server error';
|
|
103
57
|
return NextResponse.json({ error: message }, { status: 500 });
|
|
@@ -107,56 +61,31 @@ export function createIngestHandler(config: RagConfig, adapters?: CustomAdapters
|
|
|
107
61
|
|
|
108
62
|
/**
|
|
109
63
|
* createHealthHandler — factory for the health-check endpoint.
|
|
110
|
-
*
|
|
111
|
-
* Usage — in your project's `src/app/api/health/route.ts`:
|
|
112
|
-
*
|
|
113
|
-
* import { createHealthHandler } from '@rag/ai-accelerator/handlers';
|
|
114
|
-
* export const GET = createHealthHandler(myConfig, { vectorDB: new MyCustomDB() });
|
|
115
64
|
*/
|
|
116
|
-
export function createHealthHandler(config
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
function getPipeline(): RAGPipeline {
|
|
120
|
-
if (!pipeline) pipeline = new RAGPipeline(config, adapters);
|
|
121
|
-
return pipeline;
|
|
122
|
-
}
|
|
65
|
+
export function createHealthHandler(config?: Partial<RagConfig>) {
|
|
66
|
+
const plugin = new VectorPlugin(config);
|
|
123
67
|
|
|
124
68
|
return async function GET() {
|
|
125
69
|
try {
|
|
126
|
-
const
|
|
127
|
-
const health = await rag.health();
|
|
70
|
+
const health = await plugin.health();
|
|
128
71
|
const status = health.vectorDB && health.llm ? 'ok' : 'degraded';
|
|
129
72
|
return NextResponse.json({
|
|
130
73
|
status,
|
|
131
74
|
...health,
|
|
132
|
-
project: config.projectId,
|
|
133
75
|
timestamp: new Date().toISOString(),
|
|
134
76
|
});
|
|
135
77
|
} catch (err) {
|
|
136
78
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
137
|
-
return NextResponse.json(
|
|
138
|
-
{ status: 'error', error: message, timestamp: new Date().toISOString() },
|
|
139
|
-
{ status: 500 }
|
|
140
|
-
);
|
|
79
|
+
return NextResponse.json({ status: 'error', error: message }, { status: 500 });
|
|
141
80
|
}
|
|
142
81
|
};
|
|
143
82
|
}
|
|
144
83
|
|
|
145
84
|
/**
|
|
146
85
|
* createUploadHandler — factory for the file upload ingestion endpoint.
|
|
147
|
-
*
|
|
148
|
-
* Usage — in your project's `src/app/api/upload/route.ts`:
|
|
149
|
-
*
|
|
150
|
-
* import { createUploadHandler } from '@rag/ai-accelerator/handlers';
|
|
151
|
-
* export const POST = createUploadHandler(myConfig);
|
|
152
86
|
*/
|
|
153
|
-
export function createUploadHandler(config
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
function getPipeline(): RAGPipeline {
|
|
157
|
-
if (!pipeline) pipeline = new RAGPipeline(config, adapters);
|
|
158
|
-
return pipeline;
|
|
159
|
-
}
|
|
87
|
+
export function createUploadHandler(config?: Partial<RagConfig>) {
|
|
88
|
+
const plugin = new VectorPlugin(config);
|
|
160
89
|
|
|
161
90
|
return async function POST(req: NextRequest) {
|
|
162
91
|
try {
|
|
@@ -184,16 +113,8 @@ export function createUploadHandler(config: RagConfig, adapters?: CustomAdapters
|
|
|
184
113
|
})
|
|
185
114
|
);
|
|
186
115
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
const totalChunks = results.reduce((sum, r) => sum + r.chunksIngested, 0);
|
|
190
|
-
|
|
191
|
-
return NextResponse.json({
|
|
192
|
-
message: 'Upload successful',
|
|
193
|
-
filesProcessed: files.length,
|
|
194
|
-
totalChunks,
|
|
195
|
-
results,
|
|
196
|
-
});
|
|
116
|
+
const results = await plugin.ingest(documents, namespace);
|
|
117
|
+
return NextResponse.json({ message: 'Upload successful', results });
|
|
197
118
|
} catch (err) {
|
|
198
119
|
const message = err instanceof Error ? err.message : 'Upload failed';
|
|
199
120
|
return NextResponse.json({ error: message }, { status: 500 });
|
package/src/hooks/useRagChat.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
17
17
|
import axios from 'axios';
|
|
18
|
-
import { VectorMatch } from '@/
|
|
18
|
+
import { VectorMatch } from '@/types';
|
|
19
19
|
import { useStoredMessages } from './useStoredMessages';
|
|
20
20
|
|
|
21
21
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Package entry point — public API exported to consumers of this npm package.
|
|
3
3
|
* This entry point is CLIENT-SAFE and can be imported in React Client Components.
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* import { ChatWidget, ConfigProvider, useRagChat } from '@retrivora-ai/rag-engine';
|
|
7
4
|
*/
|
|
8
5
|
|
|
9
6
|
// ── React Components ─────────────────────────────────────────
|
|
@@ -18,10 +15,9 @@ export { useRagChat } from './hooks/useRagChat';
|
|
|
18
15
|
|
|
19
16
|
// ── Types (Interfaces/Types only, no Node.js deps) ─────────────
|
|
20
17
|
export type { RagConfig, VectorDBConfig, LLMConfig, EmbeddingConfig, UIConfig, RAGConfig } from './config/RagConfig';
|
|
21
|
-
export type {
|
|
18
|
+
export type { VectorMatch, UpsertDocument } from './types';
|
|
22
19
|
export type { ILLMProvider, ChatMessage, ChatOptions, EmbedOptions } from './llm/ILLMProvider';
|
|
23
|
-
export type { IngestDocument, ChatResponse } from './
|
|
20
|
+
export type { IngestDocument, ChatResponse } from './types';
|
|
24
21
|
export type { Chunk, ChunkOptions } from './rag/DocumentChunker';
|
|
25
22
|
export type { ClientConfig } from './components/ConfigProvider';
|
|
26
23
|
export type { RagMessage, UseRagChatOptions, UseRagChatReturn } from './hooks/useRagChat';
|
|
27
|
-
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { VectorDBConfig } from '../../config/RagConfig';
|
|
2
|
+
import { VectorMatch, UpsertDocument } from '../../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* BaseVectorProvider — Abstract base class for all vector database providers.
|
|
6
|
+
* Each provider (Pinecone, Milvus, Redis, etc.) must extend this class.
|
|
7
|
+
*/
|
|
8
|
+
export abstract class BaseVectorProvider {
|
|
9
|
+
protected readonly config: VectorDBConfig;
|
|
10
|
+
protected readonly indexName: string;
|
|
11
|
+
|
|
12
|
+
constructor(config: VectorDBConfig) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.indexName = config.indexName || 'default';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialise the connection (create tables, verify index, etc.)
|
|
19
|
+
*/
|
|
20
|
+
abstract initialize(): Promise<void>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Insert or update a single vector + content pair.
|
|
24
|
+
*/
|
|
25
|
+
abstract upsert(doc: UpsertDocument, namespace?: string): Promise<void>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Batch upsert for efficient ingestion of many documents.
|
|
29
|
+
*/
|
|
30
|
+
abstract batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Find the top-K most similar vectors to the query vector.
|
|
34
|
+
*/
|
|
35
|
+
abstract query(
|
|
36
|
+
vector: number[],
|
|
37
|
+
topK: number,
|
|
38
|
+
namespace?: string,
|
|
39
|
+
filter?: Record<string, unknown>
|
|
40
|
+
): Promise<VectorMatch[]>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Delete a stored vector by ID.
|
|
44
|
+
*/
|
|
45
|
+
abstract delete(id: string, namespace?: string): Promise<void>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Delete all vectors in a namespace (full data reset for a project).
|
|
49
|
+
*/
|
|
50
|
+
abstract deleteNamespace(namespace: string): Promise<void>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if the underlying DB is reachable.
|
|
54
|
+
*/
|
|
55
|
+
abstract ping(): Promise<boolean>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Gracefully close the connection.
|
|
59
|
+
*/
|
|
60
|
+
abstract disconnect(): Promise<void>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import { VectorDBConfig } from '../../config/RagConfig';
|
|
3
|
+
import { BaseVectorProvider } from './BaseVectorProvider';
|
|
4
|
+
import { VectorMatch, UpsertDocument } from '../../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ChromaDBProvider — implementation for ChromaDB using its REST API.
|
|
8
|
+
*/
|
|
9
|
+
export class ChromaDBProvider extends BaseVectorProvider {
|
|
10
|
+
private http: AxiosInstance;
|
|
11
|
+
private collectionId: string = '';
|
|
12
|
+
|
|
13
|
+
constructor(config: VectorDBConfig) {
|
|
14
|
+
super(config);
|
|
15
|
+
const opts = config.options as Record<string, unknown>;
|
|
16
|
+
const baseUrl = opts.baseUrl as string;
|
|
17
|
+
if (!baseUrl) throw new Error('[ChromaDBProvider] baseUrl is required');
|
|
18
|
+
|
|
19
|
+
this.http = axios.create({
|
|
20
|
+
baseURL: baseUrl,
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async initialize(): Promise<void> {
|
|
26
|
+
// Get or create collection
|
|
27
|
+
const { data } = await this.http.get(`/api/v1/collections/${this.indexName}`);
|
|
28
|
+
this.collectionId = data.id;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async upsert(doc: UpsertDocument, namespace?: string): Promise<void> {
|
|
32
|
+
await this.batchUpsert([doc], namespace);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
36
|
+
const payload = {
|
|
37
|
+
ids: docs.map(d => d.id),
|
|
38
|
+
embeddings: docs.map(d => d.vector),
|
|
39
|
+
documents: docs.map(d => d.content),
|
|
40
|
+
metadatas: docs.map(d => ({
|
|
41
|
+
...(d.metadata || {}),
|
|
42
|
+
...(namespace ? { namespace } : {}),
|
|
43
|
+
})),
|
|
44
|
+
};
|
|
45
|
+
await this.http.post(`/api/v1/collections/${this.collectionId}/add`, payload);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
49
|
+
async query(vector: number[], topK: number, namespace?: string, _filter?: Record<string, unknown>): Promise<VectorMatch[]> {
|
|
50
|
+
const payload = {
|
|
51
|
+
query_embeddings: [vector],
|
|
52
|
+
n_results: topK,
|
|
53
|
+
where: namespace ? { namespace: { $eq: namespace } } : undefined,
|
|
54
|
+
};
|
|
55
|
+
const { data } = await this.http.post(`/api/v1/collections/${this.collectionId}/query`, payload);
|
|
56
|
+
|
|
57
|
+
const matches: VectorMatch[] = [];
|
|
58
|
+
if (data.ids && data.ids[0]) {
|
|
59
|
+
for (let i = 0; i < data.ids[0].length; i++) {
|
|
60
|
+
matches.push({
|
|
61
|
+
id: data.ids[0][i],
|
|
62
|
+
score: data.distances ? 1 - data.distances[0][i] : 0,
|
|
63
|
+
content: data.documents ? data.documents[0][i] : '',
|
|
64
|
+
metadata: data.metadatas ? data.metadatas[0][i] : {},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return matches;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async delete(id: string, _namespace?: string): Promise<void> {
|
|
72
|
+
await this.http.post(`/api/v1/collections/${this.collectionId}/delete`, {
|
|
73
|
+
ids: [id],
|
|
74
|
+
where: _namespace ? { namespace: { $eq: _namespace } } : undefined,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async deleteNamespace(_namespace: string): Promise<void> {
|
|
79
|
+
await this.http.post(`/api/v1/collections/${this.collectionId}/delete`, {
|
|
80
|
+
where: { namespace: { $eq: _namespace } },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async ping(): Promise<boolean> {
|
|
85
|
+
try {
|
|
86
|
+
await this.http.get('/api/v1/heartbeat');
|
|
87
|
+
return true;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async disconnect(): Promise<void> {}
|
|
94
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import { VectorDBConfig } from '../../config/RagConfig';
|
|
3
|
+
import { BaseVectorProvider } from './BaseVectorProvider';
|
|
4
|
+
import { VectorMatch, UpsertDocument } from '../../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MilvusProvider — implementation for Milvus using its REST API.
|
|
8
|
+
*/
|
|
9
|
+
export class MilvusProvider extends BaseVectorProvider {
|
|
10
|
+
private http: AxiosInstance;
|
|
11
|
+
|
|
12
|
+
constructor(config: VectorDBConfig) {
|
|
13
|
+
super(config);
|
|
14
|
+
const opts = config.options as Record<string, unknown>;
|
|
15
|
+
const baseUrl = (opts.baseUrl as string) || (opts.uri as string);
|
|
16
|
+
if (!baseUrl) throw new Error('[MilvusProvider] baseUrl/uri is required');
|
|
17
|
+
|
|
18
|
+
this.http = axios.create({
|
|
19
|
+
baseURL: baseUrl,
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
...(opts.headers as Record<string, string> || {}),
|
|
23
|
+
...(opts.apiKey ? { Authorization: `Bearer ${opts.apiKey}` } : {}),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async initialize(): Promise<void> {
|
|
29
|
+
await this.ping();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async upsert(doc: UpsertDocument, namespace?: string): Promise<void> {
|
|
33
|
+
const payload = {
|
|
34
|
+
collectionName: this.indexName,
|
|
35
|
+
data: [{
|
|
36
|
+
vector: doc.vector,
|
|
37
|
+
content: doc.content,
|
|
38
|
+
metadata: doc.metadata || {},
|
|
39
|
+
...(namespace ? { namespace } : {}),
|
|
40
|
+
}],
|
|
41
|
+
};
|
|
42
|
+
await this.http.post('/v1/vector/upsert', payload);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
46
|
+
const payload = {
|
|
47
|
+
collectionName: this.indexName,
|
|
48
|
+
data: docs.map(doc => ({
|
|
49
|
+
vector: doc.vector,
|
|
50
|
+
content: doc.content,
|
|
51
|
+
metadata: doc.metadata || {},
|
|
52
|
+
...(namespace ? { namespace } : {}),
|
|
53
|
+
})),
|
|
54
|
+
};
|
|
55
|
+
await this.http.post('/v1/vector/upsert', payload);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
59
|
+
async query(vector: number[], topK: number, namespace?: string, _filter?: Record<string, unknown>): Promise<VectorMatch[]> {
|
|
60
|
+
const payload = {
|
|
61
|
+
collectionName: this.indexName,
|
|
62
|
+
vector: vector,
|
|
63
|
+
limit: topK,
|
|
64
|
+
filter: namespace ? `namespace == "${namespace}"` : undefined,
|
|
65
|
+
outputFields: ['content', 'metadata'],
|
|
66
|
+
};
|
|
67
|
+
const { data } = await this.http.post('/v1/vector/search', payload);
|
|
68
|
+
return (data.data || []).map((res: Record<string, unknown>) => ({
|
|
69
|
+
id: String(res['id']),
|
|
70
|
+
score: res['distance'] as number,
|
|
71
|
+
content: res['content'] as string,
|
|
72
|
+
metadata: res['metadata'] as Record<string, unknown>,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
77
|
+
async delete(id: string, _namespace?: string): Promise<void> {
|
|
78
|
+
await this.http.post('/v1/vector/delete', {
|
|
79
|
+
collectionName: this.indexName,
|
|
80
|
+
id: id,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
85
|
+
async deleteNamespace(_namespace: string): Promise<void> {
|
|
86
|
+
// Milvus doesn't have a direct "delete namespace" REST API easily
|
|
87
|
+
// usually handled by deleting with a filter
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async ping(): Promise<boolean> {
|
|
91
|
+
try {
|
|
92
|
+
await this.http.get('/v1/health');
|
|
93
|
+
return true;
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async disconnect(): Promise<void> {}
|
|
100
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { MongoClient, Collection, Db } from 'mongodb';
|
|
2
|
+
import { VectorDBConfig } from '../../config/RagConfig';
|
|
3
|
+
import { BaseVectorProvider } from './BaseVectorProvider';
|
|
4
|
+
import { VectorMatch, UpsertDocument } from '../../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MongoDBProvider — MongoDB Atlas Vector Search implementation.
|
|
8
|
+
*/
|
|
9
|
+
export class MongoDBProvider extends BaseVectorProvider {
|
|
10
|
+
private client: MongoClient;
|
|
11
|
+
private db?: Db;
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
private collection?: Collection<any>;
|
|
14
|
+
private dbName: string;
|
|
15
|
+
private collectionName: string;
|
|
16
|
+
private embeddingKey: string;
|
|
17
|
+
private contentKey: string;
|
|
18
|
+
private metadataKey: string;
|
|
19
|
+
|
|
20
|
+
constructor(config: VectorDBConfig) {
|
|
21
|
+
super(config);
|
|
22
|
+
const opts = config.options as Record<string, unknown>;
|
|
23
|
+
if (!opts.uri || !opts.database || !opts.collection) {
|
|
24
|
+
throw new Error('[MongoDBProvider] options uri, database, and collection are required.');
|
|
25
|
+
}
|
|
26
|
+
this.client = new MongoClient(opts.uri as string);
|
|
27
|
+
this.dbName = opts.database as string;
|
|
28
|
+
this.collectionName = opts.collection as string;
|
|
29
|
+
this.embeddingKey = (opts.embeddingKey as string) || 'embedding';
|
|
30
|
+
this.contentKey = (opts.contentKey as string) || 'content';
|
|
31
|
+
this.metadataKey = (opts.metadataKey as string) || 'metadata';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async initialize(): Promise<void> {
|
|
35
|
+
await this.client.connect();
|
|
36
|
+
this.db = this.client.db(this.dbName);
|
|
37
|
+
this.collection = this.db.collection(this.collectionName);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async upsert(doc: UpsertDocument, namespace?: string): Promise<void> {
|
|
41
|
+
const document = {
|
|
42
|
+
_id: doc.id,
|
|
43
|
+
[this.embeddingKey]: doc.vector,
|
|
44
|
+
[this.contentKey]: doc.content,
|
|
45
|
+
[this.metadataKey]: doc.metadata || {},
|
|
46
|
+
...(namespace ? { namespace } : {}),
|
|
47
|
+
};
|
|
48
|
+
await this.collection!.updateOne({ _id: doc.id }, { $set: document }, { upsert: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
52
|
+
const operations = docs.map((doc) => ({
|
|
53
|
+
updateOne: {
|
|
54
|
+
filter: { _id: doc.id },
|
|
55
|
+
update: {
|
|
56
|
+
$set: {
|
|
57
|
+
[this.embeddingKey]: doc.vector,
|
|
58
|
+
[this.contentKey]: doc.content,
|
|
59
|
+
[this.metadataKey]: doc.metadata || {},
|
|
60
|
+
...(namespace ? { namespace } : {}),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
upsert: true,
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
66
|
+
await this.collection!.bulkWrite(operations);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async query(vector: number[], topK: number, namespace?: string, filter?: Record<string, unknown>): Promise<VectorMatch[]> {
|
|
70
|
+
const pipeline: Record<string, unknown>[] = [
|
|
71
|
+
{
|
|
72
|
+
$vectorSearch: {
|
|
73
|
+
index: this.config.options.indexName || 'vector_index',
|
|
74
|
+
path: this.embeddingKey,
|
|
75
|
+
queryVector: vector,
|
|
76
|
+
numCandidates: Math.max(topK * 10, 100),
|
|
77
|
+
limit: topK,
|
|
78
|
+
...(filter || namespace ? { filter: { ...(filter || {}), ...(namespace ? { namespace } : {}) } } : {}),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
$project: {
|
|
83
|
+
score: { $meta: 'vectorSearchScore' },
|
|
84
|
+
content: `$${this.contentKey}`,
|
|
85
|
+
metadata: `$${this.metadataKey}`,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
const results = await this.collection!.aggregate(pipeline).toArray();
|
|
90
|
+
return results.map((res: Record<string, unknown>) => ({
|
|
91
|
+
id: res['_id']!.toString(),
|
|
92
|
+
score: res['score'] as number,
|
|
93
|
+
content: res['content'] as string,
|
|
94
|
+
metadata: res['metadata'] as Record<string, unknown>,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async delete(id: string, namespace?: string): Promise<void> {
|
|
99
|
+
await this.collection!.deleteOne({ _id: id, ...(namespace ? { namespace } : {}) });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async deleteNamespace(namespace: string): Promise<void> {
|
|
103
|
+
await this.collection!.deleteMany({ namespace });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async ping(): Promise<boolean> {
|
|
107
|
+
try {
|
|
108
|
+
await this.db!.command({ ping: 1 });
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async disconnect(): Promise<void> {
|
|
116
|
+
await this.client.close();
|
|
117
|
+
}
|
|
118
|
+
}
|