@retrivora-ai/rag-engine 0.3.1 → 0.3.3
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/.env.example +5 -2
- package/dist/{RagConfig-D3Inaf9N.d.mts → RagConfig-Bpp39-um.d.mts} +14 -3
- package/dist/{RagConfig-D3Inaf9N.d.ts → RagConfig-Bpp39-um.d.ts} +14 -3
- package/dist/{chunk-GT72OIOD.mjs → chunk-SNPFZBDK.mjs} +151 -11
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +151 -11
- package/dist/handlers/index.mjs +1 -1
- package/dist/{index-Ymwm-_OR.d.mts → index-CZE72wnV.d.mts} +1 -1
- package/dist/{index-BhNJQ2SS.d.ts → index-DSaQwkKA.d.ts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/server.d.mts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +151 -11
- package/dist/server.mjs +1 -1
- package/package.json +2 -1
- package/src/config/EmbeddingStrategy.ts +2 -2
- package/src/config/RagConfig.ts +9 -3
- package/src/config/constants.ts +52 -0
- package/src/config/serverConfig.ts +7 -8
- package/src/core/ConfigValidator.ts +27 -6
- package/src/llm/LLMFactory.ts +3 -0
- package/src/llm/providers/GeminiProvider.ts +147 -0
package/dist/server.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as RagConfig, C as ChatResponse, I as IngestDocument, c as VectorDBConfig, b as UpsertDocument, V as VectorMatch, L as LLMConfig, E as EmbeddingConfig, d as VectorDBProvider, e as LLMProvider, f as EmbeddingProvider } from './RagConfig-
|
|
2
|
-
export { R as RAGConfig, U as UIConfig } from './RagConfig-
|
|
1
|
+
import { a as RagConfig, C as ChatResponse, I as IngestDocument, c as VectorDBConfig, b as UpsertDocument, V as VectorMatch, L as LLMConfig, E as EmbeddingConfig, d as VectorDBProvider, e as LLMProvider, f as EmbeddingProvider } from './RagConfig-Bpp39-um.js';
|
|
2
|
+
export { R as RAGConfig, U as UIConfig } from './RagConfig-Bpp39-um.js';
|
|
3
3
|
import { C as ChatMessage, I as ILLMProvider, a as ChatOptions, E as EmbedOptions } from './DocumentChunker-BICIjSuG.js';
|
|
4
4
|
export { b as Chunk, c as ChunkOptions, D as DocumentChunker } from './DocumentChunker-BICIjSuG.js';
|
|
5
|
-
import { H as HealthCheckResult } from './index-
|
|
6
|
-
export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-
|
|
5
|
+
import { H as HealthCheckResult } from './index-DSaQwkKA.js';
|
|
6
|
+
export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-DSaQwkKA.js';
|
|
7
7
|
import 'next/server';
|
|
8
8
|
|
|
9
9
|
/**
|
package/dist/server.js
CHANGED
|
@@ -1223,7 +1223,7 @@ module.exports = __toCommonJS(server_exports);
|
|
|
1223
1223
|
// src/core/ConfigResolver.ts
|
|
1224
1224
|
init_templateUtils();
|
|
1225
1225
|
|
|
1226
|
-
// src/config/
|
|
1226
|
+
// src/config/constants.ts
|
|
1227
1227
|
var VECTOR_DB_PROVIDERS = [
|
|
1228
1228
|
"pinecone",
|
|
1229
1229
|
"pgvector",
|
|
@@ -1238,8 +1238,34 @@ var VECTOR_DB_PROVIDERS = [
|
|
|
1238
1238
|
"universal_rest",
|
|
1239
1239
|
"custom"
|
|
1240
1240
|
];
|
|
1241
|
-
var LLM_PROVIDERS = [
|
|
1242
|
-
|
|
1241
|
+
var LLM_PROVIDERS = [
|
|
1242
|
+
"openai",
|
|
1243
|
+
"anthropic",
|
|
1244
|
+
"ollama",
|
|
1245
|
+
"gemini",
|
|
1246
|
+
"rest",
|
|
1247
|
+
"universal_rest",
|
|
1248
|
+
"custom"
|
|
1249
|
+
];
|
|
1250
|
+
var EMBEDDING_PROVIDERS = [
|
|
1251
|
+
"openai",
|
|
1252
|
+
"ollama",
|
|
1253
|
+
"gemini",
|
|
1254
|
+
"rest",
|
|
1255
|
+
"universal_rest",
|
|
1256
|
+
"custom"
|
|
1257
|
+
];
|
|
1258
|
+
var PROVIDERS_WITH_EMBEDDINGS = [
|
|
1259
|
+
"openai",
|
|
1260
|
+
"ollama",
|
|
1261
|
+
"gemini",
|
|
1262
|
+
"rest",
|
|
1263
|
+
"universal_rest"
|
|
1264
|
+
];
|
|
1265
|
+
var UI_VISUAL_STYLES = ["glass", "solid"];
|
|
1266
|
+
var UI_BORDER_RADIUS_OPTIONS = ["none", "sm", "md", "lg", "xl", "full"];
|
|
1267
|
+
|
|
1268
|
+
// src/config/serverConfig.ts
|
|
1243
1269
|
function readString(env, name) {
|
|
1244
1270
|
var _a;
|
|
1245
1271
|
const value = (_a = env[name]) == null ? void 0 : _a.trim();
|
|
@@ -1306,6 +1332,7 @@ function getRagConfig(env = process.env) {
|
|
|
1306
1332
|
const llmApiKeyByProvider = {
|
|
1307
1333
|
openai: readString(env, "OPENAI_API_KEY"),
|
|
1308
1334
|
anthropic: readString(env, "ANTHROPIC_API_KEY"),
|
|
1335
|
+
gemini: readString(env, "GEMINI_API_KEY"),
|
|
1309
1336
|
ollama: void 0,
|
|
1310
1337
|
universal_rest: readString(env, "LLM_API_KEY"),
|
|
1311
1338
|
rest: readString(env, "LLM_API_KEY"),
|
|
@@ -1313,6 +1340,7 @@ function getRagConfig(env = process.env) {
|
|
|
1313
1340
|
};
|
|
1314
1341
|
const embeddingApiKeyByProvider = {
|
|
1315
1342
|
openai: readString(env, "OPENAI_API_KEY"),
|
|
1343
|
+
gemini: readString(env, "GEMINI_API_KEY"),
|
|
1316
1344
|
ollama: void 0,
|
|
1317
1345
|
universal_rest: (_q = readString(env, "EMBEDDING_API_KEY")) != null ? _q : readString(env, "OPENAI_API_KEY"),
|
|
1318
1346
|
custom: (_r = readString(env, "EMBEDDING_API_KEY")) != null ? _r : readString(env, "OPENAI_API_KEY")
|
|
@@ -1727,6 +1755,16 @@ var ConfigValidator = class {
|
|
|
1727
1755
|
});
|
|
1728
1756
|
}
|
|
1729
1757
|
break;
|
|
1758
|
+
case "gemini":
|
|
1759
|
+
if (!config.apiKey) {
|
|
1760
|
+
errors.push({
|
|
1761
|
+
field: "llm.apiKey",
|
|
1762
|
+
message: "Gemini API key is required",
|
|
1763
|
+
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
1764
|
+
severity: "error"
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
break;
|
|
1730
1768
|
case "ollama":
|
|
1731
1769
|
if (!config.baseUrl) {
|
|
1732
1770
|
errors.push({
|
|
@@ -1793,6 +1831,14 @@ var ConfigValidator = class {
|
|
|
1793
1831
|
severity: "error"
|
|
1794
1832
|
});
|
|
1795
1833
|
}
|
|
1834
|
+
if (config.provider === "gemini" && !config.apiKey) {
|
|
1835
|
+
errors.push({
|
|
1836
|
+
field: "embedding.apiKey",
|
|
1837
|
+
message: "Gemini API key is required for embedding",
|
|
1838
|
+
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
1839
|
+
severity: "error"
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1796
1842
|
if (config.provider === "ollama" && !config.baseUrl) {
|
|
1797
1843
|
errors.push({
|
|
1798
1844
|
field: "embedding.baseUrl",
|
|
@@ -1823,21 +1869,19 @@ var ConfigValidator = class {
|
|
|
1823
1869
|
}
|
|
1824
1870
|
}
|
|
1825
1871
|
if (config.borderRadius) {
|
|
1826
|
-
|
|
1827
|
-
if (!validValues.includes(config.borderRadius)) {
|
|
1872
|
+
if (!UI_BORDER_RADIUS_OPTIONS.includes(config.borderRadius)) {
|
|
1828
1873
|
errors.push({
|
|
1829
1874
|
field: "ui.borderRadius",
|
|
1830
|
-
message: `borderRadius must be one of: ${
|
|
1875
|
+
message: `borderRadius must be one of: ${UI_BORDER_RADIUS_OPTIONS.join(", ")}`,
|
|
1831
1876
|
severity: "warning"
|
|
1832
1877
|
});
|
|
1833
1878
|
}
|
|
1834
1879
|
}
|
|
1835
1880
|
if (config.visualStyle) {
|
|
1836
|
-
|
|
1837
|
-
if (!validValues.includes(config.visualStyle)) {
|
|
1881
|
+
if (!UI_VISUAL_STYLES.includes(config.visualStyle)) {
|
|
1838
1882
|
errors.push({
|
|
1839
1883
|
field: "ui.visualStyle",
|
|
1840
|
-
message: `visualStyle must be one of: ${
|
|
1884
|
+
message: `visualStyle must be one of: ${UI_VISUAL_STYLES.join(", ")}`,
|
|
1841
1885
|
severity: "warning"
|
|
1842
1886
|
});
|
|
1843
1887
|
}
|
|
@@ -2171,6 +2215,101 @@ ${context}`;
|
|
|
2171
2215
|
}
|
|
2172
2216
|
};
|
|
2173
2217
|
|
|
2218
|
+
// src/llm/providers/GeminiProvider.ts
|
|
2219
|
+
var import_genai = require("@google/genai");
|
|
2220
|
+
var GeminiProvider = class {
|
|
2221
|
+
constructor(llmConfig, embeddingConfig) {
|
|
2222
|
+
if (!llmConfig.apiKey) throw new Error("[GeminiProvider] llmConfig.apiKey is required");
|
|
2223
|
+
this.client = new import_genai.GoogleGenAI({ apiKey: llmConfig.apiKey });
|
|
2224
|
+
this.llmConfig = llmConfig;
|
|
2225
|
+
this.embeddingConfig = embeddingConfig;
|
|
2226
|
+
}
|
|
2227
|
+
async chat(messages, context, options) {
|
|
2228
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2229
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
2230
|
+
|
|
2231
|
+
Context:
|
|
2232
|
+
${context}`;
|
|
2233
|
+
const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2234
|
+
|
|
2235
|
+
Context:
|
|
2236
|
+
${context}`;
|
|
2237
|
+
const geminiMessages = messages.map((m) => ({
|
|
2238
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
2239
|
+
parts: [{ text: m.content }]
|
|
2240
|
+
}));
|
|
2241
|
+
const response = await this.client.models.generateContent({
|
|
2242
|
+
model: this.llmConfig.model,
|
|
2243
|
+
contents: geminiMessages,
|
|
2244
|
+
config: {
|
|
2245
|
+
systemInstruction,
|
|
2246
|
+
temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
|
|
2247
|
+
maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
|
|
2248
|
+
stopSequences: options == null ? void 0 : options.stop
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
return (_f = response.text) != null ? _f : "";
|
|
2252
|
+
}
|
|
2253
|
+
async embed(text, options) {
|
|
2254
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
2255
|
+
const model = (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "text-embedding-004";
|
|
2256
|
+
const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
|
|
2257
|
+
const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
|
|
2258
|
+
let content = text;
|
|
2259
|
+
const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
|
|
2260
|
+
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
2261
|
+
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
2262
|
+
if (!content.startsWith(queryPrefix)) {
|
|
2263
|
+
content = `${queryPrefix}${text}`;
|
|
2264
|
+
}
|
|
2265
|
+
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
2266
|
+
if (!content.startsWith(docPrefix)) {
|
|
2267
|
+
content = `${docPrefix}${text}`;
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
const response = await client.models.embedContent({
|
|
2271
|
+
model,
|
|
2272
|
+
contents: content
|
|
2273
|
+
});
|
|
2274
|
+
return (_j = (_i = (_h = response.embeddings) == null ? void 0 : _h[0]) == null ? void 0 : _i.values) != null ? _j : [];
|
|
2275
|
+
}
|
|
2276
|
+
async batchEmbed(texts, options) {
|
|
2277
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2278
|
+
const vectors = [];
|
|
2279
|
+
const model = (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "text-embedding-004";
|
|
2280
|
+
const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
|
|
2281
|
+
const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
|
|
2282
|
+
let contents = texts;
|
|
2283
|
+
const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
|
|
2284
|
+
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
2285
|
+
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
2286
|
+
contents = texts.map((text) => text.startsWith(queryPrefix) ? text : `${queryPrefix}${text}`);
|
|
2287
|
+
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
2288
|
+
contents = texts.map((text) => text.startsWith(docPrefix) ? text : `${docPrefix}${text}`);
|
|
2289
|
+
}
|
|
2290
|
+
const response = await client.models.embedContent({
|
|
2291
|
+
model,
|
|
2292
|
+
contents
|
|
2293
|
+
});
|
|
2294
|
+
return (_i = (_h = response.embeddings) == null ? void 0 : _h.map((e) => {
|
|
2295
|
+
var _a2;
|
|
2296
|
+
return (_a2 = e.values) != null ? _a2 : [];
|
|
2297
|
+
})) != null ? _i : [];
|
|
2298
|
+
}
|
|
2299
|
+
async ping() {
|
|
2300
|
+
try {
|
|
2301
|
+
await this.client.models.embedContent({
|
|
2302
|
+
model: "text-embedding-004",
|
|
2303
|
+
contents: "ping"
|
|
2304
|
+
});
|
|
2305
|
+
return true;
|
|
2306
|
+
} catch (err) {
|
|
2307
|
+
console.error("[GeminiProvider] Ping failed:", err);
|
|
2308
|
+
return false;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
|
|
2174
2313
|
// src/llm/providers/UniversalLLMAdapter.ts
|
|
2175
2314
|
var import_axios2 = __toESM(require("axios"));
|
|
2176
2315
|
init_templateUtils();
|
|
@@ -2368,6 +2507,8 @@ var LLMFactory = class _LLMFactory {
|
|
|
2368
2507
|
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
2369
2508
|
case "ollama":
|
|
2370
2509
|
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
2510
|
+
case "gemini":
|
|
2511
|
+
return new GeminiProvider(llmConfig, embeddingConfig);
|
|
2371
2512
|
case "rest":
|
|
2372
2513
|
case "universal_rest":
|
|
2373
2514
|
case "custom":
|
|
@@ -2759,8 +2900,7 @@ var EmbeddingStrategyResolver = class {
|
|
|
2759
2900
|
* Check if an LLM provider natively supports embeddings
|
|
2760
2901
|
*/
|
|
2761
2902
|
static supportsEmbedding(provider) {
|
|
2762
|
-
|
|
2763
|
-
return providersWithEmbedding.includes(provider);
|
|
2903
|
+
return PROVIDERS_WITH_EMBEDDINGS.includes(provider);
|
|
2764
2904
|
}
|
|
2765
2905
|
/**
|
|
2766
2906
|
* Get a human-readable description of the strategy
|
package/dist/server.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
87
|
"@anthropic-ai/sdk": "^0.90.0",
|
|
88
|
+
"@google/genai": "^1.50.1",
|
|
88
89
|
"@pinecone-database/pinecone": "^7.2.0",
|
|
89
90
|
"axios": "^1.15.0",
|
|
90
91
|
"lucide-react": "^1.8.0",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { ILLMProvider } from '../llm/ILLMProvider';
|
|
13
13
|
import { LLMFactory } from '../llm/LLMFactory';
|
|
14
14
|
import { LLMConfig, EmbeddingConfig, LLMProvider, EmbeddingProvider } from './RagConfig';
|
|
15
|
+
import { PROVIDERS_WITH_EMBEDDINGS } from './constants';
|
|
15
16
|
|
|
16
17
|
export enum EmbeddingStrategy {
|
|
17
18
|
/**
|
|
@@ -125,8 +126,7 @@ export class EmbeddingStrategyResolver {
|
|
|
125
126
|
* Check if an LLM provider natively supports embeddings
|
|
126
127
|
*/
|
|
127
128
|
private static supportsEmbedding(provider: LLMProvider | string): boolean {
|
|
128
|
-
|
|
129
|
-
return providersWithEmbedding.includes(provider);
|
|
129
|
+
return (PROVIDERS_WITH_EMBEDDINGS as readonly string[]).includes(provider);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/**
|
package/src/config/RagConfig.ts
CHANGED
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
// Vector DB
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import {
|
|
12
|
+
VECTOR_DB_PROVIDERS,
|
|
13
|
+
LLM_PROVIDERS,
|
|
14
|
+
EMBEDDING_PROVIDERS,
|
|
15
|
+
} from './constants';
|
|
16
|
+
|
|
17
|
+
export type VectorDBProvider = typeof VECTOR_DB_PROVIDERS[number];
|
|
12
18
|
|
|
13
19
|
export interface VectorDBConfig {
|
|
14
20
|
/** Which vector database to use */
|
|
@@ -40,7 +46,7 @@ export interface VectorDBConfig {
|
|
|
40
46
|
// LLM Provider
|
|
41
47
|
// ---------------------------------------------------------------------------
|
|
42
48
|
|
|
43
|
-
export type LLMProvider =
|
|
49
|
+
export type LLMProvider = typeof LLM_PROVIDERS[number];
|
|
44
50
|
|
|
45
51
|
export interface LLMConfig {
|
|
46
52
|
/** Which LLM provider to use */
|
|
@@ -72,7 +78,7 @@ export interface LLMConfig {
|
|
|
72
78
|
// Embedding
|
|
73
79
|
// ---------------------------------------------------------------------------
|
|
74
80
|
|
|
75
|
-
export type EmbeddingProvider =
|
|
81
|
+
export type EmbeddingProvider = typeof EMBEDDING_PROVIDERS[number];
|
|
76
82
|
|
|
77
83
|
export interface EmbeddingConfig {
|
|
78
84
|
/** Which embedding provider to use */
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* constants.ts — Centralized definitions of acceptable configuration values.
|
|
3
|
+
*
|
|
4
|
+
* This file serves as the single source of truth for all statically supported
|
|
5
|
+
* providers and stylistic options in the Retrivora AI RAG Engine.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const VECTOR_DB_PROVIDERS = [
|
|
9
|
+
'pinecone',
|
|
10
|
+
'pgvector',
|
|
11
|
+
'postgresql',
|
|
12
|
+
'mongodb',
|
|
13
|
+
'milvus',
|
|
14
|
+
'qdrant',
|
|
15
|
+
'chromadb',
|
|
16
|
+
'redis',
|
|
17
|
+
'weaviate',
|
|
18
|
+
'rest',
|
|
19
|
+
'universal_rest',
|
|
20
|
+
'custom',
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
export const LLM_PROVIDERS = [
|
|
24
|
+
'openai',
|
|
25
|
+
'anthropic',
|
|
26
|
+
'ollama',
|
|
27
|
+
'gemini',
|
|
28
|
+
'rest',
|
|
29
|
+
'universal_rest',
|
|
30
|
+
'custom',
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export const EMBEDDING_PROVIDERS = [
|
|
34
|
+
'openai',
|
|
35
|
+
'ollama',
|
|
36
|
+
'gemini',
|
|
37
|
+
'rest',
|
|
38
|
+
'universal_rest',
|
|
39
|
+
'custom',
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
export const PROVIDERS_WITH_EMBEDDINGS = [
|
|
43
|
+
'openai',
|
|
44
|
+
'ollama',
|
|
45
|
+
'gemini',
|
|
46
|
+
'rest',
|
|
47
|
+
'universal_rest',
|
|
48
|
+
] as const;
|
|
49
|
+
|
|
50
|
+
export const UI_VISUAL_STYLES = ['glass', 'solid'] as const;
|
|
51
|
+
|
|
52
|
+
export const UI_BORDER_RADIUS_OPTIONS = ['none', 'sm', 'md', 'lg', 'xl', 'full'] as const;
|
|
@@ -19,14 +19,11 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { RagConfig } from './RagConfig';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const LLM_PROVIDERS = ['openai', 'anthropic', 'ollama', 'rest', 'universal_rest', 'custom'] as const;
|
|
29
|
-
const EMBEDDING_PROVIDERS = ['openai', 'ollama', 'rest', 'universal_rest', 'custom'] as const;
|
|
22
|
+
import {
|
|
23
|
+
VECTOR_DB_PROVIDERS,
|
|
24
|
+
LLM_PROVIDERS,
|
|
25
|
+
EMBEDDING_PROVIDERS,
|
|
26
|
+
} from './constants';
|
|
30
27
|
|
|
31
28
|
function readString(env: Record<string, string | undefined>, name: string): string | undefined {
|
|
32
29
|
const value = env[name]?.trim();
|
|
@@ -112,6 +109,7 @@ export function getRagConfig(env: Record<string, string | undefined> = process.e
|
|
|
112
109
|
const llmApiKeyByProvider: Record<string, string | undefined> = {
|
|
113
110
|
openai: readString(env, 'OPENAI_API_KEY'),
|
|
114
111
|
anthropic: readString(env, 'ANTHROPIC_API_KEY'),
|
|
112
|
+
gemini: readString(env, 'GEMINI_API_KEY'),
|
|
115
113
|
ollama: undefined,
|
|
116
114
|
universal_rest: readString(env, 'LLM_API_KEY'),
|
|
117
115
|
rest: readString(env, 'LLM_API_KEY'),
|
|
@@ -120,6 +118,7 @@ export function getRagConfig(env: Record<string, string | undefined> = process.e
|
|
|
120
118
|
|
|
121
119
|
const embeddingApiKeyByProvider: Record<string, string | undefined> = {
|
|
122
120
|
openai: readString(env, 'OPENAI_API_KEY'),
|
|
121
|
+
gemini: readString(env, 'GEMINI_API_KEY'),
|
|
123
122
|
ollama: undefined,
|
|
124
123
|
universal_rest: readString(env, 'EMBEDDING_API_KEY') ?? readString(env, 'OPENAI_API_KEY'),
|
|
125
124
|
custom: readString(env, 'EMBEDDING_API_KEY') ?? readString(env, 'OPENAI_API_KEY'),
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { RagConfig, VectorDBConfig, LLMConfig, EmbeddingConfig } from '../config/RagConfig';
|
|
13
|
+
import {
|
|
14
|
+
UI_VISUAL_STYLES,
|
|
15
|
+
UI_BORDER_RADIUS_OPTIONS,
|
|
16
|
+
} from '../config/constants';
|
|
13
17
|
|
|
14
18
|
export interface ValidationError {
|
|
15
19
|
field: string;
|
|
@@ -380,6 +384,16 @@ export class ConfigValidator {
|
|
|
380
384
|
});
|
|
381
385
|
}
|
|
382
386
|
break;
|
|
387
|
+
case 'gemini':
|
|
388
|
+
if (!config.apiKey) {
|
|
389
|
+
errors.push({
|
|
390
|
+
field: 'llm.apiKey',
|
|
391
|
+
message: 'Gemini API key is required',
|
|
392
|
+
suggestion: 'Set GEMINI_API_KEY environment variable',
|
|
393
|
+
severity: 'error',
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
383
397
|
case 'ollama':
|
|
384
398
|
if (!config.baseUrl) {
|
|
385
399
|
errors.push({
|
|
@@ -454,6 +468,15 @@ export class ConfigValidator {
|
|
|
454
468
|
});
|
|
455
469
|
}
|
|
456
470
|
|
|
471
|
+
if (config.provider === 'gemini' && !config.apiKey) {
|
|
472
|
+
errors.push({
|
|
473
|
+
field: 'embedding.apiKey',
|
|
474
|
+
message: 'Gemini API key is required for embedding',
|
|
475
|
+
suggestion: 'Set GEMINI_API_KEY environment variable',
|
|
476
|
+
severity: 'error',
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
457
480
|
if (config.provider === 'ollama' && !config.baseUrl) {
|
|
458
481
|
errors.push({
|
|
459
482
|
field: 'embedding.baseUrl',
|
|
@@ -489,22 +512,20 @@ export class ConfigValidator {
|
|
|
489
512
|
}
|
|
490
513
|
|
|
491
514
|
if (config.borderRadius) {
|
|
492
|
-
|
|
493
|
-
if (!validValues.includes(config.borderRadius as string)) {
|
|
515
|
+
if (!UI_BORDER_RADIUS_OPTIONS.includes(config.borderRadius as any)) {
|
|
494
516
|
errors.push({
|
|
495
517
|
field: 'ui.borderRadius',
|
|
496
|
-
message: `borderRadius must be one of: ${
|
|
518
|
+
message: `borderRadius must be one of: ${UI_BORDER_RADIUS_OPTIONS.join(', ')}`,
|
|
497
519
|
severity: 'warning',
|
|
498
520
|
});
|
|
499
521
|
}
|
|
500
522
|
}
|
|
501
523
|
|
|
502
524
|
if (config.visualStyle) {
|
|
503
|
-
|
|
504
|
-
if (!validValues.includes(config.visualStyle as string)) {
|
|
525
|
+
if (!UI_VISUAL_STYLES.includes(config.visualStyle as any)) {
|
|
505
526
|
errors.push({
|
|
506
527
|
field: 'ui.visualStyle',
|
|
507
|
-
message: `visualStyle must be one of: ${
|
|
528
|
+
message: `visualStyle must be one of: ${UI_VISUAL_STYLES.join(', ')}`,
|
|
508
529
|
severity: 'warning',
|
|
509
530
|
});
|
|
510
531
|
}
|
package/src/llm/LLMFactory.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { LLMConfig, EmbeddingConfig, LLMProvider } from '../config/RagConfig';
|
|
|
10
10
|
import { OpenAIProvider } from './providers/OpenAIProvider';
|
|
11
11
|
import { AnthropicProvider } from './providers/AnthropicProvider';
|
|
12
12
|
import { OllamaProvider } from './providers/OllamaProvider';
|
|
13
|
+
import { GeminiProvider } from './providers/GeminiProvider';
|
|
13
14
|
import { UniversalLLMAdapter } from './providers/UniversalLLMAdapter';
|
|
14
15
|
|
|
15
16
|
export class LLMFactory {
|
|
@@ -21,6 +22,8 @@ export class LLMFactory {
|
|
|
21
22
|
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
22
23
|
case 'ollama':
|
|
23
24
|
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
25
|
+
case 'gemini':
|
|
26
|
+
return new GeminiProvider(llmConfig, embeddingConfig);
|
|
24
27
|
case 'rest':
|
|
25
28
|
case 'universal_rest':
|
|
26
29
|
case 'custom':
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini LLM Provider (Google Gen AI)
|
|
3
|
+
*
|
|
4
|
+
* Handles both chat completion (gemini-2.5-flash, gemini-2.5-pro, etc.) and
|
|
5
|
+
* embedding (text-embedding-004).
|
|
6
|
+
* Uses the modern @google/genai SDK.
|
|
7
|
+
*
|
|
8
|
+
* Required LLMConfig fields:
|
|
9
|
+
* - apiKey: string – Gemini API key
|
|
10
|
+
* - model: string – e.g. "gemini-2.5-flash"
|
|
11
|
+
*
|
|
12
|
+
* Required EmbeddingConfig fields (when used for embedding):
|
|
13
|
+
* - apiKey: string – same or separate key
|
|
14
|
+
* - model: string – e.g. "text-embedding-004"
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { GoogleGenAI } from '@google/genai';
|
|
18
|
+
import { ILLMProvider, ChatMessage, ChatOptions, EmbedOptions } from '../ILLMProvider';
|
|
19
|
+
import { LLMConfig, EmbeddingConfig } from '../../config/RagConfig';
|
|
20
|
+
|
|
21
|
+
export class GeminiProvider implements ILLMProvider {
|
|
22
|
+
private readonly client: GoogleGenAI;
|
|
23
|
+
private readonly llmConfig: LLMConfig;
|
|
24
|
+
private readonly embeddingConfig?: EmbeddingConfig;
|
|
25
|
+
|
|
26
|
+
constructor(llmConfig: LLMConfig, embeddingConfig?: EmbeddingConfig) {
|
|
27
|
+
if (!llmConfig.apiKey) throw new Error('[GeminiProvider] llmConfig.apiKey is required');
|
|
28
|
+
this.client = new GoogleGenAI({ apiKey: llmConfig.apiKey });
|
|
29
|
+
this.llmConfig = llmConfig;
|
|
30
|
+
this.embeddingConfig = embeddingConfig;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async chat(messages: ChatMessage[], context: string, options?: ChatOptions): Promise<string> {
|
|
34
|
+
const systemPrompt =
|
|
35
|
+
this.llmConfig.systemPrompt ??
|
|
36
|
+
`You are a helpful assistant. Answer questions based on the provided context.\n\nContext:\n${context}`;
|
|
37
|
+
|
|
38
|
+
const systemInstruction = systemPrompt.includes('{{context}}')
|
|
39
|
+
? systemPrompt.replace('{{context}}', context)
|
|
40
|
+
: `${systemPrompt}\n\nContext:\n${context}`;
|
|
41
|
+
|
|
42
|
+
// Gemini requires alternating user/model messages, starting with user.
|
|
43
|
+
// If the first message is not user, or there are consecutive same-role messages, we need to adapt.
|
|
44
|
+
// To simplify and comply with typical usage, we'll map 'assistant' to 'model'.
|
|
45
|
+
const geminiMessages = messages.map((m) => ({
|
|
46
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
47
|
+
parts: [{ text: m.content }],
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
const response = await this.client.models.generateContent({
|
|
51
|
+
model: this.llmConfig.model,
|
|
52
|
+
contents: geminiMessages,
|
|
53
|
+
config: {
|
|
54
|
+
systemInstruction,
|
|
55
|
+
temperature: options?.temperature ?? this.llmConfig.temperature ?? 0.7,
|
|
56
|
+
maxOutputTokens: options?.maxTokens ?? this.llmConfig.maxTokens ?? 1024,
|
|
57
|
+
stopSequences: options?.stop,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return response.text ?? '';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async embed(text: string, options?: EmbedOptions): Promise<number[]> {
|
|
65
|
+
const model =
|
|
66
|
+
options?.model ??
|
|
67
|
+
this.embeddingConfig?.model ??
|
|
68
|
+
'text-embedding-004';
|
|
69
|
+
|
|
70
|
+
const apiKey = this.embeddingConfig?.apiKey ?? this.llmConfig.apiKey;
|
|
71
|
+
const client = apiKey !== this.llmConfig.apiKey
|
|
72
|
+
? new GoogleGenAI({ apiKey })
|
|
73
|
+
: this.client;
|
|
74
|
+
|
|
75
|
+
let content = text;
|
|
76
|
+
|
|
77
|
+
// Dynamically apply prefixes from configuration if they exist
|
|
78
|
+
const queryPrefix = this.embeddingConfig?.queryPrefix;
|
|
79
|
+
const docPrefix = this.embeddingConfig?.documentPrefix;
|
|
80
|
+
|
|
81
|
+
if (options?.taskType === 'query' && queryPrefix) {
|
|
82
|
+
if (!content.startsWith(queryPrefix)) {
|
|
83
|
+
content = `${queryPrefix}${text}`;
|
|
84
|
+
}
|
|
85
|
+
} else if (options?.taskType === 'document' && docPrefix) {
|
|
86
|
+
if (!content.startsWith(docPrefix)) {
|
|
87
|
+
content = `${docPrefix}${text}`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const response = await client.models.embedContent({
|
|
92
|
+
model,
|
|
93
|
+
contents: content,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return response.embeddings?.[0]?.values ?? [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async batchEmbed(texts: string[], options?: EmbedOptions): Promise<number[][]> {
|
|
100
|
+
const vectors: number[][] = [];
|
|
101
|
+
// Sequential fallback for Gemini (API doesn't have a direct batch endpoint in the same way, though you can pass multiple contents.
|
|
102
|
+
// For simplicity and to match the other providers' structure, we'll iterate or pass array.
|
|
103
|
+
// The new SDK supports an array of contents.
|
|
104
|
+
const model =
|
|
105
|
+
options?.model ??
|
|
106
|
+
this.embeddingConfig?.model ??
|
|
107
|
+
'text-embedding-004';
|
|
108
|
+
|
|
109
|
+
const apiKey = this.embeddingConfig?.apiKey ?? this.llmConfig.apiKey;
|
|
110
|
+
const client = apiKey !== this.llmConfig.apiKey
|
|
111
|
+
? new GoogleGenAI({ apiKey })
|
|
112
|
+
: this.client;
|
|
113
|
+
|
|
114
|
+
let contents = texts;
|
|
115
|
+
|
|
116
|
+
// Apply prefixes if needed
|
|
117
|
+
const queryPrefix = this.embeddingConfig?.queryPrefix;
|
|
118
|
+
const docPrefix = this.embeddingConfig?.documentPrefix;
|
|
119
|
+
|
|
120
|
+
if (options?.taskType === 'query' && queryPrefix) {
|
|
121
|
+
contents = texts.map(text => text.startsWith(queryPrefix) ? text : `${queryPrefix}${text}`);
|
|
122
|
+
} else if (options?.taskType === 'document' && docPrefix) {
|
|
123
|
+
contents = texts.map(text => text.startsWith(docPrefix) ? text : `${docPrefix}${text}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const response = await client.models.embedContent({
|
|
127
|
+
model,
|
|
128
|
+
contents,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return response.embeddings?.map(e => e.values ?? []) ?? [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async ping(): Promise<boolean> {
|
|
135
|
+
try {
|
|
136
|
+
// Perform a lightweight embed call to verify connectivity
|
|
137
|
+
await this.client.models.embedContent({
|
|
138
|
+
model: 'text-embedding-004',
|
|
139
|
+
contents: 'ping',
|
|
140
|
+
});
|
|
141
|
+
return true;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error('[GeminiProvider] Ping failed:', err);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|