@retrivora-ai/rag-engine 1.8.0 → 1.8.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.
Files changed (71) hide show
  1. package/dist/DocumentChunker-Dh9TvmGG.d.mts +45 -0
  2. package/dist/DocumentChunker-Dh9TvmGG.d.ts +45 -0
  3. package/dist/{index-DPsQodME.d.mts → ILLMProvider-BOJFz3Na.d.mts} +104 -1
  4. package/dist/{index-DPsQodME.d.ts → ILLMProvider-BOJFz3Na.d.ts} +104 -1
  5. package/dist/MultiTablePostgresProvider-ZLGSKTJR.mjs +8 -0
  6. package/dist/chunk-ICKRMZQK.mjs +76 -0
  7. package/dist/{chunk-PV3MFHWU.mjs → chunk-LZVVLSDN.mjs} +977 -516
  8. package/dist/chunk-OZFBG4BA.mjs +291 -0
  9. package/dist/handlers/index.d.mts +2 -2
  10. package/dist/handlers/index.d.ts +2 -2
  11. package/dist/handlers/index.js +1269 -656
  12. package/dist/handlers/index.mjs +4 -1
  13. package/dist/{index-Bb2yEopi.d.mts → index-BwpcaziY.d.ts} +10 -2
  14. package/dist/{index-CkbTzj9J.d.ts → index-D3V9Et2M.d.mts} +10 -2
  15. package/dist/index.d.mts +24 -4
  16. package/dist/index.d.ts +24 -4
  17. package/dist/index.js +1354 -826
  18. package/dist/index.mjs +1284 -795
  19. package/dist/server.d.mts +47 -27
  20. package/dist/server.d.ts +47 -27
  21. package/dist/server.js +1417 -829
  22. package/dist/server.mjs +164 -176
  23. package/package.json +6 -2
  24. package/src/app/api/upload/route.ts +4 -0
  25. package/src/app/constants.tsx +2 -2
  26. package/src/app/page.tsx +12 -322
  27. package/src/components/AmbientBackground.tsx +29 -0
  28. package/src/components/ArchitectureCard.tsx +17 -0
  29. package/src/components/ArchitectureCardsSection.tsx +15 -0
  30. package/src/components/ChatWindow.tsx +32 -0
  31. package/src/components/CodeViewer.tsx +51 -0
  32. package/src/components/ConfigProvider.tsx +1 -0
  33. package/src/components/DocViewer.tsx +37 -0
  34. package/src/components/DocumentUpload.tsx +44 -1
  35. package/src/components/Documentation.tsx +58 -0
  36. package/src/components/DynamicChart.tsx +27 -2
  37. package/src/components/Hero.tsx +59 -0
  38. package/src/components/HourglassLoader.tsx +87 -0
  39. package/src/components/Lifecycle.tsx +37 -0
  40. package/src/components/MarkdownComponents.tsx +140 -0
  41. package/src/components/MessageBubble.tsx +124 -904
  42. package/src/components/Navbar.tsx +55 -0
  43. package/src/components/ObservabilityPanel.tsx +374 -0
  44. package/src/components/ProductCard.tsx +5 -3
  45. package/src/components/UIDispatcher.tsx +344 -0
  46. package/src/components/VisualizationRenderer.tsx +372 -250
  47. package/src/config/RagConfig.ts +5 -0
  48. package/src/config/serverConfig.ts +3 -1
  49. package/src/core/Pipeline.ts +240 -271
  50. package/src/core/ProviderRegistry.ts +2 -2
  51. package/src/core/VectorPlugin.ts +9 -0
  52. package/src/handlers/index.ts +91 -15
  53. package/src/hooks/useRagChat.ts +21 -11
  54. package/src/index.ts +9 -1
  55. package/src/llm/LLMFactory.ts +54 -2
  56. package/src/llm/providers/AnthropicProvider.ts +12 -8
  57. package/src/llm/providers/GeminiProvider.ts +188 -143
  58. package/src/llm/providers/OllamaProvider.ts +7 -3
  59. package/src/llm/providers/OpenAIProvider.ts +12 -8
  60. package/src/providers/vectordb/MultiTablePostgresProvider.ts +150 -64
  61. package/src/types/chat.ts +8 -0
  62. package/src/types/index.ts +132 -0
  63. package/src/types/props.ts +9 -1
  64. package/src/utils/ProductExtractor.ts +347 -0
  65. package/src/utils/SchemaMapper.ts +129 -0
  66. package/src/utils/UITransformer.ts +470 -209
  67. package/src/utils/synonyms.ts +78 -0
  68. package/dist/DocumentChunker-C1GEEosY.d.ts +0 -93
  69. package/dist/DocumentChunker-CFEiRopR.d.mts +0 -93
  70. package/dist/PostgreSQLProvider-BMOETDZA.mjs +0 -8
  71. package/dist/chunk-FLOSGE6A.mjs +0 -202
