@retrivora-ai/rag-engine 0.4.5 → 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.
- package/README.md +32 -57
- package/dist/{ChromaDBProvider-GI7TB7GJ.mjs → ChromaDBProvider-APQVJ5F7.mjs} +2 -2
- package/dist/{DocumentChunker-3yElxTO3.d.mts → DocumentChunker-C-sCZPhi.d.mts} +6 -6
- package/dist/{DocumentChunker-3yElxTO3.d.ts → DocumentChunker-C-sCZPhi.d.ts} +6 -6
- package/dist/{MilvusProvider-WDVTFB7D.mjs → MilvusProvider-35US67MS.mjs} +2 -2
- package/dist/{MongoDBProvider-RE3Q5S5B.mjs → MongoDBProvider-COVYZDP6.mjs} +2 -2
- package/dist/{PineconeProvider-BE2JWSPD.mjs → PineconeProvider-AWFJQDZL.mjs} +2 -2
- package/dist/{PostgreSQLProvider-5HHTK4SU.mjs → PostgreSQLProvider-IEYRJ7XJ.mjs} +2 -2
- package/dist/{QdrantProvider-XVDVBNIG.mjs → QdrantProvider-M6TQYZRO.mjs} +2 -2
- package/dist/{RagConfig-BgRDL9Vy.d.mts → RagConfig-DRJO4hGU.d.mts} +12 -1
- package/dist/{RagConfig-BgRDL9Vy.d.ts → RagConfig-DRJO4hGU.d.ts} +12 -1
- package/dist/{RedisProvider-EK2R2PQH.mjs → RedisProvider-3G5PBLZ4.mjs} +2 -2
- package/dist/{SimpleGraphProvider-M6T7SE7D.mjs → SimpleGraphProvider-UK7DJW37.mjs} +1 -1
- package/dist/{UniversalVectorProvider-YIDRX6VT.mjs → UniversalVectorProvider-FYQ3B2PW.mjs} +3 -3
- package/dist/{WeaviateProvider-4CAPQ7UY.mjs → WeaviateProvider-ITHO36IL.mjs} +2 -2
- package/dist/{chunk-5KNBWQM6.mjs → chunk-4A47RCG2.mjs} +5 -1
- package/dist/{chunk-EDLTMSNY.mjs → chunk-67AJ6SMD.mjs} +1 -1
- package/dist/{chunk-PQKTC73Y.mjs → chunk-7SOSCZGS.mjs} +67 -6
- package/dist/{chunk-LJWWPTWE.mjs → chunk-FLOSGE6A.mjs} +76 -14
- package/dist/{chunk-H6RKMU7W.mjs → chunk-NXUCKY5L.mjs} +1 -1
- package/dist/{chunk-KTS3LLHY.mjs → chunk-OOQXNLXD.mjs} +5 -5
- package/dist/{chunk-PRC5CZIZ.mjs → chunk-P4HAQ7KB.mjs} +1184 -1359
- package/dist/chunk-QMIKLALV.mjs +57 -0
- package/dist/{chunk-3QWAK3RZ.mjs → chunk-TYHTZIDP.mjs} +6 -2
- package/dist/{chunk-GQT5LF4G.mjs → chunk-U6KHVZLF.mjs} +2 -2
- package/dist/{chunk-RK2UDJA2.mjs → chunk-WGSZNY3X.mjs} +1 -1
- package/dist/{chunk-XCNXPECE.mjs → chunk-ZNBKHNJ4.mjs} +55 -1
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +1415 -1391
- package/dist/handlers/index.mjs +3 -3
- package/dist/index-CrGMwXfO.d.ts +112 -0
- package/dist/index-v669iV-k.d.mts +112 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +2 -2
- package/dist/server.d.mts +104 -158
- package/dist/server.d.ts +104 -158
- package/dist/server.js +1414 -1390
- package/dist/server.mjs +12 -12
- package/package.json +5 -1
- package/src/config/RagConfig.ts +7 -0
- package/src/core/ConfigValidator.ts +66 -492
- package/src/core/LangChainAgent.ts +78 -0
- package/src/core/Pipeline.ts +210 -240
- package/src/core/ProviderHealthCheck.ts +35 -406
- package/src/core/ProviderInterfaces.ts +37 -0
- package/src/core/ProviderRegistry.ts +70 -55
- package/src/core/QueryProcessor.ts +173 -0
- package/src/llm/ILLMProvider.ts +10 -0
- package/src/llm/LLMFactory.ts +33 -13
- package/src/llm/providers/AnthropicProvider.ts +55 -15
- package/src/llm/providers/GeminiProvider.ts +51 -0
- package/src/llm/providers/OllamaProvider.ts +100 -15
- package/src/llm/providers/OpenAIProvider.ts +60 -11
- package/src/providers/vectordb/BaseVectorProvider.ts +11 -0
- package/src/providers/vectordb/MilvusProvider.ts +4 -0
- package/src/providers/vectordb/MongoDBProvider.ts +72 -8
- package/src/providers/vectordb/PineconeProvider.ts +60 -5
- package/src/providers/vectordb/PostgreSQLProvider.ts +84 -14
- package/src/providers/vectordb/QdrantProvider.ts +4 -0
- package/src/providers/vectordb/WeaviateProvider.ts +8 -4
- package/src/rag/DocumentChunker.ts +15 -19
- package/src/rag/LlamaIndexIngestor.ts +61 -0
- package/src/rag/Reranker.ts +20 -0
- package/src/server.ts +1 -1
- package/src/types/index.ts +9 -0
- package/dist/chunk-FWCSY2DS.mjs +0 -37
- package/dist/index-7qeLTPBL.d.mts +0 -114
- 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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
:
|
|
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 *
|
|
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 } : {}) } }
|
|
@@ -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
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
80
|
-
vector: ${JSON.stringify(vector)}
|
|
81
|
-
}
|
|
85
|
+
${searchParams}
|
|
82
86
|
limit: ${topK}
|
|
83
87
|
${namespace ? `where: { path: ["namespace"], operator: Equal, valueString: "${namespace}" }` : ''}
|
|
84
88
|
) {
|