@retrivora-ai/rag-engine 0.1.5 → 0.1.7
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/{DocumentChunker-cfaMidtA.d.mts → DocumentChunker-BEyzadsv.d.mts} +2 -2
- package/dist/{DocumentChunker-cfaMidtA.d.ts → DocumentChunker-BEyzadsv.d.ts} +2 -2
- package/dist/{PineconeProvider-NJ675H7U.mjs → PineconeProvider-ZRAFNFEC.mjs} +1 -1
- package/dist/{PostgreSQLProvider-ISNMD3BE.mjs → PostgreSQLProvider-ZNXA67IM.mjs} +1 -1
- package/dist/{QdrantProvider-LJWOIGES.mjs → QdrantProvider-VAED5VA7.mjs} +1 -1
- package/dist/{RagConfig-DG_0f8ka.d.mts → RagConfig-hBGXJmSx.d.mts} +3 -3
- package/dist/{RagConfig-DG_0f8ka.d.ts → RagConfig-hBGXJmSx.d.ts} +3 -3
- package/dist/chunk-7YQWGERZ.mjs +1764 -0
- package/dist/chunk-CWQQHAF6.mjs +157 -0
- package/dist/{chunk-S5DRHETN.mjs → chunk-IUTAZ7QR.mjs} +31 -2
- package/dist/{chunk-AALIF3AL.mjs → chunk-ZM6TYIDH.mjs} +3 -3
- package/dist/handlers/index.d.mts +3 -44
- package/dist/handlers/index.d.ts +3 -44
- package/dist/handlers/index.js +1371 -60
- package/dist/handlers/index.mjs +1 -1
- package/dist/index-Bx182KKn.d.ts +64 -0
- package/dist/index-Ck2pt7-8.d.mts +64 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/server.d.mts +74 -18
- package/dist/server.d.ts +74 -18
- package/dist/server.js +1371 -60
- package/dist/server.mjs +4 -4
- package/package.json +2 -1
- package/src/config/serverConfig.ts +4 -0
- package/src/core/BatchProcessor.ts +347 -0
- package/src/core/ConfigValidator.ts +568 -0
- package/src/core/Pipeline.ts +89 -39
- package/src/core/ProviderHealthCheck.ts +568 -0
- package/src/core/VectorPlugin.ts +49 -8
- package/src/handlers/index.ts +2 -2
- package/src/providers/vectordb/BaseVectorProvider.ts +1 -1
- package/src/providers/vectordb/MilvusProvider.ts +1 -1
- package/src/providers/vectordb/MongoDBProvider.ts +1 -1
- package/src/providers/vectordb/PineconeProvider.ts +4 -4
- package/src/providers/vectordb/PostgreSQLProvider.ts +35 -3
- package/src/providers/vectordb/QdrantProvider.ts +81 -4
- package/src/rag/DocumentChunker.ts +2 -2
- package/src/types/index.ts +3 -3
- package/dist/chunk-6FODXNUF.mjs +0 -91
- package/dist/chunk-BP4U4TT5.mjs +0 -548
|
@@ -31,7 +31,7 @@ export class PineconeProvider extends BaseVectorProvider {
|
|
|
31
31
|
async upsert(doc: UpsertDocument, namespace?: string): Promise<void> {
|
|
32
32
|
await this.index(namespace).upsert({
|
|
33
33
|
records: [{
|
|
34
|
-
id: doc.id,
|
|
34
|
+
id: String(doc.id),
|
|
35
35
|
values: doc.vector,
|
|
36
36
|
metadata: { content: doc.content, ...(doc.metadata ?? {}) } as RecordMetadata,
|
|
37
37
|
}],
|
|
@@ -42,7 +42,7 @@ export class PineconeProvider extends BaseVectorProvider {
|
|
|
42
42
|
const BATCH = 100;
|
|
43
43
|
for (let i = 0; i < docs.length; i += BATCH) {
|
|
44
44
|
const records = docs.slice(i, i + BATCH).map((d) => ({
|
|
45
|
-
id: d.id,
|
|
45
|
+
id: String(d.id),
|
|
46
46
|
values: d.vector,
|
|
47
47
|
metadata: { content: d.content, ...(d.metadata ?? {}) } as RecordMetadata,
|
|
48
48
|
}));
|
|
@@ -65,8 +65,8 @@ export class PineconeProvider extends BaseVectorProvider {
|
|
|
65
65
|
}));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
async delete(id: string, namespace?: string): Promise<void> {
|
|
69
|
-
await this.index(namespace).deleteOne({ id });
|
|
68
|
+
async delete(id: string | number, namespace?: string): Promise<void> {
|
|
69
|
+
await this.index(namespace).deleteOne({ id: String(id) });
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
async deleteNamespace(namespace: string): Promise<void> {
|
|
@@ -60,8 +60,40 @@ export class PostgreSQLProvider extends BaseVectorProvider {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
async batchUpsert(docs: UpsertDocument[], namespace = ''): Promise<void> {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if (docs.length === 0) return;
|
|
64
|
+
|
|
65
|
+
const client = await this.pool.connect();
|
|
66
|
+
try {
|
|
67
|
+
await client.query('BEGIN');
|
|
68
|
+
|
|
69
|
+
const BATCH_SIZE = 50;
|
|
70
|
+
for (let i = 0; i < docs.length; i += BATCH_SIZE) {
|
|
71
|
+
const batch = docs.slice(i, i + BATCH_SIZE);
|
|
72
|
+
const values: unknown[] = [];
|
|
73
|
+
const valuePlaceholders = batch.map((doc, idx) => {
|
|
74
|
+
const offset = idx * 5;
|
|
75
|
+
values.push(doc.id, namespace, doc.content, JSON.stringify(doc.metadata ?? {}), `[${doc.vector.join(',')}]`);
|
|
76
|
+
return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}::vector)`;
|
|
77
|
+
}).join(', ');
|
|
78
|
+
|
|
79
|
+
const query = `
|
|
80
|
+
INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
|
|
81
|
+
VALUES ${valuePlaceholders}
|
|
82
|
+
ON CONFLICT (id) DO UPDATE
|
|
83
|
+
SET namespace = EXCLUDED.namespace,
|
|
84
|
+
content = EXCLUDED.content,
|
|
85
|
+
metadata = EXCLUDED.metadata,
|
|
86
|
+
embedding = EXCLUDED.embedding
|
|
87
|
+
`;
|
|
88
|
+
await client.query(query, values);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await client.query('COMMIT');
|
|
92
|
+
} catch (error) {
|
|
93
|
+
await client.query('ROLLBACK');
|
|
94
|
+
throw error;
|
|
95
|
+
} finally {
|
|
96
|
+
client.release();
|
|
65
97
|
}
|
|
66
98
|
}
|
|
67
99
|
|
|
@@ -99,7 +131,7 @@ export class PostgreSQLProvider extends BaseVectorProvider {
|
|
|
99
131
|
}));
|
|
100
132
|
}
|
|
101
133
|
|
|
102
|
-
async delete(id: string, namespace?: string): Promise<void> {
|
|
134
|
+
async delete(id: string | number, namespace?: string): Promise<void> {
|
|
103
135
|
const where = namespace ? 'WHERE id = $1 AND namespace = $2' : 'WHERE id = $1';
|
|
104
136
|
const params = namespace ? [id, namespace] : [id];
|
|
105
137
|
await this.pool.query(`DELETE FROM ${this.tableName} ${where}`, params);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import crypto from 'crypto';
|
|
2
3
|
import { VectorDBConfig } from '../../config/RagConfig';
|
|
3
4
|
import { BaseVectorProvider } from './BaseVectorProvider';
|
|
4
5
|
import { VectorMatch, UpsertDocument } from '../../types';
|
|
@@ -26,6 +27,67 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
26
27
|
|
|
27
28
|
async initialize(): Promise<void> {
|
|
28
29
|
await this.ping();
|
|
30
|
+
await this.ensureCollection();
|
|
31
|
+
await this.ensureIndex();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ensures the collection exists. Creates it if missing.
|
|
36
|
+
*/
|
|
37
|
+
private async ensureCollection(): Promise<void> {
|
|
38
|
+
try {
|
|
39
|
+
const opts = this.config.options as Record<string, unknown>;
|
|
40
|
+
const dimensions = (opts.dimensions as number) || 1536;
|
|
41
|
+
|
|
42
|
+
await this.http.get(`/collections/${this.indexName}`);
|
|
43
|
+
console.log(`[QdrantProvider] ✅ Collection "${this.indexName}" already exists.`);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (axios.isAxiosError(err) && err.response?.status === 404) {
|
|
46
|
+
const opts = this.config.options as Record<string, unknown>;
|
|
47
|
+
const dimensions = (opts.dimensions as number) || 1536;
|
|
48
|
+
|
|
49
|
+
console.log(`[QdrantProvider] ⏳ Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
|
|
50
|
+
await this.http.put(`/collections/${this.indexName}`, {
|
|
51
|
+
vectors: {
|
|
52
|
+
size: dimensions,
|
|
53
|
+
distance: 'Cosine',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
console.log(`[QdrantProvider] ✅ Created collection "${this.indexName}"`);
|
|
57
|
+
} else {
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Ensures that the 'namespace' field has a keyword index for efficient filtering.
|
|
65
|
+
* Qdrant requires this for search filters to work in many configurations.
|
|
66
|
+
*/
|
|
67
|
+
private async ensureIndex(): Promise<void> {
|
|
68
|
+
try {
|
|
69
|
+
await this.http.put(`/collections/${this.indexName}/index`, {
|
|
70
|
+
field_name: 'namespace',
|
|
71
|
+
field_schema: 'keyword',
|
|
72
|
+
});
|
|
73
|
+
console.log(`[QdrantProvider] ✅ Ensured keyword index for "namespace" on collection "${this.indexName}"`);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
let status: number | undefined;
|
|
76
|
+
let data: unknown;
|
|
77
|
+
|
|
78
|
+
if (axios.isAxiosError(err)) {
|
|
79
|
+
status = err.response?.status;
|
|
80
|
+
data = err.response?.data;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 409 means it already exists, which is fine.
|
|
84
|
+
if (status === 409) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
89
|
+
console.warn(`[QdrantProvider] ⚠️ Could not ensure namespace index (Status: ${status}):`, JSON.stringify(data || errorMessage));
|
|
90
|
+
}
|
|
29
91
|
}
|
|
30
92
|
|
|
31
93
|
async upsert(doc: UpsertDocument, namespace?: string): Promise<void> {
|
|
@@ -35,7 +97,7 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
35
97
|
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
36
98
|
const payload = {
|
|
37
99
|
points: docs.map(doc => ({
|
|
38
|
-
id: doc.id,
|
|
100
|
+
id: this.normalizeId(doc.id),
|
|
39
101
|
vector: doc.vector,
|
|
40
102
|
payload: {
|
|
41
103
|
content: doc.content,
|
|
@@ -67,13 +129,12 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
67
129
|
}
|
|
68
130
|
|
|
69
131
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
70
|
-
async delete(id: string, _namespace?: string): Promise<void> {
|
|
132
|
+
async delete(id: string | number, _namespace?: string): Promise<void> {
|
|
71
133
|
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
72
|
-
points: [id],
|
|
134
|
+
points: [this.normalizeId(id)],
|
|
73
135
|
});
|
|
74
136
|
}
|
|
75
137
|
|
|
76
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
77
138
|
async deleteNamespace(_namespace: string): Promise<void> {
|
|
78
139
|
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
79
140
|
filter: {
|
|
@@ -91,5 +152,21 @@ export class QdrantProvider extends BaseVectorProvider {
|
|
|
91
152
|
}
|
|
92
153
|
}
|
|
93
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
157
|
+
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
158
|
+
*/
|
|
159
|
+
private normalizeId(id: string | number): string | number {
|
|
160
|
+
if (typeof id === 'number') return id;
|
|
161
|
+
|
|
162
|
+
// Check if already a valid UUID
|
|
163
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
164
|
+
if (uuidRegex.test(id)) return id.toLowerCase();
|
|
165
|
+
|
|
166
|
+
// Hash string to deterministic UUID
|
|
167
|
+
const hash = crypto.createHash('md5').update(id).digest('hex');
|
|
168
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
94
171
|
async disconnect(): Promise<void> {}
|
|
95
172
|
}
|
|
@@ -20,7 +20,7 @@ export interface ChunkOptions {
|
|
|
20
20
|
/** Overlap between consecutive chunks in characters (default 200) */
|
|
21
21
|
chunkOverlap?: number;
|
|
22
22
|
/** Source document identifier used as ID prefix */
|
|
23
|
-
docId?: string;
|
|
23
|
+
docId?: string | number;
|
|
24
24
|
/** Extra metadata to attach to every chunk */
|
|
25
25
|
metadata?: Record<string, unknown>;
|
|
26
26
|
}
|
|
@@ -96,7 +96,7 @@ export class DocumentChunker {
|
|
|
96
96
|
* Chunk multiple documents at once.
|
|
97
97
|
*/
|
|
98
98
|
chunkMany(
|
|
99
|
-
documents: Array<{ content: string; docId?: string; metadata?: Record<string, unknown> }>
|
|
99
|
+
documents: Array<{ content: string; docId?: string | number; metadata?: Record<string, unknown> }>
|
|
100
100
|
): Chunk[] {
|
|
101
101
|
return documents.flatMap((doc) =>
|
|
102
102
|
this.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata })
|
package/src/types/index.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
export interface VectorMatch {
|
|
2
|
-
id: string;
|
|
2
|
+
id: string | number;
|
|
3
3
|
score: number;
|
|
4
4
|
content: string;
|
|
5
5
|
metadata?: Record<string, unknown>;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export interface UpsertDocument {
|
|
9
|
-
id: string;
|
|
9
|
+
id: string | number;
|
|
10
10
|
vector: number[];
|
|
11
11
|
content: string;
|
|
12
12
|
metadata?: Record<string, unknown>;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface IngestDocument {
|
|
16
|
-
docId: string;
|
|
16
|
+
docId: string | number;
|
|
17
17
|
content: string;
|
|
18
18
|
metadata?: Record<string, unknown>;
|
|
19
19
|
}
|
package/dist/chunk-6FODXNUF.mjs
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BaseVectorProvider
|
|
3
|
-
} from "./chunk-VOIWNO5O.mjs";
|
|
4
|
-
import {
|
|
5
|
-
__spreadValues
|
|
6
|
-
} from "./chunk-I4E63NIC.mjs";
|
|
7
|
-
|
|
8
|
-
// src/providers/vectordb/QdrantProvider.ts
|
|
9
|
-
import axios from "axios";
|
|
10
|
-
var QdrantProvider = class extends BaseVectorProvider {
|
|
11
|
-
constructor(config) {
|
|
12
|
-
super(config);
|
|
13
|
-
const opts = config.options;
|
|
14
|
-
const baseUrl = opts.baseUrl;
|
|
15
|
-
if (!baseUrl) throw new Error("[QdrantProvider] baseUrl is required");
|
|
16
|
-
this.http = axios.create({
|
|
17
|
-
baseURL: baseUrl,
|
|
18
|
-
headers: __spreadValues({
|
|
19
|
-
"Content-Type": "application/json"
|
|
20
|
-
}, opts.apiKey ? { "api-key": opts.apiKey } : {})
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
async initialize() {
|
|
24
|
-
await this.ping();
|
|
25
|
-
}
|
|
26
|
-
async upsert(doc, namespace) {
|
|
27
|
-
await this.batchUpsert([doc], namespace);
|
|
28
|
-
}
|
|
29
|
-
async batchUpsert(docs, namespace) {
|
|
30
|
-
const payload = {
|
|
31
|
-
points: docs.map((doc) => ({
|
|
32
|
-
id: doc.id,
|
|
33
|
-
vector: doc.vector,
|
|
34
|
-
payload: __spreadValues({
|
|
35
|
-
content: doc.content,
|
|
36
|
-
metadata: doc.metadata || {}
|
|
37
|
-
}, namespace ? { namespace } : {})
|
|
38
|
-
}))
|
|
39
|
-
};
|
|
40
|
-
await this.http.put(`/collections/${this.indexName}/points`, payload);
|
|
41
|
-
}
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
43
|
-
async query(vector, topK, namespace, _filter) {
|
|
44
|
-
const payload = {
|
|
45
|
-
vector,
|
|
46
|
-
limit: topK,
|
|
47
|
-
with_payload: true,
|
|
48
|
-
filter: namespace ? {
|
|
49
|
-
must: [{ key: "namespace", match: { value: namespace } }]
|
|
50
|
-
} : void 0
|
|
51
|
-
};
|
|
52
|
-
const { data } = await this.http.post(`/collections/${this.indexName}/points/search`, payload);
|
|
53
|
-
return (data.result || []).map((res) => {
|
|
54
|
-
var _a, _b;
|
|
55
|
-
return {
|
|
56
|
-
id: res["id"],
|
|
57
|
-
score: res["score"],
|
|
58
|
-
content: ((_a = res["payload"]) == null ? void 0 : _a.content) || "",
|
|
59
|
-
metadata: ((_b = res["payload"]) == null ? void 0 : _b.metadata) || {}
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
64
|
-
async delete(id, _namespace) {
|
|
65
|
-
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
66
|
-
points: [id]
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
70
|
-
async deleteNamespace(_namespace) {
|
|
71
|
-
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
72
|
-
filter: {
|
|
73
|
-
must: [{ key: "namespace", match: { value: _namespace } }]
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
async ping() {
|
|
78
|
-
try {
|
|
79
|
-
await this.http.get("/healthz");
|
|
80
|
-
return true;
|
|
81
|
-
} catch (e) {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async disconnect() {
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export {
|
|
90
|
-
QdrantProvider
|
|
91
|
-
};
|