@retrivora-ai/rag-engine 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ChromaDBProvider-QNI7UCX4.mjs → ChromaDBProvider-2JZSBOQX.mjs} +1 -1
- package/dist/{MilvusProvider-OO6QGZDZ.mjs → MilvusProvider-CJDBCBVI.mjs} +1 -1
- package/dist/{PineconeProvider-ZRAFNFEC.mjs → PineconeProvider-E6L5Z2FO.mjs} +1 -1
- package/dist/{QdrantProvider-NYGHAA7C.mjs → QdrantProvider-JITRNJQN.mjs} +1 -1
- package/dist/{RagConfig-hBGXJmSx.d.mts → RagConfig-D_rSf8ep.d.mts} +1 -1
- package/dist/{RagConfig-hBGXJmSx.d.ts → RagConfig-D_rSf8ep.d.ts} +1 -1
- package/dist/{RedisProvider-ASONNYBI.mjs → RedisProvider-3VKFQXXD.mjs} +1 -1
- package/dist/UniversalVectorProvider-E6L4U4OX.mjs +9 -0
- package/dist/{WeaviateProvider-PSDCUGC7.mjs → WeaviateProvider-MXIPP44J.mjs} +1 -1
- package/dist/{chunk-5K23H7JL.mjs → chunk-26EMHLIN.mjs} +40 -2
- package/dist/{chunk-VPNRDXIA.mjs → chunk-7BQI4A5J.mjs} +17 -11
- package/dist/{chunk-ZM6TYIDH.mjs → chunk-MFWJZVF3.mjs} +3 -1
- package/dist/{chunk-V75V7BT2.mjs → chunk-TSX6DQXX.mjs} +2 -2
- package/dist/{chunk-HUGLYKD6.mjs → chunk-XZPVJS2B.mjs} +27 -9
- package/dist/chunk-Y6HQZDCJ.mjs +156 -0
- package/dist/{chunk-HSBXE2WV.mjs → chunk-YIYDJQJM.mjs} +745 -192
- package/dist/{chunk-7NXI6ZWX.mjs → chunk-YST6KYBJ.mjs} +8 -5
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +924 -626
- package/dist/handlers/index.mjs +1 -2
- package/dist/index-BJ8CUArE.d.mts +114 -0
- package/dist/index-DtNprGGj.d.ts +114 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/server.d.mts +575 -17
- package/dist/server.d.ts +575 -17
- package/dist/server.js +1280 -643
- package/dist/server.mjs +314 -16
- package/package.json +11 -2
- package/src/config/ConfigBuilder.ts +373 -0
- package/src/config/EmbeddingStrategy.ts +147 -0
- package/src/config/serverConfig.ts +51 -17
- package/src/core/ConfigValidator.ts +77 -52
- package/src/core/Pipeline.ts +28 -26
- package/src/core/PluginManager.ts +277 -0
- package/src/core/ProviderHealthCheck.ts +79 -140
- package/src/core/ProviderRegistry.ts +38 -15
- package/src/providers/vectordb/ChromaDBProvider.ts +37 -12
- package/src/providers/vectordb/MilvusProvider.ts +25 -10
- package/src/providers/vectordb/PineconeProvider.ts +17 -2
- package/src/providers/vectordb/QdrantProvider.ts +46 -2
- package/src/providers/vectordb/RedisProvider.ts +34 -11
- package/src/providers/vectordb/UniversalVectorProvider.ts +220 -0
- package/src/providers/vectordb/WeaviateProvider.ts +17 -10
- package/src/server.ts +28 -10
- package/dist/LLMFactory-JFOY2V4X.mjs +0 -8
- package/dist/chunk-JI6VD5TJ.mjs +0 -387
- package/dist/index-Bx182KKn.d.ts +0 -64
- package/dist/index-Ck2pt7-8.d.mts +0 -64
- package/src/test-refactor.ts +0 -59
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConfigBuilder — Fluent, type-safe configuration builder for RagConfig
|
|
3
|
+
*
|
|
4
|
+
* Simplifies host application setup with a builder pattern while maintaining
|
|
5
|
+
* type safety and validation.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Fluent API for easy configuration
|
|
9
|
+
* - Type-safe provider selection
|
|
10
|
+
* - Automatic environment variable resolution
|
|
11
|
+
* - Built-in validation
|
|
12
|
+
* - Support for presets
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const config = new ConfigBuilder()
|
|
16
|
+
* .vectorDb('pinecone', { apiKey: process.env.PINECONE_API_KEY })
|
|
17
|
+
* .llm('openai', 'gpt-4o', process.env.OPENAI_API_KEY)
|
|
18
|
+
* .embedding('openai', 'text-embedding-3-small')
|
|
19
|
+
* .projectId('my-app')
|
|
20
|
+
* .build();
|
|
21
|
+
*
|
|
22
|
+
* const plugin = new VectorPlugin(config);
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { RagConfig, VectorDBConfig, LLMConfig, EmbeddingConfig, VectorDBProvider, LLMProvider, EmbeddingProvider } from './RagConfig';
|
|
26
|
+
|
|
27
|
+
export class ConfigBuilder {
|
|
28
|
+
private config: Partial<RagConfig> = {
|
|
29
|
+
projectId: 'default-project',
|
|
30
|
+
rag: {
|
|
31
|
+
chunkSize: 1000,
|
|
32
|
+
chunkOverlap: 200,
|
|
33
|
+
topK: 5,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Set the project/application ID for namespacing
|
|
39
|
+
*/
|
|
40
|
+
projectId(id: string): this {
|
|
41
|
+
this.config.projectId = id;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Configure the vector database provider
|
|
47
|
+
*/
|
|
48
|
+
vectorDb(
|
|
49
|
+
provider: VectorDBProvider | 'universal-rest' | 'auto',
|
|
50
|
+
options?: Record<string, unknown>
|
|
51
|
+
): this {
|
|
52
|
+
if (provider === 'auto') {
|
|
53
|
+
// Auto-detect from environment
|
|
54
|
+
this.config.vectorDb = this.autoDetectVectorDb();
|
|
55
|
+
} else {
|
|
56
|
+
this.config.vectorDb = {
|
|
57
|
+
provider: provider as VectorDBProvider,
|
|
58
|
+
indexName: (options?.indexName as string) ?? 'default',
|
|
59
|
+
options: {
|
|
60
|
+
...options,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Configure the LLM provider for chat
|
|
69
|
+
*/
|
|
70
|
+
llm(
|
|
71
|
+
provider: LLMProvider | 'auto',
|
|
72
|
+
model?: string,
|
|
73
|
+
apiKey?: string,
|
|
74
|
+
options?: Record<string, unknown>
|
|
75
|
+
): this {
|
|
76
|
+
if (provider === 'auto') {
|
|
77
|
+
this.config.llm = this.autoDetectLLM();
|
|
78
|
+
} else {
|
|
79
|
+
this.config.llm = {
|
|
80
|
+
provider: provider as LLMProvider,
|
|
81
|
+
model: model ?? 'default-model',
|
|
82
|
+
apiKey,
|
|
83
|
+
systemPrompt: options?.systemPrompt as string | undefined,
|
|
84
|
+
maxTokens: (options?.maxTokens as number) ?? 1024,
|
|
85
|
+
temperature: (options?.temperature as number) ?? 0.7,
|
|
86
|
+
baseUrl: options?.baseUrl as string | undefined,
|
|
87
|
+
options,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Configure the embedding provider
|
|
95
|
+
*/
|
|
96
|
+
embedding(
|
|
97
|
+
provider: EmbeddingProvider | 'auto',
|
|
98
|
+
model?: string,
|
|
99
|
+
apiKey?: string,
|
|
100
|
+
options?: Record<string, unknown>
|
|
101
|
+
): this {
|
|
102
|
+
if (provider === 'auto') {
|
|
103
|
+
this.config.embedding = this.autoDetectEmbedding();
|
|
104
|
+
} else {
|
|
105
|
+
this.config.embedding = {
|
|
106
|
+
provider: provider as EmbeddingProvider,
|
|
107
|
+
model: model ?? 'default-embedding',
|
|
108
|
+
apiKey,
|
|
109
|
+
baseUrl: options?.baseUrl as string | undefined,
|
|
110
|
+
options,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Set RAG-specific parameters
|
|
118
|
+
*/
|
|
119
|
+
rag(options: {
|
|
120
|
+
chunkSize?: number;
|
|
121
|
+
chunkOverlap?: number;
|
|
122
|
+
topK?: number;
|
|
123
|
+
}): this {
|
|
124
|
+
this.config.rag = {
|
|
125
|
+
...(this.config.rag ?? {}),
|
|
126
|
+
...options,
|
|
127
|
+
};
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Set UI/UX parameters
|
|
133
|
+
*/
|
|
134
|
+
ui(options: {
|
|
135
|
+
branding?: string;
|
|
136
|
+
primaryColor?: string;
|
|
137
|
+
darkMode?: boolean;
|
|
138
|
+
}): this {
|
|
139
|
+
this.config.ui = options;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Build and return the configuration
|
|
147
|
+
*/
|
|
148
|
+
build(): RagConfig {
|
|
149
|
+
const finalConfig = this.config as RagConfig;
|
|
150
|
+
return finalConfig;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Build and return as JSON for serialization
|
|
155
|
+
*/
|
|
156
|
+
toJSON(): string {
|
|
157
|
+
return JSON.stringify(this.build());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Private helper methods for auto-detection
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
private autoDetectVectorDb(): VectorDBConfig {
|
|
165
|
+
// Check for Pinecone
|
|
166
|
+
if (process.env.PINECONE_API_KEY && process.env.PINECONE_INDEX) {
|
|
167
|
+
return {
|
|
168
|
+
provider: 'pinecone',
|
|
169
|
+
indexName: process.env.PINECONE_INDEX,
|
|
170
|
+
options: { apiKey: process.env.PINECONE_API_KEY },
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check for Qdrant
|
|
175
|
+
if (process.env.QDRANT_URL) {
|
|
176
|
+
return {
|
|
177
|
+
provider: 'qdrant',
|
|
178
|
+
indexName: process.env.QDRANT_COLLECTION || 'documents',
|
|
179
|
+
options: {
|
|
180
|
+
url: process.env.QDRANT_URL,
|
|
181
|
+
apiKey: process.env.QDRANT_API_KEY,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check for PostgreSQL/pgvector
|
|
187
|
+
if (process.env.DATABASE_URL) {
|
|
188
|
+
return {
|
|
189
|
+
provider: 'postgresql',
|
|
190
|
+
indexName: process.env.PG_TABLE || 'documents',
|
|
191
|
+
options: {
|
|
192
|
+
connectionString: process.env.DATABASE_URL,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check for MongoDB
|
|
198
|
+
if (process.env.MONGODB_URI) {
|
|
199
|
+
return {
|
|
200
|
+
provider: 'mongodb',
|
|
201
|
+
indexName: process.env.MONGODB_COLLECTION || 'documents',
|
|
202
|
+
options: {
|
|
203
|
+
uri: process.env.MONGODB_URI,
|
|
204
|
+
database: process.env.MONGODB_DB || 'ai_db',
|
|
205
|
+
collection: process.env.MONGODB_COLLECTION || 'documents',
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check for Redis
|
|
211
|
+
if (process.env.REDIS_URL) {
|
|
212
|
+
return {
|
|
213
|
+
provider: 'redis',
|
|
214
|
+
indexName: process.env.REDIS_INDEX || 'documents',
|
|
215
|
+
options: {
|
|
216
|
+
url: process.env.REDIS_URL,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Default to Qdrant localhost
|
|
222
|
+
return {
|
|
223
|
+
provider: 'qdrant',
|
|
224
|
+
indexName: 'documents',
|
|
225
|
+
options: {
|
|
226
|
+
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private autoDetectLLM(): LLMConfig {
|
|
232
|
+
// Check for OpenAI
|
|
233
|
+
if (process.env.OPENAI_API_KEY) {
|
|
234
|
+
return {
|
|
235
|
+
provider: 'openai',
|
|
236
|
+
model: process.env.OPENAI_MODEL || 'gpt-4o-mini',
|
|
237
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
238
|
+
maxTokens: parseInt(process.env.OPENAI_MAX_TOKENS || '1024'),
|
|
239
|
+
temperature: parseFloat(process.env.OPENAI_TEMPERATURE || '0.7'),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check for Anthropic
|
|
244
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
245
|
+
return {
|
|
246
|
+
provider: 'anthropic',
|
|
247
|
+
model: process.env.ANTHROPIC_MODEL || 'claude-3-haiku-20240307',
|
|
248
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
249
|
+
maxTokens: parseInt(process.env.ANTHROPIC_MAX_TOKENS || '1024'),
|
|
250
|
+
temperature: parseFloat(process.env.ANTHROPIC_TEMPERATURE || '0.7'),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check for Ollama
|
|
255
|
+
if (process.env.OLLAMA_BASE_URL) {
|
|
256
|
+
return {
|
|
257
|
+
provider: 'ollama',
|
|
258
|
+
model: process.env.OLLAMA_MODEL || 'mistral',
|
|
259
|
+
baseUrl: process.env.OLLAMA_BASE_URL,
|
|
260
|
+
maxTokens: parseInt(process.env.OLLAMA_MAX_TOKENS || '1024'),
|
|
261
|
+
temperature: parseFloat(process.env.OLLAMA_TEMPERATURE || '0.7'),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Default to OpenAI (requires API key at runtime)
|
|
266
|
+
return {
|
|
267
|
+
provider: 'openai',
|
|
268
|
+
model: 'gpt-4o-mini',
|
|
269
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private autoDetectEmbedding(): EmbeddingConfig {
|
|
274
|
+
// Check for dedicated embedding provider
|
|
275
|
+
if (process.env.EMBEDDING_PROVIDER) {
|
|
276
|
+
const provider = process.env.EMBEDDING_PROVIDER as EmbeddingProvider;
|
|
277
|
+
return {
|
|
278
|
+
provider,
|
|
279
|
+
model: process.env.EMBEDDING_MODEL || 'default',
|
|
280
|
+
apiKey: process.env.EMBEDDING_API_KEY,
|
|
281
|
+
baseUrl: process.env.EMBEDDING_BASE_URL,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Default to OpenAI embeddings
|
|
286
|
+
return {
|
|
287
|
+
provider: 'openai',
|
|
288
|
+
model: 'text-embedding-3-small',
|
|
289
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Preset configurations for common provider combinations
|
|
296
|
+
*/
|
|
297
|
+
export const PRESETS = {
|
|
298
|
+
/**
|
|
299
|
+
* OpenAI + Pinecone: Production-ready cloud setup
|
|
300
|
+
*/
|
|
301
|
+
'openai-pinecone': {
|
|
302
|
+
vectorDb: 'pinecone' as const,
|
|
303
|
+
llm: 'openai' as const,
|
|
304
|
+
embedding: 'openai' as const,
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Claude + Qdrant: Open-source vector DB + proprietary LLM
|
|
309
|
+
*/
|
|
310
|
+
'claude-qdrant': {
|
|
311
|
+
vectorDb: 'qdrant' as const,
|
|
312
|
+
llm: 'anthropic' as const,
|
|
313
|
+
embedding: 'openai' as const,
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Local development: Ollama + local Qdrant
|
|
318
|
+
*/
|
|
319
|
+
'local-dev': {
|
|
320
|
+
vectorDb: 'qdrant' as const,
|
|
321
|
+
llm: 'ollama' as const,
|
|
322
|
+
embedding: 'ollama' as const,
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Fully open-source: Ollama LLM + Qdrant vector DB + Ollama embeddings
|
|
327
|
+
*/
|
|
328
|
+
'fully-open-source': {
|
|
329
|
+
vectorDb: 'qdrant' as const,
|
|
330
|
+
llm: 'ollama' as const,
|
|
331
|
+
embedding: 'ollama' as const,
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* PostgreSQL stack: pgvector + OpenAI
|
|
336
|
+
*/
|
|
337
|
+
'postgres-openai': {
|
|
338
|
+
vectorDb: 'postgresql' as const,
|
|
339
|
+
llm: 'openai' as const,
|
|
340
|
+
embedding: 'openai' as const,
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Enterprise MongoDB: MongoDB Atlas with OpenAI
|
|
345
|
+
*/
|
|
346
|
+
'mongodb-openai': {
|
|
347
|
+
vectorDb: 'mongodb' as const,
|
|
348
|
+
llm: 'openai' as const,
|
|
349
|
+
embedding: 'openai' as const,
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Redis stack for caching + search
|
|
354
|
+
*/
|
|
355
|
+
'redis-openai': {
|
|
356
|
+
vectorDb: 'redis' as const,
|
|
357
|
+
llm: 'openai' as const,
|
|
358
|
+
embedding: 'openai' as const,
|
|
359
|
+
},
|
|
360
|
+
} as const;
|
|
361
|
+
|
|
362
|
+
export type PresetName = keyof typeof PRESETS;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Helper to create config from preset
|
|
366
|
+
*/
|
|
367
|
+
export function createFromPreset(presetName: PresetName): ConfigBuilder {
|
|
368
|
+
const preset = PRESETS[presetName];
|
|
369
|
+
return new ConfigBuilder()
|
|
370
|
+
.vectorDb(preset.vectorDb, { apiKey: process.env[`${preset.vectorDb.toUpperCase()}_API_KEY`] })
|
|
371
|
+
.llm(preset.llm, undefined, process.env[`${preset.llm.toUpperCase()}_API_KEY`])
|
|
372
|
+
.embedding(preset.embedding, undefined, process.env[`${preset.embedding.toUpperCase()}_API_KEY`]);
|
|
373
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmbeddingStrategy — Unified strategy for handling different embedding scenarios
|
|
3
|
+
*
|
|
4
|
+
* Automatically determines whether to:
|
|
5
|
+
* 1. Use the LLM provider's built-in embedding (integrated)
|
|
6
|
+
* 2. Use a separate embedding provider (fallback)
|
|
7
|
+
* 3. Use an external embedding service
|
|
8
|
+
*
|
|
9
|
+
* This removes special-case handling from Pipeline and consolidates embedding logic.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { ILLMProvider } from '../llm/ILLMProvider';
|
|
13
|
+
import { LLMFactory } from '../llm/LLMFactory';
|
|
14
|
+
import { LLMConfig, EmbeddingConfig, LLMProvider, EmbeddingProvider } from './RagConfig';
|
|
15
|
+
|
|
16
|
+
export enum EmbeddingStrategy {
|
|
17
|
+
/**
|
|
18
|
+
* LLM provider handles both chat and embeddings
|
|
19
|
+
* Example: OpenAI for both GPT and embeddings
|
|
20
|
+
*/
|
|
21
|
+
INTEGRATED = 'integrated',
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Use separate embedding provider different from LLM
|
|
25
|
+
* Example: Anthropic (chat) + OpenAI (embeddings)
|
|
26
|
+
*/
|
|
27
|
+
SEPARATE = 'separate',
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Use standalone embedding service
|
|
31
|
+
* Example: Dedicated embedding API
|
|
32
|
+
*/
|
|
33
|
+
EXTERNAL = 'external',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface EmbeddingStrategyResult {
|
|
37
|
+
strategy: EmbeddingStrategy;
|
|
38
|
+
embeddingProvider: ILLMProvider;
|
|
39
|
+
llmProvider: ILLMProvider;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Determines the optimal embedding strategy and initializes providers
|
|
44
|
+
*/
|
|
45
|
+
export class EmbeddingStrategyResolver {
|
|
46
|
+
/**
|
|
47
|
+
* Determine strategy based on LLM and embedding configs
|
|
48
|
+
*/
|
|
49
|
+
static determineStrategy(
|
|
50
|
+
llmConfig: LLMConfig,
|
|
51
|
+
embeddingConfig?: EmbeddingConfig
|
|
52
|
+
): EmbeddingStrategy {
|
|
53
|
+
// If embedding config not provided, use integrated strategy
|
|
54
|
+
if (!embeddingConfig) {
|
|
55
|
+
return this.supportsEmbedding(llmConfig.provider)
|
|
56
|
+
? EmbeddingStrategy.INTEGRATED
|
|
57
|
+
: EmbeddingStrategy.SEPARATE;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// If embedding config differs from LLM provider, use separate
|
|
61
|
+
if (embeddingConfig.provider !== (llmConfig.provider as EmbeddingProvider)) {
|
|
62
|
+
return EmbeddingStrategy.SEPARATE;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// If same provider but different models/configs, use external
|
|
66
|
+
if (embeddingConfig.model !== llmConfig.model) {
|
|
67
|
+
return EmbeddingStrategy.EXTERNAL;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Default to integrated
|
|
71
|
+
return EmbeddingStrategy.INTEGRATED;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve and initialize providers according to the strategy
|
|
76
|
+
*/
|
|
77
|
+
static async resolve(
|
|
78
|
+
llmConfig: LLMConfig,
|
|
79
|
+
embeddingConfig?: EmbeddingConfig
|
|
80
|
+
): Promise<EmbeddingStrategyResult> {
|
|
81
|
+
const strategy = this.determineStrategy(llmConfig, embeddingConfig);
|
|
82
|
+
|
|
83
|
+
const llmProvider = LLMFactory.create(llmConfig, embeddingConfig);
|
|
84
|
+
|
|
85
|
+
let embeddingProvider: ILLMProvider;
|
|
86
|
+
|
|
87
|
+
switch (strategy) {
|
|
88
|
+
case EmbeddingStrategy.INTEGRATED:
|
|
89
|
+
// Use the same provider for embeddings
|
|
90
|
+
embeddingProvider = llmProvider;
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case EmbeddingStrategy.SEPARATE:
|
|
94
|
+
// Create a separate embedding provider
|
|
95
|
+
if (!embeddingConfig) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'[EmbeddingStrategyResolver] SEPARATE strategy requires embeddingConfig'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
embeddingProvider = LLMFactory.createEmbeddingProvider(embeddingConfig);
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
case EmbeddingStrategy.EXTERNAL:
|
|
104
|
+
// Use the embedding config as a separate provider
|
|
105
|
+
if (!embeddingConfig) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
'[EmbeddingStrategyResolver] EXTERNAL strategy requires embeddingConfig'
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
embeddingProvider = LLMFactory.createEmbeddingProvider(embeddingConfig);
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
default:
|
|
114
|
+
throw new Error(`[EmbeddingStrategyResolver] Unknown strategy: ${strategy}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
strategy,
|
|
119
|
+
embeddingProvider,
|
|
120
|
+
llmProvider,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if an LLM provider natively supports embeddings
|
|
126
|
+
*/
|
|
127
|
+
private static supportsEmbedding(provider: LLMProvider | string): boolean {
|
|
128
|
+
const providersWithEmbedding = ['openai', 'ollama', 'rest', 'universal_rest'];
|
|
129
|
+
return providersWithEmbedding.includes(provider);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get a human-readable description of the strategy
|
|
134
|
+
*/
|
|
135
|
+
static getDescription(strategy: EmbeddingStrategy): string {
|
|
136
|
+
switch (strategy) {
|
|
137
|
+
case EmbeddingStrategy.INTEGRATED:
|
|
138
|
+
return 'Using LLM provider for both chat and embeddings';
|
|
139
|
+
case EmbeddingStrategy.SEPARATE:
|
|
140
|
+
return 'Using separate embedding provider';
|
|
141
|
+
case EmbeddingStrategy.EXTERNAL:
|
|
142
|
+
return 'Using external embedding service';
|
|
143
|
+
default:
|
|
144
|
+
return 'Unknown strategy';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -3,11 +3,28 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This keeps secrets server-side and never exposes them to the browser.
|
|
5
5
|
* Consumer projects set these env vars in their .env.local file.
|
|
6
|
+
*
|
|
7
|
+
* Supported VECTOR_DB_PROVIDER values and their required env vars:
|
|
8
|
+
* pinecone → PINECONE_API_KEY, PINECONE_INDEX
|
|
9
|
+
* pgvector /
|
|
10
|
+
* postgresql → PGVECTOR_CONNECTION_STRING, VECTOR_DB_INDEX
|
|
11
|
+
* mongodb → MONGODB_URI, MONGODB_DB, MONGODB_COLLECTION
|
|
12
|
+
* qdrant → QDRANT_URL, QDRANT_API_KEY
|
|
13
|
+
* milvus → MILVUS_URL (e.g. http://localhost:19530)
|
|
14
|
+
* chromadb → CHROMADB_URL (e.g. http://localhost:8000)
|
|
15
|
+
* weaviate → WEAVIATE_URL, WEAVIATE_API_KEY
|
|
16
|
+
* redis → REDIS_URL, REDIS_API_KEY
|
|
17
|
+
* rest /
|
|
18
|
+
* universal_rest→ VECTOR_BASE_URL, VECTOR_DB_REST_API_KEY
|
|
6
19
|
*/
|
|
7
20
|
|
|
8
21
|
import { RagConfig } from './RagConfig';
|
|
9
22
|
|
|
10
|
-
const VECTOR_DB_PROVIDERS = [
|
|
23
|
+
const VECTOR_DB_PROVIDERS = [
|
|
24
|
+
'pinecone', 'pgvector', 'postgresql', 'mongodb', 'milvus',
|
|
25
|
+
'qdrant', 'chromadb', 'redis', 'weaviate', 'rest', 'universal_rest', 'custom',
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
11
28
|
const LLM_PROVIDERS = ['openai', 'anthropic', 'ollama', 'rest', 'universal_rest', 'custom'] as const;
|
|
12
29
|
const EMBEDDING_PROVIDERS = ['openai', 'ollama', 'rest', 'universal_rest', 'custom'] as const;
|
|
13
30
|
|
|
@@ -19,12 +36,10 @@ function readString(env: Record<string, string | undefined>, name: string): stri
|
|
|
19
36
|
function readNumber(env: Record<string, string | undefined>, name: string, fallback: number): number {
|
|
20
37
|
const value = env[name];
|
|
21
38
|
if (!value) return fallback;
|
|
22
|
-
|
|
23
39
|
const parsed = Number(value);
|
|
24
40
|
if (!Number.isFinite(parsed)) {
|
|
25
41
|
throw new Error(`[getRagConfig] ${name} must be a valid number`);
|
|
26
42
|
}
|
|
27
|
-
|
|
28
43
|
return parsed;
|
|
29
44
|
}
|
|
30
45
|
|
|
@@ -38,12 +53,15 @@ function readEnum<T extends readonly string[]>(
|
|
|
38
53
|
if ((allowed as readonly string[]).includes(value)) {
|
|
39
54
|
return value as T[number];
|
|
40
55
|
}
|
|
41
|
-
|
|
42
56
|
throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(', ')}`);
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
export function getRagConfig(env: Record<string, string | undefined> = process.env): RagConfig {
|
|
46
|
-
const projectId =
|
|
60
|
+
const projectId =
|
|
61
|
+
readString(env, 'RAG_PROJECT_ID') ??
|
|
62
|
+
readString(env, 'NEXT_PUBLIC_PROJECT_ID') ??
|
|
63
|
+
'__default__';
|
|
64
|
+
|
|
47
65
|
const vectorProvider = readEnum(env, 'VECTOR_DB_PROVIDER', 'pinecone', VECTOR_DB_PROVIDERS);
|
|
48
66
|
const llmProvider = readEnum(env, 'LLM_PROVIDER', 'openai', LLM_PROVIDERS);
|
|
49
67
|
const embeddingProvider = readEnum(env, 'EMBEDDING_PROVIDER', 'openai', EMBEDDING_PROVIDERS);
|
|
@@ -51,31 +69,44 @@ export function getRagConfig(env: Record<string, string | undefined> = process.e
|
|
|
51
69
|
|
|
52
70
|
// Build provider-specific vectorDb options
|
|
53
71
|
const vectorDbOptions: Record<string, unknown> = {};
|
|
72
|
+
|
|
54
73
|
if (vectorProvider === 'pinecone') {
|
|
55
74
|
vectorDbOptions.apiKey = readString(env, 'PINECONE_API_KEY') ?? '';
|
|
56
|
-
|
|
57
|
-
} else if (vectorProvider === 'pgvector') {
|
|
75
|
+
} else if (vectorProvider === 'pgvector' || vectorProvider === 'postgresql') {
|
|
58
76
|
vectorDbOptions.connectionString = readString(env, 'PGVECTOR_CONNECTION_STRING') ?? '';
|
|
59
77
|
vectorDbOptions.dimensions = embeddingDimensions;
|
|
78
|
+
} else if (vectorProvider === 'mongodb') {
|
|
79
|
+
vectorDbOptions.uri = readString(env, 'MONGODB_URI') ?? '';
|
|
80
|
+
vectorDbOptions.database = readString(env, 'MONGODB_DB') ?? '';
|
|
81
|
+
vectorDbOptions.collection = readString(env, 'MONGODB_COLLECTION') ?? '';
|
|
82
|
+
vectorDbOptions.indexName = readString(env, 'MONGODB_INDEX_NAME') ?? 'vector_index';
|
|
83
|
+
} else if (vectorProvider === 'qdrant') {
|
|
84
|
+
vectorDbOptions.baseUrl = readString(env, 'QDRANT_URL') ?? 'http://localhost:6333';
|
|
85
|
+
vectorDbOptions.apiKey = readString(env, 'QDRANT_API_KEY');
|
|
86
|
+
vectorDbOptions.dimensions = embeddingDimensions;
|
|
87
|
+
} else if (vectorProvider === 'milvus') {
|
|
88
|
+
vectorDbOptions.baseUrl = readString(env, 'MILVUS_URL') ?? 'http://localhost:19530';
|
|
89
|
+
vectorDbOptions.apiKey = readString(env, 'MILVUS_API_KEY');
|
|
90
|
+
} else if (vectorProvider === 'chromadb') {
|
|
91
|
+
vectorDbOptions.baseUrl = readString(env, 'CHROMADB_URL') ?? 'http://localhost:8000';
|
|
92
|
+
} else if (vectorProvider === 'weaviate') {
|
|
93
|
+
vectorDbOptions.baseUrl = readString(env, 'WEAVIATE_URL') ?? 'http://localhost:8080';
|
|
94
|
+
vectorDbOptions.apiKey = readString(env, 'WEAVIATE_API_KEY');
|
|
95
|
+
} else if (vectorProvider === 'redis') {
|
|
96
|
+
vectorDbOptions.baseUrl = readString(env, 'REDIS_URL') ?? '';
|
|
97
|
+
vectorDbOptions.apiKey = readString(env, 'REDIS_API_KEY');
|
|
60
98
|
} else if (vectorProvider === 'rest') {
|
|
61
99
|
vectorDbOptions.baseUrl = readString(env, 'VECTOR_DB_REST_URL') ?? '';
|
|
62
100
|
vectorDbOptions.headers = readString(env, 'VECTOR_DB_REST_API_KEY')
|
|
63
101
|
? { 'api-key': readString(env, 'VECTOR_DB_REST_API_KEY') }
|
|
64
102
|
: {};
|
|
65
103
|
} else if (vectorProvider === 'universal_rest') {
|
|
66
|
-
vectorDbOptions.baseUrl =
|
|
104
|
+
vectorDbOptions.baseUrl =
|
|
105
|
+
readString(env, 'VECTOR_BASE_URL') ?? readString(env, 'VECTOR_DB_REST_URL') ?? '';
|
|
67
106
|
vectorDbOptions.profile = readString(env, 'VECTOR_UNIVERSAL_PROFILE');
|
|
68
107
|
vectorDbOptions.headers = readString(env, 'VECTOR_DB_REST_API_KEY')
|
|
69
108
|
? { Authorization: `Bearer ${readString(env, 'VECTOR_DB_REST_API_KEY')}` }
|
|
70
109
|
: {};
|
|
71
|
-
} else if (vectorProvider === 'mongodb') {
|
|
72
|
-
vectorDbOptions.uri = readString(env, 'MONGODB_URI') ?? '';
|
|
73
|
-
vectorDbOptions.database = readString(env, 'MONGODB_DB') ?? '';
|
|
74
|
-
vectorDbOptions.collection = readString(env, 'MONGODB_COLLECTION') ?? '';
|
|
75
|
-
vectorDbOptions.indexName = readString(env, 'MONGODB_INDEX_NAME') ?? 'vector_index';
|
|
76
|
-
} else if (vectorProvider === 'qdrant') {
|
|
77
|
-
vectorDbOptions.baseUrl = readString(env, 'QDRANT_URL') ?? 'http://localhost:6333';
|
|
78
|
-
vectorDbOptions.apiKey = readString(env, 'QDRANT_API_KEY');
|
|
79
110
|
}
|
|
80
111
|
|
|
81
112
|
const llmApiKeyByProvider: Record<string, string | undefined> = {
|
|
@@ -83,11 +114,14 @@ export function getRagConfig(env: Record<string, string | undefined> = process.e
|
|
|
83
114
|
anthropic: readString(env, 'ANTHROPIC_API_KEY'),
|
|
84
115
|
ollama: undefined,
|
|
85
116
|
universal_rest: readString(env, 'LLM_API_KEY'),
|
|
117
|
+
rest: readString(env, 'LLM_API_KEY'),
|
|
118
|
+
custom: readString(env, 'LLM_API_KEY'),
|
|
86
119
|
};
|
|
87
120
|
|
|
88
121
|
const embeddingApiKeyByProvider: Record<string, string | undefined> = {
|
|
89
122
|
openai: readString(env, 'OPENAI_API_KEY'),
|
|
90
123
|
ollama: undefined,
|
|
124
|
+
universal_rest: readString(env, 'EMBEDDING_API_KEY') ?? readString(env, 'OPENAI_API_KEY'),
|
|
91
125
|
custom: readString(env, 'EMBEDDING_API_KEY') ?? readString(env, 'OPENAI_API_KEY'),
|
|
92
126
|
};
|
|
93
127
|
|
|
@@ -131,7 +165,7 @@ export function getRagConfig(env: Record<string, string | undefined> = process.e
|
|
|
131
165
|
welcomeMessage:
|
|
132
166
|
readString(env, 'NEXT_PUBLIC_WELCOME_MESSAGE') ??
|
|
133
167
|
readString(env, 'UI_WELCOME_MESSAGE') ??
|
|
134
|
-
|
|
168
|
+
"Hello! I'm your AI assistant. Ask me anything about your documents.",
|
|
135
169
|
visualStyle: (readString(env, 'NEXT_PUBLIC_UI_VISUAL_STYLE') ?? readString(env, 'UI_VISUAL_STYLE') ?? 'glass') as 'glass' | 'solid',
|
|
136
170
|
borderRadius: (readString(env, 'NEXT_PUBLIC_UI_BORDER_RADIUS') ?? readString(env, 'UI_BORDER_RADIUS') ?? 'xl') as 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full',
|
|
137
171
|
},
|