@retrivora-ai/rag-engine 0.3.0 → 0.3.2
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-Crz-l02S.d.mts} +2 -2
- package/dist/{RagConfig-D3Inaf9N.d.ts → RagConfig-Crz-l02S.d.ts} +2 -2
- package/dist/{chunk-GT72OIOD.mjs → chunk-7BPQDPOC.mjs} +116 -1
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +116 -1
- package/dist/handlers/index.mjs +1 -1
- package/dist/{index-Ymwm-_OR.d.mts → index-Cd4CdmEi.d.mts} +1 -1
- package/dist/{index-BhNJQ2SS.d.ts → index-DQ1pXmlK.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 +120 -3
- package/dist/server.mjs +5 -3
- package/package.json +2 -1
- package/src/config/EmbeddingStrategy.ts +1 -1
- package/src/config/RagConfig.ts +2 -2
- package/src/core/ConfigValidator.ts +19 -0
- package/src/llm/LLMFactory.ts +3 -0
- package/src/llm/providers/GeminiProvider.ts +147 -0
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +5 -2
package/.env.example
CHANGED
|
@@ -20,7 +20,7 @@ VECTOR_DB_REST_API_KEY=your-rest-api-key
|
|
|
20
20
|
# VECTOR_DB_UPSERT_PATH=/upsert
|
|
21
21
|
|
|
22
22
|
# ─── LLM ──────────────────────────────────────────────────────
|
|
23
|
-
# Choose: openai | anthropic | ollama | universal_rest
|
|
23
|
+
# Choose: openai | anthropic | ollama | gemini | universal_rest
|
|
24
24
|
LLM_PROVIDER=openai
|
|
25
25
|
LLM_MODEL=gpt-4o
|
|
26
26
|
LLM_MAX_TOKENS=1024
|
|
@@ -33,6 +33,9 @@ OPENAI_API_KEY=sk-...
|
|
|
33
33
|
# Anthropic (if provider=anthropic)
|
|
34
34
|
ANTHROPIC_API_KEY=sk-ant-...
|
|
35
35
|
|
|
36
|
+
# Gemini (if provider=gemini)
|
|
37
|
+
GEMINI_API_KEY=AIza...
|
|
38
|
+
|
|
36
39
|
# Ollama (if provider=ollama)
|
|
37
40
|
LLM_BASE_URL=http://localhost:11434
|
|
38
41
|
|
|
@@ -52,7 +55,7 @@ VECTOR_BASE_URL=http://localhost:6333
|
|
|
52
55
|
VECTOR_DB_INDEX=my-index
|
|
53
56
|
|
|
54
57
|
# ─── Embedding ────────────────────────────────────────────────
|
|
55
|
-
# Choose: openai | ollama | custom
|
|
58
|
+
# Choose: openai | ollama | gemini | custom
|
|
56
59
|
EMBEDDING_PROVIDER=openai
|
|
57
60
|
EMBEDDING_MODEL=text-embedding-3-small
|
|
58
61
|
EMBEDDING_DIMENSIONS=1536
|
|
@@ -51,7 +51,7 @@ interface VectorDBConfig {
|
|
|
51
51
|
*/
|
|
52
52
|
options: Record<string, unknown>;
|
|
53
53
|
}
|
|
54
|
-
type LLMProvider = 'openai' | 'anthropic' | 'ollama' | 'rest' | 'universal_rest' | 'custom';
|
|
54
|
+
type LLMProvider = 'openai' | 'anthropic' | 'ollama' | 'gemini' | 'rest' | 'universal_rest' | 'custom';
|
|
55
55
|
interface LLMConfig {
|
|
56
56
|
/** Which LLM provider to use */
|
|
57
57
|
provider: LLMProvider;
|
|
@@ -77,7 +77,7 @@ interface LLMConfig {
|
|
|
77
77
|
*/
|
|
78
78
|
options?: Record<string, unknown>;
|
|
79
79
|
}
|
|
80
|
-
type EmbeddingProvider = 'openai' | 'ollama' | 'rest' | 'universal_rest' | 'custom';
|
|
80
|
+
type EmbeddingProvider = 'openai' | 'ollama' | 'gemini' | 'rest' | 'universal_rest' | 'custom';
|
|
81
81
|
interface EmbeddingConfig {
|
|
82
82
|
/** Which embedding provider to use */
|
|
83
83
|
provider: EmbeddingProvider;
|
|
@@ -51,7 +51,7 @@ interface VectorDBConfig {
|
|
|
51
51
|
*/
|
|
52
52
|
options: Record<string, unknown>;
|
|
53
53
|
}
|
|
54
|
-
type LLMProvider = 'openai' | 'anthropic' | 'ollama' | 'rest' | 'universal_rest' | 'custom';
|
|
54
|
+
type LLMProvider = 'openai' | 'anthropic' | 'ollama' | 'gemini' | 'rest' | 'universal_rest' | 'custom';
|
|
55
55
|
interface LLMConfig {
|
|
56
56
|
/** Which LLM provider to use */
|
|
57
57
|
provider: LLMProvider;
|
|
@@ -77,7 +77,7 @@ interface LLMConfig {
|
|
|
77
77
|
*/
|
|
78
78
|
options?: Record<string, unknown>;
|
|
79
79
|
}
|
|
80
|
-
type EmbeddingProvider = 'openai' | 'ollama' | 'rest' | 'universal_rest' | 'custom';
|
|
80
|
+
type EmbeddingProvider = 'openai' | 'ollama' | 'gemini' | 'rest' | 'universal_rest' | 'custom';
|
|
81
81
|
interface EmbeddingConfig {
|
|
82
82
|
/** Which embedding provider to use */
|
|
83
83
|
provider: EmbeddingProvider;
|
|
@@ -515,6 +515,16 @@ var ConfigValidator = class {
|
|
|
515
515
|
});
|
|
516
516
|
}
|
|
517
517
|
break;
|
|
518
|
+
case "gemini":
|
|
519
|
+
if (!config.apiKey) {
|
|
520
|
+
errors.push({
|
|
521
|
+
field: "llm.apiKey",
|
|
522
|
+
message: "Gemini API key is required",
|
|
523
|
+
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
524
|
+
severity: "error"
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
break;
|
|
518
528
|
case "ollama":
|
|
519
529
|
if (!config.baseUrl) {
|
|
520
530
|
errors.push({
|
|
@@ -581,6 +591,14 @@ var ConfigValidator = class {
|
|
|
581
591
|
severity: "error"
|
|
582
592
|
});
|
|
583
593
|
}
|
|
594
|
+
if (config.provider === "gemini" && !config.apiKey) {
|
|
595
|
+
errors.push({
|
|
596
|
+
field: "embedding.apiKey",
|
|
597
|
+
message: "Gemini API key is required for embedding",
|
|
598
|
+
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
599
|
+
severity: "error"
|
|
600
|
+
});
|
|
601
|
+
}
|
|
584
602
|
if (config.provider === "ollama" && !config.baseUrl) {
|
|
585
603
|
errors.push({
|
|
586
604
|
field: "embedding.baseUrl",
|
|
@@ -959,6 +977,101 @@ ${context}`;
|
|
|
959
977
|
}
|
|
960
978
|
};
|
|
961
979
|
|
|
980
|
+
// src/llm/providers/GeminiProvider.ts
|
|
981
|
+
import { GoogleGenAI } from "@google/genai";
|
|
982
|
+
var GeminiProvider = class {
|
|
983
|
+
constructor(llmConfig, embeddingConfig) {
|
|
984
|
+
if (!llmConfig.apiKey) throw new Error("[GeminiProvider] llmConfig.apiKey is required");
|
|
985
|
+
this.client = new GoogleGenAI({ apiKey: llmConfig.apiKey });
|
|
986
|
+
this.llmConfig = llmConfig;
|
|
987
|
+
this.embeddingConfig = embeddingConfig;
|
|
988
|
+
}
|
|
989
|
+
async chat(messages, context, options) {
|
|
990
|
+
var _a, _b, _c, _d, _e, _f;
|
|
991
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
992
|
+
|
|
993
|
+
Context:
|
|
994
|
+
${context}`;
|
|
995
|
+
const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
996
|
+
|
|
997
|
+
Context:
|
|
998
|
+
${context}`;
|
|
999
|
+
const geminiMessages = messages.map((m) => ({
|
|
1000
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
1001
|
+
parts: [{ text: m.content }]
|
|
1002
|
+
}));
|
|
1003
|
+
const response = await this.client.models.generateContent({
|
|
1004
|
+
model: this.llmConfig.model,
|
|
1005
|
+
contents: geminiMessages,
|
|
1006
|
+
config: {
|
|
1007
|
+
systemInstruction,
|
|
1008
|
+
temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
|
|
1009
|
+
maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
|
|
1010
|
+
stopSequences: options == null ? void 0 : options.stop
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
return (_f = response.text) != null ? _f : "";
|
|
1014
|
+
}
|
|
1015
|
+
async embed(text, options) {
|
|
1016
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1017
|
+
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";
|
|
1018
|
+
const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
|
|
1019
|
+
const client = apiKey !== this.llmConfig.apiKey ? new GoogleGenAI({ apiKey }) : this.client;
|
|
1020
|
+
let content = text;
|
|
1021
|
+
const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
|
|
1022
|
+
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
1023
|
+
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
1024
|
+
if (!content.startsWith(queryPrefix)) {
|
|
1025
|
+
content = `${queryPrefix}${text}`;
|
|
1026
|
+
}
|
|
1027
|
+
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
1028
|
+
if (!content.startsWith(docPrefix)) {
|
|
1029
|
+
content = `${docPrefix}${text}`;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
const response = await client.models.embedContent({
|
|
1033
|
+
model,
|
|
1034
|
+
contents: content
|
|
1035
|
+
});
|
|
1036
|
+
return (_j = (_i = (_h = response.embeddings) == null ? void 0 : _h[0]) == null ? void 0 : _i.values) != null ? _j : [];
|
|
1037
|
+
}
|
|
1038
|
+
async batchEmbed(texts, options) {
|
|
1039
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1040
|
+
const vectors = [];
|
|
1041
|
+
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";
|
|
1042
|
+
const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
|
|
1043
|
+
const client = apiKey !== this.llmConfig.apiKey ? new GoogleGenAI({ apiKey }) : this.client;
|
|
1044
|
+
let contents = texts;
|
|
1045
|
+
const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
|
|
1046
|
+
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
1047
|
+
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
1048
|
+
contents = texts.map((text) => text.startsWith(queryPrefix) ? text : `${queryPrefix}${text}`);
|
|
1049
|
+
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
1050
|
+
contents = texts.map((text) => text.startsWith(docPrefix) ? text : `${docPrefix}${text}`);
|
|
1051
|
+
}
|
|
1052
|
+
const response = await client.models.embedContent({
|
|
1053
|
+
model,
|
|
1054
|
+
contents
|
|
1055
|
+
});
|
|
1056
|
+
return (_i = (_h = response.embeddings) == null ? void 0 : _h.map((e) => {
|
|
1057
|
+
var _a2;
|
|
1058
|
+
return (_a2 = e.values) != null ? _a2 : [];
|
|
1059
|
+
})) != null ? _i : [];
|
|
1060
|
+
}
|
|
1061
|
+
async ping() {
|
|
1062
|
+
try {
|
|
1063
|
+
await this.client.models.embedContent({
|
|
1064
|
+
model: "text-embedding-004",
|
|
1065
|
+
contents: "ping"
|
|
1066
|
+
});
|
|
1067
|
+
return true;
|
|
1068
|
+
} catch (err) {
|
|
1069
|
+
console.error("[GeminiProvider] Ping failed:", err);
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
|
|
962
1075
|
// src/llm/providers/UniversalLLMAdapter.ts
|
|
963
1076
|
import axios2 from "axios";
|
|
964
1077
|
|
|
@@ -1155,6 +1268,8 @@ var LLMFactory = class _LLMFactory {
|
|
|
1155
1268
|
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
1156
1269
|
case "ollama":
|
|
1157
1270
|
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
1271
|
+
case "gemini":
|
|
1272
|
+
return new GeminiProvider(llmConfig, embeddingConfig);
|
|
1158
1273
|
case "rest":
|
|
1159
1274
|
case "universal_rest":
|
|
1160
1275
|
case "custom":
|
|
@@ -1546,7 +1661,7 @@ var EmbeddingStrategyResolver = class {
|
|
|
1546
1661
|
* Check if an LLM provider natively supports embeddings
|
|
1547
1662
|
*/
|
|
1548
1663
|
static supportsEmbedding(provider) {
|
|
1549
|
-
const providersWithEmbedding = ["openai", "ollama", "rest", "universal_rest"];
|
|
1664
|
+
const providersWithEmbedding = ["openai", "ollama", "gemini", "rest", "universal_rest"];
|
|
1550
1665
|
return providersWithEmbedding.includes(provider);
|
|
1551
1666
|
}
|
|
1552
1667
|
/**
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import '../RagConfig-
|
|
2
|
-
export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-
|
|
1
|
+
import '../RagConfig-Crz-l02S.mjs';
|
|
2
|
+
export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-Cd4CdmEi.mjs';
|
|
3
3
|
import 'next/server';
|
package/dist/handlers/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import '../RagConfig-
|
|
2
|
-
export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-
|
|
1
|
+
import '../RagConfig-Crz-l02S.js';
|
|
2
|
+
export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-DQ1pXmlK.js';
|
|
3
3
|
import 'next/server';
|
package/dist/handlers/index.js
CHANGED
|
@@ -1683,6 +1683,16 @@ var ConfigValidator = class {
|
|
|
1683
1683
|
});
|
|
1684
1684
|
}
|
|
1685
1685
|
break;
|
|
1686
|
+
case "gemini":
|
|
1687
|
+
if (!config.apiKey) {
|
|
1688
|
+
errors.push({
|
|
1689
|
+
field: "llm.apiKey",
|
|
1690
|
+
message: "Gemini API key is required",
|
|
1691
|
+
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
1692
|
+
severity: "error"
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
break;
|
|
1686
1696
|
case "ollama":
|
|
1687
1697
|
if (!config.baseUrl) {
|
|
1688
1698
|
errors.push({
|
|
@@ -1749,6 +1759,14 @@ var ConfigValidator = class {
|
|
|
1749
1759
|
severity: "error"
|
|
1750
1760
|
});
|
|
1751
1761
|
}
|
|
1762
|
+
if (config.provider === "gemini" && !config.apiKey) {
|
|
1763
|
+
errors.push({
|
|
1764
|
+
field: "embedding.apiKey",
|
|
1765
|
+
message: "Gemini API key is required for embedding",
|
|
1766
|
+
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
1767
|
+
severity: "error"
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1752
1770
|
if (config.provider === "ollama" && !config.baseUrl) {
|
|
1753
1771
|
errors.push({
|
|
1754
1772
|
field: "embedding.baseUrl",
|
|
@@ -2127,6 +2145,101 @@ ${context}`;
|
|
|
2127
2145
|
}
|
|
2128
2146
|
};
|
|
2129
2147
|
|
|
2148
|
+
// src/llm/providers/GeminiProvider.ts
|
|
2149
|
+
var import_genai = require("@google/genai");
|
|
2150
|
+
var GeminiProvider = class {
|
|
2151
|
+
constructor(llmConfig, embeddingConfig) {
|
|
2152
|
+
if (!llmConfig.apiKey) throw new Error("[GeminiProvider] llmConfig.apiKey is required");
|
|
2153
|
+
this.client = new import_genai.GoogleGenAI({ apiKey: llmConfig.apiKey });
|
|
2154
|
+
this.llmConfig = llmConfig;
|
|
2155
|
+
this.embeddingConfig = embeddingConfig;
|
|
2156
|
+
}
|
|
2157
|
+
async chat(messages, context, options) {
|
|
2158
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2159
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
2160
|
+
|
|
2161
|
+
Context:
|
|
2162
|
+
${context}`;
|
|
2163
|
+
const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2164
|
+
|
|
2165
|
+
Context:
|
|
2166
|
+
${context}`;
|
|
2167
|
+
const geminiMessages = messages.map((m) => ({
|
|
2168
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
2169
|
+
parts: [{ text: m.content }]
|
|
2170
|
+
}));
|
|
2171
|
+
const response = await this.client.models.generateContent({
|
|
2172
|
+
model: this.llmConfig.model,
|
|
2173
|
+
contents: geminiMessages,
|
|
2174
|
+
config: {
|
|
2175
|
+
systemInstruction,
|
|
2176
|
+
temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
|
|
2177
|
+
maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
|
|
2178
|
+
stopSequences: options == null ? void 0 : options.stop
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2181
|
+
return (_f = response.text) != null ? _f : "";
|
|
2182
|
+
}
|
|
2183
|
+
async embed(text, options) {
|
|
2184
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
2185
|
+
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";
|
|
2186
|
+
const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
|
|
2187
|
+
const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
|
|
2188
|
+
let content = text;
|
|
2189
|
+
const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
|
|
2190
|
+
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
2191
|
+
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
2192
|
+
if (!content.startsWith(queryPrefix)) {
|
|
2193
|
+
content = `${queryPrefix}${text}`;
|
|
2194
|
+
}
|
|
2195
|
+
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
2196
|
+
if (!content.startsWith(docPrefix)) {
|
|
2197
|
+
content = `${docPrefix}${text}`;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
const response = await client.models.embedContent({
|
|
2201
|
+
model,
|
|
2202
|
+
contents: content
|
|
2203
|
+
});
|
|
2204
|
+
return (_j = (_i = (_h = response.embeddings) == null ? void 0 : _h[0]) == null ? void 0 : _i.values) != null ? _j : [];
|
|
2205
|
+
}
|
|
2206
|
+
async batchEmbed(texts, options) {
|
|
2207
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2208
|
+
const vectors = [];
|
|
2209
|
+
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";
|
|
2210
|
+
const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
|
|
2211
|
+
const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
|
|
2212
|
+
let contents = texts;
|
|
2213
|
+
const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
|
|
2214
|
+
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
2215
|
+
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
2216
|
+
contents = texts.map((text) => text.startsWith(queryPrefix) ? text : `${queryPrefix}${text}`);
|
|
2217
|
+
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
2218
|
+
contents = texts.map((text) => text.startsWith(docPrefix) ? text : `${docPrefix}${text}`);
|
|
2219
|
+
}
|
|
2220
|
+
const response = await client.models.embedContent({
|
|
2221
|
+
model,
|
|
2222
|
+
contents
|
|
2223
|
+
});
|
|
2224
|
+
return (_i = (_h = response.embeddings) == null ? void 0 : _h.map((e) => {
|
|
2225
|
+
var _a2;
|
|
2226
|
+
return (_a2 = e.values) != null ? _a2 : [];
|
|
2227
|
+
})) != null ? _i : [];
|
|
2228
|
+
}
|
|
2229
|
+
async ping() {
|
|
2230
|
+
try {
|
|
2231
|
+
await this.client.models.embedContent({
|
|
2232
|
+
model: "text-embedding-004",
|
|
2233
|
+
contents: "ping"
|
|
2234
|
+
});
|
|
2235
|
+
return true;
|
|
2236
|
+
} catch (err) {
|
|
2237
|
+
console.error("[GeminiProvider] Ping failed:", err);
|
|
2238
|
+
return false;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
|
|
2130
2243
|
// src/llm/providers/UniversalLLMAdapter.ts
|
|
2131
2244
|
var import_axios2 = __toESM(require("axios"));
|
|
2132
2245
|
init_templateUtils();
|
|
@@ -2285,6 +2398,8 @@ var LLMFactory = class _LLMFactory {
|
|
|
2285
2398
|
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
2286
2399
|
case "ollama":
|
|
2287
2400
|
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
2401
|
+
case "gemini":
|
|
2402
|
+
return new GeminiProvider(llmConfig, embeddingConfig);
|
|
2288
2403
|
case "rest":
|
|
2289
2404
|
case "universal_rest":
|
|
2290
2405
|
case "custom":
|
|
@@ -2670,7 +2785,7 @@ var EmbeddingStrategyResolver = class {
|
|
|
2670
2785
|
* Check if an LLM provider natively supports embeddings
|
|
2671
2786
|
*/
|
|
2672
2787
|
static supportsEmbedding(provider) {
|
|
2673
|
-
const providersWithEmbedding = ["openai", "ollama", "rest", "universal_rest"];
|
|
2788
|
+
const providersWithEmbedding = ["openai", "ollama", "gemini", "rest", "universal_rest"];
|
|
2674
2789
|
return providersWithEmbedding.includes(provider);
|
|
2675
2790
|
}
|
|
2676
2791
|
/**
|
package/dist/handlers/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, a as RagConfig, C as ChatResponse } from './RagConfig-
|
|
1
|
+
import { c as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, a as RagConfig, C as ChatResponse } from './RagConfig-Crz-l02S.mjs';
|
|
2
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, a as RagConfig, C as ChatResponse } from './RagConfig-
|
|
1
|
+
import { c as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, a as RagConfig, C as ChatResponse } from './RagConfig-Crz-l02S.js';
|
|
2
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
3
|
|
|
4
4
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -2,8 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import React$1, { ReactNode } from 'react';
|
|
3
3
|
import { C as ChatMessage } from './DocumentChunker-BICIjSuG.mjs';
|
|
4
4
|
export { a as ChatOptions, b as Chunk, c as ChunkOptions, E as EmbedOptions, I as ILLMProvider } from './DocumentChunker-BICIjSuG.mjs';
|
|
5
|
-
import { V as VectorMatch, U as UIConfig } from './RagConfig-
|
|
6
|
-
export { C as ChatResponse, E as EmbeddingConfig, I as IngestDocument, L as LLMConfig, R as RAGConfig, a as RagConfig, b as UpsertDocument, c as VectorDBConfig } from './RagConfig-
|
|
5
|
+
import { V as VectorMatch, U as UIConfig } from './RagConfig-Crz-l02S.mjs';
|
|
6
|
+
export { C as ChatResponse, E as EmbeddingConfig, I as IngestDocument, L as LLMConfig, R as RAGConfig, a as RagConfig, b as UpsertDocument, c as VectorDBConfig } from './RagConfig-Crz-l02S.mjs';
|
|
7
7
|
|
|
8
8
|
interface ChatWidgetProps {
|
|
9
9
|
/** Position of the floating button. Defaults to bottom-right. */
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import React$1, { ReactNode } from 'react';
|
|
3
3
|
import { C as ChatMessage } from './DocumentChunker-BICIjSuG.js';
|
|
4
4
|
export { a as ChatOptions, b as Chunk, c as ChunkOptions, E as EmbedOptions, I as ILLMProvider } from './DocumentChunker-BICIjSuG.js';
|
|
5
|
-
import { V as VectorMatch, U as UIConfig } from './RagConfig-
|
|
6
|
-
export { C as ChatResponse, E as EmbeddingConfig, I as IngestDocument, L as LLMConfig, R as RAGConfig, a as RagConfig, b as UpsertDocument, c as VectorDBConfig } from './RagConfig-
|
|
5
|
+
import { V as VectorMatch, U as UIConfig } from './RagConfig-Crz-l02S.js';
|
|
6
|
+
export { C as ChatResponse, E as EmbeddingConfig, I as IngestDocument, L as LLMConfig, R as RAGConfig, a as RagConfig, b as UpsertDocument, c as VectorDBConfig } from './RagConfig-Crz-l02S.js';
|
|
7
7
|
|
|
8
8
|
interface ChatWidgetProps {
|
|
9
9
|
/** Position of the floating button. Defaults to bottom-right. */
|
package/dist/server.d.mts
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-Crz-l02S.mjs';
|
|
2
|
+
export { R as RAGConfig, U as UIConfig } from './RagConfig-Crz-l02S.mjs';
|
|
3
3
|
import { C as ChatMessage, I as ILLMProvider, a as ChatOptions, E as EmbedOptions } from './DocumentChunker-BICIjSuG.mjs';
|
|
4
4
|
export { b as Chunk, c as ChunkOptions, D as DocumentChunker } from './DocumentChunker-BICIjSuG.mjs';
|
|
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-Cd4CdmEi.mjs';
|
|
6
|
+
export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-Cd4CdmEi.mjs';
|
|
7
7
|
import 'next/server';
|
|
8
8
|
|
|
9
9
|
/**
|
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-Crz-l02S.js';
|
|
2
|
+
export { R as RAGConfig, U as UIConfig } from './RagConfig-Crz-l02S.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-DQ1pXmlK.js';
|
|
6
|
+
export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-DQ1pXmlK.js';
|
|
7
7
|
import 'next/server';
|
|
8
8
|
|
|
9
9
|
/**
|
package/dist/server.js
CHANGED
|
@@ -1727,6 +1727,16 @@ var ConfigValidator = class {
|
|
|
1727
1727
|
});
|
|
1728
1728
|
}
|
|
1729
1729
|
break;
|
|
1730
|
+
case "gemini":
|
|
1731
|
+
if (!config.apiKey) {
|
|
1732
|
+
errors.push({
|
|
1733
|
+
field: "llm.apiKey",
|
|
1734
|
+
message: "Gemini API key is required",
|
|
1735
|
+
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
1736
|
+
severity: "error"
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
break;
|
|
1730
1740
|
case "ollama":
|
|
1731
1741
|
if (!config.baseUrl) {
|
|
1732
1742
|
errors.push({
|
|
@@ -1793,6 +1803,14 @@ var ConfigValidator = class {
|
|
|
1793
1803
|
severity: "error"
|
|
1794
1804
|
});
|
|
1795
1805
|
}
|
|
1806
|
+
if (config.provider === "gemini" && !config.apiKey) {
|
|
1807
|
+
errors.push({
|
|
1808
|
+
field: "embedding.apiKey",
|
|
1809
|
+
message: "Gemini API key is required for embedding",
|
|
1810
|
+
suggestion: "Set GEMINI_API_KEY environment variable",
|
|
1811
|
+
severity: "error"
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1796
1814
|
if (config.provider === "ollama" && !config.baseUrl) {
|
|
1797
1815
|
errors.push({
|
|
1798
1816
|
field: "embedding.baseUrl",
|
|
@@ -2171,6 +2189,101 @@ ${context}`;
|
|
|
2171
2189
|
}
|
|
2172
2190
|
};
|
|
2173
2191
|
|
|
2192
|
+
// src/llm/providers/GeminiProvider.ts
|
|
2193
|
+
var import_genai = require("@google/genai");
|
|
2194
|
+
var GeminiProvider = class {
|
|
2195
|
+
constructor(llmConfig, embeddingConfig) {
|
|
2196
|
+
if (!llmConfig.apiKey) throw new Error("[GeminiProvider] llmConfig.apiKey is required");
|
|
2197
|
+
this.client = new import_genai.GoogleGenAI({ apiKey: llmConfig.apiKey });
|
|
2198
|
+
this.llmConfig = llmConfig;
|
|
2199
|
+
this.embeddingConfig = embeddingConfig;
|
|
2200
|
+
}
|
|
2201
|
+
async chat(messages, context, options) {
|
|
2202
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2203
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
2204
|
+
|
|
2205
|
+
Context:
|
|
2206
|
+
${context}`;
|
|
2207
|
+
const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2208
|
+
|
|
2209
|
+
Context:
|
|
2210
|
+
${context}`;
|
|
2211
|
+
const geminiMessages = messages.map((m) => ({
|
|
2212
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
2213
|
+
parts: [{ text: m.content }]
|
|
2214
|
+
}));
|
|
2215
|
+
const response = await this.client.models.generateContent({
|
|
2216
|
+
model: this.llmConfig.model,
|
|
2217
|
+
contents: geminiMessages,
|
|
2218
|
+
config: {
|
|
2219
|
+
systemInstruction,
|
|
2220
|
+
temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
|
|
2221
|
+
maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
|
|
2222
|
+
stopSequences: options == null ? void 0 : options.stop
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
2225
|
+
return (_f = response.text) != null ? _f : "";
|
|
2226
|
+
}
|
|
2227
|
+
async embed(text, options) {
|
|
2228
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
2229
|
+
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";
|
|
2230
|
+
const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
|
|
2231
|
+
const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
|
|
2232
|
+
let content = text;
|
|
2233
|
+
const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
|
|
2234
|
+
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
2235
|
+
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
2236
|
+
if (!content.startsWith(queryPrefix)) {
|
|
2237
|
+
content = `${queryPrefix}${text}`;
|
|
2238
|
+
}
|
|
2239
|
+
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
2240
|
+
if (!content.startsWith(docPrefix)) {
|
|
2241
|
+
content = `${docPrefix}${text}`;
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
const response = await client.models.embedContent({
|
|
2245
|
+
model,
|
|
2246
|
+
contents: content
|
|
2247
|
+
});
|
|
2248
|
+
return (_j = (_i = (_h = response.embeddings) == null ? void 0 : _h[0]) == null ? void 0 : _i.values) != null ? _j : [];
|
|
2249
|
+
}
|
|
2250
|
+
async batchEmbed(texts, options) {
|
|
2251
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2252
|
+
const vectors = [];
|
|
2253
|
+
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";
|
|
2254
|
+
const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
|
|
2255
|
+
const client = apiKey !== this.llmConfig.apiKey ? new import_genai.GoogleGenAI({ apiKey }) : this.client;
|
|
2256
|
+
let contents = texts;
|
|
2257
|
+
const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
|
|
2258
|
+
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
2259
|
+
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
2260
|
+
contents = texts.map((text) => text.startsWith(queryPrefix) ? text : `${queryPrefix}${text}`);
|
|
2261
|
+
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
2262
|
+
contents = texts.map((text) => text.startsWith(docPrefix) ? text : `${docPrefix}${text}`);
|
|
2263
|
+
}
|
|
2264
|
+
const response = await client.models.embedContent({
|
|
2265
|
+
model,
|
|
2266
|
+
contents
|
|
2267
|
+
});
|
|
2268
|
+
return (_i = (_h = response.embeddings) == null ? void 0 : _h.map((e) => {
|
|
2269
|
+
var _a2;
|
|
2270
|
+
return (_a2 = e.values) != null ? _a2 : [];
|
|
2271
|
+
})) != null ? _i : [];
|
|
2272
|
+
}
|
|
2273
|
+
async ping() {
|
|
2274
|
+
try {
|
|
2275
|
+
await this.client.models.embedContent({
|
|
2276
|
+
model: "text-embedding-004",
|
|
2277
|
+
contents: "ping"
|
|
2278
|
+
});
|
|
2279
|
+
return true;
|
|
2280
|
+
} catch (err) {
|
|
2281
|
+
console.error("[GeminiProvider] Ping failed:", err);
|
|
2282
|
+
return false;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2174
2287
|
// src/llm/providers/UniversalLLMAdapter.ts
|
|
2175
2288
|
var import_axios2 = __toESM(require("axios"));
|
|
2176
2289
|
init_templateUtils();
|
|
@@ -2368,6 +2481,8 @@ var LLMFactory = class _LLMFactory {
|
|
|
2368
2481
|
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
2369
2482
|
case "ollama":
|
|
2370
2483
|
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
2484
|
+
case "gemini":
|
|
2485
|
+
return new GeminiProvider(llmConfig, embeddingConfig);
|
|
2371
2486
|
case "rest":
|
|
2372
2487
|
case "universal_rest":
|
|
2373
2488
|
case "custom":
|
|
@@ -2759,7 +2874,7 @@ var EmbeddingStrategyResolver = class {
|
|
|
2759
2874
|
* Check if an LLM provider natively supports embeddings
|
|
2760
2875
|
*/
|
|
2761
2876
|
static supportsEmbedding(provider) {
|
|
2762
|
-
const providersWithEmbedding = ["openai", "ollama", "rest", "universal_rest"];
|
|
2877
|
+
const providersWithEmbedding = ["openai", "ollama", "gemini", "rest", "universal_rest"];
|
|
2763
2878
|
return providersWithEmbedding.includes(provider);
|
|
2764
2879
|
}
|
|
2765
2880
|
/**
|
|
@@ -3911,6 +4026,8 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
3911
4026
|
const entityHints = Array.isArray(_filter == null ? void 0 : _filter.__entityHints) ? _filter.__entityHints.filter(
|
|
3912
4027
|
(hint) => typeof hint === "object" && hint !== null && typeof hint.value === "string"
|
|
3913
4028
|
).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
|
|
4029
|
+
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
4030
|
+
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
|
|
3914
4031
|
const queryPromises = this.tables.map(async (table) => {
|
|
3915
4032
|
try {
|
|
3916
4033
|
let sqlQuery = "";
|
|
@@ -3928,8 +4045,8 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
3928
4045
|
sqlQuery = `
|
|
3929
4046
|
SELECT *,
|
|
3930
4047
|
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
3931
|
-
COALESCE(ts_rank(to_tsvector('english', t
|
|
3932
|
-
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', t
|
|
4048
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) AS keyword_score,
|
|
4049
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
3933
4050
|
FROM "${table}" t
|
|
3934
4051
|
ORDER BY hybrid_score DESC
|
|
3935
4052
|
LIMIT 50
|
package/dist/server.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
createIngestHandler,
|
|
35
35
|
createUploadHandler,
|
|
36
36
|
getRagConfig
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-7BPQDPOC.mjs";
|
|
38
38
|
import "./chunk-EDLTMSNY.mjs";
|
|
39
39
|
import {
|
|
40
40
|
PineconeProvider
|
|
@@ -419,6 +419,8 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
419
419
|
const entityHints = Array.isArray(_filter == null ? void 0 : _filter.__entityHints) ? _filter.__entityHints.filter(
|
|
420
420
|
(hint) => typeof hint === "object" && hint !== null && typeof hint.value === "string"
|
|
421
421
|
).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
|
|
422
|
+
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
423
|
+
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
|
|
422
424
|
const queryPromises = this.tables.map(async (table) => {
|
|
423
425
|
try {
|
|
424
426
|
let sqlQuery = "";
|
|
@@ -436,8 +438,8 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
436
438
|
sqlQuery = `
|
|
437
439
|
SELECT *,
|
|
438
440
|
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
439
|
-
COALESCE(ts_rank(to_tsvector('english', t
|
|
440
|
-
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', t
|
|
441
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) AS keyword_score,
|
|
442
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
441
443
|
FROM "${table}" t
|
|
442
444
|
ORDER BY hybrid_score DESC
|
|
443
445
|
LIMIT 50
|
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.2",
|
|
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",
|
|
@@ -125,7 +125,7 @@ export class EmbeddingStrategyResolver {
|
|
|
125
125
|
* Check if an LLM provider natively supports embeddings
|
|
126
126
|
*/
|
|
127
127
|
private static supportsEmbedding(provider: LLMProvider | string): boolean {
|
|
128
|
-
const providersWithEmbedding = ['openai', 'ollama', 'rest', 'universal_rest'];
|
|
128
|
+
const providersWithEmbedding = ['openai', 'ollama', 'gemini', 'rest', 'universal_rest'];
|
|
129
129
|
return providersWithEmbedding.includes(provider);
|
|
130
130
|
}
|
|
131
131
|
|
package/src/config/RagConfig.ts
CHANGED
|
@@ -40,7 +40,7 @@ export interface VectorDBConfig {
|
|
|
40
40
|
// LLM Provider
|
|
41
41
|
// ---------------------------------------------------------------------------
|
|
42
42
|
|
|
43
|
-
export type LLMProvider = 'openai' | 'anthropic' | 'ollama' | 'rest' | 'universal_rest' | 'custom';
|
|
43
|
+
export type LLMProvider = 'openai' | 'anthropic' | 'ollama' | 'gemini' | 'rest' | 'universal_rest' | 'custom';
|
|
44
44
|
|
|
45
45
|
export interface LLMConfig {
|
|
46
46
|
/** Which LLM provider to use */
|
|
@@ -72,7 +72,7 @@ export interface LLMConfig {
|
|
|
72
72
|
// Embedding
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
74
|
|
|
75
|
-
export type EmbeddingProvider = 'openai' | 'ollama' | 'rest' | 'universal_rest' | 'custom';
|
|
75
|
+
export type EmbeddingProvider = 'openai' | 'ollama' | 'gemini' | 'rest' | 'universal_rest' | 'custom';
|
|
76
76
|
|
|
77
77
|
export interface EmbeddingConfig {
|
|
78
78
|
/** Which embedding provider to use */
|
|
@@ -380,6 +380,16 @@ export class ConfigValidator {
|
|
|
380
380
|
});
|
|
381
381
|
}
|
|
382
382
|
break;
|
|
383
|
+
case 'gemini':
|
|
384
|
+
if (!config.apiKey) {
|
|
385
|
+
errors.push({
|
|
386
|
+
field: 'llm.apiKey',
|
|
387
|
+
message: 'Gemini API key is required',
|
|
388
|
+
suggestion: 'Set GEMINI_API_KEY environment variable',
|
|
389
|
+
severity: 'error',
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
break;
|
|
383
393
|
case 'ollama':
|
|
384
394
|
if (!config.baseUrl) {
|
|
385
395
|
errors.push({
|
|
@@ -454,6 +464,15 @@ export class ConfigValidator {
|
|
|
454
464
|
});
|
|
455
465
|
}
|
|
456
466
|
|
|
467
|
+
if (config.provider === 'gemini' && !config.apiKey) {
|
|
468
|
+
errors.push({
|
|
469
|
+
field: 'embedding.apiKey',
|
|
470
|
+
message: 'Gemini API key is required for embedding',
|
|
471
|
+
suggestion: 'Set GEMINI_API_KEY environment variable',
|
|
472
|
+
severity: 'error',
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
457
476
|
if (config.provider === 'ollama' && !config.baseUrl) {
|
|
458
477
|
errors.push({
|
|
459
478
|
field: 'embedding.baseUrl',
|
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
|
+
}
|
|
@@ -148,6 +148,9 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
148
148
|
.filter(Boolean)
|
|
149
149
|
: [];
|
|
150
150
|
|
|
151
|
+
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
152
|
+
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(', ')}]`);
|
|
153
|
+
|
|
151
154
|
const queryPromises = this.tables.map(async (table) => {
|
|
152
155
|
try {
|
|
153
156
|
let sqlQuery = '';
|
|
@@ -170,8 +173,8 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
170
173
|
sqlQuery = `
|
|
171
174
|
SELECT *,
|
|
172
175
|
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
173
|
-
COALESCE(ts_rank(to_tsvector('english', t
|
|
174
|
-
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', t
|
|
176
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) AS keyword_score,
|
|
177
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
175
178
|
FROM "${table}" t
|
|
176
179
|
ORDER BY hybrid_score DESC
|
|
177
180
|
LIMIT 50
|