@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
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProviderHealthCheck.ts — Pre-flight validation for vector DB and LLM providers.
|
|
3
|
+
*
|
|
4
|
+
* Performs connectivity tests, credential validation, and capability checks
|
|
5
|
+
* before initializing providers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { VectorDBConfig, LLMConfig, EmbeddingConfig } from '../config/RagConfig';
|
|
9
|
+
import { ConfigValidator } from './ConfigValidator';
|
|
10
|
+
|
|
11
|
+
export interface HealthCheckResult {
|
|
12
|
+
healthy: boolean;
|
|
13
|
+
provider: string;
|
|
14
|
+
version?: string;
|
|
15
|
+
capabilities?: Record<string, unknown>;
|
|
16
|
+
error?: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ProviderHealthCheck {
|
|
21
|
+
/**
|
|
22
|
+
* Validates vector database configuration before initialization.
|
|
23
|
+
* Performs connectivity checks and verifies required resources exist.
|
|
24
|
+
*/
|
|
25
|
+
static async checkVectorProvider(config: VectorDBConfig): Promise<HealthCheckResult> {
|
|
26
|
+
const timestamp = Date.now();
|
|
27
|
+
|
|
28
|
+
// First validate configuration
|
|
29
|
+
const configErrors = ConfigValidator.validate({
|
|
30
|
+
projectId: 'health-check',
|
|
31
|
+
vectorDb: config,
|
|
32
|
+
llm: { provider: 'openai', model: 'gpt-4o' }, // dummy
|
|
33
|
+
embedding: { provider: 'openai', model: 'text-embedding-3-small' },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const vectorDbErrors = configErrors.filter((e) => e.field.startsWith('vectorDb'));
|
|
37
|
+
if (vectorDbErrors.length > 0) {
|
|
38
|
+
return {
|
|
39
|
+
healthy: false,
|
|
40
|
+
provider: config.provider,
|
|
41
|
+
error: `Configuration validation failed: ${vectorDbErrors.map((e) => e.message).join('; ')}`,
|
|
42
|
+
timestamp,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
switch (config.provider) {
|
|
48
|
+
case 'pinecone':
|
|
49
|
+
return await this.checkPinecone(config, timestamp);
|
|
50
|
+
case 'pgvector':
|
|
51
|
+
case 'postgresql':
|
|
52
|
+
return await this.checkPostgres(config, timestamp);
|
|
53
|
+
case 'mongodb':
|
|
54
|
+
return await this.checkMongoDB(config, timestamp);
|
|
55
|
+
case 'milvus':
|
|
56
|
+
return await this.checkMilvus(config, timestamp);
|
|
57
|
+
case 'qdrant':
|
|
58
|
+
return await this.checkQdrant(config, timestamp);
|
|
59
|
+
case 'chromadb':
|
|
60
|
+
return await this.checkChromaDB(config, timestamp);
|
|
61
|
+
case 'redis':
|
|
62
|
+
return await this.checkRedis(config, timestamp);
|
|
63
|
+
case 'weaviate':
|
|
64
|
+
return await this.checkWeaviate(config, timestamp);
|
|
65
|
+
case 'rest':
|
|
66
|
+
case 'universal_rest':
|
|
67
|
+
return await this.checkRestAPI(config, timestamp);
|
|
68
|
+
default:
|
|
69
|
+
return {
|
|
70
|
+
healthy: false,
|
|
71
|
+
provider: config.provider,
|
|
72
|
+
error: `Unsupported provider: ${config.provider}`,
|
|
73
|
+
timestamp,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
healthy: false,
|
|
79
|
+
provider: config.provider,
|
|
80
|
+
error: error instanceof Error ? error.message : String(error),
|
|
81
|
+
timestamp,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private static async checkPinecone(config: VectorDBConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
87
|
+
const opts = config.options as Record<string, string>;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Dynamic import to avoid requiring all dependencies
|
|
91
|
+
const { Pinecone } = await import('@pinecone-database/pinecone');
|
|
92
|
+
const client = new Pinecone({ apiKey: opts.apiKey });
|
|
93
|
+
|
|
94
|
+
const indexes = await client.listIndexes();
|
|
95
|
+
const indexNames = indexes.indexes?.map((i) => i.name) ?? [];
|
|
96
|
+
|
|
97
|
+
if (!indexNames.includes(config.indexName)) {
|
|
98
|
+
return {
|
|
99
|
+
healthy: false,
|
|
100
|
+
provider: 'pinecone',
|
|
101
|
+
error: `Index "${config.indexName}" not found. Available: ${indexNames.join(', ')}`,
|
|
102
|
+
timestamp,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
healthy: true,
|
|
108
|
+
provider: 'pinecone',
|
|
109
|
+
capabilities: {
|
|
110
|
+
indexes: indexNames.length,
|
|
111
|
+
targetIndex: config.indexName,
|
|
112
|
+
},
|
|
113
|
+
timestamp,
|
|
114
|
+
};
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return {
|
|
117
|
+
healthy: false,
|
|
118
|
+
provider: 'pinecone',
|
|
119
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
120
|
+
timestamp,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private static async checkPostgres(config: VectorDBConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
126
|
+
const opts = config.options as Record<string, string>;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const { Client } = await import('pg');
|
|
130
|
+
const client = new Client({ connectionString: opts.connectionString });
|
|
131
|
+
|
|
132
|
+
await client.connect();
|
|
133
|
+
|
|
134
|
+
// Check if pgvector extension is installed
|
|
135
|
+
const result = await client.query(`
|
|
136
|
+
SELECT EXISTS(
|
|
137
|
+
SELECT 1 FROM pg_extension WHERE extname = 'vector'
|
|
138
|
+
);
|
|
139
|
+
`);
|
|
140
|
+
|
|
141
|
+
const hasVector = result.rows[0].exists;
|
|
142
|
+
|
|
143
|
+
await client.end();
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
healthy: true,
|
|
147
|
+
provider: 'postgresql',
|
|
148
|
+
capabilities: { pgvectorInstalled: hasVector },
|
|
149
|
+
timestamp,
|
|
150
|
+
};
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return {
|
|
153
|
+
healthy: false,
|
|
154
|
+
provider: 'postgresql',
|
|
155
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
156
|
+
timestamp,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private static async checkMongoDB(config: VectorDBConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
162
|
+
const opts = config.options as Record<string, string>;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const { MongoClient } = await import('mongodb');
|
|
166
|
+
const client = new MongoClient(opts.uri);
|
|
167
|
+
|
|
168
|
+
await client.connect();
|
|
169
|
+
|
|
170
|
+
const db = client.db(opts.database);
|
|
171
|
+
const collections = await db.listCollections().toArray();
|
|
172
|
+
const collectionNames = collections.map((c) => c.name);
|
|
173
|
+
|
|
174
|
+
const hasCollection = collectionNames.includes(opts.collection);
|
|
175
|
+
|
|
176
|
+
await client.close();
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
healthy: true,
|
|
180
|
+
provider: 'mongodb',
|
|
181
|
+
capabilities: {
|
|
182
|
+
collections: collectionNames.length,
|
|
183
|
+
targetCollection: hasCollection ? opts.collection : 'NOT FOUND',
|
|
184
|
+
},
|
|
185
|
+
timestamp,
|
|
186
|
+
};
|
|
187
|
+
} catch (error) {
|
|
188
|
+
return {
|
|
189
|
+
healthy: false,
|
|
190
|
+
provider: 'mongodb',
|
|
191
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
192
|
+
timestamp,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private static async checkMilvus(config: VectorDBConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
198
|
+
const opts = config.options as Record<string, unknown>;
|
|
199
|
+
const host = (opts.host as string) || 'localhost';
|
|
200
|
+
const port = (opts.port as number) || 19530;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
// Try HTTP health check endpoint
|
|
204
|
+
const controller = new AbortController();
|
|
205
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
206
|
+
|
|
207
|
+
const response = await fetch(`http://${host}:${port}/healthz`, {
|
|
208
|
+
signal: controller.signal,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
clearTimeout(timeoutId);
|
|
212
|
+
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
throw new Error(`Health check returned ${response.status}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
healthy: true,
|
|
219
|
+
provider: 'milvus',
|
|
220
|
+
capabilities: { endpoint: `${host}:${port}` },
|
|
221
|
+
timestamp,
|
|
222
|
+
};
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return {
|
|
225
|
+
healthy: false,
|
|
226
|
+
provider: 'milvus',
|
|
227
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
228
|
+
timestamp,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private static async checkQdrant(config: VectorDBConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
234
|
+
const opts = config.options as Record<string, unknown>;
|
|
235
|
+
const baseUrl = (opts.baseUrl as string) || (opts.url as string) || 'http://localhost:6333';
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const apiKey = opts.apiKey as string | undefined;
|
|
239
|
+
const response = await fetch(`${baseUrl}/`, {
|
|
240
|
+
headers: apiKey ? { 'api-key': apiKey } : {},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
throw new Error(`Health check returned ${response.status}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const health = await response.json();
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
healthy: true,
|
|
251
|
+
provider: 'qdrant',
|
|
252
|
+
capabilities: health as Record<string, unknown>,
|
|
253
|
+
timestamp,
|
|
254
|
+
};
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return {
|
|
257
|
+
healthy: false,
|
|
258
|
+
provider: 'qdrant',
|
|
259
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
260
|
+
timestamp,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private static async checkChromaDB(config: VectorDBConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
266
|
+
const opts = config.options as Record<string, unknown>;
|
|
267
|
+
const host = (opts.host as string) || 'localhost';
|
|
268
|
+
const port = (opts.port as number) || 8000;
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const response = await fetch(`http://${host}:${port}/api/v1/heartbeat`);
|
|
272
|
+
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
throw new Error(`Health check returned ${response.status}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
healthy: true,
|
|
279
|
+
provider: 'chromadb',
|
|
280
|
+
capabilities: { endpoint: `${host}:${port}` },
|
|
281
|
+
timestamp,
|
|
282
|
+
};
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
healthy: false,
|
|
286
|
+
provider: 'chromadb',
|
|
287
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
288
|
+
timestamp,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private static async checkRedis(config: VectorDBConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
294
|
+
const opts = config.options as Record<string, string>;
|
|
295
|
+
const url = opts.url || `redis://${opts.host}:${opts.port}`;
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
// Try to connect via simple TCP check using fetch
|
|
299
|
+
// Note: Full Redis connection would require redis package as optional dependency
|
|
300
|
+
await fetch(url);
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
healthy: true,
|
|
304
|
+
provider: 'redis',
|
|
305
|
+
capabilities: { endpoint: url },
|
|
306
|
+
timestamp,
|
|
307
|
+
};
|
|
308
|
+
} catch (error) {
|
|
309
|
+
// If fetch fails, try to gracefully handle (redis may not have HTTP interface)
|
|
310
|
+
return {
|
|
311
|
+
healthy: false,
|
|
312
|
+
provider: 'redis',
|
|
313
|
+
error: `Connection check failed: ${error instanceof Error ? error.message : String(error)}. Ensure Redis is running at ${url}`,
|
|
314
|
+
timestamp,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private static async checkWeaviate(config: VectorDBConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
320
|
+
const opts = config.options as Record<string, string>;
|
|
321
|
+
const url = (opts.url || 'http://localhost:8080').replace(/\/$/, '');
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const response = await fetch(`${url}/.well-known/ready`);
|
|
325
|
+
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new Error(`Health check returned ${response.status}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
healthy: true,
|
|
332
|
+
provider: 'weaviate',
|
|
333
|
+
capabilities: { endpoint: url },
|
|
334
|
+
timestamp,
|
|
335
|
+
};
|
|
336
|
+
} catch (error) {
|
|
337
|
+
return {
|
|
338
|
+
healthy: false,
|
|
339
|
+
provider: 'weaviate',
|
|
340
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
341
|
+
timestamp,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private static async checkRestAPI(config: VectorDBConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
347
|
+
const opts = config.options as Record<string, string>;
|
|
348
|
+
const baseUrl = (opts.baseUrl || '').replace(/\/$/, '');
|
|
349
|
+
|
|
350
|
+
if (!baseUrl) {
|
|
351
|
+
return {
|
|
352
|
+
healthy: false,
|
|
353
|
+
provider: 'rest',
|
|
354
|
+
error: 'baseUrl is required',
|
|
355
|
+
timestamp,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const response = await fetch(`${baseUrl}/health`, {
|
|
361
|
+
headers: opts.headers ? JSON.parse(opts.headers) : {},
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
healthy: response.ok,
|
|
366
|
+
provider: 'rest',
|
|
367
|
+
error: response.ok ? undefined : `Health check returned ${response.status}`,
|
|
368
|
+
timestamp,
|
|
369
|
+
};
|
|
370
|
+
} catch (error) {
|
|
371
|
+
return {
|
|
372
|
+
healthy: false,
|
|
373
|
+
provider: 'rest',
|
|
374
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
375
|
+
timestamp,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Validates LLM provider configuration.
|
|
382
|
+
*/
|
|
383
|
+
static async checkLLMProvider(config: LLMConfig): Promise<HealthCheckResult> {
|
|
384
|
+
const timestamp = Date.now();
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
switch (config.provider) {
|
|
388
|
+
case 'openai':
|
|
389
|
+
return await this.checkOpenAI(config, timestamp);
|
|
390
|
+
case 'anthropic':
|
|
391
|
+
return await this.checkAnthropic(config, timestamp);
|
|
392
|
+
case 'ollama':
|
|
393
|
+
return await this.checkOllama(config, timestamp);
|
|
394
|
+
case 'rest':
|
|
395
|
+
case 'universal_rest':
|
|
396
|
+
return await this.checkLLMRestAPI(config, timestamp);
|
|
397
|
+
default:
|
|
398
|
+
return {
|
|
399
|
+
healthy: false,
|
|
400
|
+
provider: config.provider,
|
|
401
|
+
error: `Unsupported provider: ${config.provider}`,
|
|
402
|
+
timestamp,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
} catch (error) {
|
|
406
|
+
return {
|
|
407
|
+
healthy: false,
|
|
408
|
+
provider: config.provider,
|
|
409
|
+
error: error instanceof Error ? error.message : String(error),
|
|
410
|
+
timestamp,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private static async checkOpenAI(config: LLMConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
416
|
+
try {
|
|
417
|
+
const OpenAI = await import('openai');
|
|
418
|
+
const client = new OpenAI.default({ apiKey: config.apiKey });
|
|
419
|
+
|
|
420
|
+
// Test models endpoint
|
|
421
|
+
const models = await client.models.list();
|
|
422
|
+
|
|
423
|
+
const hasModel = models.data.some((m) => m.id === config.model);
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
healthy: true,
|
|
427
|
+
provider: 'openai',
|
|
428
|
+
capabilities: {
|
|
429
|
+
model: config.model,
|
|
430
|
+
available: hasModel,
|
|
431
|
+
totalModels: models.data.length,
|
|
432
|
+
},
|
|
433
|
+
timestamp,
|
|
434
|
+
};
|
|
435
|
+
} catch (error) {
|
|
436
|
+
return {
|
|
437
|
+
healthy: false,
|
|
438
|
+
provider: 'openai',
|
|
439
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
440
|
+
timestamp,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private static async checkAnthropic(config: LLMConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
446
|
+
try {
|
|
447
|
+
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
448
|
+
const client = new Anthropic({ apiKey: config.apiKey });
|
|
449
|
+
|
|
450
|
+
// Try a minimal API call to verify credentials
|
|
451
|
+
await client.messages.create({
|
|
452
|
+
model: config.model,
|
|
453
|
+
max_tokens: 10,
|
|
454
|
+
messages: [{ role: 'user', content: 'ping' }],
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
healthy: true,
|
|
459
|
+
provider: 'anthropic',
|
|
460
|
+
capabilities: { model: config.model },
|
|
461
|
+
timestamp,
|
|
462
|
+
};
|
|
463
|
+
} catch (error) {
|
|
464
|
+
return {
|
|
465
|
+
healthy: false,
|
|
466
|
+
provider: 'anthropic',
|
|
467
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
468
|
+
timestamp,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private static async checkOllama(config: LLMConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
474
|
+
const baseUrl = (config.baseUrl || 'http://localhost:11434').replace(/\/$/, '');
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
const response = await fetch(`${baseUrl}/api/tags`);
|
|
478
|
+
|
|
479
|
+
if (!response.ok) {
|
|
480
|
+
throw new Error(`Health check returned ${response.status}`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const data = await response.json() as Record<string, unknown>;
|
|
484
|
+
const models = (data.models as Array<{ name: string }>) || [];
|
|
485
|
+
const hasModel = models.some((m) => m.name === config.model);
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
healthy: true,
|
|
489
|
+
provider: 'ollama',
|
|
490
|
+
capabilities: {
|
|
491
|
+
model: config.model,
|
|
492
|
+
available: hasModel,
|
|
493
|
+
totalModels: models.length,
|
|
494
|
+
},
|
|
495
|
+
timestamp,
|
|
496
|
+
};
|
|
497
|
+
} catch (error) {
|
|
498
|
+
return {
|
|
499
|
+
healthy: false,
|
|
500
|
+
provider: 'ollama',
|
|
501
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
502
|
+
timestamp,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private static async checkLLMRestAPI(config: LLMConfig, timestamp: number): Promise<HealthCheckResult> {
|
|
508
|
+
const baseUrlRaw = config.baseUrl || (config.options as Record<string, unknown>)?.baseUrl;
|
|
509
|
+
const baseUrl = (typeof baseUrlRaw === 'string' ? baseUrlRaw : '').replace(/\/$/, '');
|
|
510
|
+
|
|
511
|
+
if (!baseUrl) {
|
|
512
|
+
return {
|
|
513
|
+
healthy: false,
|
|
514
|
+
provider: config.provider,
|
|
515
|
+
error: 'baseUrl is required',
|
|
516
|
+
timestamp,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
const headers = (config.options as Record<string, unknown>)?.headers as Record<string, string> | undefined;
|
|
522
|
+
const response = await fetch(`${baseUrl}/health`, {
|
|
523
|
+
headers: headers || undefined,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
healthy: response.ok,
|
|
528
|
+
provider: config.provider,
|
|
529
|
+
error: response.ok ? undefined : `Health check returned ${response.status}`,
|
|
530
|
+
timestamp,
|
|
531
|
+
};
|
|
532
|
+
} catch (error) {
|
|
533
|
+
return {
|
|
534
|
+
healthy: false,
|
|
535
|
+
provider: config.provider,
|
|
536
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
537
|
+
timestamp,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Runs comprehensive health checks on all configured providers.
|
|
544
|
+
*/
|
|
545
|
+
static async checkAll(
|
|
546
|
+
vectorDbConfig: VectorDBConfig,
|
|
547
|
+
llmConfig: LLMConfig,
|
|
548
|
+
embeddingConfig?: EmbeddingConfig
|
|
549
|
+
): Promise<{
|
|
550
|
+
vectorDb: HealthCheckResult;
|
|
551
|
+
llm: HealthCheckResult;
|
|
552
|
+
embedding?: HealthCheckResult;
|
|
553
|
+
allHealthy: boolean;
|
|
554
|
+
}> {
|
|
555
|
+
const [vectorDb, llm, embedding] = await Promise.all([
|
|
556
|
+
this.checkVectorProvider(vectorDbConfig),
|
|
557
|
+
this.checkLLMProvider(llmConfig),
|
|
558
|
+
embeddingConfig ? this.checkLLMProvider(embeddingConfig as LLMConfig) : Promise.resolve(undefined),
|
|
559
|
+
]);
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
vectorDb,
|
|
563
|
+
llm,
|
|
564
|
+
embedding,
|
|
565
|
+
allHealthy: vectorDb.healthy && llm.healthy && (!embedding || embedding.healthy),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
package/src/core/VectorPlugin.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { RagConfig } from '../config/RagConfig';
|
|
2
2
|
import { ConfigResolver } from './ConfigResolver';
|
|
3
|
+
import { ConfigValidator } from './ConfigValidator';
|
|
3
4
|
import { Pipeline } from './Pipeline';
|
|
5
|
+
import { ProviderHealthCheck, HealthCheckResult } from './ProviderHealthCheck';
|
|
4
6
|
import { IngestDocument, ChatResponse } from '../types';
|
|
5
7
|
import { ChatMessage } from '../llm/ILLMProvider';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* VectorPlugin — main orchestrator class.
|
|
9
11
|
* This is the primary interface for host applications.
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Configuration resolution from host + environment
|
|
15
|
+
* - Configuration validation with detailed error messages
|
|
16
|
+
* - Provider health checks before initialization
|
|
17
|
+
* - Multi-provider support (Vector DBs & LLMs)
|
|
18
|
+
* - Per-tenant data isolation via namespacing
|
|
10
19
|
*/
|
|
11
20
|
export class VectorPlugin {
|
|
12
21
|
private config: RagConfig;
|
|
@@ -15,10 +24,29 @@ export class VectorPlugin {
|
|
|
15
24
|
/**
|
|
16
25
|
* Initializes the plugin with the host configuration.
|
|
17
26
|
* @param hostConfig - Configuration object passed from the host application.
|
|
27
|
+
* @throws Error if configuration is invalid or providers are unhealthy
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const plugin = new VectorPlugin({
|
|
31
|
+
* projectId: 'my-app',
|
|
32
|
+
* vectorDb: {
|
|
33
|
+
* provider: 'pinecone',
|
|
34
|
+
* indexName: 'my-index',
|
|
35
|
+
* options: { apiKey: process.env.PINECONE_API_KEY }
|
|
36
|
+
* },
|
|
37
|
+
* llm: {
|
|
38
|
+
* provider: 'openai',
|
|
39
|
+
* model: 'gpt-4o',
|
|
40
|
+
* apiKey: process.env.OPENAI_API_KEY
|
|
41
|
+
* }
|
|
42
|
+
* });
|
|
18
43
|
*/
|
|
19
44
|
constructor(hostConfig?: Partial<RagConfig>) {
|
|
20
45
|
this.config = ConfigResolver.resolve(hostConfig);
|
|
21
|
-
|
|
46
|
+
|
|
47
|
+
// Validate configuration early with detailed error messages
|
|
48
|
+
ConfigValidator.validateAndThrow(this.config);
|
|
49
|
+
|
|
22
50
|
this.pipeline = new Pipeline(this.config);
|
|
23
51
|
}
|
|
24
52
|
|
|
@@ -29,6 +57,25 @@ export class VectorPlugin {
|
|
|
29
57
|
return this.config;
|
|
30
58
|
}
|
|
31
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Perform pre-flight health checks on all configured providers.
|
|
62
|
+
* Useful to verify connectivity before running operations.
|
|
63
|
+
*
|
|
64
|
+
* @returns Health status for vector DB, LLM, and embedding providers
|
|
65
|
+
*/
|
|
66
|
+
async checkHealth(): Promise<{
|
|
67
|
+
vectorDb: HealthCheckResult;
|
|
68
|
+
llm: HealthCheckResult;
|
|
69
|
+
embedding?: HealthCheckResult;
|
|
70
|
+
allHealthy: boolean;
|
|
71
|
+
}> {
|
|
72
|
+
return ProviderHealthCheck.checkAll(
|
|
73
|
+
this.config.vectorDb,
|
|
74
|
+
this.config.llm,
|
|
75
|
+
this.config.embedding
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
32
79
|
/**
|
|
33
80
|
* Run a chat query.
|
|
34
81
|
*/
|
|
@@ -39,14 +86,8 @@ export class VectorPlugin {
|
|
|
39
86
|
/**
|
|
40
87
|
* Ingest documents into the vector database.
|
|
41
88
|
*/
|
|
42
|
-
async ingest(documents: IngestDocument[], namespace?: string): Promise<Array<{ docId: string; chunksIngested: number }>> {
|
|
89
|
+
async ingest(documents: IngestDocument[], namespace?: string): Promise<Array<{ docId: string | number; chunksIngested: number }>> {
|
|
43
90
|
return this.pipeline.ingest(documents, namespace);
|
|
44
91
|
}
|
|
45
92
|
|
|
46
|
-
/**
|
|
47
|
-
* Check the health of the connected providers.
|
|
48
|
-
*/
|
|
49
|
-
async health(): Promise<Record<string, boolean>> {
|
|
50
|
-
return this.pipeline.health();
|
|
51
|
-
}
|
|
52
93
|
}
|
package/src/handlers/index.ts
CHANGED
|
@@ -67,8 +67,8 @@ export function createHealthHandler(config?: Partial<RagConfig>) {
|
|
|
67
67
|
|
|
68
68
|
return async function GET() {
|
|
69
69
|
try {
|
|
70
|
-
const health = await plugin.
|
|
71
|
-
const status = health.
|
|
70
|
+
const health = await plugin.checkHealth();
|
|
71
|
+
const status = health.allHealthy ? 'ok' : 'degraded';
|
|
72
72
|
return NextResponse.json({
|
|
73
73
|
status,
|
|
74
74
|
...health,
|
|
@@ -42,7 +42,7 @@ export abstract class BaseVectorProvider {
|
|
|
42
42
|
/**
|
|
43
43
|
* Delete a stored vector by ID.
|
|
44
44
|
*/
|
|
45
|
-
abstract delete(id: string, namespace?: string): Promise<void>;
|
|
45
|
+
abstract delete(id: string | number, namespace?: string): Promise<void>;
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Delete all vectors in a namespace (full data reset for a project).
|
|
@@ -74,7 +74,7 @@ export class MilvusProvider extends BaseVectorProvider {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
77
|
-
async delete(id: string, _namespace?: string): Promise<void> {
|
|
77
|
+
async delete(id: string | number, _namespace?: string): Promise<void> {
|
|
78
78
|
await this.http.post('/v1/vector/delete', {
|
|
79
79
|
collectionName: this.indexName,
|
|
80
80
|
id: id,
|
|
@@ -95,7 +95,7 @@ export class MongoDBProvider extends BaseVectorProvider {
|
|
|
95
95
|
}));
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
async delete(id: string, namespace?: string): Promise<void> {
|
|
98
|
+
async delete(id: string | number, namespace?: string): Promise<void> {
|
|
99
99
|
await this.collection!.deleteOne({ _id: id, ...(namespace ? { namespace } : {}) });
|
|
100
100
|
}
|
|
101
101
|
|