@retrivora-ai/rag-engine 0.4.4 → 1.0.0

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.
Files changed (73) hide show
  1. package/README.md +32 -57
  2. package/dist/{ChromaDBProvider-GI7TB7GJ.mjs → ChromaDBProvider-APQVJ5F7.mjs} +2 -2
  3. package/dist/{DocumentChunker-3yElxTO3.d.mts → DocumentChunker-C-sCZPhi.d.mts} +6 -6
  4. package/dist/{DocumentChunker-3yElxTO3.d.ts → DocumentChunker-C-sCZPhi.d.ts} +6 -6
  5. package/dist/{MilvusProvider-WDVTFB7D.mjs → MilvusProvider-35US67MS.mjs} +2 -2
  6. package/dist/{MongoDBProvider-ZKW34AEL.mjs → MongoDBProvider-COVYZDP6.mjs} +2 -2
  7. package/dist/{PineconeProvider-BE2JWSPD.mjs → PineconeProvider-AWFJQDZL.mjs} +2 -2
  8. package/dist/{PostgreSQLProvider-5HHTK4SU.mjs → PostgreSQLProvider-IEYRJ7XJ.mjs} +2 -2
  9. package/dist/{QdrantProvider-XVDVBNIG.mjs → QdrantProvider-M6TQYZRO.mjs} +2 -2
  10. package/dist/{RagConfig-BgRDL9Vy.d.mts → RagConfig-DRJO4hGU.d.mts} +12 -1
  11. package/dist/{RagConfig-BgRDL9Vy.d.ts → RagConfig-DRJO4hGU.d.ts} +12 -1
  12. package/dist/{RedisProvider-EK2R2PQH.mjs → RedisProvider-3G5PBLZ4.mjs} +2 -2
  13. package/dist/{SimpleGraphProvider-M6T7SE7D.mjs → SimpleGraphProvider-UK7DJW37.mjs} +1 -1
  14. package/dist/{UniversalVectorProvider-YIDRX6VT.mjs → UniversalVectorProvider-FYQ3B2PW.mjs} +3 -3
  15. package/dist/{WeaviateProvider-4CAPQ7UY.mjs → WeaviateProvider-ITHO36IL.mjs} +2 -2
  16. package/dist/{chunk-5KNBWQM6.mjs → chunk-4A47RCG2.mjs} +5 -1
  17. package/dist/{chunk-EDLTMSNY.mjs → chunk-67AJ6SMD.mjs} +1 -1
  18. package/dist/{chunk-IWHCAQEA.mjs → chunk-7SOSCZGS.mjs} +68 -7
  19. package/dist/{chunk-LJWWPTWE.mjs → chunk-FLOSGE6A.mjs} +76 -14
  20. package/dist/{chunk-H6RKMU7W.mjs → chunk-NXUCKY5L.mjs} +1 -1
  21. package/dist/{chunk-KTS3LLHY.mjs → chunk-OOQXNLXD.mjs} +5 -5
  22. package/dist/{chunk-OKY5P6RA.mjs → chunk-P4HAQ7KB.mjs} +1186 -1345
  23. package/dist/chunk-QMIKLALV.mjs +57 -0
  24. package/dist/{chunk-3QWAK3RZ.mjs → chunk-TYHTZIDP.mjs} +6 -2
  25. package/dist/{chunk-GQT5LF4G.mjs → chunk-U6KHVZLF.mjs} +2 -2
  26. package/dist/{chunk-RK2UDJA2.mjs → chunk-WGSZNY3X.mjs} +1 -1
  27. package/dist/{chunk-XCNXPECE.mjs → chunk-ZNBKHNJ4.mjs} +55 -1
  28. package/dist/handlers/index.d.mts +2 -2
  29. package/dist/handlers/index.d.ts +2 -2
  30. package/dist/handlers/index.js +1417 -1377
  31. package/dist/handlers/index.mjs +3 -3
  32. package/dist/index-CrGMwXfO.d.ts +112 -0
  33. package/dist/index-v669iV-k.d.mts +112 -0
  34. package/dist/index.d.mts +5 -5
  35. package/dist/index.d.ts +5 -5
  36. package/dist/index.mjs +2 -2
  37. package/dist/server.d.mts +104 -158
  38. package/dist/server.d.ts +104 -158
  39. package/dist/server.js +1419 -1379
  40. package/dist/server.mjs +12 -12
  41. package/package.json +5 -1
  42. package/src/components/DocumentUpload.tsx +1 -1
  43. package/src/config/RagConfig.ts +7 -0
  44. package/src/core/ConfigValidator.ts +66 -492
  45. package/src/core/LangChainAgent.ts +78 -0
  46. package/src/core/Pipeline.ts +210 -222
  47. package/src/core/ProviderHealthCheck.ts +35 -406
  48. package/src/core/ProviderInterfaces.ts +37 -0
  49. package/src/core/ProviderRegistry.ts +70 -55
  50. package/src/core/QueryProcessor.ts +173 -0
  51. package/src/llm/ILLMProvider.ts +10 -0
  52. package/src/llm/LLMFactory.ts +33 -13
  53. package/src/llm/providers/AnthropicProvider.ts +55 -15
  54. package/src/llm/providers/GeminiProvider.ts +51 -0
  55. package/src/llm/providers/OllamaProvider.ts +100 -15
  56. package/src/llm/providers/OpenAIProvider.ts +60 -11
  57. package/src/providers/vectordb/BaseVectorProvider.ts +11 -0
  58. package/src/providers/vectordb/MilvusProvider.ts +4 -0
  59. package/src/providers/vectordb/MongoDBProvider.ts +75 -11
  60. package/src/providers/vectordb/PineconeProvider.ts +60 -5
  61. package/src/providers/vectordb/PostgreSQLProvider.ts +84 -14
  62. package/src/providers/vectordb/QdrantProvider.ts +4 -0
  63. package/src/providers/vectordb/WeaviateProvider.ts +8 -4
  64. package/src/rag/DocumentChunker.ts +15 -19
  65. package/src/rag/EntityExtractor.ts +1 -1
  66. package/src/rag/LlamaIndexIngestor.ts +61 -0
  67. package/src/rag/Reranker.ts +20 -0
  68. package/src/server.ts +1 -1
  69. package/src/types/index.ts +9 -0
  70. package/src/utils/DocumentParser.ts +1 -1
  71. package/dist/chunk-FWCSY2DS.mjs +0 -37
  72. package/dist/index-7qeLTPBL.d.mts +0 -114
  73. package/dist/index-DowY4_K0.d.ts +0 -114
