@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.
- package/dist/DocumentChunker-Dh9TvmGG.d.mts +45 -0
- package/dist/DocumentChunker-Dh9TvmGG.d.ts +45 -0
- package/dist/{index-DPsQodME.d.mts → ILLMProvider-BOJFz3Na.d.mts} +104 -1
- package/dist/{index-DPsQodME.d.ts → ILLMProvider-BOJFz3Na.d.ts} +104 -1
- package/dist/MultiTablePostgresProvider-ZLGSKTJR.mjs +8 -0
- package/dist/chunk-ICKRMZQK.mjs +76 -0
- package/dist/{chunk-PV3MFHWU.mjs → chunk-LZVVLSDN.mjs} +977 -516
- package/dist/chunk-OZFBG4BA.mjs +291 -0
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +1269 -656
- package/dist/handlers/index.mjs +4 -1
- package/dist/{index-Bb2yEopi.d.mts → index-BwpcaziY.d.ts} +10 -2
- package/dist/{index-CkbTzj9J.d.ts → index-D3V9Et2M.d.mts} +10 -2
- package/dist/index.d.mts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +1354 -826
- package/dist/index.mjs +1284 -795
- package/dist/server.d.mts +47 -27
- package/dist/server.d.ts +47 -27
- package/dist/server.js +1417 -829
- package/dist/server.mjs +164 -176
- package/package.json +6 -2
- package/src/app/api/upload/route.ts +4 -0
- package/src/app/constants.tsx +2 -2
- package/src/app/page.tsx +12 -322
- package/src/components/AmbientBackground.tsx +29 -0
- package/src/components/ArchitectureCard.tsx +17 -0
- package/src/components/ArchitectureCardsSection.tsx +15 -0
- package/src/components/ChatWindow.tsx +32 -0
- package/src/components/CodeViewer.tsx +51 -0
- package/src/components/ConfigProvider.tsx +1 -0
- package/src/components/DocViewer.tsx +37 -0
- package/src/components/DocumentUpload.tsx +44 -1
- package/src/components/Documentation.tsx +58 -0
- package/src/components/DynamicChart.tsx +27 -2
- package/src/components/Hero.tsx +59 -0
- package/src/components/HourglassLoader.tsx +87 -0
- package/src/components/Lifecycle.tsx +37 -0
- package/src/components/MarkdownComponents.tsx +140 -0
- package/src/components/MessageBubble.tsx +124 -904
- package/src/components/Navbar.tsx +55 -0
- package/src/components/ObservabilityPanel.tsx +374 -0
- package/src/components/ProductCard.tsx +5 -3
- package/src/components/UIDispatcher.tsx +344 -0
- package/src/components/VisualizationRenderer.tsx +372 -250
- package/src/config/RagConfig.ts +5 -0
- package/src/config/serverConfig.ts +3 -1
- package/src/core/Pipeline.ts +240 -271
- package/src/core/ProviderRegistry.ts +2 -2
- package/src/core/VectorPlugin.ts +9 -0
- package/src/handlers/index.ts +91 -15
- package/src/hooks/useRagChat.ts +21 -11
- package/src/index.ts +9 -1
- package/src/llm/LLMFactory.ts +54 -2
- package/src/llm/providers/AnthropicProvider.ts +12 -8
- package/src/llm/providers/GeminiProvider.ts +188 -143
- package/src/llm/providers/OllamaProvider.ts +7 -3
- package/src/llm/providers/OpenAIProvider.ts +12 -8
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +150 -64
- package/src/types/chat.ts +8 -0
- package/src/types/index.ts +132 -0
- package/src/types/props.ts +9 -1
- package/src/utils/ProductExtractor.ts +347 -0
- package/src/utils/SchemaMapper.ts +129 -0
- package/src/utils/UITransformer.ts +470 -209
- package/src/utils/synonyms.ts +78 -0
- package/dist/DocumentChunker-C1GEEosY.d.ts +0 -93
- package/dist/DocumentChunker-CFEiRopR.d.mts +0 -93
- package/dist/PostgreSQLProvider-BMOETDZA.mjs +0 -8
- 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 = (
|
|
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 = (
|
|
101
|
-
vectorDbOptions.database = (
|
|
102
|
-
vectorDbOptions.collection = (
|
|
103
|
-
vectorDbOptions.indexName = (
|
|
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 = (
|
|
106
|
-
vectorDbOptions.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 = (
|
|
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 = (
|
|
117
|
+
vectorDbOptions.baseUrl = (__ = readString(env, "CHROMADB_URL")) != null ? __ : "http://localhost:8000";
|
|
113
118
|
} else if (vectorProvider === "weaviate") {
|
|
114
|
-
vectorDbOptions.baseUrl = (
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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: (
|
|
141
|
-
custom: (
|
|
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: (
|
|
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: (
|
|
153
|
-
apiKey: (
|
|
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: (
|
|
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: (
|
|
176
|
-
subtitle: (
|
|
177
|
-
primaryColor: (
|
|
178
|
-
accentColor: (
|
|
179
|
-
logoUrl: (
|
|
180
|
-
placeholder: (
|
|
181
|
-
showSources: ((
|
|
182
|
-
welcomeMessage: (
|
|
183
|
-
visualStyle: (
|
|
184
|
-
borderRadius: (
|
|
185
|
-
allowUpload: ((
|
|
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
|
|
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:
|
|
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: (
|
|
325
|
-
temperature: (
|
|
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 (
|
|
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
|
|
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:
|
|
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: (
|
|
355
|
-
temperature: (
|
|
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 = ((
|
|
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
|
|
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 =
|
|
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: (
|
|
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
|
|
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 =
|
|
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: (
|
|
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 {
|
|
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)
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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:
|
|
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({
|
|
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
|
|
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 {
|
|
785
|
-
const
|
|
786
|
-
|
|
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
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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
|
-
|
|
866
|
+
return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
|
|
815
867
|
|
|
816
868
|
Context:
|
|
817
869
|
${context}`;
|
|
818
|
-
|
|
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
|
-
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
-
|
|
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
|
|
838
|
-
const
|
|
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
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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 =
|
|
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
|
|
884
|
-
const
|
|
885
|
-
|
|
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
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
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 "${
|
|
953
|
+
console.error(`[GeminiProvider] Embedding failed for model "${modelName}":`, err.message);
|
|
907
954
|
throw err;
|
|
908
955
|
});
|
|
909
|
-
return
|
|
956
|
+
return response.embedding.values;
|
|
910
957
|
}
|
|
911
958
|
async batchEmbed(texts, options) {
|
|
912
|
-
|
|
913
|
-
const model = this.
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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 "${
|
|
983
|
+
console.error(`[GeminiProvider] Batch embedding failed for model "${modelName}":`, err.message);
|
|
932
984
|
throw err;
|
|
933
985
|
});
|
|
934
|
-
return
|
|
935
|
-
var
|
|
936
|
-
return (
|
|
937
|
-
})
|
|
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
|
-
|
|
942
|
-
model:
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
1250
|
-
return
|
|
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
|
|
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
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
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 (
|
|
2263
|
-
return
|
|
2341
|
+
if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
|
|
2342
|
+
return this.transformToPieChart(filteredData);
|
|
2264
2343
|
}
|
|
2265
|
-
if (
|
|
2266
|
-
return
|
|
2344
|
+
if (hasProducts) {
|
|
2345
|
+
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
2267
2346
|
}
|
|
2268
|
-
if (
|
|
2269
|
-
return
|
|
2347
|
+
if (this.hasMultipleFields(filteredData)) {
|
|
2348
|
+
return this.transformToTable(filteredData);
|
|
2270
2349
|
}
|
|
2271
|
-
|
|
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 = [
|
|
2394
|
-
|
|
2395
|
-
|
|
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 = ["
|
|
2404
|
-
|
|
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
|
|
2650
|
+
* Helper: Extract property from metadata using mapping, AI training, case-insensitivity, and synonyms.
|
|
2410
2651
|
*/
|
|
2411
|
-
static
|
|
2412
|
-
|
|
2413
|
-
if (meta
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
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
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
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:
|
|
2430
|
-
price:
|
|
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 (
|
|
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
|
|
3150
|
+
var _a;
|
|
2624
3151
|
if (this.initialised) return;
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
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
|
-
|
|
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 (((
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
3468
|
-
|
|
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
|
|
3471
|
-
|
|
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 =
|
|
3545
|
-
|
|
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
|
-
|
|
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,
|