@@ -1,3 +1,6 @@
1
+ import {
2
+ resolveMetadataValue
3
+ } from "./chunk-ICKRMZQK.mjs";
1
4
  import {
2
5
  buildPayload,
3
6
  mergeDefined,
@@ -83,7 +86,7 @@ function getRagConfig(baseConfig, env = process.env) {
83
86
  return getEnvConfig(env, baseConfig);
84
87
  }
85
88
  function getEnvConfig(env = process.env, base) {
86
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa, _qa, _ra, _sa, _ta, _ua, _va;
89
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa, _qa, _ra, _sa, _ta, _ua, _va, _wa, _xa, _ya, _za, _Aa, _Ba, _Ca, _Da, _Ea, _Fa;
87
90
  const projectId = (_c = (_b = (_a = readString(env, "RAG_PROJECT_ID")) != null ? _a : readString(env, "NEXT_PUBLIC_PROJECT_ID")) != null ? _b : base == null ? void 0 : base.projectId) != null ? _c : "__default__";
88
91
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
89
92
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -94,33 +97,35 @@ function getEnvConfig(env = process.env, base) {
94
97
  vectorDbOptions.apiKey = (_g = (_f = readString(env, "PINECONE_API_KEY")) != null ? _f : (_e = (_d = base == null ? void 0 : base.vectorDb) == null ? void 0 : _d.options) == null ? void 0 : _e.apiKey) != null ? _g : "";
95
98
  vectorDbOptions.indexName = (_l = (_i = readString(env, "PINECONE_INDEX")) != null ? _i : (_h = base == null ? void 0 : base.vectorDb) == null ? void 0 : _h.indexName) != null ? _l : (_k = (_j = base == null ? void 0 : base.vectorDb) == null ? void 0 : _j.options) == null ? void 0 : _k.indexName;
96
99
  } else if (vectorProvider === "pgvector" || vectorProvider === "postgresql") {
97
- vectorDbOptions.connectionString = (_p = (_o = readString(env, "PGVECTOR_CONNECTION_STRING")) != null ? _o : (_n = (_m = base == null ? void 0 : base.vectorDb) == null ? void 0 : _m.options) == null ? void 0 : _n.connectionString) != null ? _p : "";
100
+ vectorDbOptions.connectionString = (_q = (_p = (_m = readString(env, "PGVECTOR_CONNECTION_STRING")) != null ? _m : readString(env, "POSTGRES_URL")) != null ? _p : (_o = (_n = base == null ? void 0 : base.vectorDb) == null ? void 0 : _n.options) == null ? void 0 : _o.connectionString) != null ? _q : "";
101
+ vectorDbOptions.tables = (_v = (_s = (_r = readString(env, "VECTOR_DB_TABLES")) != null ? _r : readString(env, "POSTGRES_TABLES")) == null ? void 0 : _s.split(",").map((t) => t.trim())) != null ? _v : (_u = (_t = base == null ? void 0 : base.vectorDb) == null ? void 0 : _t.options) == null ? void 0 : _u.tables;
102
+ vectorDbOptions.searchFields = (_z = (_w = readString(env, "POSTGRES_SEARCH_FIELDS")) == null ? void 0 : _w.split(",").map((f) => f.trim())) != null ? _z : (_y = (_x = base == null ? void 0 : base.vectorDb) == null ? void 0 : _x.options) == null ? void 0 : _y.searchFields;
98
103
  vectorDbOptions.dimensions = embeddingDimensions;
99
104
  } else if (vectorProvider === "mongodb") {
100
- vectorDbOptions.uri = (_t = (_s = readString(env, "MONGODB_URI")) != null ? _s : (_r = (_q = base == null ? void 0 : base.vectorDb) == null ? void 0 : _q.options) == null ? void 0 : _r.uri) != null ? _t : "";
101
- vectorDbOptions.database = (_x = (_w = readString(env, "MONGODB_DB")) != null ? _w : (_v = (_u = base == null ? void 0 : base.vectorDb) == null ? void 0 : _u.options) == null ? void 0 : _v.database) != null ? _x : "";
102
- vectorDbOptions.collection = (_B = (_A = readString(env, "MONGODB_COLLECTION")) != null ? _A : (_z = (_y = base == null ? void 0 : base.vectorDb) == null ? void 0 : _y.options) == null ? void 0 : _z.collection) != null ? _B : "";
103
- vectorDbOptions.indexName = (_H = (_G = (_D = readString(env, "MONGODB_INDEX_NAME")) != null ? _D : (_C = base == null ? void 0 : base.vectorDb) == null ? void 0 : _C.indexName) != null ? _G : (_F = (_E = base == null ? void 0 : base.vectorDb) == null ? void 0 : _E.options) == null ? void 0 : _F.indexName) != null ? _H : "vector_index";
105
+ vectorDbOptions.uri = (_D = (_C = readString(env, "MONGODB_URI")) != null ? _C : (_B = (_A = base == null ? void 0 : base.vectorDb) == null ? void 0 : _A.options) == null ? void 0 : _B.uri) != null ? _D : "";
106
+ vectorDbOptions.database = (_H = (_G = readString(env, "MONGODB_DB")) != null ? _G : (_F = (_E = base == null ? void 0 : base.vectorDb) == null ? void 0 : _E.options) == null ? void 0 : _F.database) != null ? _H : "";
107
+ vectorDbOptions.collection = (_L = (_K = readString(env, "MONGODB_COLLECTION")) != null ? _K : (_J = (_I = base == null ? void 0 : base.vectorDb) == null ? void 0 : _I.options) == null ? void 0 : _J.collection) != null ? _L : "";
108
+ vectorDbOptions.indexName = (_R = (_Q = (_N = readString(env, "MONGODB_INDEX_NAME")) != null ? _N : (_M = base == null ? void 0 : base.vectorDb) == null ? void 0 : _M.indexName) != null ? _Q : (_P = (_O = base == null ? void 0 : base.vectorDb) == null ? void 0 : _O.options) == null ? void 0 : _P.indexName) != null ? _R : "vector_index";
104
109
  } else if (vectorProvider === "qdrant") {
105
- vectorDbOptions.baseUrl = (_L = (_K = readString(env, "QDRANT_URL")) != null ? _K : (_J = (_I = base == null ? void 0 : base.vectorDb) == null ? void 0 : _I.options) == null ? void 0 : _J.baseUrl) != null ? _L : "http://localhost:6333";
106
- vectorDbOptions.apiKey = (_O = readString(env, "QDRANT_API_KEY")) != null ? _O : (_N = (_M = base == null ? void 0 : base.vectorDb) == null ? void 0 : _M.options) == null ? void 0 : _N.apiKey;
110
+ vectorDbOptions.baseUrl = (_V = (_U = readString(env, "QDRANT_URL")) != null ? _U : (_T = (_S = base == null ? void 0 : base.vectorDb) == null ? void 0 : _S.options) == null ? void 0 : _T.baseUrl) != null ? _V : "http://localhost:6333";
111
+ vectorDbOptions.apiKey = (_Y = readString(env, "QDRANT_API_KEY")) != null ? _Y : (_X = (_W = base == null ? void 0 : base.vectorDb) == null ? void 0 : _W.options) == null ? void 0 : _X.apiKey;
107
112
  vectorDbOptions.dimensions = embeddingDimensions;
108
113
  } else if (vectorProvider === "milvus") {
109
- vectorDbOptions.baseUrl = (_P = readString(env, "MILVUS_URL")) != null ? _P : "http://localhost:19530";
114
+ vectorDbOptions.baseUrl = (_Z = readString(env, "MILVUS_URL")) != null ? _Z : "http://localhost:19530";
110
115
  vectorDbOptions.apiKey = readString(env, "MILVUS_API_KEY");
111
116
  } else if (vectorProvider === "chromadb") {
112
- vectorDbOptions.baseUrl = (_Q = readString(env, "CHROMADB_URL")) != null ? _Q : "http://localhost:8000";
117
+ vectorDbOptions.baseUrl = (__ = readString(env, "CHROMADB_URL")) != null ? __ : "http://localhost:8000";
113
118
  } else if (vectorProvider === "weaviate") {
114
- vectorDbOptions.baseUrl = (_R = readString(env, "WEAVIATE_URL")) != null ? _R : "http://localhost:8080";
119
+ vectorDbOptions.baseUrl = (_$ = readString(env, "WEAVIATE_URL")) != null ? _$ : "http://localhost:8080";
115
120
  vectorDbOptions.apiKey = readString(env, "WEAVIATE_API_KEY");
116
121
  } else if (vectorProvider === "redis") {
117
- vectorDbOptions.baseUrl = (_S = readString(env, "REDIS_URL")) != null ? _S : "";
122
+ vectorDbOptions.baseUrl = (_aa = readString(env, "REDIS_URL")) != null ? _aa : "";
118
123
  vectorDbOptions.apiKey = readString(env, "REDIS_API_KEY");
119
124
  } else if (vectorProvider === "rest") {
120
- vectorDbOptions.baseUrl = (_T = readString(env, "VECTOR_DB_REST_URL")) != null ? _T : "";
125
+ vectorDbOptions.baseUrl = (_ba = readString(env, "VECTOR_DB_REST_URL")) != null ? _ba : "";
121
126
  vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { "api-key": readString(env, "VECTOR_DB_REST_API_KEY") } : {};
122
127
  } else if (vectorProvider === "universal_rest") {
123
- vectorDbOptions.baseUrl = (_V = (_U = readString(env, "VECTOR_BASE_URL")) != null ? _U : readString(env, "VECTOR_DB_REST_URL")) != null ? _V : "";
128
+ vectorDbOptions.baseUrl = (_da = (_ca = readString(env, "VECTOR_BASE_URL")) != null ? _ca : readString(env, "VECTOR_DB_REST_URL")) != null ? _da : "";
124
129
  vectorDbOptions.profile = readString(env, "VECTOR_UNIVERSAL_PROFILE");
125
130
  vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { Authorization: `Bearer ${readString(env, "VECTOR_DB_REST_API_KEY")}` } : {};
126
131
  }
@@ -137,20 +142,20 @@ function getEnvConfig(env = process.env, base) {
137
142
  openai: readString(env, "OPENAI_API_KEY"),
138
143
  gemini: readString(env, "GEMINI_API_KEY"),
139
144
  ollama: void 0,
140
- universal_rest: (_W = readString(env, "EMBEDDING_API_KEY")) != null ? _W : readString(env, "OPENAI_API_KEY"),
141
- custom: (_X = readString(env, "EMBEDDING_API_KEY")) != null ? _X : readString(env, "OPENAI_API_KEY")
145
+ universal_rest: (_ea = readString(env, "EMBEDDING_API_KEY")) != null ? _ea : readString(env, "OPENAI_API_KEY"),
146
+ custom: (_fa = readString(env, "EMBEDDING_API_KEY")) != null ? _fa : readString(env, "OPENAI_API_KEY")
142
147
  };
143
148
  return {
144
149
  projectId,
145
150
  vectorDb: {
146
151
  provider: vectorProvider,
147
- indexName: (_Z = (_Y = readString(env, "VECTOR_DB_INDEX")) != null ? _Y : vectorDbOptions.indexName) != null ? _Z : "rag-index",
152
+ indexName: (_ha = (_ga = readString(env, "VECTOR_DB_INDEX")) != null ? _ga : vectorDbOptions.indexName) != null ? _ha : "rag-index",
148
153
  options: vectorDbOptions
149
154
  },
150
155
  llm: {
151
156
  provider: llmProvider,
152
- model: (__ = readString(env, "LLM_MODEL")) != null ? __ : "gpt-4o",
153
- apiKey: (_$ = llmApiKeyByProvider[llmProvider]) != null ? _$ : "",
157
+ model: (_ia = readString(env, "LLM_MODEL")) != null ? _ia : "gpt-4o",
158
+ apiKey: (_ja = llmApiKeyByProvider[llmProvider]) != null ? _ja : "",
154
159
  baseUrl: readString(env, "LLM_BASE_URL"),
155
160
  systemPrompt: readString(env, "LLM_SYSTEM_PROMPT"),
156
161
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
@@ -161,7 +166,7 @@ function getEnvConfig(env = process.env, base) {
161
166
  },
162
167
  embedding: {
163
168
  provider: embeddingProvider,
164
- model: (_aa = readString(env, "EMBEDDING_MODEL")) != null ? _aa : "text-embedding-3-small",
169
+ model: (_ka = readString(env, "EMBEDDING_MODEL")) != null ? _ka : "text-embedding-3-small",
165
170
  apiKey: embeddingApiKeyByProvider[embeddingProvider],
166
171
  baseUrl: readString(env, "EMBEDDING_BASE_URL"),
167
172
  dimensions: embeddingDimensions,
@@ -172,17 +177,17 @@ function getEnvConfig(env = process.env, base) {
172
177
  }
173
178
  },
174
179
  ui: {
175
- title: (_ca = (_ba = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _ba : readString(env, "UI_TITLE")) != null ? _ca : "AI Assistant",
176
- subtitle: (_ea = (_da = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _da : readString(env, "UI_SUBTITLE")) != null ? _ea : "Powered by RAG",
177
- primaryColor: (_ga = (_fa = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _fa : readString(env, "UI_PRIMARY_COLOR")) != null ? _ga : "#10b981",
178
- accentColor: (_ia = (_ha = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ha : readString(env, "UI_ACCENT_COLOR")) != null ? _ia : "#3b82f6",
179
- logoUrl: (_ja = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _ja : readString(env, "UI_LOGO_URL"),
180
- placeholder: (_la = (_ka = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ka : readString(env, "UI_PLACEHOLDER")) != null ? _la : "Ask me anything\u2026",
181
- showSources: ((_na = (_ma = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _ma : readString(env, "UI_SHOW_SOURCES")) != null ? _na : "true") !== "false",
182
- welcomeMessage: (_pa = (_oa = readString(env, "NEXT_PUBLIC_WELCOME_MESSAGE")) != null ? _oa : readString(env, "UI_WELCOME_MESSAGE")) != null ? _pa : "Hello! I'm your AI assistant. Ask me anything about your documents.",
183
- visualStyle: (_ra = (_qa = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _qa : readString(env, "UI_VISUAL_STYLE")) != null ? _ra : "glass",
184
- borderRadius: (_ta = (_sa = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _sa : readString(env, "UI_BORDER_RADIUS")) != null ? _ta : "xl",
185
- allowUpload: ((_va = (_ua = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _ua : readString(env, "UI_ALLOW_UPLOAD")) != null ? _va : "false") === "true"
180
+ title: (_ma = (_la = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _la : readString(env, "UI_TITLE")) != null ? _ma : "AI Assistant",
181
+ subtitle: (_oa = (_na = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _na : readString(env, "UI_SUBTITLE")) != null ? _oa : "Powered by RAG",
182
+ primaryColor: (_qa = (_pa = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _pa : readString(env, "UI_PRIMARY_COLOR")) != null ? _qa : "#10b981",
183
+ accentColor: (_sa = (_ra = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ra : readString(env, "UI_ACCENT_COLOR")) != null ? _sa : "#3b82f6",
184
+ logoUrl: (_ta = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _ta : readString(env, "UI_LOGO_URL"),
185
+ placeholder: (_va = (_ua = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ua : readString(env, "UI_PLACEHOLDER")) != null ? _va : "Ask me anything\u2026",
186
+ showSources: ((_xa = (_wa = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _wa : readString(env, "UI_SHOW_SOURCES")) != null ? _xa : "true") !== "false",
187
+ welcomeMessage: (_za = (_ya = readString(env, "NEXT_PUBLIC_WELCOME_MESSAGE")) != null ? _ya : readString(env, "UI_WELCOME_MESSAGE")) != null ? _za : "Hello! I'm your AI assistant. Ask me anything about your documents.",
188
+ visualStyle: (_Ba = (_Aa = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _Aa : readString(env, "UI_VISUAL_STYLE")) != null ? _Ba : "glass",
189
+ borderRadius: (_Da = (_Ca = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _Ca : readString(env, "UI_BORDER_RADIUS")) != null ? _Da : "xl",
190
+ allowUpload: ((_Fa = (_Ea = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _Ea : readString(env, "UI_ALLOW_UPLOAD")) != null ? _Fa : "false") === "true"
186
191
  },
187
192
  rag: {
188
193
  topK: readNumber(env, "RAG_TOP_K", 5),
@@ -299,14 +304,14 @@ var OpenAIProvider = class {
299
304
  };
300
305
  }
301
306
  async chat(messages, context, options) {
302
- var _a, _b, _c, _d, _e, _f, _g, _h;
303
- const systemContent = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
307
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
308
+ const resolvedSystemPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Answer questions based on the provided context.
304
309
 
305
310
  Context:
306
311
  ${context}`;
307
312
  const systemMessage = {
308
313
  role: "system",
309
- content: systemContent.includes("{{context}}") ? systemContent.replace("{{context}}", context) : `${systemContent}
314
+ content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
310
315
 
311
316
  Context:
312
317
  ${context}`
@@ -321,22 +326,22 @@ ${context}`
321
326
  const completion = await this.client.chat.completions.create({
322
327
  model: this.llmConfig.model,
323
328
  messages: formattedMessages,
324
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
325
- temperature: (_e = (_d = options == null ? void 0 : options.temperature) != null ? _d : this.llmConfig.temperature) != null ? _e : 0.7,
329
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
330
+ temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
326
331
  stop: options == null ? void 0 : options.stop
327
332
  });
328
- return (_h = (_g = (_f = completion.choices[0]) == null ? void 0 : _f.message) == null ? void 0 : _g.content) != null ? _h : "";
333
+ return (_i = (_h = (_g = completion.choices[0]) == null ? void 0 : _g.message) == null ? void 0 : _h.content) != null ? _i : "";
329
334
  }
330
335
  chatStream(messages, context, options) {
331
336
  return __asyncGenerator(this, null, function* () {
332
- var _a, _b, _c, _d, _e, _f, _g;
333
- const systemContent = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
337
+ var _a, _b, _c, _d, _e, _f, _g, _h;
338
+ const resolvedSystemPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Answer questions based on the provided context.
334
339
 
335
340
  Context:
336
341
  ${context}`;
337
342
  const systemMessage = {
338
343
  role: "system",
339
- content: systemContent.includes("{{context}}") ? systemContent.replace("{{context}}", context) : `${systemContent}
344
+ content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
340
345
 
341
346
  Context:
342
347
  ${context}`
@@ -351,8 +356,8 @@ ${context}`
351
356
  const stream = yield new __await(this.client.chat.completions.create({
352
357
  model: this.llmConfig.model,
353
358
  messages: formattedMessages,
354
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
355
- temperature: (_e = (_d = options == null ? void 0 : options.temperature) != null ? _d : this.llmConfig.temperature) != null ? _e : 0.7,
359
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
360
+ temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
356
361
  stop: options == null ? void 0 : options.stop,
357
362
  stream: true
358
363
  }));
@@ -362,7 +367,7 @@ ${context}`
362
367
  try {
363
368
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
364
369
  const chunk = temp.value;
365
- const content = ((_g = (_f = chunk.choices[0]) == null ? void 0 : _f.delta) == null ? void 0 : _g.content) || "";
370
+ const content = ((_h = (_g = chunk.choices[0]) == null ? void 0 : _g.delta) == null ? void 0 : _h.content) || "";
366
371
  if (content) yield content;
367
372
  }
368
373
  } catch (temp) {
@@ -460,12 +465,12 @@ var AnthropicProvider = class {
460
465
  };
461
466
  }
462
467
  async chat(messages, context, options) {
463
- var _a, _b, _c;
464
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
468
+ var _a, _b, _c, _d;
469
+ const resolvedPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Use the context below to answer the user's question accurately.
465
470
 
466
471
  Context:
467
472
  ${context}`;
468
- const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
473
+ const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
469
474
 
470
475
  Context:
471
476
  ${context}`;
@@ -475,7 +480,7 @@ ${context}`;
475
480
  }));
476
481
  const response = await this.client.messages.create({
477
482
  model: this.llmConfig.model,
478
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
483
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
479
484
  system,
480
485
  messages: anthropicMessages
481
486
  });
@@ -484,12 +489,12 @@ ${context}`;
484
489
  }
485
490
  chatStream(messages, context, options) {
486
491
  return __asyncGenerator(this, null, function* () {
487
- var _a, _b, _c;
488
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
492
+ var _a, _b, _c, _d;
493
+ const resolvedPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Use the context below to answer the user's question accurately.
489
494
 
490
495
  Context:
491
496
  ${context}`;
492
- const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
497
+ const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
493
498
 
494
499
  Context:
495
500
  ${context}`;
@@ -499,7 +504,7 @@ ${context}`;
499
504
  }));
500
505
  const stream = yield new __await(this.client.messages.create({
501
506
  model: this.llmConfig.model,
502
- max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
507
+ max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
503
508
  system,
504
509
  messages: anthropicMessages,
505
510
  stream: true
@@ -614,7 +619,7 @@ var OllamaProvider = class {
614
619
  }
615
620
  async chat(messages, context, options) {
616
621
  var _a, _b, _c, _d;
617
- const system = this.buildSystemPrompt(context);
622
+ const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
618
623
  const { data } = await this.http.post("/api/chat", {
619
624
  model: this.llmConfig.model,
620
625
  stream: false,
@@ -632,7 +637,7 @@ var OllamaProvider = class {
632
637
  chatStream(messages, context, options) {
633
638
  return __asyncGenerator(this, null, function* () {
634
639
  var _a, _b, _c, _d, _e, _f;
635
- const system = this.buildSystemPrompt(context);
640
+ const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
636
641
  const response = yield new __await(this.http.post("/api/chat", {
637
642
  model: this.llmConfig.model,
638
643
  stream: true,
@@ -688,8 +693,11 @@ var OllamaProvider = class {
688
693
  }
689
694
  });
690
695
  }
691
- buildSystemPrompt(context) {
696
+ buildSystemPrompt(context, overridePrompt) {
692
697
  var _a;
698
+ if (overridePrompt) {
699
+ return overridePrompt;
700
+ }
693
701
  const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
694
702
 
695
703
  Context:
@@ -741,20 +749,41 @@ ${context}`;
741
749
  };
742
750
 
743
751
  // src/llm/providers/GeminiProvider.ts
744
- import { GoogleGenAI } from "@google/genai";
752
+ import { GoogleGenerativeAI } from "@google/generative-ai";
753
+ var DEFAULT_EMBEDDING_MODEL = "text-embedding-004";
754
+ var DEFAULT_TEMPERATURE = 0.7;
755
+ var DEFAULT_MAX_TOKENS = 1024;
756
+ function sanitizeModel(model) {
757
+ return model ? model.split(":")[0] : model;
758
+ }
759
+ function buildClient(apiKey) {
760
+ return new GoogleGenerativeAI(apiKey);
761
+ }
762
+ function applyPrefix(text, taskType, queryPrefix, docPrefix) {
763
+ if (taskType === "query" && queryPrefix && !text.startsWith(queryPrefix)) {
764
+ return `${queryPrefix}${text}`;
765
+ }
766
+ if (taskType === "document" && docPrefix && !text.startsWith(docPrefix)) {
767
+ return `${docPrefix}${text}`;
768
+ }
769
+ return text;
770
+ }
745
771
  var GeminiProvider = class {
746
772
  constructor(llmConfig, embeddingConfig) {
747
- if (!llmConfig.apiKey) throw new Error("[GeminiProvider] llmConfig.apiKey is required");
748
- this.client = new GoogleGenAI({ apiKey: llmConfig.apiKey });
749
- this.llmConfig = __spreadProps(__spreadValues({}, llmConfig), {
750
- model: this.sanitizeModel(llmConfig.model)
751
- });
773
+ if (!llmConfig.apiKey) {
774
+ throw new Error("[GeminiProvider] llmConfig.apiKey is required");
775
+ }
776
+ this.llmConfig = __spreadProps(__spreadValues({}, llmConfig), { model: sanitizeModel(llmConfig.model) });
777
+ this.client = buildClient(this.llmConfig.apiKey);
752
778
  if (embeddingConfig) {
753
779
  this.embeddingConfig = __spreadProps(__spreadValues({}, embeddingConfig), {
754
- model: this.sanitizeModel(embeddingConfig.model)
780
+ model: sanitizeModel(embeddingConfig.model)
755
781
  });
756
782
  }
757
783
  }
784
+ // -------------------------------------------------------------------------
785
+ // Static factory helpers
786
+ // -------------------------------------------------------------------------
758
787
  static getValidator() {
759
788
  return {
760
789
  validate(config) {
@@ -768,7 +797,11 @@ var GeminiProvider = class {
768
797
  });
769
798
  }
770
799
  if (!config.model) {
771
- errors.push({ field: "llm.model", message: "Gemini model name is required", severity: "error" });
800
+ errors.push({
801
+ field: "llm.model",
802
+ message: "Gemini model name is required",
803
+ severity: "error"
804
+ });
772
805
  }
773
806
  return errors;
774
807
  }
@@ -777,13 +810,15 @@ var GeminiProvider = class {
777
810
  static getHealthChecker() {
778
811
  return {
779
812
  async check(config) {
813
+ var _a, _b;
780
814
  const timestamp = Date.now();
781
- const apiKey = config.apiKey || process.env.GOOGLE_GENAI_API_KEY || "";
782
- const modelName = config.model;
815
+ const apiKey = (_b = (_a = config.apiKey) != null ? _a : process.env.GOOGLE_GENAI_API_KEY) != null ? _b : "";
816
+ const modelName = sanitizeModel(config.model);
783
817
  try {
784
- const { GoogleGenAI: GoogleGenAI2 } = await import("@google/genai");
785
- const genAI = new GoogleGenAI2({ apiKey });
786
- await genAI.models.get({ model: modelName });
818
+ const { GoogleGenerativeAI: GoogleGenerativeAI2 } = await import("@google/generative-ai");
819
+ const ai = new GoogleGenerativeAI2(apiKey);
820
+ const model = ai.getGenerativeModel({ model: DEFAULT_EMBEDDING_MODEL });
821
+ await model.embedContent("health-check");
787
822
  return {
788
823
  healthy: true,
789
824
  provider: "gemini",
@@ -801,70 +836,90 @@ var GeminiProvider = class {
801
836
  }
802
837
  };
803
838
  }
804
- sanitizeModel(model) {
805
- if (!model) return model;
806
- return model.split(":")[0];
839
+ // -------------------------------------------------------------------------
840
+ // Private utilities
841
+ // -------------------------------------------------------------------------
842
+ /** Resolve the embedding client — uses a separate client when the embedding
843
+ * API key differs from the LLM API key. */
844
+ get embeddingClient() {
845
+ var _a;
846
+ if (((_a = this.embeddingConfig) == null ? void 0 : _a.apiKey) && this.embeddingConfig.apiKey !== this.llmConfig.apiKey) {
847
+ return buildClient(this.embeddingConfig.apiKey);
848
+ }
849
+ return this.client;
807
850
  }
808
- async chat(messages, context, options) {
809
- var _a, _b, _c, _d, _e, _f;
810
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
851
+ /** Resolve the embedding model to use, in order of specificity. */
852
+ resolveEmbeddingModel(optionsModel) {
853
+ var _a, _b;
854
+ return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
855
+ }
856
+ /**
857
+ * Build the system instruction string, inserting the RAG context either via
858
+ * the `{{context}}` placeholder or by appending it.
859
+ */
860
+ buildSystemInstruction(context) {
861
+ var _a;
862
+ const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
811
863
 
812
864
  Context:
813
865
  ${context}`;
814
- const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
866
+ return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
815
867
 
816
868
  Context:
817
869
  ${context}`;
818
- const geminiMessages = messages.map((m) => ({
870
+ }
871
+ /**
872
+ * Convert ChatMessage[] to the Gemini contents format.
873
+ *
874
+ * Rules enforced by the Gemini API:
875
+ * - Only `user` and `model` roles are allowed in `contents`.
876
+ * - System messages must be passed via `systemInstruction`, not here.
877
+ * - Messages must strictly alternate between `user` and `model`.
878
+ */
879
+ buildGeminiContents(messages) {
880
+ return messages.filter((m) => m.role !== "system").map((m) => ({
819
881
  role: m.role === "assistant" ? "model" : "user",
820
882
  parts: [{ text: m.content }]
821
883
  }));
822
- const response = await this.client.models.generateContent({
884
+ }
885
+ // -------------------------------------------------------------------------
886
+ // ILLMProvider — chat
887
+ // -------------------------------------------------------------------------
888
+ async chat(messages, context, options) {
889
+ var _a, _b, _c, _d;
890
+ const model = this.client.getGenerativeModel({
823
891
  model: this.llmConfig.model,
824
- contents: geminiMessages,
825
- config: {
826
- systemInstruction,
827
- temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
828
- maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
892
+ systemInstruction: this.buildSystemInstruction(context)
893
+ });
894
+ const result = await model.generateContent({
895
+ contents: this.buildGeminiContents(messages),
896
+ generationConfig: {
897
+ temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
898
+ maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
829
899
  stopSequences: options == null ? void 0 : options.stop
830
900
  }
831
901
  });
832
- const text = typeof response.text === "function" ? response.text() : (_f = response.text) != null ? _f : "";
833
- return text;
902
+ return result.response.text();
834
903
  }
835
904
  chatStream(messages, context, options) {
836
905
  return __asyncGenerator(this, null, function* () {
837
- var _a, _b, _c, _d, _e, _f;
838
- const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
839
-
840
- Context:
841
- ${context}`;
842
- const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
843
-
844
- Context:
845
- ${context}`;
846
- const geminiMessages = messages.map((m) => ({
847
- role: m.role === "assistant" ? "model" : "user",
848
- parts: [{ text: m.content }]
849
- }));
850
- const result = yield new __await(this.client.models.generateContentStream({
906
+ var _a, _b, _c, _d;
907
+ const model = this.client.getGenerativeModel({
851
908
  model: this.llmConfig.model,
852
- contents: geminiMessages,
853
- config: {
854
- systemInstruction,
855
- temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
856
- maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
909
+ systemInstruction: this.buildSystemInstruction(context)
910
+ });
911
+ const result = yield new __await(model.generateContentStream({
912
+ contents: this.buildGeminiContents(messages),
913
+ generationConfig: {
914
+ temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
915
+ maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
857
916
  stopSequences: options == null ? void 0 : options.stop
858
917
  }
859
918
  }));
860
- const stream = (_f = result == null ? void 0 : result.stream) != null ? _f : result;
861
- if (!stream) {
862
- throw new Error("[GeminiProvider] generateContentStream returned undefined");
863
- }
864
919
  try {
865
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
920
+ for (var iter = __forAwait(result.stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
866
921
  const chunk = temp.value;
867
- const text = typeof chunk.text === "function" ? chunk.text() : chunk.text;
922
+ const text = chunk.text();
868
923
  if (text) yield text;
869
924
  }
870
925
  } catch (temp) {
@@ -879,69 +934,69 @@ ${context}`;
879
934
  }
880
935
  });
881
936
  }
937
+ // -------------------------------------------------------------------------
938
+ // ILLMProvider — embeddings
939
+ // -------------------------------------------------------------------------
882
940
  async embed(text, options) {
883
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
884
- const model = this.sanitizeModel(
885
- (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "text-embedding-004"
941
+ var _a, _b;
942
+ const content = applyPrefix(
943
+ text,
944
+ options == null ? void 0 : options.taskType,
945
+ (_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
946
+ (_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
886
947
  );
887
- const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
888
- const client = apiKey !== this.llmConfig.apiKey ? new GoogleGenAI({ apiKey }) : this.client;
889
- let content = text;
890
- const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
891
- const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
892
- if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
893
- if (!content.startsWith(queryPrefix)) {
894
- content = `${queryPrefix}${text}`;
895
- }
896
- } else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
897
- if (!content.startsWith(docPrefix)) {
898
- content = `${docPrefix}${text}`;
899
- }
900
- }
901
- const response = await client.models.embedContent({
902
- model,
903
- contents: content,
904
- config: ((_h = this.embeddingConfig) == null ? void 0 : _h.dimensions) ? { outputDimensionality: this.embeddingConfig.dimensions } : void 0
948
+ const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
949
+ const model = this.embeddingClient.getGenerativeModel({ model: modelName });
950
+ const response = await model.embedContent({
951
+ content: { role: "user", parts: [{ text: content }] }
905
952
  }).catch((err) => {
906
- console.error(`[GeminiProvider] Embedding failed for model "${model}":`, err.message);
953
+ console.error(`[GeminiProvider] Embedding failed for model "${modelName}":`, err.message);
907
954
  throw err;
908
955
  });
909
- return (_k = (_j = (_i = response.embeddings) == null ? void 0 : _i[0]) == null ? void 0 : _j.values) != null ? _k : [];
956
+ return response.embedding.values;
910
957
  }
911
958
  async batchEmbed(texts, options) {
912
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
913
- const model = this.sanitizeModel(
914
- (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "text-embedding-004"
915
- );
916
- const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
917
- const client = apiKey !== this.llmConfig.apiKey ? new GoogleGenAI({ apiKey }) : this.client;
918
- let contents = texts;
919
- const queryPrefix = (_f = this.embeddingConfig) == null ? void 0 : _f.queryPrefix;
920
- const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
921
- if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
922
- contents = texts.map((text) => text.startsWith(queryPrefix) ? text : `${queryPrefix}${text}`);
923
- } else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
924
- contents = texts.map((text) => text.startsWith(docPrefix) ? text : `${docPrefix}${text}`);
925
- }
926
- const response = await client.models.embedContent({
927
- model,
928
- contents,
929
- config: ((_h = this.embeddingConfig) == null ? void 0 : _h.dimensions) ? { outputDimensionality: this.embeddingConfig.dimensions } : void 0
959
+ const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
960
+ const model = this.embeddingClient.getGenerativeModel({ model: modelName });
961
+ const requests = texts.map((text) => {
962
+ var _a, _b;
963
+ return {
964
+ content: {
965
+ role: "user",
966
+ parts: [{
967
+ text: applyPrefix(
968
+ text,
969
+ options == null ? void 0 : options.taskType,
970
+ (_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
971
+ (_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
972
+ )
973
+ }]
974
+ }
975
+ };
976
+ });
977
+ const response = await model.batchEmbedContents({
978
+ requests: requests.map((r) => ({
979
+ model: `models/${modelName}`,
980
+ content: r.content
981
+ }))
930
982
  }).catch((err) => {
931
- console.error(`[GeminiProvider] Batch embedding failed for model "${model}":`, err.message);
983
+ console.error(`[GeminiProvider] Batch embedding failed for model "${modelName}":`, err.message);
932
984
  throw err;
933
985
  });
934
- return (_j = (_i = response.embeddings) == null ? void 0 : _i.map((e) => {
935
- var _a2;
936
- return (_a2 = e.values) != null ? _a2 : [];
937
- })) != null ? _j : [];
986
+ return response.embeddings.map((e) => {
987
+ var _a;
988
+ return (_a = e.values) != null ? _a : [];
989
+ });
938
990
  }
991
+ // -------------------------------------------------------------------------
992
+ // ILLMProvider — health
993
+ // -------------------------------------------------------------------------
939
994
  async ping() {
940
995
  try {
941
- await this.client.models.embedContent({
942
- model: "text-embedding-004",
943
- contents: "ping"
996
+ const model = this.embeddingClient.getGenerativeModel({
997
+ model: this.resolveEmbeddingModel()
944
998
  });
999
+ await model.embedContent("ping");
945
1000
  return true;
946
1001
  } catch (err) {
947
1002
  console.error("[GeminiProvider] Ping failed:", err);
@@ -1136,9 +1191,51 @@ ${context != null ? context : "None"}` },
1136
1191
  };
1137
1192
 
1138
1193
  // src/llm/LLMFactory.ts
1194
+ var customProviders = /* @__PURE__ */ new Map();
1139
1195
  var LLMFactory = class _LLMFactory {
1196
+ /**
1197
+ * Register a custom LLM provider factory at runtime.
1198
+ *
1199
+ * Use this to add support for any LLM backend (Azure OpenAI, Cohere, Mistral,
1200
+ * Bedrock, etc.) without modifying this library's source code.
1201
+ *
1202
+ * @example
1203
+ * // In your Next.js app initialization:
1204
+ * import { LLMFactory } from '@retrivora-ai/rag-engine/server';
1205
+ * import { MyCustomProvider } from './providers/MyCustomProvider';
1206
+ *
1207
+ * LLMFactory.register('my-provider', (config) => new MyCustomProvider(config));
1208
+ *
1209
+ * // Then set in your .env.local:
1210
+ * // LLM_PROVIDER=my-provider
1211
+ */
1212
+ static register(name, factory) {
1213
+ customProviders.set(name.toLowerCase(), factory);
1214
+ console.log(`[LLMFactory] Registered custom provider: "${name}"`);
1215
+ }
1216
+ /**
1217
+ * Unregister a previously registered custom provider.
1218
+ */
1219
+ static unregister(name) {
1220
+ customProviders.delete(name.toLowerCase());
1221
+ }
1222
+ /**
1223
+ * List all registered provider names (built-in + custom).
1224
+ */
1225
+ static listProviders() {
1226
+ return [
1227
+ "openai",
1228
+ "anthropic",
1229
+ "ollama",
1230
+ "gemini",
1231
+ "rest",
1232
+ "universal_rest",
1233
+ "custom",
1234
+ ...Array.from(customProviders.keys())
1235
+ ];
1236
+ }
1140
1237
  static create(llmConfig, embeddingConfig) {
1141
- var _a;
1238
+ var _a, _b;
1142
1239
  switch (llmConfig.provider) {
1143
1240
  case "openai":
1144
1241
  return new OpenAIProvider(llmConfig, embeddingConfig);
@@ -1152,11 +1249,19 @@ var LLMFactory = class _LLMFactory {
1152
1249
  case "universal_rest":
1153
1250
  case "custom":
1154
1251
  return new UniversalLLMAdapter(llmConfig);
1155
- default:
1156
- if (llmConfig.baseUrl || ((_a = llmConfig.options) == null ? void 0 : _a.baseUrl)) {
1252
+ default: {
1253
+ const providerName = String((_a = llmConfig.provider) != null ? _a : "").toLowerCase();
1254
+ const customFactory = customProviders.get(providerName);
1255
+ if (customFactory) {
1256
+ return customFactory(llmConfig);
1257
+ }
1258
+ if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
1157
1259
  return new UniversalLLMAdapter(llmConfig);
1158
1260
  }
1159
- throw new Error(`[LLMFactory] Unknown provider "${llmConfig.provider}"`);
1261
+ throw new Error(
1262
+ `[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
1263
+ );
1264
+ }
1160
1265
  }
1161
1266
  }
1162
1267
  static getValidator(provider) {
@@ -1246,8 +1351,8 @@ var ProviderRegistry = class {
1246
1351
  }
1247
1352
  case "pgvector":
1248
1353
  case "postgresql": {
1249
- const { PostgreSQLProvider } = await import("./PostgreSQLProvider-BMOETDZA.mjs");
1250
- return PostgreSQLProvider;
1354
+ const { MultiTablePostgresProvider } = await import("./MultiTablePostgresProvider-ZLGSKTJR.mjs");
1355
+ return MultiTablePostgresProvider;
1251
1356
  }
1252
1357
  case "mongodb": {
1253
1358
  const { MongoDBProvider } = await import("./MongoDBProvider-YNKC7EJ6.mjs");
@@ -2215,69 +2320,40 @@ var QueryProcessor = class {
2215
2320
  var UITransformer = class {
2216
2321
  /**
2217
2322
  * Main transformation method
2218
- * Analyzes user query and retrieved data to determine the best visualization format
2219
- *
2220
- * @param userQuery - The original user question/query
2221
- * @param retrievedData - Vector database retrieval results
2222
- * @returns Structured JSON response for UI rendering
2323
+ * Analyzes user query and retrieved data to determine if a product carousel is needed.
2223
2324
  */
2224
- static transform(userQuery, retrievedData) {
2325
+ static transform(userQuery, retrievedData, config, trainedSchema) {
2225
2326
  if (!retrievedData || retrievedData.length === 0) {
2226
2327
  return this.createTextResponse("No data available", "No relevant data found for your query.");
2227
2328
  }
2228
- const analysis = this.analyzeData(userQuery, retrievedData);
2229
- switch (analysis.suggestedType) {
2230
- case "product_carousel":
2231
- return this.transformToProductCarousel(retrievedData);
2232
- case "pie_chart":
2233
- return this.transformToPieChart(retrievedData);
2234
- case "bar_chart":
2235
- return this.transformToBarChart(retrievedData);
2236
- case "line_chart":
2237
- return this.transformToLineChart(retrievedData);
2238
- case "table":
2239
- return this.transformToTable(retrievedData);
2240
- default:
2241
- return this.transformToText(retrievedData);
2329
+ const isStockRequest = this.isStockQuery(userQuery);
2330
+ const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
2331
+ const categories = this.detectCategories(filteredData);
2332
+ const hasProducts = filteredData.some((item) => this.isProductData(item));
2333
+ const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
2334
+ const isTrendQuery = this.isTrendQuery(userQuery);
2335
+ if (isTrendQuery && isTimeSeries) {
2336
+ return this.transformToLineChart(filteredData);
2242
2337
  }
2243
- }
2244
- /**
2245
- * Analyzes the data and query to suggest the best visualization type
2246
- */
2247
- static analyzeData(query, data) {
2248
- const queryLower = query.toLowerCase();
2249
- const hasProducts = data.some(
2250
- (item) => this.isProductData(item)
2251
- );
2252
- const hasTimeSeries = data.some(
2253
- (item) => this.isTimeSeriesData(item)
2254
- );
2255
- const hasCategories = this.detectCategories(data).length > 0;
2256
- const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
2257
- const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
2258
- const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
2259
- if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
2260
- return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
2338
+ if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
2339
+ return this.transformToProductCarousel(filteredData, config, trainedSchema);
2261
2340
  }
2262
- if (isTrendQuery && hasTimeSeries) {
2263
- return { suggestedType: "line_chart", confidence: 0.9, hasProducts, hasTimeSeries, hasCategories };
2341
+ if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
2342
+ return this.transformToPieChart(filteredData);
2264
2343
  }
2265
- if (isDistributionQuery && hasCategories) {
2266
- return { suggestedType: "pie_chart", confidence: 0.85, hasProducts, hasTimeSeries, hasCategories };
2344
+ if (hasProducts) {
2345
+ return this.transformToProductCarousel(filteredData, config, trainedSchema);
2267
2346
  }
2268
- if (isComparisonQuery && hasCategories && data.length > 2) {
2269
- return { suggestedType: "bar_chart", confidence: 0.8, hasProducts, hasTimeSeries, hasCategories };
2347
+ if (this.hasMultipleFields(filteredData)) {
2348
+ return this.transformToTable(filteredData);
2270
2349
  }
2271
- if (data.length > 5 && this.hasMultipleFields(data)) {
2272
- return { suggestedType: "table", confidence: 0.75, hasProducts, hasTimeSeries, hasCategories };
2273
- }
2274
- return { suggestedType: "text", confidence: 0.5, hasProducts, hasTimeSeries, hasCategories };
2350
+ return this.transformToText(filteredData);
2275
2351
  }
2276
2352
  /**
2277
2353
  * Transform data to product carousel format
2278
2354
  */
2279
- static transformToProductCarousel(data) {
2280
- const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
2355
+ static transformToProductCarousel(data, config, trainedSchema) {
2356
+ const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item, config, trainedSchema)).filter((p) => p !== null);
2281
2357
  return {
2282
2358
  type: "product_carousel",
2283
2359
  title: "Recommended Products",
@@ -2307,28 +2383,6 @@ var UITransformer = class {
2307
2383
  data: pieData
2308
2384
  };
2309
2385
  }
2310
- /**
2311
- * Transform data to bar chart format
2312
- */
2313
- static transformToBarChart(data) {
2314
- const categories = this.detectCategories(data);
2315
- const categoryData = this.aggregateByCategory(data, categories);
2316
- const barData = Object.entries(categoryData).map(([category, value]) => {
2317
- const { inStockCount, outOfStockCount } = this.calculateStockCounts(category, data);
2318
- return {
2319
- category,
2320
- value,
2321
- inStockCount,
2322
- outOfStockCount
2323
- };
2324
- });
2325
- return {
2326
- type: "bar_chart",
2327
- title: "Comparison by Category",
2328
- description: `Comparing ${categories.length} categories`,
2329
- data: barData
2330
- };
2331
- }
2332
2386
  /**
2333
2387
  * Transform data to line chart format
2334
2388
  */
@@ -2385,49 +2439,253 @@ var UITransformer = class {
2385
2439
  data: { content }
2386
2440
  };
2387
2441
  }
2442
+ static parseTransformationResponse(raw) {
2443
+ const payloadText = this.extractJsonCandidate(raw);
2444
+ if (!payloadText) return null;
2445
+ try {
2446
+ const parsed = JSON.parse(payloadText);
2447
+ return this.normalizeTransformation(parsed);
2448
+ } catch (e) {
2449
+ return null;
2450
+ }
2451
+ }
2452
+ static extractJsonCandidate(raw) {
2453
+ if (!raw) return null;
2454
+ const cleaned = raw.replace(/```(?:json|chart|ui)?\s*/gi, "").replace(/```/g, "").trim();
2455
+ const start = cleaned.indexOf("{");
2456
+ if (start === -1) return null;
2457
+ let depth = 0;
2458
+ let inString = false;
2459
+ let escape = false;
2460
+ for (let i = start; i < cleaned.length; i += 1) {
2461
+ const char = cleaned[i];
2462
+ if (escape) {
2463
+ escape = false;
2464
+ continue;
2465
+ }
2466
+ if (char === "\\") {
2467
+ escape = true;
2468
+ continue;
2469
+ }
2470
+ if (char === '"') {
2471
+ inString = !inString;
2472
+ continue;
2473
+ }
2474
+ if (inString) continue;
2475
+ if (char === "{") depth += 1;
2476
+ if (char === "}") {
2477
+ depth -= 1;
2478
+ if (depth === 0) {
2479
+ return cleaned.slice(start, i + 1);
2480
+ }
2481
+ }
2482
+ }
2483
+ return null;
2484
+ }
2485
+ static normalizeTransformation(payload) {
2486
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2487
+ if (!payload || typeof payload !== "object") return null;
2488
+ const payloadObj = payload;
2489
+ const type = this.normalizeVisualizationType(String((_c = (_b = (_a = payloadObj.type) != null ? _a : payloadObj.view) != null ? _b : payloadObj.chartType) != null ? _c : ""));
2490
+ if (!type) return null;
2491
+ const title = String((_e = (_d = payloadObj.title) != null ? _d : payloadObj.heading) != null ? _e : "Visualization");
2492
+ const description = payloadObj.description ? String(payloadObj.description) : void 0;
2493
+ const rawData = (_j = (_i = (_h = (_g = (_f = payloadObj.data) != null ? _f : payloadObj.table) != null ? _g : payloadObj.rows) != null ? _h : payloadObj.items) != null ? _i : payloadObj.content) != null ? _j : null;
2494
+ const data = type === "text" && typeof rawData === "string" ? { content: rawData } : rawData;
2495
+ const transformation = {
2496
+ type,
2497
+ title,
2498
+ description,
2499
+ data
2500
+ };
2501
+ return this.validateTransformation(transformation) ? transformation : null;
2502
+ }
2503
+ static normalizeVisualizationType(type) {
2504
+ var _a;
2505
+ const mapping = {
2506
+ pie: "pie_chart",
2507
+ pie_chart: "pie_chart",
2508
+ bar: "bar_chart",
2509
+ bar_chart: "bar_chart",
2510
+ line: "line_chart",
2511
+ line_chart: "line_chart",
2512
+ radar: "radar_chart",
2513
+ radar_chart: "radar_chart",
2514
+ table: "table",
2515
+ text: "text",
2516
+ product_carousel: "product_carousel",
2517
+ carousel: "carousel"
2518
+ };
2519
+ return (_a = mapping[type.toLowerCase()]) != null ? _a : null;
2520
+ }
2521
+ static validateTransformation(transformation) {
2522
+ const { type, data } = transformation;
2523
+ switch (type) {
2524
+ case "pie_chart":
2525
+ case "bar_chart":
2526
+ return Array.isArray(data) && data.every(
2527
+ (item) => item !== null && typeof item === "object" && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
2528
+ );
2529
+ case "line_chart":
2530
+ return Array.isArray(data) && data.every(
2531
+ (item) => item !== null && typeof item === "object" && "timestamp" in item && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
2532
+ );
2533
+ case "radar_chart":
2534
+ return Array.isArray(data) && data.every(
2535
+ (item) => item !== null && typeof item === "object" && "attribute" in item
2536
+ );
2537
+ case "table":
2538
+ return typeof data === "object" && data !== null && Array.isArray(data.columns) && Array.isArray(data.rows);
2539
+ case "text":
2540
+ return typeof data === "object" && data !== null && typeof data.content === "string";
2541
+ case "product_carousel":
2542
+ case "carousel":
2543
+ return Array.isArray(data) && data.every(
2544
+ (item) => item !== null && typeof item === "object" && ("id" in item || "name" in item)
2545
+ );
2546
+ default:
2547
+ return false;
2548
+ }
2549
+ }
2388
2550
  /**
2389
2551
  * Helper: Check if data item is product-related
2390
2552
  */
2391
2553
  static isProductData(item) {
2392
2554
  const content = (item.content || "").toLowerCase();
2393
- const productKeywords = ["product", "price", "stock", "item", "sku", "brand", "model"];
2394
- return productKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
2395
- (k) => ["name", "price", "product", "sku"].includes(k.toLowerCase())
2555
+ const productKeywords = [
2556
+ "product",
2557
+ "price",
2558
+ "stock",
2559
+ "item",
2560
+ "sku",
2561
+ "brand",
2562
+ "model",
2563
+ "msrp",
2564
+ "inventory",
2565
+ "buy",
2566
+ "shop",
2567
+ "beauty",
2568
+ "care",
2569
+ "cosmetic",
2570
+ "facial",
2571
+ "cream",
2572
+ "serum",
2573
+ "mask",
2574
+ "makeup",
2575
+ "fragrance"
2576
+ ];
2577
+ const hasKeywords = productKeywords.some((kw) => content.includes(kw));
2578
+ const hasMetadataKey = Object.keys(item.metadata || {}).some(
2579
+ (k) => ["name", "price", "product", "sku", "brand", "model", "cost", "item"].includes(k.toLowerCase())
2396
2580
  );
2581
+ const hasPricePattern = /\$\s*\d+/.test(content);
2582
+ return hasKeywords || hasMetadataKey || hasPricePattern;
2397
2583
  }
2398
2584
  /**
2399
2585
  * Helper: Check if data contains time series
2400
2586
  */
2401
2587
  static isTimeSeriesData(item) {
2402
2588
  const content = (item.content || "").toLowerCase();
2403
- const timeKeywords = ["date", "time", "year", "month", "week", "day", "hour", "trend", "historical"];
2404
- return timeKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
2589
+ const timeKeywords = ["trend", "historical", "growth", "decline", "change", "increase", "decrease"];
2590
+ const hasTimeKeyword = timeKeywords.some((kw) => content.includes(kw));
2591
+ const metadata = item.metadata || {};
2592
+ const maybeDateKeys = Object.keys(metadata).filter(
2405
2593
  (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
2406
2594
  );
2595
+ const hasValidDateValue = maybeDateKeys.some((key) => {
2596
+ const value = metadata[key];
2597
+ if (typeof value === "string") {
2598
+ return !Number.isNaN(Date.parse(value)) && value.trim().length > 0;
2599
+ }
2600
+ return typeof value === "number";
2601
+ });
2602
+ return hasTimeKeyword || hasValidDateValue;
2603
+ }
2604
+ static shouldShowCategoryChart(query, categories) {
2605
+ if (categories.length < 2) {
2606
+ return false;
2607
+ }
2608
+ const normalized = query.toLowerCase();
2609
+ const chartKeywords = [
2610
+ "distribution",
2611
+ "breakdown",
2612
+ "by category",
2613
+ "by type",
2614
+ "compare",
2615
+ "share",
2616
+ "percentage",
2617
+ "segmentation",
2618
+ "split",
2619
+ "category breakdown",
2620
+ "category distribution"
2621
+ ];
2622
+ return chartKeywords.some((keyword) => normalized.includes(keyword));
2623
+ }
2624
+ static isTrendQuery(query) {
2625
+ const normalized = query.toLowerCase();
2626
+ const trendKeywords = [
2627
+ "trend",
2628
+ "over time",
2629
+ "historical",
2630
+ "growth",
2631
+ "decline",
2632
+ "increase",
2633
+ "decrease",
2634
+ "year",
2635
+ "month",
2636
+ "week",
2637
+ "day",
2638
+ "comparison",
2639
+ "compare",
2640
+ "changes",
2641
+ "timeline"
2642
+ ];
2643
+ return trendKeywords.some((keyword) => normalized.includes(keyword));
2644
+ }
2645
+ static isStockQuery(query) {
2646
+ const normalized = query.toLowerCase();
2647
+ return normalized.includes("in stock") || normalized.includes("available") || normalized.includes("availability") || normalized.includes("inventory") || normalized.includes("stock status");
2407
2648
  }
2408
2649
  /**
2409
- * Helper: Extract product information from vector match
2650
+ * Helper: Extract property from metadata using mapping, AI training, case-insensitivity, and synonyms.
2410
2651
  */
2411
- static extractProductInfo(item) {
2412
- const meta = item.metadata || {};
2413
- if (meta.name || meta.product) {
2414
- return {
2415
- id: item.id,
2416
- name: meta.name || meta.product,
2417
- price: meta.price,
2418
- image: meta.image || meta.imageUrl,
2419
- brand: meta.brand,
2420
- description: item.content,
2421
- inStock: this.determineStockStatus(item)
2422
- };
2652
+ static getDynamicVal(meta, uiKey, config, trainedSchema) {
2653
+ var _a;
2654
+ if (!meta) return void 0;
2655
+ const mapping = (_a = config == null ? void 0 : config.rag) == null ? void 0 : _a.uiMapping;
2656
+ if (mapping && mapping[uiKey]) {
2657
+ const mappedKey = mapping[uiKey];
2658
+ if (meta[mappedKey] !== void 0) return meta[mappedKey];
2659
+ }
2660
+ if (trainedSchema && typeof trainedSchema === "object" && trainedSchema !== null) {
2661
+ const trainedKey = trainedSchema[uiKey];
2662
+ if (trainedKey && meta[trainedKey] !== void 0) return meta[trainedKey];
2423
2663
  }
2424
- const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
2425
- const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
2426
- if (nameMatch) {
2664
+ return resolveMetadataValue(meta, uiKey);
2665
+ }
2666
+ static extractProductInfo(item, config, trainedSchema) {
2667
+ const meta = item.metadata || {};
2668
+ const name = this.getDynamicVal(meta, "name", config, trainedSchema);
2669
+ const price = this.getDynamicVal(meta, "price", config, trainedSchema);
2670
+ const brand = this.getDynamicVal(meta, "brand", config, trainedSchema);
2671
+ if (name || this.isProductData(item)) {
2672
+ let finalName = name ? String(name) : void 0;
2673
+ if (!finalName) {
2674
+ const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
2675
+ finalName = nameMatch ? nameMatch[1].trim() : item.content.split("\n")[0].substring(0, 60);
2676
+ }
2677
+ let finalPrice = typeof price === "number" || typeof price === "string" ? price : void 0;
2678
+ if (!finalPrice) {
2679
+ const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d,.]+)/i) || item.content.match(/\$\s*([\d,.]+)/);
2680
+ if (priceMatch) finalPrice = priceMatch[1].replace(/,/g, "");
2681
+ }
2682
+ const imageValue = this.getDynamicVal(meta, "image", config, trainedSchema);
2427
2683
  return {
2428
2684
  id: item.id,
2429
- name: nameMatch[1],
2430
- price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
2685
+ name: finalName,
2686
+ price: finalPrice,
2687
+ image: typeof imageValue === "string" ? imageValue : void 0,
2688
+ brand: brand ? String(brand) : void 0,
2431
2689
  description: item.content,
2432
2690
  inStock: this.determineStockStatus(item)
2433
2691
  };
@@ -2451,6 +2709,10 @@ var UITransformer = class {
2451
2709
  const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
2452
2710
  tags.forEach((t) => categories.add(String(t)));
2453
2711
  }
2712
+ const contentCategories = Array.from(new Set(
2713
+ Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm)).map((match) => match[1].trim()).filter(Boolean)
2714
+ ));
2715
+ contentCategories.forEach((category) => categories.add(category));
2454
2716
  const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
2455
2717
  if (categoryMatch) {
2456
2718
  categories.add(categoryMatch[1].trim());
@@ -2469,7 +2731,7 @@ var UITransformer = class {
2469
2731
  data.forEach((item) => {
2470
2732
  const meta = item.metadata || {};
2471
2733
  const itemCategory = meta.category || meta.type || "Other";
2472
- if (result.hasOwnProperty(itemCategory)) {
2734
+ if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
2473
2735
  result[itemCategory]++;
2474
2736
  } else {
2475
2737
  result["Other"] = (result["Other"] || 0) + 1;
@@ -2571,6 +2833,213 @@ var UITransformer = class {
2571
2833
  });
2572
2834
  return fieldCount.size > 2;
2573
2835
  }
2836
+ // ─── LLM-Driven Visualization Decision ────────────────────────────────────
2837
+ /**
2838
+ * analyzeAndDecide — sends user question + RAG data to the LLM with a
2839
+ * structured system prompt and parses the JSON response into a
2840
+ * UITransformationResponse.
2841
+ *
2842
+ * This is the recommended entry point for production use. The heuristic
2843
+ * `transform()` method is used as a fallback if the LLM call fails.
2844
+ *
2845
+ * System prompt instructs the LLM to:
2846
+ * - Analyze the question and retrieved data
2847
+ * - Choose the best visualization: bar_chart | line_chart | pie_chart | table | text
2848
+ * - Return a strict JSON object — no prose, no markdown fences
2849
+ *
2850
+ * @param query - the original user question
2851
+ * @param sources - vector DB matches returned by RAG retrieval
2852
+ * @param llm - any ILLMProvider instance (OpenAI, Anthropic, Ollama, Gemini, REST…)
2853
+ * @returns - a validated UITransformationResponse (type + title + description + data)
2854
+ */
2855
+ static async analyzeAndDecide(query, sources, llm) {
2856
+ try {
2857
+ const context = this.buildContextSummary(sources);
2858
+ const systemPrompt = this.buildVisualizationSystemPrompt();
2859
+ const userPrompt = [
2860
+ `USER QUESTION: ${query}`,
2861
+ "",
2862
+ "RETRIEVED DATA (JSON):",
2863
+ context
2864
+ ].join("\n");
2865
+ const rawResponse = await llm.chat(
2866
+ [{ role: "user", content: userPrompt }],
2867
+ "",
2868
+ { systemPrompt, temperature: 0 }
2869
+ );
2870
+ const parsed = this.parseTransformationResponse(rawResponse);
2871
+ if (parsed) {
2872
+ console.debug("[UITransformer] LLM chose visualization type:", parsed.type);
2873
+ return parsed;
2874
+ }
2875
+ console.warn("[UITransformer] LLM returned unparseable response; falling back to heuristic.");
2876
+ } catch (err) {
2877
+ console.warn("[UITransformer] analyzeAndDecide LLM call failed; falling back to heuristic.", err);
2878
+ }
2879
+ return this.transform(query, sources);
2880
+ }
2881
+ /**
2882
+ * Build the system prompt that instructs the LLM to return a visualization JSON.
2883
+ */
2884
+ static buildVisualizationSystemPrompt() {
2885
+ return `You are a data visualization expert embedded in a RAG chat system.
2886
+ You will receive a user question and structured data retrieved from a vector database.
2887
+ Your ONLY job is to analyze this information and return a single JSON object that tells
2888
+ the frontend how to visualize it.
2889
+
2890
+ Return ONLY a valid JSON object \u2014 no markdown code fences, no explanation, no prose.
2891
+
2892
+ The JSON must have this exact shape:
2893
+ {
2894
+ "type": "bar_chart" | "line_chart" | "pie_chart" | "radar_chart" | "table" | "text",
2895
+ "title": "A concise, descriptive title for the visualization",
2896
+ "description": "One sentence describing what the visualization shows",
2897
+ "data": <structured data \u2014 see rules below>
2898
+ }
2899
+
2900
+ DATA SHAPE per type:
2901
+ - bar_chart: array of { "category": string, "value": number }
2902
+ - line_chart: array of { "timestamp": string, "value": number, "label": string }
2903
+ - pie_chart: array of { "label": string, "value": number }
2904
+ - radar_chart: array of { "attribute": string, "[series1]": number, "[series2]": number, ... }
2905
+ Example for radar_chart:
2906
+ [
2907
+ { "attribute": "Longevity", "Dolce Shine": 4, "CK One": 3 },
2908
+ { "attribute": "Sillage", "Dolce Shine": 3, "CK One": 4 },
2909
+ { "attribute": "Freshness", "Dolce Shine": 5, "CK One": 4 }
2910
+ ]
2911
+ - table: { "columns": string[], "rows": (string|number)[][] }
2912
+ - text: { "content": "<prose answer>" }
2913
+
2914
+ DECISION RULES (follow strictly):
2915
+ 1. bar_chart \u2192 comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
2916
+ 2. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
2917
+ 3. pie_chart \u2192 proportional breakdown or percentage distribution
2918
+ 4. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes (e.g. comparing features, ratings, or characteristics of 2 specific products). If the user asks to "compare" products and the data contains multiple dimensions or ratings for them, you MUST use this.
2919
+ 5. table \u2192 multi-field structured records where each item has \u2265 3 attributes
2920
+ 6. text \u2192 conversational or free-form answers where no chart adds value
2921
+
2922
+ IMPORTANT:
2923
+ - Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
2924
+ - Ensure all "value" fields are numbers, not strings.
2925
+ - For bar/line/pie, keep at most 12 data points for readability.
2926
+ - Never include nested objects or arrays inside bar_chart / line_chart / pie_chart data items.`;
2927
+ }
2928
+ /**
2929
+ * Serialize retrieved vector matches into a compact JSON context string.
2930
+ * Limits the total character count to avoid exceeding LLM context windows.
2931
+ */
2932
+ static buildContextSummary(sources, maxChars = 6e3) {
2933
+ const items = sources.map((s, i) => {
2934
+ var _a, _b, _c, _d;
2935
+ return {
2936
+ index: i + 1,
2937
+ content: (_b = (_a = s.content) == null ? void 0 : _a.substring(0, 400)) != null ? _b : "",
2938
+ metadata: (_c = s.metadata) != null ? _c : {},
2939
+ score: (_d = s.score) != null ? _d : 0
2940
+ };
2941
+ });
2942
+ const full = JSON.stringify(items, null, 2);
2943
+ if (full.length <= maxChars) return full;
2944
+ const partial = [];
2945
+ let chars = 2;
2946
+ for (const item of items) {
2947
+ const chunk = JSON.stringify(item);
2948
+ if (chars + chunk.length + 2 > maxChars) break;
2949
+ partial.push(item);
2950
+ chars += chunk.length + 2;
2951
+ }
2952
+ return JSON.stringify(partial, null, 2) + "\n// ... truncated";
2953
+ }
2954
+ };
2955
+
2956
+ // src/utils/SchemaMapper.ts
2957
+ var SchemaMapper = class {
2958
+ /**
2959
+ * Trains the plugin on a set of keys.
2960
+ * This is done once per schema and cached.
2961
+ */
2962
+ static async train(llm, projectId, keys) {
2963
+ const cacheKey = `${projectId}:${keys.sort().join(",")}`;
2964
+ if (this.cache.has(cacheKey)) {
2965
+ return this.cache.get(cacheKey);
2966
+ }
2967
+ console.log(`[SchemaMapper] \u{1F9E0} Training AI on new schema keys: ${keys.join(", ")}`);
2968
+ const propertyList = Object.entries(this.TARGET_PROPERTIES).map(([prop, desc]) => `- ${prop} (${desc})`).join("\n");
2969
+ const prompt = `
2970
+ Given these metadata keys from a database: [${keys.join(", ")}]
2971
+
2972
+ Identify which keys best correspond to these standard UI properties:
2973
+ ${propertyList}
2974
+
2975
+ Return ONLY a JSON object where the keys are the UI properties and the values are the matching database keys.
2976
+ If no good match is found for a property, omit it.
2977
+
2978
+ Example:
2979
+ {
2980
+ "name": "Product_Title",
2981
+ "price": "MSRP_USD",
2982
+ "brand": "VendorName"
2983
+ }
2984
+ `;
2985
+ try {
2986
+ const response = await llm.chat(
2987
+ [
2988
+ { role: "system", content: "You are a database schema expert. You ONLY respond with valid JSON." },
2989
+ { role: "user", content: prompt }
2990
+ ],
2991
+ ""
2992
+ );
2993
+ const startIdx = response.indexOf("{");
2994
+ if (startIdx !== -1) {
2995
+ let braceCount = 0;
2996
+ let endIdx = -1;
2997
+ for (let i = startIdx; i < response.length; i++) {
2998
+ if (response[i] === "{") braceCount++;
2999
+ else if (response[i] === "}") {
3000
+ braceCount--;
3001
+ if (braceCount === 0) {
3002
+ endIdx = i;
3003
+ break;
3004
+ }
3005
+ }
3006
+ }
3007
+ if (endIdx !== -1) {
3008
+ const jsonContent = response.substring(startIdx, endIdx + 1);
3009
+ const cleanJson = this.sanitizeJson(jsonContent);
3010
+ const mapping = JSON.parse(cleanJson);
3011
+ this.cache.set(cacheKey, mapping);
3012
+ return mapping;
3013
+ }
3014
+ }
3015
+ } catch (error) {
3016
+ console.warn("[SchemaMapper] AI training failed, falling back to heuristics:", error);
3017
+ }
3018
+ return {};
3019
+ }
3020
+ /**
3021
+ * Forgiving JSON parser that fixes common AI formatting mistakes.
3022
+ */
3023
+ static sanitizeJson(s) {
3024
+ return s.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "").replace(/[\u201C\u201D\u2018\u2019]/g, '"').replace(/'/g, '"').replace(/([{,]\s*)([a-zA-Z0-9_]+?)\s*:/g, '$1"$2":').replace(/,\s*([}\]])/g, "$1").trim();
3025
+ }
3026
+ static getCached(projectId, keys) {
3027
+ const cacheKey = `${projectId}:${keys.sort().join(",")}`;
3028
+ return this.cache.get(cacheKey);
3029
+ }
3030
+ };
3031
+ SchemaMapper.cache = /* @__PURE__ */ new Map();
3032
+ /**
3033
+ * Descriptions of standard UI properties to help the AI map fields accurately.
3034
+ */
3035
+ SchemaMapper.TARGET_PROPERTIES = {
3036
+ name: "The primary title, name, or label of the item",
3037
+ price: "The numeric cost, price, MSRP, or amount",
3038
+ brand: "The manufacturer, vendor, brand, or maker",
3039
+ image: "The URL to an image, thumbnail, photo, or picture",
3040
+ stock: "The availability, inventory count, quantity, or stock level",
3041
+ description: "The detailed text content, summary, or body info",
3042
+ category: "The group, department, type, or category name"
2574
3043
  };
2575
3044
 
2576
3045
  // src/core/Pipeline.ts
@@ -2603,6 +3072,57 @@ var LRUEmbeddingCache = class {
2603
3072
  return this.cache.size;
2604
3073
  }
2605
3074
  };
3075
+ function estimateTokens(text) {
3076
+ return Math.ceil(text.length / 4);
3077
+ }
3078
+ var MODEL_COST_PER_1K = {
3079
+ "gpt-4o": { input: 25e-4, output: 0.01 },
3080
+ "gpt-4o-mini": { input: 15e-5, output: 6e-4 },
3081
+ "gpt-4-turbo": { input: 0.01, output: 0.03 },
3082
+ "gpt-3.5-turbo": { input: 5e-4, output: 15e-4 },
3083
+ "claude-3-5-sonnet": { input: 3e-3, output: 0.015 },
3084
+ "claude-3-haiku": { input: 25e-5, output: 125e-5 },
3085
+ "gemini-1.5-flash": { input: 75e-6, output: 3e-4 },
3086
+ "gemini-1.5-pro": { input: 125e-5, output: 5e-3 }
3087
+ };
3088
+ function estimateCostUsd(promptTokens, completionTokens, model) {
3089
+ if (!model) return void 0;
3090
+ const key = Object.keys(MODEL_COST_PER_1K).find((k) => model.toLowerCase().includes(k));
3091
+ if (!key) return void 0;
3092
+ const prices = MODEL_COST_PER_1K[key];
3093
+ return promptTokens / 1e3 * prices.input + completionTokens / 1e3 * prices.output;
3094
+ }
3095
+ async function scoreHallucination(llm, answer, context) {
3096
+ const maxContextChars = 3e3;
3097
+ const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
3098
+ const prompt = `You are an AI quality checker. Given the CONTEXT retrieved from a knowledge base and the ANSWER generated from it, rate how well-grounded the answer is.
3099
+
3100
+ CONTEXT:
3101
+ ${truncatedContext}
3102
+
3103
+ ANSWER:
3104
+ ${answer}
3105
+
3106
+ Return ONLY a valid JSON object with no markdown fences:
3107
+ {"score": <float 0-1>, "reason": "<one sentence>"}
3108
+
3109
+ Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
3110
+ try {
3111
+ const raw = await llm.chat(
3112
+ [{ role: "user", content: prompt }],
3113
+ "",
3114
+ { temperature: 0, maxTokens: 120 }
3115
+ );
3116
+ const jsonMatch = raw.match(/\{[\s\S]*\}/);
3117
+ if (!jsonMatch) return void 0;
3118
+ const parsed = JSON.parse(jsonMatch[0]);
3119
+ if (typeof parsed.score === "number" && typeof parsed.reason === "string") {
3120
+ return { score: Math.min(1, Math.max(0, parsed.score)), reason: parsed.reason };
3121
+ }
3122
+ } catch (e) {
3123
+ }
3124
+ return void 0;
3125
+ }
2606
3126
  var Pipeline = class {
2607
3127
  constructor(config) {
2608
3128
  this.config = config;
@@ -2619,206 +3139,30 @@ var Pipeline = class {
2619
3139
  }
2620
3140
  this.reranker = new Reranker();
2621
3141
  }
3142
+ /**
3143
+ * Expose the underlying LLM provider (set after initialize()).
3144
+ * Used by the stream handler to pass to UITransformer.analyzeAndDecide().
3145
+ */
3146
+ getLLMProvider() {
3147
+ return this.initialised ? this.llmProvider : void 0;
3148
+ }
2622
3149
  async initialize() {
2623
- var _a, _b;
3150
+ var _a;
2624
3151
  if (this.initialised) return;
2625
- const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
2626
- const chartInstruction = `
2627
-
2628
- ${CHART_MARKER}
2629
- ### UNIVERSAL UI PROTOCOL V7 (STRICT MODE)
2630
-
2631
- You are responsible for returning a SINGLE structured UI block when visualization is required.
2632
-
2633
- ---
2634
-
2635
- ## 1. INTENT CLASSIFICATION (MANDATORY)
2636
-
2637
- Classify the query into ONE of the following intents:
2638
-
2639
- - "explore" \u2192 user wants to browse items (e.g., "show me products")
2640
- - "analyze" \u2192 user wants aggregation/distribution (e.g., "distribution of products")
2641
- - "compare" \u2192 user wants side-by-side comparison
2642
- - "detail" \u2192 user wants structured/tabular data
2643
-
2644
- ---
2645
-
2646
- ## 2. VIEW SELECTION (DYNAMIC)
2647
-
2648
- Choose the best primary "view" ("carousel", "chart", or "table") based on the data:
2649
- - TRENDS/GROWTH \u2192 "chart" (type: "line")
2650
- - DISTRIBUTIONS/RATIOS \u2192 "chart" (type: "pie")
2651
- - COMPARISONS \u2192 "chart" (type: "bar")
2652
- - PRODUCT LISTS \u2192 "carousel"
2653
- - RAW DATA/SPECS \u2192 "table"
2654
-
2655
- \u26A0\uFE0F Dynamic Overrides:
2656
- - If query is "show products", ALWAYS use carousel.
2657
- - If data is statistical, ALWAYS use a chart.
2658
- - NEVER explain why you chose a view.
2659
-
2660
- ---
2661
-
2662
- ## 3. RESPONSE FORMAT (STRICT JSON ONLY INSIDE BLOCK)
2663
-
2664
- \`\`\`ui
2665
- {
2666
- "view": "carousel" | "chart" | "table",
2667
- "title": "string",
2668
- "description": "string",
2669
- "metadata": {
2670
- "intent": "explore" | "analyze" | "compare" | "detail",
2671
- "confidence": 0.0-1.0
2672
- },
2673
-
2674
- // CHART ONLY
2675
- "chart": {
2676
- "type": "pie" | "bar" | "line",
2677
- "xKey": "label",
2678
- "yKeys": ["value"],
2679
- "data": []
2680
- },
2681
-
2682
- // TABLE ONLY
2683
- "table": {
2684
- "columns": [],
2685
- "rows": []
2686
- },
2687
-
2688
- // CAROUSEL ONLY
2689
- "carousel": {
2690
- "items": []
2691
- },
2692
-
2693
- "insights": []
2694
- }
2695
- \`\`\`
2696
-
2697
- ---
2698
-
2699
- ## 4. DATA CONTRACT (CRITICAL)
2700
-
2701
- ### \u{1F539} CAROUSEL ITEM FORMAT
2702
- {
2703
- "id": "string",
2704
- "name": "string",
2705
- "brand": "string",
2706
- "price": number,
2707
- "image": "url",
2708
- "link": "url",
2709
- "inStock": boolean
2710
- }
2711
-
2712
- ### \u{1F539} CHART DATA FORMAT
2713
- {
2714
- "label": "string",
2715
- "value": number,
2716
- "inStockCount": number (optional),
2717
- "outOfStockCount": number (optional)
2718
- }
2719
-
2720
- ### \u{1F539} TABLE FORMAT
2721
- - columns MUST match row keys
2722
- - rows MUST be flat objects
2723
-
2724
- ---
2725
-
2726
- ## 5. DATA RULES
2727
-
2728
- - NEVER hallucinate fields or data.
2729
- - ONLY use data provided in the retrieved context.
2730
- - STRICT DATA ISOLATION: The labels and numbers in the EXAMPLES section below are placeholders. NEVER use them.
2731
- - IF NO DATA IS FOUND: You MUST NOT generate a chart. You MUST NOT say "This example assumes" or provide hypothetical numbers. Simply state "No relevant data found in the database." and STOP.
2732
- - NO META-COMMENTARY: NEVER talk about the task itself (e.g., "I've used JSON", "In a real-world app", "Dedicated library").
2733
- - NEVER explain YOUR formatting choice.
2734
- - Aggregate BEFORE rendering chart.
2735
- - DYNAMIC INTELLIGENCE: Autonomously extract relevant categories/values from the context and calculate totals for the "data" array based on the user query.
2736
-
2737
- ---
2738
-
2739
- ## 6. SMART RULES (IMPORTANT)
2740
-
2741
- - If user asks BOTH:
2742
- "show products AND distribution"
2743
- \u2192 PRIORITIZE "explore" \u2192 carousel
2744
-
2745
- - If dataset size > 20:
2746
- \u2192 aggregate \u2192 chart or table
2747
-
2748
- ---
2749
-
2750
- ## 7. OUTPUT RULES
2751
-
2752
- - ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block.
2753
- - JSON must be VALID.
2754
- - FIELD MAPPING (MANDATORY): Always use "label" for the name/category and "value" for the numeric count.
2755
- - NO xKey or yKeys: Do not include these fields; the UI uses "label" and "value" by default.
2756
- - SILENT DATA: If a chart is provided, DO NOT say "Here is the JSON" or "Below is the data". Simply output the block.
2757
- - TEXT OUTSIDE THE BLOCK: One brief sentence maximum.
2758
-
2759
- ---
2760
-
2761
- ## 8. EXAMPLES
2762
-
2763
- User: "show me beauty products"
2764
-
2765
- \`\`\`ui
2766
- {
2767
- "view": "carousel",
2768
- "title": "TITLE",
2769
- "description": "DESCRIPTION",
2770
- "metadata": {
2771
- "intent": "explore",
2772
- "confidence": 0.95
2773
- },
2774
- "carousel": {
2775
- "items": [
2776
- {
2777
- "id": "ID",
2778
- "name": "PRODUCT_NAME",
2779
- "brand": "BRAND",
2780
- "price": 0,
2781
- "image": "URL",
2782
- "link": "URL",
2783
- "inStock": true
2784
- }
2785
- ]
2786
- },
2787
- "insights": ["INSIGHT"]
2788
- }
2789
- \`\`\`
2790
-
2791
- ---
2792
-
2793
-
2794
-
2795
- \`\`\`ui
2796
- {
2797
- "view": "chart",
2798
- "title": "TITLE",
2799
- "description": "DESCRIPTION",
2800
- "metadata": {
2801
- "intent": "analyze",
2802
- "confidence": 0.98
2803
- },
2804
- "chart": {
2805
- "type": "pie",
2806
- "data": [
2807
- { "label": "LABEL_A", "value": 0, "inStockCount": 0, "outOfStockCount": 0 },
2808
- { "label": "LABEL_B", "value": 0, "inStockCount": 0, "outOfStockCount": 0 }
2809
- ]
2810
- },
2811
- "insights": ["INSIGHT"]
2812
- }
2813
- \`\`\`
3152
+ const chartInstruction = `You are a helpful product assistant. Use the provided context to answer questions accurately.
3153
+
3154
+ ### UI STYLE RULES (CRITICAL):
3155
+ - NEVER generate markdown tables. If you do, the UI will break.
3156
+ - NEVER generate HTML tags like <figure>, <tbody>, <tr>, etc.
3157
+ - NEVER generate text-based charts or graphs.
3158
+ - ONLY use plain text and bullet points.
3159
+
3160
+ ### PRODUCT DISPLAY:
3161
+ - When recommending products, simply list their names, prices, and features in a friendly, conversational manner.
3162
+ - The UI will automatically detect these products and show high-quality product cards in a carousel below your message.
3163
+ - Do NOT try to format product lists as tables.
2814
3164
  `;
2815
- if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
2816
- let cleanPrompt = this.config.llm.systemPrompt || "";
2817
- cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
2818
- cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
2819
- this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
2820
- }
2821
- console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
3165
+ this.config.llm.systemPrompt = chartInstruction;
2822
3166
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
2823
3167
  const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
2824
3168
  this.config.llm,
@@ -2832,7 +3176,7 @@ User: "show me beauty products"
2832
3176
  this.entityExtractor = new EntityExtractor(this.llmProvider);
2833
3177
  }
2834
3178
  await this.vectorDB.initialize();
2835
- if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
3179
+ if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic") {
2836
3180
  this.agent = new LangChainAgent(this, this.config);
2837
3181
  await this.agent.initialize(this.llmProvider);
2838
3182
  }
@@ -2840,7 +3184,6 @@ User: "show me beauty products"
2840
3184
  }
2841
3185
  /**
2842
3186
  * Ingest documents with automatic chunking, embedding, and batch upsert.
2843
- * Handles retries for transient failures.
2844
3187
  */
2845
3188
  async ingest(documents, namespace) {
2846
3189
  await this.initialize();
@@ -2857,10 +3200,7 @@ User: "show me beauty products"
2857
3200
  metadata: chunk.metadata
2858
3201
  }));
2859
3202
  const totalProcessed = await this.processUpserts(upsertDocs, ns);
2860
- results.push({
2861
- docId: doc.docId,
2862
- chunksIngested: totalProcessed
2863
- });
3203
+ results.push({ docId: doc.docId, chunksIngested: totalProcessed });
2864
3204
  if (this.graphDB && this.entityExtractor) {
2865
3205
  await this.processGraphIngestion(doc.docId, chunks);
2866
3206
  }
@@ -2871,23 +3211,18 @@ User: "show me beauty products"
2871
3211
  }
2872
3212
  return results;
2873
3213
  }
2874
- /**
2875
- * Step 1: Chunk the document content.
2876
- */
3214
+ /** Step 1: Chunk the document content. */
2877
3215
  async prepareChunks(doc) {
2878
3216
  var _a, _b;
2879
3217
  if (this.llamaIngestor) {
2880
- return await this.llamaIngestor.chunk(doc.content, {
3218
+ return this.llamaIngestor.chunk(doc.content, {
2881
3219
  docId: doc.docId,
2882
3220
  metadata: doc.metadata,
2883
3221
  chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
2884
3222
  chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
2885
3223
  });
2886
3224
  }
2887
- return this.chunker.chunk(doc.content, {
2888
- docId: doc.docId,
2889
- metadata: doc.metadata
2890
- });
3225
+ return this.chunker.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata });
2891
3226
  }
2892
3227
  /**
2893
3228
  * Step 2: Generate embeddings for chunks with retry logic.
@@ -2911,9 +3246,7 @@ User: "show me beauty products"
2911
3246
  }
2912
3247
  return vectors;
2913
3248
  }
2914
- /**
2915
- * Step 3: Upsert chunks to vector database with retry logic.
2916
- */
3249
+ /** Step 3: Upsert chunks to vector database with retry logic. */
2917
3250
  async processUpserts(upsertDocs, namespace) {
2918
3251
  const upsertBatchOptions = {
2919
3252
  batchSize: 100,
@@ -2930,14 +3263,10 @@ User: "show me beauty products"
2930
3263
  }
2931
3264
  return upsertResult.totalProcessed;
2932
3265
  }
2933
- /**
2934
- * Step 4: Optional graph-based entity extraction and ingestion.
2935
- */
3266
+ /** Step 4: Optional graph-based entity extraction and ingestion. */
2936
3267
  async processGraphIngestion(docId, chunks) {
2937
- console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
2938
3268
  const extractionOptions = {
2939
3269
  batchSize: 2,
2940
- // Low concurrency for LLM extraction
2941
3270
  maxRetries: 1,
2942
3271
  initialDelayMs: 500
2943
3272
  };
@@ -2950,7 +3279,7 @@ User: "show me beauty products"
2950
3279
  if (nodes.length > 0) await this.graphDB.addNodes(nodes);
2951
3280
  if (edges.length > 0) await this.graphDB.addEdges(edges);
2952
3281
  } catch (err) {
2953
- console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
3282
+ console.warn(`[Pipeline] Entity extraction failed for chunk in doc ${docId}:`, err);
2954
3283
  }
2955
3284
  }
2956
3285
  },
@@ -2961,7 +3290,6 @@ User: "show me beauty products"
2961
3290
  var _a;
2962
3291
  await this.initialize();
2963
3292
  if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
2964
- console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
2965
3293
  const agentReply = await this.agent.run(question, history);
2966
3294
  return { reply: agentReply, sources: [] };
2967
3295
  }
@@ -2970,6 +3298,7 @@ User: "show me beauty products"
2970
3298
  let sources = [];
2971
3299
  let graphData;
2972
3300
  let uiTransformation;
3301
+ let trace;
2973
3302
  try {
2974
3303
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2975
3304
  const chunk = temp.value;
@@ -2979,6 +3308,7 @@ User: "show me beauty products"
2979
3308
  if ("sources" in chunk) sources = chunk.sources;
2980
3309
  if ("graphData" in chunk) graphData = chunk.graphData;
2981
3310
  if ("ui_transformation" in chunk) uiTransformation = chunk.ui_transformation;
3311
+ if ("trace" in chunk) trace = chunk.trace;
2982
3312
  }
2983
3313
  }
2984
3314
  } catch (temp) {
@@ -2991,38 +3321,47 @@ User: "show me beauty products"
2991
3321
  throw error[0];
2992
3322
  }
2993
3323
  }
2994
- return { reply, sources, graphData, ui_transformation: uiTransformation };
3324
+ return { reply, sources, graphData, ui_transformation: uiTransformation, trace };
2995
3325
  }
2996
3326
  /**
2997
3327
  * High-performance streaming RAG flow.
2998
- * Yields text chunks first, then the retrieval metadata at the end.
3328
+ * Yields text chunks first, then the retrieval metadata + observability trace at the end.
2999
3329
  */
3000
3330
  askStream(_0) {
3001
3331
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
3002
- var _a, _b, _c, _d, _e, _f, _g;
3332
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
3003
3333
  yield new __await(this.initialize());
3004
3334
  const ns = namespace != null ? namespace : this.config.projectId;
3005
3335
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
3006
3336
  const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
3337
+ const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
3338
+ const requestStart = performance.now();
3007
3339
  try {
3008
3340
  let searchQuery = question;
3341
+ let rewrittenQuery;
3009
3342
  if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
3010
3343
  searchQuery = yield new __await(this.rewriteQuery(question, history));
3344
+ rewrittenQuery = searchQuery !== question ? searchQuery : void 0;
3011
3345
  }
3012
3346
  const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
3013
3347
  const filter = QueryProcessor.buildQueryFilter(question, hints);
3014
- console.log(`[Pipeline] \u{1F9E9} Extracted filters:`, JSON.stringify(filter));
3348
+ const embedStart = performance.now();
3015
3349
  const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
3016
3350
  namespace: ns,
3017
3351
  topK: topK * 2,
3018
3352
  filter
3019
3353
  }));
3354
+ const retrieveEnd = performance.now();
3355
+ const embedMs = retrieveEnd - embedStart;
3356
+ const retrieveMs = retrieveEnd - embedStart;
3357
+ const rerankStart = performance.now();
3020
3358
  let sources = rawSources.filter((m) => m.score >= scoreThreshold);
3021
3359
  if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
3022
3360
  sources = yield new __await(this.reranker.rerank(sources, question, topK));
3023
3361
  } else {
3024
3362
  sources = sources.slice(0, topK);
3025
3363
  }
3364
+ const rerankMs = performance.now() - rerankStart;
3026
3365
  let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
3027
3366
  ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
3028
3367
  if (graphData && graphData.nodes.length > 0) {
@@ -3035,7 +3374,16 @@ ${graphContext}
3035
3374
  VECTOR CONTEXT:
3036
3375
  ${context}`;
3037
3376
  }
3038
- const messages = [...history, { role: "user", content: question }];
3377
+ const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
3378
+ const trainingPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys) : Promise.resolve(void 0);
3379
+ const restrictionSuffix = "\n\n(IMPORTANT: Use plain text only. NEVER generate tables, HTML figures, or text charts. If listing products, use simple bullet points.)";
3380
+ const hardenedHistory = [...history];
3381
+ const userQuestion = { role: "user", content: question + restrictionSuffix };
3382
+ const messages = [...hardenedHistory, userQuestion];
3383
+ const systemPrompt = (_h = this.config.llm.systemPrompt) != null ? _h : "";
3384
+ const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
3385
+ let fullReply = "";
3386
+ const generateStart = performance.now();
3039
3387
  if (this.llmProvider.chatStream) {
3040
3388
  const stream = this.llmProvider.chatStream(messages, context);
3041
3389
  if (!stream) {
@@ -3044,6 +3392,7 @@ ${context}`;
3044
3392
  try {
3045
3393
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
3046
3394
  const chunk = temp.value;
3395
+ fullReply += chunk;
3047
3396
  yield chunk;
3048
3397
  }
3049
3398
  } catch (temp) {
@@ -3058,14 +3407,58 @@ ${context}`;
3058
3407
  }
3059
3408
  } else {
3060
3409
  const reply = yield new __await(this.llmProvider.chat(messages, context));
3410
+ fullReply = reply;
3061
3411
  yield reply;
3062
3412
  }
3063
- const uiTransformation = UITransformer.transform(question, sources);
3413
+ const generateMs = performance.now() - generateStart;
3414
+ const totalMs = performance.now() - requestStart;
3415
+ const latency = {
3416
+ embedMs: Math.round(embedMs),
3417
+ retrieveMs: Math.round(retrieveMs),
3418
+ rerankMs: ((_i = this.config.rag) == null ? void 0 : _i.useReranking) ? Math.round(rerankMs) : void 0,
3419
+ generateMs: Math.round(generateMs),
3420
+ totalMs: Math.round(totalMs)
3421
+ };
3422
+ const promptText = systemPrompt + "\n" + context + "\n" + userPrompt;
3423
+ const promptTokens = estimateTokens(promptText);
3424
+ const completionTokens = estimateTokens(fullReply);
3425
+ const tokens = {
3426
+ promptTokens,
3427
+ completionTokens,
3428
+ totalTokens: promptTokens + completionTokens,
3429
+ estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
3430
+ };
3431
+ const hallucinationResult = yield new __await(scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0));
3432
+ const trainedSchema = yield new __await(trainingPromise);
3433
+ const uiTransformation = yield new __await(this.generateUiTransformation(question, sources, context, trainedSchema));
3434
+ const trace = {
3435
+ requestId,
3436
+ query: question,
3437
+ rewrittenQuery,
3438
+ systemPrompt,
3439
+ userPrompt: question + restrictionSuffix,
3440
+ chunks: sources.map((s) => {
3441
+ var _a2;
3442
+ return {
3443
+ id: s.id,
3444
+ score: s.score,
3445
+ content: s.content,
3446
+ metadata: (_a2 = s.metadata) != null ? _a2 : {},
3447
+ namespace: ns
3448
+ };
3449
+ }),
3450
+ latency,
3451
+ tokens,
3452
+ hallucinationScore: hallucinationResult == null ? void 0 : hallucinationResult.score,
3453
+ hallucinationReason: hallucinationResult == null ? void 0 : hallucinationResult.reason,
3454
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3455
+ };
3064
3456
  yield {
3065
3457
  reply: "",
3066
3458
  sources,
3067
3459
  graphData,
3068
- ui_transformation: uiTransformation
3460
+ ui_transformation: uiTransformation,
3461
+ trace
3069
3462
  };
3070
3463
  } catch (error2) {
3071
3464
  throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
@@ -3076,6 +3469,17 @@ ${context}`;
3076
3469
  * Universal retrieval method combining all enabled providers.
3077
3470
  * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
3078
3471
  */
3472
+ async generateUiTransformation(question, sources, _context, trainedSchema) {
3473
+ if (!sources || sources.length === 0) {
3474
+ return UITransformer.transform(question, sources, this.config, trainedSchema);
3475
+ }
3476
+ try {
3477
+ return await UITransformer.analyzeAndDecide(question, sources, this.llmProvider);
3478
+ } catch (err) {
3479
+ console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
3480
+ return UITransformer.transform(question, sources, this.config, trainedSchema);
3481
+ }
3482
+ }
3079
3483
  async retrieve(query, options) {
3080
3484
  var _a, _b, _c;
3081
3485
  const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
@@ -3093,12 +3497,9 @@ ${context}`;
3093
3497
  const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
3094
3498
  return { sources, graphData };
3095
3499
  }
3096
- /**
3097
- * Rewrite the user query for better retrieval performance.
3098
- */
3500
+ /** Rewrite the user query for better retrieval performance. */
3099
3501
  async rewriteQuery(question, history) {
3100
- const prompt = `
3101
- Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
3502
+ const prompt = `Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
3102
3503
  Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
3103
3504
 
3104
3505
  History:
@@ -3116,23 +3517,16 @@ Optimized Search Query:`;
3116
3517
  );
3117
3518
  return rewrite.trim() || question;
3118
3519
  }
3119
- /**
3120
- * Generate 3-5 short, relevant questions based on the vector database content.
3121
- */
3520
+ /** Generate 3-5 short, relevant questions based on the vector database content. */
3122
3521
  async getSuggestions(query, namespace) {
3123
- if (!query || query.trim().length < 3) {
3124
- return [];
3125
- }
3522
+ if (!query || query.trim().length < 3) return [];
3126
3523
  await this.initialize();
3127
3524
  const ns = namespace != null ? namespace : this.config.projectId;
3128
3525
  try {
3129
3526
  const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
3130
- if (sources.length === 0) {
3131
- return [];
3132
- }
3527
+ if (sources.length === 0) return [];
3133
3528
  const context = sources.map((s) => s.content).join("\n\n---\n\n");
3134
- const prompt = `
3135
- Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
3529
+ const prompt = `Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
3136
3530
  Focus on questions that can be answered by the context.
3137
3531
  Keep each question under 10 words and make them very specific to the content.
3138
3532
  Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
@@ -3278,6 +3672,14 @@ var VectorPlugin = class {
3278
3672
  getConfig() {
3279
3673
  return this.config;
3280
3674
  }
3675
+ /**
3676
+ * Get the initialized LLM provider (available after the first request).
3677
+ * Used by handlers to pass to UITransformer.analyzeAndDecide() for
3678
+ * LLM-driven visualization decisions.
3679
+ */
3680
+ getLLMProvider() {
3681
+ return this.pipeline.getLLMProvider();
3682
+ }
3281
3683
  /**
3282
3684
  * Perform pre-flight health checks on all configured providers.
3283
3685
  * Useful to verify connectivity before running operations.
@@ -3399,7 +3801,12 @@ function sseMetaFrame(meta) {
3399
3801
  `;
3400
3802
  }
3401
3803
  function sseUIFrame(uiTransformation) {
3402
- return `data: ${JSON.stringify(__spreadValues({ type: "ui_transformation" }, uiTransformation != null ? uiTransformation : {}))}
3804
+ return `data: ${JSON.stringify({ type: "ui_transformation", data: uiTransformation })}
3805
+
3806
+ `;
3807
+ }
3808
+ function sseObservabilityFrame(trace) {
3809
+ return `data: ${JSON.stringify({ type: "observability", data: trace })}
3403
3810
 
3404
3811
  `;
3405
3812
  }
@@ -3454,6 +3861,7 @@ function createStreamHandler(configOrPlugin) {
3454
3861
  const encoder = new TextEncoder();
3455
3862
  const stream = new ReadableStream({
3456
3863
  async start(controller) {
3864
+ var _a, _b;
3457
3865
  const enqueue = (text) => controller.enqueue(encoder.encode(text));
3458
3866
  try {
3459
3867
  const pipelineStream = plugin.chatStream(message, history, namespace);
@@ -3464,13 +3872,25 @@ function createStreamHandler(configOrPlugin) {
3464
3872
  enqueue(sseTextFrame(chunk));
3465
3873
  } else {
3466
3874
  enqueue(sseMetaFrame(chunk));
3467
- const sources = (chunk == null ? void 0 : chunk.sources) || [];
3468
- if (sources && sources.length > 0) {
3875
+ const responseChunk = chunk;
3876
+ const sources = (responseChunk == null ? void 0 : responseChunk.sources) || [];
3877
+ if (responseChunk == null ? void 0 : responseChunk.trace) {
3878
+ enqueue(sseObservabilityFrame(responseChunk.trace));
3879
+ }
3880
+ if (sources.length > 0) {
3469
3881
  try {
3470
- const uiTransformation = UITransformer.transform(message, sources);
3471
- enqueue(sseUIFrame(uiTransformation));
3882
+ const llmProvider = (_a = plugin.getLLMProvider) == null ? void 0 : _a.call(plugin);
3883
+ const uiTransformation = (_b = responseChunk == null ? void 0 : responseChunk.ui_transformation) != null ? _b : llmProvider ? await UITransformer.analyzeAndDecide(message, sources, llmProvider) : UITransformer.transform(message, sources, plugin.getConfig());
3884
+ if (uiTransformation) {
3885
+ enqueue(sseUIFrame(uiTransformation));
3886
+ }
3472
3887
  } catch (transformError) {
3473
3888
  console.warn("[createStreamHandler] UI transformation warning:", transformError);
3889
+ try {
3890
+ const fallback = UITransformer.transform(message, sources, plugin.getConfig());
3891
+ if (fallback) enqueue(sseUIFrame(fallback));
3892
+ } catch (e) {
3893
+ }
3474
3894
  }
3475
3895
  }
3476
3896
  }
@@ -3538,24 +3958,64 @@ function createUploadHandler(configOrPlugin) {
3538
3958
  const formData = await req.formData();
3539
3959
  const files = formData.getAll("files");
3540
3960
  const namespace = formData.get("namespace") || void 0;
3961
+ const dimensionRaw = formData.get("dimension");
3962
+ const dimension = dimensionRaw ? parseInt(dimensionRaw, 10) : void 0;
3541
3963
  if (!files || files.length === 0) {
3542
3964
  return NextResponse.json({ error: "No files provided" }, { status: 400 });
3543
3965
  }
3544
- const documents = await Promise.all(
3545
- files.map(async (file) => {
3966
+ const documents = [];
3967
+ for (const file of files) {
3968
+ if (file.name.toLowerCase().endsWith(".csv") || file.type === "text/csv") {
3969
+ const text = await file.text();
3970
+ const Papa = await import("papaparse");
3971
+ const parsed = Papa.default.parse(text, { header: true, skipEmptyLines: true });
3972
+ if (parsed.data && parsed.data.length > 0) {
3973
+ let i = 0;
3974
+ let lastRowData = null;
3975
+ for (const row of parsed.data) {
3976
+ i++;
3977
+ const rowData = row;
3978
+ const groupingKey = Object.keys(rowData)[0];
3979
+ if (lastRowData && groupingKey && rowData[groupingKey] && rowData[groupingKey] === lastRowData[groupingKey]) {
3980
+ for (const key of Object.keys(rowData)) {
3981
+ if (!rowData[key] && lastRowData[key]) {
3982
+ rowData[key] = lastRowData[key];
3983
+ }
3984
+ }
3985
+ }
3986
+ lastRowData = __spreadValues({}, rowData);
3987
+ const contentParts = [];
3988
+ for (const [key, val] of Object.entries(rowData)) {
3989
+ if (key && val) {
3990
+ contentParts.push(`${key}: ${val}`);
3991
+ }
3992
+ }
3993
+ documents.push({
3994
+ docId: `${file.name}-row-${i}`,
3995
+ content: contentParts.join(", "),
3996
+ metadata: __spreadValues(__spreadValues({
3997
+ fileName: file.name,
3998
+ fileSize: file.size,
3999
+ fileType: file.type,
4000
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
4001
+ }, dimension ? { dimension } : {}), rowData)
4002
+ });
4003
+ }
4004
+ }
4005
+ } else {
3546
4006
  const content = await DocumentParser.parse(file, file.name, file.type);
3547
- return {
4007
+ documents.push({
3548
4008
  docId: file.name,
3549
4009
  content,
3550
- metadata: {
4010
+ metadata: __spreadValues({
3551
4011
  fileName: file.name,
3552
4012
  fileSize: file.size,
3553
4013
  fileType: file.type,
3554
4014
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
3555
- }
3556
- };
3557
- })
3558
- );
4015
+ }, dimension ? { dimension } : {})
4016
+ });
4017
+ }
4018
+ }
3559
4019
  const results = await plugin.ingest(documents, namespace);
3560
4020
  return NextResponse.json({ message: "Upload successful", results });
3561
4021
  } catch (err) {
@@ -3606,6 +4066,7 @@ export {
3606
4066
  sseTextFrame,
3607
4067
  sseMetaFrame,
3608
4068
  sseUIFrame,
4069
+ sseObservabilityFrame,
3609
4070
  sseErrorFrame,
3610
4071
  createChatHandler,
3611
4072
  createStreamHandler,