@retrivora-ai/rag-engine 0.3.1 → 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 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-D3Inaf9N.mjs';
2
- export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-Ymwm-_OR.mjs';
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';
@@ -1,3 +1,3 @@
1
- import '../RagConfig-D3Inaf9N.js';
2
- export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-BhNJQ2SS.js';
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';
@@ -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
  /**
@@ -3,7 +3,7 @@ import {
3
3
  createHealthHandler,
4
4
  createIngestHandler,
5
5
  createUploadHandler
6
- } from "../chunk-GT72OIOD.mjs";
6
+ } from "../chunk-7BPQDPOC.mjs";
7
7
  import "../chunk-EDLTMSNY.mjs";
8
8
  import "../chunk-FWCSY2DS.mjs";
9
9
  export {
@@ -1,4 +1,4 @@
1
- import { c as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, a as RagConfig, C as ChatResponse } from './RagConfig-D3Inaf9N.mjs';
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-D3Inaf9N.js';
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-D3Inaf9N.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-D3Inaf9N.mjs';
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-D3Inaf9N.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-D3Inaf9N.js';
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-D3Inaf9N.mjs';
2
- export { R as RAGConfig, U as UIConfig } from './RagConfig-D3Inaf9N.mjs';
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-Ymwm-_OR.mjs';
6
- export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-Ymwm-_OR.mjs';
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-D3Inaf9N.js';
2
- export { R as RAGConfig, U as UIConfig } from './RagConfig-D3Inaf9N.js';
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-BhNJQ2SS.js';
6
- export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-BhNJQ2SS.js';
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
  /**
package/dist/server.mjs CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  createIngestHandler,
35
35
  createUploadHandler,
36
36
  getRagConfig
37
- } from "./chunk-GT72OIOD.mjs";
37
+ } from "./chunk-7BPQDPOC.mjs";
38
38
  import "./chunk-EDLTMSNY.mjs";
39
39
  import {
40
40
  PineconeProvider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "0.3.1",
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
 
@@ -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',
@@ -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
+ }