@retrivora-ai/rag-engine 1.7.4 → 1.7.6
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/{chunk-PKK7EEVP.mjs → chunk-XGVKIPNY.mjs} +11 -4
- package/dist/handlers/index.js +11 -4
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +13 -0
- package/dist/index.mjs +13 -0
- package/dist/server.js +24 -7
- package/dist/server.mjs +14 -4
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +51 -1
- package/src/core/Pipeline.ts +10 -2
- package/src/llm/providers/GeminiProvider.ts +7 -2
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +25 -3
|
@@ -829,7 +829,8 @@ ${context}`;
|
|
|
829
829
|
stopSequences: options == null ? void 0 : options.stop
|
|
830
830
|
}
|
|
831
831
|
});
|
|
832
|
-
|
|
832
|
+
const text = typeof response.text === "function" ? response.text() : (_f = response.text) != null ? _f : "";
|
|
833
|
+
return text;
|
|
833
834
|
}
|
|
834
835
|
chatStream(messages, context, options) {
|
|
835
836
|
return __asyncGenerator(this, null, function* () {
|
|
@@ -863,7 +864,7 @@ ${context}`;
|
|
|
863
864
|
try {
|
|
864
865
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
865
866
|
const chunk = temp.value;
|
|
866
|
-
const text = chunk.text;
|
|
867
|
+
const text = typeof chunk.text === "function" ? chunk.text() : chunk.text;
|
|
867
868
|
if (text) yield text;
|
|
868
869
|
}
|
|
869
870
|
} catch (temp) {
|
|
@@ -2361,11 +2362,14 @@ Classify the query into ONE of the following intents:
|
|
|
2361
2362
|
|
|
2362
2363
|
## 5. DATA RULES
|
|
2363
2364
|
|
|
2364
|
-
- NEVER hallucinate fields
|
|
2365
|
+
- NEVER hallucinate fields or data
|
|
2365
2366
|
- ONLY use retrieved vector DB data
|
|
2366
2367
|
- NO placeholders ("...", "N/A")
|
|
2367
2368
|
- If image/link missing \u2192 REMOVE FIELD
|
|
2368
2369
|
- Aggregate BEFORE rendering chart
|
|
2370
|
+
- IF NO DATA IS FOUND in the retrieved context:
|
|
2371
|
+
\u2192 DO NOT output a \`\`\`ui\`\`\` block
|
|
2372
|
+
\u2192 Instead, state clearly that no relevant products or data were found.
|
|
2369
2373
|
|
|
2370
2374
|
---
|
|
2371
2375
|
|
|
@@ -2445,7 +2449,10 @@ User: "distribution of products across categories"
|
|
|
2445
2449
|
\`\`\`
|
|
2446
2450
|
`;
|
|
2447
2451
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
2448
|
-
|
|
2452
|
+
let cleanPrompt = this.config.llm.systemPrompt || "";
|
|
2453
|
+
cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
|
|
2454
|
+
cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
|
|
2455
|
+
this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
|
|
2449
2456
|
}
|
|
2450
2457
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
2451
2458
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
package/dist/handlers/index.js
CHANGED
|
@@ -2437,7 +2437,8 @@ ${context}`;
|
|
|
2437
2437
|
stopSequences: options == null ? void 0 : options.stop
|
|
2438
2438
|
}
|
|
2439
2439
|
});
|
|
2440
|
-
|
|
2440
|
+
const text = typeof response.text === "function" ? response.text() : (_f = response.text) != null ? _f : "";
|
|
2441
|
+
return text;
|
|
2441
2442
|
}
|
|
2442
2443
|
chatStream(messages, context, options) {
|
|
2443
2444
|
return __asyncGenerator(this, null, function* () {
|
|
@@ -2471,7 +2472,7 @@ ${context}`;
|
|
|
2471
2472
|
try {
|
|
2472
2473
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2473
2474
|
const chunk = temp.value;
|
|
2474
|
-
const text = chunk.text;
|
|
2475
|
+
const text = typeof chunk.text === "function" ? chunk.text() : chunk.text;
|
|
2475
2476
|
if (text) yield text;
|
|
2476
2477
|
}
|
|
2477
2478
|
} catch (temp) {
|
|
@@ -3925,11 +3926,14 @@ Classify the query into ONE of the following intents:
|
|
|
3925
3926
|
|
|
3926
3927
|
## 5. DATA RULES
|
|
3927
3928
|
|
|
3928
|
-
- NEVER hallucinate fields
|
|
3929
|
+
- NEVER hallucinate fields or data
|
|
3929
3930
|
- ONLY use retrieved vector DB data
|
|
3930
3931
|
- NO placeholders ("...", "N/A")
|
|
3931
3932
|
- If image/link missing \u2192 REMOVE FIELD
|
|
3932
3933
|
- Aggregate BEFORE rendering chart
|
|
3934
|
+
- IF NO DATA IS FOUND in the retrieved context:
|
|
3935
|
+
\u2192 DO NOT output a \`\`\`ui\`\`\` block
|
|
3936
|
+
\u2192 Instead, state clearly that no relevant products or data were found.
|
|
3933
3937
|
|
|
3934
3938
|
---
|
|
3935
3939
|
|
|
@@ -4009,7 +4013,10 @@ User: "distribution of products across categories"
|
|
|
4009
4013
|
\`\`\`
|
|
4010
4014
|
`;
|
|
4011
4015
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
4012
|
-
|
|
4016
|
+
let cleanPrompt = this.config.llm.systemPrompt || "";
|
|
4017
|
+
cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
|
|
4018
|
+
cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
|
|
4019
|
+
this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
|
|
4013
4020
|
}
|
|
4014
4021
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
4015
4022
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
package/dist/handlers/index.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -637,6 +637,11 @@ function normalizeTabularRows(rows, columns) {
|
|
|
637
637
|
}).filter((row) => Object.keys(row).length > 0);
|
|
638
638
|
}
|
|
639
639
|
function resolveStructuredRows(config) {
|
|
640
|
+
var _a, _b, _c, _d;
|
|
641
|
+
if (((_a = config.carousel) == null ? void 0 : _a.items) && Array.isArray(config.carousel.items)) return config.carousel.items;
|
|
642
|
+
if (((_b = config.chart) == null ? void 0 : _b.data) && Array.isArray(config.chart.data)) return config.chart.data;
|
|
643
|
+
if (((_c = config.table) == null ? void 0 : _c.rows) && Array.isArray(config.table.rows)) return config.table.rows;
|
|
644
|
+
if (((_d = config.table) == null ? void 0 : _d.data) && Array.isArray(config.table.data)) return config.table.data;
|
|
640
645
|
if (Array.isArray(config.data) && config.data.length > 0) return config.data;
|
|
641
646
|
if (Array.isArray(config.items) && config.items.length > 0) return config.items;
|
|
642
647
|
if (Array.isArray(config.products) && config.products.length > 0) return config.products;
|
|
@@ -652,6 +657,14 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
652
657
|
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
653
658
|
const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
|
|
654
659
|
const config2 = __spreadValues({}, parsed);
|
|
660
|
+
if (config2.chart) {
|
|
661
|
+
if (!config2.chartType) config2.chartType = config2.chart.type;
|
|
662
|
+
if (!config2.xAxisKey) config2.xAxisKey = config2.chart.xKey;
|
|
663
|
+
if (!config2.dataKeys) config2.dataKeys = config2.chart.yKeys;
|
|
664
|
+
}
|
|
665
|
+
if (config2.table) {
|
|
666
|
+
if (!config2.columns) config2.columns = config2.table.columns;
|
|
667
|
+
}
|
|
655
668
|
const resolvedRows = resolveStructuredRows(config2);
|
|
656
669
|
const hasProductLikeData = resolvedRows.length > 0 && typeof resolvedRows[0] === "object" && resolvedRows[0] !== null && !Array.isArray(resolvedRows[0]) && (["price", "brand", "image", "link", "thumbnail"].some((key) => key in resolvedRows[0]) || Object.keys(resolvedRows[0]).length >= 4);
|
|
657
670
|
const hasAggregationLikeData = resolvedRows.length > 0 && typeof resolvedRows[0] === "object" && resolvedRows[0] !== null && !Array.isArray(resolvedRows[0]) && (["value", "count", "total", "percentage"].some((key) => key in resolvedRows[0]) || Object.keys(resolvedRows[0]).length <= 3 && Object.values(resolvedRows[0]).some((v) => typeof v === "number"));
|
package/dist/index.mjs
CHANGED
|
@@ -600,6 +600,11 @@ function normalizeTabularRows(rows, columns) {
|
|
|
600
600
|
}).filter((row) => Object.keys(row).length > 0);
|
|
601
601
|
}
|
|
602
602
|
function resolveStructuredRows(config) {
|
|
603
|
+
var _a, _b, _c, _d;
|
|
604
|
+
if (((_a = config.carousel) == null ? void 0 : _a.items) && Array.isArray(config.carousel.items)) return config.carousel.items;
|
|
605
|
+
if (((_b = config.chart) == null ? void 0 : _b.data) && Array.isArray(config.chart.data)) return config.chart.data;
|
|
606
|
+
if (((_c = config.table) == null ? void 0 : _c.rows) && Array.isArray(config.table.rows)) return config.table.rows;
|
|
607
|
+
if (((_d = config.table) == null ? void 0 : _d.data) && Array.isArray(config.table.data)) return config.table.data;
|
|
603
608
|
if (Array.isArray(config.data) && config.data.length > 0) return config.data;
|
|
604
609
|
if (Array.isArray(config.items) && config.items.length > 0) return config.items;
|
|
605
610
|
if (Array.isArray(config.products) && config.products.length > 0) return config.products;
|
|
@@ -615,6 +620,14 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
615
620
|
const clean = rawContent.replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "").trim();
|
|
616
621
|
const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
|
|
617
622
|
const config2 = __spreadValues({}, parsed);
|
|
623
|
+
if (config2.chart) {
|
|
624
|
+
if (!config2.chartType) config2.chartType = config2.chart.type;
|
|
625
|
+
if (!config2.xAxisKey) config2.xAxisKey = config2.chart.xKey;
|
|
626
|
+
if (!config2.dataKeys) config2.dataKeys = config2.chart.yKeys;
|
|
627
|
+
}
|
|
628
|
+
if (config2.table) {
|
|
629
|
+
if (!config2.columns) config2.columns = config2.table.columns;
|
|
630
|
+
}
|
|
618
631
|
const resolvedRows = resolveStructuredRows(config2);
|
|
619
632
|
const hasProductLikeData = resolvedRows.length > 0 && typeof resolvedRows[0] === "object" && resolvedRows[0] !== null && !Array.isArray(resolvedRows[0]) && (["price", "brand", "image", "link", "thumbnail"].some((key) => key in resolvedRows[0]) || Object.keys(resolvedRows[0]).length >= 4);
|
|
620
633
|
const hasAggregationLikeData = resolvedRows.length > 0 && typeof resolvedRows[0] === "object" && resolvedRows[0] !== null && !Array.isArray(resolvedRows[0]) && (["value", "count", "total", "percentage"].some((key) => key in resolvedRows[0]) || Object.keys(resolvedRows[0]).length <= 3 && Object.values(resolvedRows[0]).some((v) => typeof v === "number"));
|
package/dist/server.js
CHANGED
|
@@ -2482,7 +2482,8 @@ ${context}`;
|
|
|
2482
2482
|
stopSequences: options == null ? void 0 : options.stop
|
|
2483
2483
|
}
|
|
2484
2484
|
});
|
|
2485
|
-
|
|
2485
|
+
const text = typeof response.text === "function" ? response.text() : (_f = response.text) != null ? _f : "";
|
|
2486
|
+
return text;
|
|
2486
2487
|
}
|
|
2487
2488
|
chatStream(messages, context, options) {
|
|
2488
2489
|
return __asyncGenerator(this, null, function* () {
|
|
@@ -2516,7 +2517,7 @@ ${context}`;
|
|
|
2516
2517
|
try {
|
|
2517
2518
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2518
2519
|
const chunk = temp.value;
|
|
2519
|
-
const text = chunk.text;
|
|
2520
|
+
const text = typeof chunk.text === "function" ? chunk.text() : chunk.text;
|
|
2520
2521
|
if (text) yield text;
|
|
2521
2522
|
}
|
|
2522
2523
|
} catch (temp) {
|
|
@@ -4015,11 +4016,14 @@ Classify the query into ONE of the following intents:
|
|
|
4015
4016
|
|
|
4016
4017
|
## 5. DATA RULES
|
|
4017
4018
|
|
|
4018
|
-
- NEVER hallucinate fields
|
|
4019
|
+
- NEVER hallucinate fields or data
|
|
4019
4020
|
- ONLY use retrieved vector DB data
|
|
4020
4021
|
- NO placeholders ("...", "N/A")
|
|
4021
4022
|
- If image/link missing \u2192 REMOVE FIELD
|
|
4022
4023
|
- Aggregate BEFORE rendering chart
|
|
4024
|
+
- IF NO DATA IS FOUND in the retrieved context:
|
|
4025
|
+
\u2192 DO NOT output a \`\`\`ui\`\`\` block
|
|
4026
|
+
\u2192 Instead, state clearly that no relevant products or data were found.
|
|
4023
4027
|
|
|
4024
4028
|
---
|
|
4025
4029
|
|
|
@@ -4099,7 +4103,10 @@ User: "distribution of products across categories"
|
|
|
4099
4103
|
\`\`\`
|
|
4100
4104
|
`;
|
|
4101
4105
|
if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
|
|
4102
|
-
|
|
4106
|
+
let cleanPrompt = this.config.llm.systemPrompt || "";
|
|
4107
|
+
cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
|
|
4108
|
+
cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
|
|
4109
|
+
this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
|
|
4103
4110
|
}
|
|
4104
4111
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
4105
4112
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
@@ -4923,6 +4930,16 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
4923
4930
|
).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
|
|
4924
4931
|
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
4925
4932
|
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
|
|
4933
|
+
const getDynamicKeywordQuery = () => {
|
|
4934
|
+
if (entityHints.length > 0) {
|
|
4935
|
+
return entityHints.map((h) => h.includes(" ") ? `"${h}"` : h).join(" & ");
|
|
4936
|
+
}
|
|
4937
|
+
if (queryText) {
|
|
4938
|
+
return queryText.toLowerCase().replace(/\b(show|me|the|product[s]?|item[s]?|card[s]?|of|category|find|list|browse|what|are|display|all|under|for|in|with|about)\b/g, "").trim().replace(/\s+/g, " & ");
|
|
4939
|
+
}
|
|
4940
|
+
return "";
|
|
4941
|
+
};
|
|
4942
|
+
const dynamicKeywordQuery = getDynamicKeywordQuery();
|
|
4926
4943
|
const queryPromises = this.tables.map(async (table) => {
|
|
4927
4944
|
try {
|
|
4928
4945
|
let sqlQuery = "";
|
|
@@ -4940,13 +4957,13 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
4940
4957
|
sqlQuery = `
|
|
4941
4958
|
SELECT *,
|
|
4942
4959
|
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
4943
|
-
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text),
|
|
4944
|
-
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text),
|
|
4960
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
4961
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
4945
4962
|
FROM "${table}" t
|
|
4946
4963
|
ORDER BY hybrid_score DESC
|
|
4947
4964
|
LIMIT 50
|
|
4948
4965
|
`;
|
|
4949
|
-
params = [vectorLiteral,
|
|
4966
|
+
params = [vectorLiteral, dynamicKeywordQuery];
|
|
4950
4967
|
} else {
|
|
4951
4968
|
sqlQuery = `
|
|
4952
4969
|
SELECT *,
|
package/dist/server.mjs
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
sseFrame,
|
|
40
40
|
sseMetaFrame,
|
|
41
41
|
sseTextFrame
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-XGVKIPNY.mjs";
|
|
43
43
|
import "./chunk-YLTMFW4M.mjs";
|
|
44
44
|
import {
|
|
45
45
|
PineconeProvider
|
|
@@ -325,6 +325,16 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
325
325
|
).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
|
|
326
326
|
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
327
327
|
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
|
|
328
|
+
const getDynamicKeywordQuery = () => {
|
|
329
|
+
if (entityHints.length > 0) {
|
|
330
|
+
return entityHints.map((h) => h.includes(" ") ? `"${h}"` : h).join(" & ");
|
|
331
|
+
}
|
|
332
|
+
if (queryText) {
|
|
333
|
+
return queryText.toLowerCase().replace(/\b(show|me|the|product[s]?|item[s]?|card[s]?|of|category|find|list|browse|what|are|display|all|under|for|in|with|about)\b/g, "").trim().replace(/\s+/g, " & ");
|
|
334
|
+
}
|
|
335
|
+
return "";
|
|
336
|
+
};
|
|
337
|
+
const dynamicKeywordQuery = getDynamicKeywordQuery();
|
|
328
338
|
const queryPromises = this.tables.map(async (table) => {
|
|
329
339
|
try {
|
|
330
340
|
let sqlQuery = "";
|
|
@@ -342,13 +352,13 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
342
352
|
sqlQuery = `
|
|
343
353
|
SELECT *,
|
|
344
354
|
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
345
|
-
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text),
|
|
346
|
-
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text),
|
|
355
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
356
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
347
357
|
FROM "${table}" t
|
|
348
358
|
ORDER BY hybrid_score DESC
|
|
349
359
|
LIMIT 50
|
|
350
360
|
`;
|
|
351
|
-
params = [vectorLiteral,
|
|
361
|
+
params = [vectorLiteral, dynamicKeywordQuery];
|
|
352
362
|
} else {
|
|
353
363
|
sqlQuery = `
|
|
354
364
|
SELECT *,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.6",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -381,14 +381,46 @@ interface UIConfig {
|
|
|
381
381
|
rows?: Record<string, unknown>[];
|
|
382
382
|
products?: Record<string, unknown>[];
|
|
383
383
|
results?: Record<string, unknown>[];
|
|
384
|
+
|
|
385
|
+
// V7 Protocol Fields
|
|
386
|
+
carousel?: {
|
|
387
|
+
items: Record<string, unknown>[];
|
|
388
|
+
};
|
|
389
|
+
chart?: {
|
|
390
|
+
type: 'pie' | 'bar' | 'line';
|
|
391
|
+
xKey: string;
|
|
392
|
+
yKeys: string[];
|
|
393
|
+
data: Record<string, unknown>[];
|
|
394
|
+
};
|
|
395
|
+
table?: {
|
|
396
|
+
columns: string[];
|
|
397
|
+
rows?: Record<string, unknown>[];
|
|
398
|
+
data?: Record<string, unknown>[];
|
|
399
|
+
};
|
|
384
400
|
}
|
|
385
401
|
|
|
386
|
-
interface RawUIConfig extends Omit<UIConfig, 'data' | 'items' | 'rows' | 'products' | 'results'> {
|
|
402
|
+
interface RawUIConfig extends Omit<UIConfig, 'data' | 'items' | 'rows' | 'products' | 'results' | 'carousel' | 'chart' | 'table'> {
|
|
387
403
|
data: Array<Record<string, unknown> | unknown[]>;
|
|
388
404
|
items?: Array<Record<string, unknown> | unknown[]>;
|
|
389
405
|
rows?: Array<Record<string, unknown> | unknown[]>;
|
|
390
406
|
products?: Array<Record<string, unknown> | unknown[]>;
|
|
391
407
|
results?: Array<Record<string, unknown> | unknown[]>;
|
|
408
|
+
|
|
409
|
+
// V7 Protocol Fields (Raw)
|
|
410
|
+
carousel?: {
|
|
411
|
+
items: Array<Record<string, unknown> | unknown[]>;
|
|
412
|
+
};
|
|
413
|
+
chart?: {
|
|
414
|
+
type: 'pie' | 'bar' | 'line';
|
|
415
|
+
xKey: string;
|
|
416
|
+
yKeys: string[];
|
|
417
|
+
data: Array<Record<string, unknown> | unknown[]>;
|
|
418
|
+
};
|
|
419
|
+
table?: {
|
|
420
|
+
columns: string[];
|
|
421
|
+
rows?: Array<Record<string, unknown> | unknown[]>;
|
|
422
|
+
data?: Array<Record<string, unknown> | unknown[]>;
|
|
423
|
+
};
|
|
392
424
|
}
|
|
393
425
|
|
|
394
426
|
function normalizeTabularRows(
|
|
@@ -420,6 +452,13 @@ function normalizeTabularRows(
|
|
|
420
452
|
}
|
|
421
453
|
|
|
422
454
|
function resolveStructuredRows(config: Partial<RawUIConfig> & Record<string, unknown>) {
|
|
455
|
+
// V7 Nested Structures
|
|
456
|
+
if (config.carousel?.items && Array.isArray(config.carousel.items)) return config.carousel.items;
|
|
457
|
+
if (config.chart?.data && Array.isArray(config.chart.data)) return config.chart.data;
|
|
458
|
+
if (config.table?.rows && Array.isArray(config.table.rows)) return config.table.rows;
|
|
459
|
+
if (config.table?.data && Array.isArray(config.table.data)) return config.table.data;
|
|
460
|
+
|
|
461
|
+
// V6 Flat Structures
|
|
423
462
|
if (Array.isArray(config.data) && config.data.length > 0) return config.data;
|
|
424
463
|
if (Array.isArray(config.items) && config.items.length > 0) return config.items;
|
|
425
464
|
if (Array.isArray(config.products) && config.products.length > 0) return config.products;
|
|
@@ -445,6 +484,17 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
|
|
|
445
484
|
.trim();
|
|
446
485
|
const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
|
|
447
486
|
const config = { ...parsed } as Partial<RawUIConfig> & Record<string, unknown>;
|
|
487
|
+
|
|
488
|
+
// V7 Field Mapping
|
|
489
|
+
if (config.chart) {
|
|
490
|
+
if (!config.chartType) config.chartType = config.chart.type;
|
|
491
|
+
if (!config.xAxisKey) config.xAxisKey = config.chart.xKey;
|
|
492
|
+
if (!config.dataKeys) config.dataKeys = config.chart.yKeys;
|
|
493
|
+
}
|
|
494
|
+
if (config.table) {
|
|
495
|
+
if (!config.columns) config.columns = config.table.columns;
|
|
496
|
+
}
|
|
497
|
+
|
|
448
498
|
const resolvedRows = resolveStructuredRows(config);
|
|
449
499
|
|
|
450
500
|
// ─── Intent Healing & Auto-Detection ───────────────────────────────────
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -203,11 +203,14 @@ Classify the query into ONE of the following intents:
|
|
|
203
203
|
|
|
204
204
|
## 5. DATA RULES
|
|
205
205
|
|
|
206
|
-
- NEVER hallucinate fields
|
|
206
|
+
- NEVER hallucinate fields or data
|
|
207
207
|
- ONLY use retrieved vector DB data
|
|
208
208
|
- NO placeholders ("...", "N/A")
|
|
209
209
|
- If image/link missing → REMOVE FIELD
|
|
210
210
|
- Aggregate BEFORE rendering chart
|
|
211
|
+
- IF NO DATA IS FOUND in the retrieved context:
|
|
212
|
+
→ DO NOT output a \`\`\`ui\`\`\` block
|
|
213
|
+
→ Instead, state clearly that no relevant products or data were found.
|
|
211
214
|
|
|
212
215
|
---
|
|
213
216
|
|
|
@@ -287,7 +290,12 @@ User: "distribution of products across categories"
|
|
|
287
290
|
\`\`\`
|
|
288
291
|
`;
|
|
289
292
|
if (!this.config.llm.systemPrompt?.includes(CHART_MARKER)) {
|
|
290
|
-
|
|
293
|
+
// Clean up ANY existing protocol markers (V1-V7) to avoid prompt bloat
|
|
294
|
+
let cleanPrompt = this.config.llm.systemPrompt || '';
|
|
295
|
+
cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, '');
|
|
296
|
+
cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, '');
|
|
297
|
+
|
|
298
|
+
this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
|
|
291
299
|
}
|
|
292
300
|
|
|
293
301
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
@@ -128,7 +128,10 @@ export class GeminiProvider implements ILLMProvider {
|
|
|
128
128
|
},
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
const text = typeof (response as { text: unknown }).text === 'function'
|
|
132
|
+
? (response as { text: () => string }).text()
|
|
133
|
+
: (response.text ?? '');
|
|
134
|
+
return text;
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
async *chatStream(messages: ChatMessage[], context: string, options?: ChatOptions): AsyncIterable<string> {
|
|
@@ -162,7 +165,9 @@ export class GeminiProvider implements ILLMProvider {
|
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
for await (const chunk of stream) {
|
|
165
|
-
const text = chunk.text
|
|
168
|
+
const text = typeof (chunk as { text: unknown }).text === 'function'
|
|
169
|
+
? (chunk as { text: () => string }).text()
|
|
170
|
+
: (chunk as { text?: string }).text;
|
|
166
171
|
if (text) yield text;
|
|
167
172
|
}
|
|
168
173
|
}
|
|
@@ -151,6 +151,28 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
151
151
|
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
152
152
|
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(', ')}]`);
|
|
153
153
|
|
|
154
|
+
// Dynamic Keyword Search: Prioritize intelligently extracted hints over raw text
|
|
155
|
+
const getDynamicKeywordQuery = () => {
|
|
156
|
+
if (entityHints.length > 0) {
|
|
157
|
+
// Use extracted entities as high-signal keywords
|
|
158
|
+
return entityHints
|
|
159
|
+
.map(h => h.includes(' ') ? `"${h}"` : h) // Wrap multi-word hints in quotes
|
|
160
|
+
.join(' & ');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (queryText) {
|
|
164
|
+
// Fallback to cleaned raw text if no high-signal hints were extracted
|
|
165
|
+
return queryText
|
|
166
|
+
.toLowerCase()
|
|
167
|
+
.replace(/\b(show|me|the|product[s]?|item[s]?|card[s]?|of|category|find|list|browse|what|are|display|all|under|for|in|with|about)\b/g, '')
|
|
168
|
+
.trim()
|
|
169
|
+
.replace(/\s+/g, ' & ');
|
|
170
|
+
}
|
|
171
|
+
return '';
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const dynamicKeywordQuery = getDynamicKeywordQuery();
|
|
175
|
+
|
|
154
176
|
const queryPromises = this.tables.map(async (table) => {
|
|
155
177
|
try {
|
|
156
178
|
let sqlQuery = '';
|
|
@@ -173,13 +195,13 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
173
195
|
sqlQuery = `
|
|
174
196
|
SELECT *,
|
|
175
197
|
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
176
|
-
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text),
|
|
177
|
-
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text),
|
|
198
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
199
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
178
200
|
FROM "${table}" t
|
|
179
201
|
ORDER BY hybrid_score DESC
|
|
180
202
|
LIMIT 50
|
|
181
203
|
`;
|
|
182
|
-
params = [vectorLiteral,
|
|
204
|
+
params = [vectorLiteral, dynamicKeywordQuery];
|
|
183
205
|
} else {
|
|
184
206
|
// Fallback to Vector-only Search
|
|
185
207
|
sqlQuery = `
|