@@ -1,18 +1,12 @@
1
1
  /**
2
2
  * Ollama LLM Provider (local / self-hosted)
3
- *
4
- * Communicates with an Ollama server via its REST API.
5
- * Supports both chat completion and embedding (requires a model with embed support,
6
- * e.g. "nomic-embed-text", "mxbai-embed-large").
7
- *
8
- * Required LLMConfig fields:
9
- * - model: string – e.g. "llama3", "mistral", "gemma2"
10
- * - baseUrl?: string – Ollama server URL (default "http://localhost:11434")
11
3
  */
12
4
 
13
5
  import axios, { AxiosInstance } from 'axios';
14
6
  import { ILLMProvider, ChatMessage, ChatOptions, EmbedOptions } from '../ILLMProvider';
15
7
  import { LLMConfig, EmbeddingConfig } from '../../config/RagConfig';
8
+ import { IProviderValidator, IProviderHealthChecker, HealthCheckResult } from '../../core/ProviderInterfaces';
9
+ import { ValidationError } from '../../core/ConfigValidator';
16
10
 
17
11
  interface OllamaChatResponse {
18
12
  message: { content: string };
@@ -34,14 +28,63 @@ export class OllamaProvider implements ILLMProvider {
34
28
  this.embeddingConfig = embeddingConfig;
35
29
  }
36
30
 
37
- async chat(messages: ChatMessage[], context: string, options?: ChatOptions): Promise<string> {
38
- const systemPrompt =
39
- this.llmConfig.systemPrompt ??
40
- `You are a helpful assistant. Use the provided context to answer the user's question.\n\nContext:\n${context}`;
31
+ static getValidator(): IProviderValidator {
32
+ return {
33
+ validate(config: Record<string, unknown>): ValidationError[] {
34
+ const errors: ValidationError[] = [];
35
+ if (!config.model) {
36
+ const isEmbedding = config.provider === 'ollama' && 'dimensions' in config;
37
+ const prefix = isEmbedding ? 'embedding' : 'llm';
38
+ errors.push({
39
+ field: `${prefix}.model`,
40
+ message: 'Ollama model name is required',
41
+ suggestion: 'e.g., "llama3" or "mistral"',
42
+ severity: 'error',
43
+ });
44
+ }
45
+ return errors;
46
+ }
47
+ };
48
+ }
41
49
 
42
- const system = systemPrompt.includes('{{context}}')
43
- ? systemPrompt.replace('{{context}}', context)
44
- : `${systemPrompt}\n\nContext:\n${context}`;
50
+ static getHealthChecker(): IProviderHealthChecker {
51
+ return {
52
+ async check(config: Record<string, unknown>): Promise<HealthCheckResult> {
53
+ const timestamp = Date.now();
54
+ const baseUrl = (config.baseUrl as string) || 'http://localhost:11434';
55
+ const modelName = config.model as string;
56
+
57
+ try {
58
+ const axios = (await import('axios')).default;
59
+ const { data } = await axios.get(`${baseUrl}/api/tags`);
60
+ const models = (data.models as { name: string }[]) || [];
61
+ const hasModel = models.some((m) => m.name === modelName || m.name.startsWith(`${modelName}:`));
62
+
63
+ return {
64
+ healthy: true,
65
+ provider: 'ollama',
66
+ capabilities: {
67
+ baseUrl,
68
+ model: modelName,
69
+ available: hasModel,
70
+ totalModels: models.length
71
+ },
72
+ timestamp,
73
+ };
74
+ } catch {
75
+ return {
76
+ healthy: false,
77
+ provider: 'ollama',
78
+ error: `Ollama server not reachable at ${baseUrl}. Is it running?`,
79
+ timestamp,
80
+ };
81
+ }
82
+ }
83
+ };
84
+ }
85
+
86
+ async chat(messages: ChatMessage[], context: string, options?: ChatOptions): Promise<string> {
87
+ const system = this.buildSystemPrompt(context);
45
88
 
46
89
  const { data } = await this.http.post<OllamaChatResponse>('/api/chat', {
47
90
  model: this.llmConfig.model,
@@ -59,6 +102,48 @@ export class OllamaProvider implements ILLMProvider {
59
102
  return data.message.content;
60
103
  }
61
104
 
105
+ async *chatStream(messages: ChatMessage[], context: string, options?: ChatOptions): AsyncIterable<string> {
106
+ const system = this.buildSystemPrompt(context);
107
+
108
+ const response = await this.http.post('/api/chat', {
109
+ model: this.llmConfig.model,
110
+ stream: true,
111
+ options: {
112
+ temperature: options?.temperature ?? this.llmConfig.temperature ?? 0.7,
113
+ num_predict: options?.maxTokens ?? this.llmConfig.maxTokens ?? 1024,
114
+ },
115
+ messages: [
116
+ { role: 'system', content: system },
117
+ ...messages.map((m) => ({ role: m.role, content: m.content })),
118
+ ],
119
+ }, { responseType: 'stream' });
120
+
121
+ for await (const chunk of response.data) {
122
+ const lines = chunk.toString().split('\n').filter(Boolean);
123
+ for (const line of lines) {
124
+ try {
125
+ const json = JSON.parse(line);
126
+ if (json.message?.content) {
127
+ yield json.message.content;
128
+ }
129
+ if (json.done) return;
130
+ } catch {
131
+ // Ignore parsing errors for partial lines
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ private buildSystemPrompt(context: string): string {
138
+ const systemPrompt =
139
+ this.llmConfig.systemPrompt ??
140
+ `You are a helpful assistant. Use the provided context to answer the user's question.\n\nContext:\n${context}`;
141
+
142
+ return systemPrompt.includes('{{context}}')
143
+ ? systemPrompt.replace('{{context}}', context)
144
+ : `${systemPrompt}\n\nContext:\n${context}`;
145
+ }
146
+
62
147
  async embed(text: string, options?: EmbedOptions): Promise<number[]> {
63
148
  const model =
64
149
  options?.model ??
@@ -1,21 +1,12 @@
1
1
  /**
2
2
  * OpenAI LLM Provider
3
- *
4
- * Handles both chat completion (GPT-4o, GPT-4-turbo, etc.) and
5
- * embedding (text-embedding-ada-002, text-embedding-3-small/large).
6
- *
7
- * Required LLMConfig fields:
8
- * - apiKey: string – OpenAI API key
9
- * - model: string – e.g. "gpt-4o"
10
- *
11
- * Required EmbeddingConfig fields (when used for embedding):
12
- * - apiKey: string – same or separate key
13
- * - model: string – e.g. "text-embedding-3-small"
14
3
  */
15
4
 
16
5
  import OpenAI from 'openai';
17
6
  import { ILLMProvider, ChatMessage, ChatOptions, EmbedOptions } from '../ILLMProvider';
18
7
  import { LLMConfig, EmbeddingConfig } from '../../config/RagConfig';
8
+ import { IProviderValidator, IProviderHealthChecker, HealthCheckResult } from '../../core/ProviderInterfaces';
9
+ import { ValidationError } from '../../core/ConfigValidator';
19
10
 
20
11
  export class OpenAIProvider implements ILLMProvider {
21
12
  private readonly client: OpenAI;
@@ -29,6 +20,64 @@ export class OpenAIProvider implements ILLMProvider {
29
20
  this.embeddingConfig = embeddingConfig;
30
21
  }
31
22
 
23
+ static getValidator(): IProviderValidator {
24
+ return {
25
+ validate(config: Record<string, unknown>): ValidationError[] {
26
+ const errors: ValidationError[] = [];
27
+ const isEmbedding = config.provider === 'openai' && 'dimensions' in config;
28
+ const prefix = isEmbedding ? 'embedding' : 'llm';
29
+
30
+ if (!config.apiKey) {
31
+ errors.push({
32
+ field: `${prefix}.apiKey`,
33
+ message: 'OpenAI API key is required',
34
+ suggestion: 'Set OPENAI_API_KEY environment variable',
35
+ severity: 'error',
36
+ });
37
+ }
38
+ if (!config.model) {
39
+ errors.push({
40
+ field: `${prefix}.model`,
41
+ message: 'OpenAI model name is required',
42
+ suggestion: isEmbedding ? 'e.g., "text-embedding-3-small"' : 'e.g., "gpt-4o"',
43
+ severity: 'error',
44
+ });
45
+ }
46
+ return errors;
47
+ }
48
+ };
49
+ }
50
+
51
+ static getHealthChecker(): IProviderHealthChecker {
52
+ return {
53
+ async check(config: Record<string, unknown>): Promise<HealthCheckResult> {
54
+ const timestamp = Date.now();
55
+ const apiKey = config.apiKey as string;
56
+ const modelName = config.model as string;
57
+
58
+ try {
59
+ const OpenAI = await import('openai');
60
+ const client = new OpenAI.default({ apiKey });
61
+ const models = await client.models.list();
62
+ const hasModel = models.data.some((m) => m.id === modelName);
63
+ return {
64
+ healthy: true,
65
+ provider: 'openai',
66
+ capabilities: { model: modelName, available: hasModel, totalModels: models.data.length },
67
+ timestamp,
68
+ };
69
+ } catch (error) {
70
+ return {
71
+ healthy: false,
72
+ provider: 'openai',
73
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
74
+ timestamp,
75
+ };
76
+ }
77
+ }
78
+ };
79
+ }
80
+
32
81
  async chat(messages: ChatMessage[], context: string, options?: ChatOptions): Promise<string> {
33
82
  const systemContent =
34
83
  this.llmConfig.systemPrompt ??
@@ -1,5 +1,6 @@
1
1
  import { VectorDBConfig } from '../../config/RagConfig';
2
2
  import { VectorMatch, UpsertDocument } from '../../types';
3
+ import { IProviderValidator, IProviderHealthChecker } from '../../core/ProviderInterfaces';
3
4
 
4
5
  /**
5
6
  * BaseVectorProvider — Abstract base class for all vector database providers.
@@ -59,6 +60,16 @@ export abstract class BaseVectorProvider {
59
60
  */
60
61
  abstract disconnect(): Promise<void>;
61
62
 
63
+ /**
64
+ * Returns a validator for this provider's configuration.
65
+ */
66
+ static getValidator?(): IProviderValidator;
67
+
68
+ /**
69
+ * Returns a health checker for this provider.
70
+ */
71
+ static getHealthChecker?(): IProviderHealthChecker;
72
+
62
73
  /**
63
74
  * Remove internal keys (starting with __) and queryText from a filter object
64
75
  * before passing it to an external provider that might not support them.
@@ -76,6 +76,10 @@ export class MilvusProvider extends BaseVectorProvider {
76
76
  limit: topK,
77
77
  filter: namespace ? `namespace == "${namespace}"` : undefined,
78
78
  outputFields: ['content', 'metadata'],
79
+ searchParams: {
80
+ nprobe: (this.config.options.nprobe as number) || 16,
81
+ ef: (this.config.options.efSearch as number) || Math.max(topK * 10, 64)
82
+ }
79
83
  };
80
84
  const { data } = await this.http.post('/v1/vector/search', payload);
81
85
  return (data.data || []).map((res: Record<string, unknown>) => ({
@@ -2,6 +2,8 @@ import { MongoClient, Collection, Db } from 'mongodb';
2
2
  import { VectorDBConfig } from '../../config/RagConfig';
3
3
  import { BaseVectorProvider } from './BaseVectorProvider';
4
4
  import { VectorMatch, UpsertDocument } from '../../types';
5
+ import { IProviderValidator, IProviderHealthChecker, HealthCheckResult } from '../../core/ProviderInterfaces';
6
+ import { ValidationError } from '../../core/ConfigValidator';
5
7
 
6
8
  /**
7
9
  * MongoDBProvider — MongoDB Atlas Vector Search implementation.
@@ -31,6 +33,75 @@ export class MongoDBProvider extends BaseVectorProvider {
31
33
  this.metadataKey = (opts.metadataKey as string) || 'metadata';
32
34
  }
33
35
 
36
+ static getValidator(): IProviderValidator {
37
+ return {
38
+ validate(config: Record<string, unknown>): ValidationError[] {
39
+ const errors: ValidationError[] = [];
40
+ const opts = (config.options || {}) as Record<string, unknown>;
41
+
42
+ if (!opts.uri) {
43
+ errors.push({
44
+ field: 'vectorDb.options.uri',
45
+ message: 'MongoDB connection URI is required',
46
+ suggestion: 'Set MONGODB_URI environment variable',
47
+ severity: 'error',
48
+ });
49
+ }
50
+ if (!opts.database) {
51
+ errors.push({
52
+ field: 'vectorDb.options.database',
53
+ message: 'MongoDB database name is required',
54
+ severity: 'error',
55
+ });
56
+ }
57
+ if (!opts.collection) {
58
+ errors.push({
59
+ field: 'vectorDb.options.collection',
60
+ message: 'MongoDB collection name is required',
61
+ severity: 'error',
62
+ });
63
+ }
64
+ return errors;
65
+ }
66
+ };
67
+ }
68
+
69
+ static getHealthChecker(): IProviderHealthChecker {
70
+ return {
71
+ async check(config: Record<string, unknown>): Promise<HealthCheckResult> {
72
+ const opts = (config.options || {}) as Record<string, string>;
73
+ const timestamp = Date.now();
74
+ try {
75
+ const { MongoClient } = await import('mongodb');
76
+ const client = new MongoClient(opts.uri);
77
+ await client.connect();
78
+ const db = client.db(opts.database);
79
+ await db.command({ ping: 1 });
80
+ const collections = await db.listCollections({ name: opts.collection }).toArray();
81
+ await client.close();
82
+
83
+ return {
84
+ healthy: true,
85
+ provider: 'mongodb',
86
+ capabilities: {
87
+ database: opts.database,
88
+ collection: opts.collection,
89
+ exists: collections.length > 0
90
+ },
91
+ timestamp,
92
+ };
93
+ } catch (error) {
94
+ return {
95
+ healthy: false,
96
+ provider: 'mongodb',
97
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
98
+ timestamp,
99
+ };
100
+ }
101
+ }
102
+ };
103
+ }
104
+
34
105
  async initialize(): Promise<void> {
35
106
  await this.client.connect();
36
107
  this.db = this.client.db(this.dbName);
@@ -74,7 +145,7 @@ export class MongoDBProvider extends BaseVectorProvider {
74
145
  index: this.config.indexName || 'vector_index',
75
146
  path: this.embeddingKey,
76
147
  queryVector: vector,
77
- numCandidates: Math.max(topK * 10, 100),
148
+ numCandidates: (this.config.options.numCandidates as number) || Math.max(topK * 20, 200),
78
149
  limit: topK,
79
150
  ...(Object.keys(publicFilter).length > 0 || namespace
80
151
  ? { filter: { ...publicFilter, ...(namespace ? { namespace } : {}) } }
@@ -94,10 +165,10 @@ export class MongoDBProvider extends BaseVectorProvider {
94
165
 
95
166
  const results = await this.collection!.aggregate(pipeline).toArray();
96
167
 
97
- return results.map((res: any) => ({
168
+ return (results as unknown as Array<{ _id: string; score: number; [key: string]: unknown }>).map((res) => ({
98
169
  id: res._id,
99
- content: res[this.contentKey],
100
- metadata: res[this.metadataKey],
170
+ content: res[this.contentKey] as string,
171
+ metadata: (res[this.metadataKey] as Record<string, unknown>) || {},
101
172
  score: res.score,
102
173
  }));
103
174
  }
@@ -110,23 +181,16 @@ export class MongoDBProvider extends BaseVectorProvider {
110
181
  await this.collection!.deleteMany({ namespace });
111
182
  }
112
183
 
113
- /**
114
- * Sanitise and flatten filter for MongoDB.
115
- * Strips internal engine fields and keywords.
116
- */
117
184
  protected sanitizeFilter(filter?: Record<string, unknown>): Record<string, unknown> {
118
185
  const sanitized = super.sanitizeFilter(filter);
119
186
  const mongoFilter: Record<string, unknown> = {};
120
187
 
121
188
  for (const [key, value] of Object.entries(sanitized)) {
122
- // Skip internal engine fields and generic keywords that don't exist in metadata
123
189
  if (key.startsWith('__') || key === 'keywords' || key === 'queryText') continue;
124
190
 
125
- // If the key is already a special key, keep it (e.g. namespace)
126
191
  if (key === 'namespace') {
127
192
  mongoFilter[key] = value;
128
193
  } else {
129
- // Assume all other non-prefixed fields are metadata fields
130
194
  mongoFilter[`${this.metadataKey}.${key}`] = value;
131
195
  }
132
196
  }
@@ -2,14 +2,11 @@ import { Pinecone, RecordMetadata } from '@pinecone-database/pinecone';
2
2
  import { VectorDBConfig } from '../../config/RagConfig';
3
3
  import { BaseVectorProvider } from './BaseVectorProvider';
4
4
  import { VectorMatch, UpsertDocument } from '../../types';
5
+ import { IProviderValidator, IProviderHealthChecker, HealthCheckResult } from '../../core/ProviderInterfaces';
6
+ import { ValidationError } from '../../core/ConfigValidator';
5
7
 
6
8
  /**
7
9
  * PineconeProvider — Pinecone vector database implementation.
8
- *
9
- * Compatible with @pinecone-database/pinecone v7.x SDK.
10
- *
11
- * Required options: { apiKey: string }
12
- * The `environment` option is no longer needed (removed in SDK v5+).
13
10
  */
14
11
  export class PineconeProvider extends BaseVectorProvider {
15
12
  private client!: Pinecone;
@@ -22,6 +19,64 @@ export class PineconeProvider extends BaseVectorProvider {
22
19
  this.apiKey = opts.apiKey;
23
20
  }
24
21
 
22
+ static getValidator(): IProviderValidator {
23
+ return {
24
+ validate(config: Record<string, unknown>): ValidationError[] {
25
+ const errors: ValidationError[] = [];
26
+ const opts = (config.options || {}) as Record<string, unknown>;
27
+
28
+ if (!opts.apiKey) {
29
+ errors.push({
30
+ field: 'vectorDb.options.apiKey',
31
+ message: 'Pinecone API key is required',
32
+ suggestion: 'Set PINECONE_API_KEY environment variable',
33
+ severity: 'error',
34
+ });
35
+ }
36
+ return errors;
37
+ }
38
+ };
39
+ }
40
+
41
+ static getHealthChecker(): IProviderHealthChecker {
42
+ return {
43
+ async check(config: Record<string, unknown>): Promise<HealthCheckResult> {
44
+ const opts = (config.options || {}) as Record<string, string>;
45
+ const indexName = config.indexName as string;
46
+ const timestamp = Date.now();
47
+
48
+ try {
49
+ const { Pinecone } = await import('@pinecone-database/pinecone');
50
+ const client = new Pinecone({ apiKey: opts.apiKey });
51
+ const indexes = await client.listIndexes();
52
+ const indexNames = indexes.indexes?.map((i) => i.name) ?? [];
53
+
54
+ if (!indexNames.includes(indexName)) {
55
+ return {
56
+ healthy: false,
57
+ provider: 'pinecone',
58
+ error: `Index "${indexName}" not found. Available: ${indexNames.join(', ')}`,
59
+ timestamp,
60
+ };
61
+ }
62
+ return {
63
+ healthy: true,
64
+ provider: 'pinecone',
65
+ capabilities: { indexes: indexNames.length, targetIndex: indexName },
66
+ timestamp,
67
+ };
68
+ } catch (error) {
69
+ return {
70
+ healthy: false,
71
+ provider: 'pinecone',
72
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
73
+ timestamp,
74
+ };
75
+ }
76
+ }
77
+ };
78
+ }
79
+
25
80
  async initialize(): Promise<void> {
26
81
  this.client = new Pinecone({ apiKey: this.apiKey });
27
82
  const indexes = await this.client.listIndexes();
@@ -2,6 +2,8 @@ import { Pool, PoolClient } from 'pg';
2
2
  import { VectorDBConfig } from '../../config/RagConfig';
3
3
  import { BaseVectorProvider } from './BaseVectorProvider';
4
4
  import { VectorMatch, UpsertDocument } from '../../types';
5
+ import { IProviderValidator, IProviderHealthChecker, HealthCheckResult } from '../../core/ProviderInterfaces';
6
+ import { ValidationError } from '../../core/ConfigValidator';
5
7
 
6
8
  /**
7
9
  * PostgreSQLProvider — PostgreSQL implementation using the pgvector extension.
@@ -21,6 +23,66 @@ export class PostgreSQLProvider extends BaseVectorProvider {
21
23
  this.dimensions = (opts.dimensions as number) ?? 1536;
22
24
  }
23
25
 
26
+ static getValidator(): IProviderValidator {
27
+ return {
28
+ validate(config: Record<string, unknown>): ValidationError[] {
29
+ const errors: ValidationError[] = [];
30
+ const opts = (config.options || {}) as Record<string, unknown>;
31
+
32
+ if (!opts.connectionString) {
33
+ errors.push({
34
+ field: 'vectorDb.options.connectionString',
35
+ message: 'PostgreSQL connection string is required',
36
+ suggestion: 'Set PGVECTOR_CONNECTION_STRING environment variable',
37
+ severity: 'error',
38
+ });
39
+ }
40
+
41
+ if (opts.tables && typeof opts.tables !== 'string' && !Array.isArray(opts.tables)) {
42
+ errors.push({
43
+ field: 'vectorDb.options.tables',
44
+ message: 'PostgreSQL tables must be a string or a string array',
45
+ severity: 'error',
46
+ });
47
+ }
48
+
49
+ return errors;
50
+ }
51
+ };
52
+ }
53
+
54
+ static getHealthChecker(): IProviderHealthChecker {
55
+ return {
56
+ async check(config: Record<string, unknown>): Promise<HealthCheckResult> {
57
+ const opts = (config.options || {}) as Record<string, string>;
58
+ const timestamp = Date.now();
59
+ try {
60
+ const { Client } = await import('pg');
61
+ const client = new Client({ connectionString: opts.connectionString });
62
+ await client.connect();
63
+ const result = await client.query(`
64
+ SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
65
+ `);
66
+ const hasVector = result.rows[0].exists;
67
+ await client.end();
68
+ return {
69
+ healthy: true,
70
+ provider: 'postgresql',
71
+ capabilities: { pgvectorInstalled: hasVector },
72
+ timestamp,
73
+ };
74
+ } catch (error) {
75
+ return {
76
+ healthy: false,
77
+ provider: 'postgresql',
78
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
79
+ timestamp,
80
+ };
81
+ }
82
+ }
83
+ };
84
+ }
85
+
24
86
  async initialize(): Promise<void> {
25
87
  this.pool = new Pool({ connectionString: this.connectionString });
26
88
  const client: PoolClient = await this.pool.connect();
@@ -115,21 +177,29 @@ export class PostgreSQLProvider extends BaseVectorProvider {
115
177
  whereClause = whereClause ? `${whereClause} AND ${filterConditions}` : `WHERE ${filterConditions}`;
116
178
  }
117
179
 
118
- const result = await this.pool.query(
119
- `SELECT id, content, metadata, 1 - (embedding <=> $1::vector) AS score
120
- FROM ${this.tableName}
121
- ${whereClause}
122
- ORDER BY embedding <=> $1::vector
123
- LIMIT $2`,
124
- params
125
- );
180
+ const client = await this.pool.connect();
181
+ try {
182
+ const efSearch = (this.config.options.efSearch as number) || Math.max(topK * 10, 40);
183
+ await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
126
184
 
127
- return result.rows.map((row: Record<string, unknown>) => ({
128
- id: String(row['id']),
129
- score: parseFloat(String(row['score'])),
130
- content: String(row['content']),
131
- metadata: row['metadata'] as Record<string, unknown>,
132
- }));
185
+ const result = await client.query(
186
+ `SELECT id, content, metadata, 1 - (embedding <=> $1::vector) AS score
187
+ FROM ${this.tableName}
188
+ ${whereClause}
189
+ ORDER BY embedding <=> $1::vector
190
+ LIMIT $2`,
191
+ params
192
+ );
193
+
194
+ return result.rows.map((row: Record<string, unknown>) => ({
195
+ id: String(row['id']),
196
+ score: parseFloat(String(row['score'])),
197
+ content: String(row['content']),
198
+ metadata: row['metadata'] as Record<string, unknown>,
199
+ }));
200
+ } finally {
201
+ client.release();
202
+ }
133
203
  }
134
204
 
135
205
  async delete(id: string | number, namespace?: string): Promise<void> {
@@ -112,6 +112,10 @@ export class QdrantProvider extends BaseVectorProvider {
112
112
  vector: vector,
113
113
  limit: topK,
114
114
  with_payload: true,
115
+ params: {
116
+ hnsw_ef: (this.config.options.efSearch as number) || Math.max(topK * 20, 128),
117
+ exact: false
118
+ },
115
119
  filter: namespace ? {
116
120
  must: [{ key: 'namespace', match: { value: namespace } }]
117
121
  } : undefined,
@@ -69,16 +69,20 @@ export class WeaviateProvider extends BaseVectorProvider {
69
69
  await this.http.post('/v1/batch/objects', payload);
70
70
  }
71
71
 
72
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
73
72
  async query(vector: number[], topK: number, namespace?: string, _filter?: Record<string, unknown>): Promise<VectorMatch[]> {
73
+ const sanitizedFilter = this.sanitizeFilter(_filter);
74
+ const queryText = sanitizedFilter.queryText as string;
75
+
76
+ const searchParams = queryText
77
+ ? `hybrid: { query: ${JSON.stringify(queryText)}, alpha: 0.5 }`
78
+ : `nearVector: { vector: ${JSON.stringify(vector)} }`;
79
+
74
80
  const graphqlQuery = {
75
81
  query: `
76
82
  {
77
83
  Get {
78
84
  ${this.indexName}(
79
- nearVector: {
80
- vector: ${JSON.stringify(vector)}
81
- }
85
+ ${searchParams}
82
86
  limit: ${topK}
83
87
  ${namespace ? `where: { path: ["namespace"], operator: Equal, valueString: "${namespace}" }` : ''}
84
88
  ) {