@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.
@@ -829,7 +829,8 @@ ${context}`;
829
829
  stopSequences: options == null ? void 0 : options.stop
830
830
  }
831
831
  });
832
- return (_f = response.text) != null ? _f : "";
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
- this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
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);
@@ -2437,7 +2437,8 @@ ${context}`;
2437
2437
  stopSequences: options == null ? void 0 : options.stop
2438
2438
  }
2439
2439
  });
2440
- return (_f = response.text) != null ? _f : "";
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
- this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
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);
@@ -10,7 +10,7 @@ import {
10
10
  sseMetaFrame,
11
11
  sseTextFrame,
12
12
  sseUIFrame
13
- } from "../chunk-PKK7EEVP.mjs";
13
+ } from "../chunk-XGVKIPNY.mjs";
14
14
  import "../chunk-YLTMFW4M.mjs";
15
15
  import "../chunk-X4TOT24V.mjs";
16
16
  export {
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
- return (_f = response.text) != null ? _f : "";
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
- this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
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), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) AS keyword_score,
4944
- ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
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, queryText];
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-PKK7EEVP.mjs";
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), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) AS keyword_score,
346
- ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
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, queryText];
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.4",
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 ───────────────────────────────────
@@ -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
- this.config.llm.systemPrompt = (this.config.llm.systemPrompt || '') + chartInstruction;
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
- return response.text ?? '';
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), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) AS keyword_score,
177
- ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
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, queryText];
204
+ params = [vectorLiteral, dynamicKeywordQuery];
183
205
  } else {
184
206
  // Fallback to Vector-only Search
185
207
  sqlQuery = `