@retrivora-ai/rag-engine 1.7.3 → 1.7.5

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/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) {
@@ -3913,73 +3914,196 @@ var Pipeline = class {
3913
3914
  async initialize() {
3914
3915
  var _a, _b;
3915
3916
  if (this.initialised) return;
3916
- const CHART_MARKER = "<!-- UI_PROTOCOL_V6 -->";
3917
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
3917
3918
  const chartInstruction = `
3918
3919
 
3919
3920
  ${CHART_MARKER}
3920
- ### UNIVERSAL UI PROTOCOL
3921
- When the user asks for a visual representation, comparison, distribution, breakdown, table, list, catalog, products, carousel, stock view, or category summary, you MUST include exactly one \`\`\`ui\`\`\` block.
3921
+ ### UNIVERSAL UI PROTOCOL V7 (STRICT MODE)
3922
3922
 
3923
- 1. VIEW SELECTION
3924
- - Use "carousel" for browsing specific products, items, or a catalog. This is the DEFAULT for "show me", "what are", or "find me" requests for a specific category (e.g. "beauty").
3925
- - Use "chart" ONLY for numerical aggregations: distributions, percentages, counts, comparisons over time, or trends (e.g. "how many beauty products?").
3926
- - Use "table" for detailed technical specifications, row-heavy comparisons, or when the user explicitly asks for a list/table format.
3923
+ You are responsible for returning a SINGLE structured UI block when visualization is required.
3927
3924
 
3928
- 2. REQUIRED JSON SHAPE
3929
- {
3930
- "view": "chart" | "carousel" | "table",
3931
- "title": "Short heading",
3932
- "description": "One sentence describing the view",
3933
- "chartType": "pie" | "bar" | "line", // Required if view is "chart"
3934
- "xAxisKey": "label",
3935
- "dataKeys": ["value"],
3936
- "columns": ["dynamicFieldKey1", "dynamicFieldKey2"],
3937
- "insights": ["Short takeaway 1", "Short takeaway 2"],
3938
- "data": []
3939
- }
3925
+ ---
3940
3926
 
3941
- 3. DATA RULES
3942
- - Build the UI payload from retrieved context only. Do not invent products or values.
3943
- - For CAROUSEL: "data" must be a list of product objects (id, name, brand, price, image, link).
3944
- - For CHART: "data" must be aggregated counts or sums. Never put raw product lists in a chart view.
3945
- - If the user asks for "products in beauty", do NOT return a pie chart of categories; return a carousel of the beauty products themselves.
3927
+ ## 1. INTENT CLASSIFICATION (MANDATORY)
3946
3928
 
3947
- 4. FORMAT RULES
3948
- - Return valid JSON inside the \`\`\`ui\`\`\` block.
3949
- - Put natural language explanation before or after the block, not inside.
3929
+ Classify the query into ONE of the following intents:
3930
+
3931
+ - "explore" \u2192 user wants to browse items (e.g., "show me products")
3932
+ - "analyze" \u2192 user wants aggregation/distribution (e.g., "distribution of products")
3933
+ - "compare" \u2192 user wants side-by-side comparison
3934
+ - "detail" \u2192 user wants structured/tabular data
3935
+
3936
+ ---
3937
+
3938
+ ## 2. VIEW MAPPING (STRICT)
3939
+
3940
+ | Intent | View |
3941
+ |----------|-----------|
3942
+ | explore | carousel |
3943
+ | analyze | chart |
3944
+ | compare | table |
3945
+ | detail | table |
3946
+
3947
+ \u26A0\uFE0F Override Rules:
3948
+ - If query contains "distribution", "percentage", "ratio" \u2192 ALWAYS chart
3949
+ - If query contains "show products", "list items" \u2192 ALWAYS carousel
3950
+ - NEVER mix views
3951
+
3952
+ ---
3953
+
3954
+ ## 3. RESPONSE FORMAT (STRICT JSON ONLY INSIDE BLOCK)
3955
+
3956
+ \`\`\`ui
3957
+ {
3958
+ "view": "carousel" | "chart" | "table",
3959
+ "title": "string",
3960
+ "description": "string",
3961
+ "metadata": {
3962
+ "intent": "explore" | "analyze" | "compare" | "detail",
3963
+ "confidence": 0.0-1.0
3964
+ },
3965
+
3966
+ // CHART ONLY
3967
+ "chart": {
3968
+ "type": "pie" | "bar" | "line",
3969
+ "xKey": "label",
3970
+ "yKeys": ["value"],
3971
+ "data": []
3972
+ },
3973
+
3974
+ // TABLE ONLY
3975
+ "table": {
3976
+ "columns": [],
3977
+ "rows": []
3978
+ },
3979
+
3980
+ // CAROUSEL ONLY
3981
+ "carousel": {
3982
+ "items": []
3983
+ },
3984
+
3985
+ "insights": []
3986
+ }
3987
+ \`\`\`
3988
+
3989
+ ---
3990
+
3991
+ ## 4. DATA CONTRACT (CRITICAL)
3992
+
3993
+ ### \u{1F539} CAROUSEL ITEM FORMAT
3994
+ {
3995
+ "id": "string",
3996
+ "name": "string",
3997
+ "brand": "string",
3998
+ "price": number,
3999
+ "image": "url",
4000
+ "link": "url",
4001
+ "inStock": boolean
4002
+ }
4003
+
4004
+ ### \u{1F539} CHART DATA FORMAT
4005
+ {
4006
+ "label": "string",
4007
+ "value": number,
4008
+ "inStock": boolean (optional)
4009
+ }
4010
+
4011
+ ### \u{1F539} TABLE FORMAT
4012
+ - columns MUST match row keys
4013
+ - rows MUST be flat objects
4014
+
4015
+ ---
4016
+
4017
+ ## 5. DATA RULES
4018
+
4019
+ - NEVER hallucinate fields
4020
+ - ONLY use retrieved vector DB data
4021
+ - NO placeholders ("...", "N/A")
4022
+ - If image/link missing \u2192 REMOVE FIELD
4023
+ - Aggregate BEFORE rendering chart
4024
+
4025
+ ---
4026
+
4027
+ ## 6. SMART RULES (IMPORTANT)
4028
+
4029
+ - If user asks BOTH:
4030
+ "show products AND distribution"
4031
+ \u2192 PRIORITIZE "explore" \u2192 carousel
4032
+
4033
+ - If dataset size > 20:
4034
+ \u2192 aggregate \u2192 chart or table
4035
+
4036
+ ---
4037
+
4038
+ ## 7. OUTPUT RULES
4039
+
4040
+ - ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block
4041
+ - JSON must be VALID
4042
+ - No explanation inside block
4043
+
4044
+ ---
4045
+
4046
+ ## 8. EXAMPLES
3950
4047
 
3951
- 5. EXAMPLES
3952
4048
  User: "show me beauty products"
3953
- Assistant:
4049
+
3954
4050
  \`\`\`ui
3955
4051
  {
3956
4052
  "view": "carousel",
3957
4053
  "title": "Beauty Products",
3958
- "description": "Browse our latest skincare and makeup collection.",
3959
- "data": [
3960
- { "id": "1", "name": "Mascara", "brand": "Essence", "price": "$9.99", "image": "...", "link": "..." },
3961
- { "id": "2", "name": "Lipstick", "brand": "Chic", "price": "$12.99", "image": "...", "link": "..." }
3962
- ]
4054
+ "description": "Browse available beauty items",
4055
+ "metadata": {
4056
+ "intent": "explore",
4057
+ "confidence": 0.95
4058
+ },
4059
+ "carousel": {
4060
+ "items": [
4061
+ {
4062
+ "id": "1",
4063
+ "name": "Mascara",
4064
+ "brand": "Essence",
4065
+ "price": 9.99,
4066
+ "image": "https://example.com/mascara.jpg",
4067
+ "link": "https://example.com/p1",
4068
+ "inStock": true
4069
+ }
4070
+ ]
4071
+ },
4072
+ "insights": ["Most products are affordable"]
3963
4073
  }
3964
4074
  \`\`\`
3965
4075
 
3966
- User: "breakdown of products by category"
3967
- Assistant:
4076
+ ---
4077
+
4078
+ User: "distribution of products across categories"
4079
+
3968
4080
  \`\`\`ui
3969
4081
  {
3970
4082
  "view": "chart",
3971
- "title": "Category Breakdown",
3972
- "chartType": "pie",
3973
- "xAxisKey": "label",
3974
- "dataKeys": ["value"],
3975
- "data": [
3976
- { "label": "Beauty", "value": 15 },
3977
- { "label": "Fragrances", "value": 8 }
3978
- ]
4083
+ "title": "Product Distribution",
4084
+ "description": "Category-wise product distribution",
4085
+ "metadata": {
4086
+ "intent": "analyze",
4087
+ "confidence": 0.98
4088
+ },
4089
+ "chart": {
4090
+ "type": "pie",
4091
+ "xKey": "label",
4092
+ "yKeys": ["value"],
4093
+ "data": [
4094
+ { "label": "Beauty", "value": 15 },
4095
+ { "label": "Electronics", "value": 10 }
4096
+ ]
4097
+ },
4098
+ "insights": ["Beauty dominates inventory"]
3979
4099
  }
3980
- \`\`\``;
4100
+ \`\`\`
4101
+ `;
3981
4102
  if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
3982
- this.config.llm.systemPrompt = (this.config.llm.systemPrompt || "") + chartInstruction;
4103
+ let cleanPrompt = this.config.llm.systemPrompt || "";
4104
+ cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
4105
+ cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
4106
+ this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
3983
4107
  }
3984
4108
  console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
3985
4109
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
@@ -4803,11 +4927,15 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
4803
4927
  ).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
4804
4928
  console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
4805
4929
  console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
4930
+ const cleanKeywordQuery = (text) => {
4931
+ return text.toLowerCase().replace(/\b(show|me|the|product|cards|of|category|find|list|browse|what|are|display)\b/g, "").trim().replace(/\s+/g, " & ");
4932
+ };
4806
4933
  const queryPromises = this.tables.map(async (table) => {
4807
4934
  try {
4808
4935
  let sqlQuery = "";
4809
4936
  let params = [];
4810
4937
  if (queryText) {
4938
+ const cleanedText = cleanKeywordQuery(queryText) || queryText;
4811
4939
  const hasEntityHints = entityHints.length > 0;
4812
4940
  const exactNameScoreExpr = hasEntityHints ? `+ (
4813
4941
  SELECT COALESCE(MAX(
@@ -4820,13 +4948,13 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
4820
4948
  sqlQuery = `
4821
4949
  SELECT *,
4822
4950
  (1 - (embedding <=> $1::vector)) AS vector_score,
4823
- COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) AS keyword_score,
4824
- ((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
4951
+ COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
4952
+ ((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
4825
4953
  FROM "${table}" t
4826
4954
  ORDER BY hybrid_score DESC
4827
4955
  LIMIT 50
4828
4956
  `;
4829
- params = [vectorLiteral, queryText];
4957
+ params = [vectorLiteral, cleanedText];
4830
4958
  } else {
4831
4959
  sqlQuery = `
4832
4960
  SELECT *,
package/dist/server.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-M32ZXLTT.mjs";
42
+ } from "./chunk-QOO37S2Z.mjs";
43
43
  import "./chunk-YLTMFW4M.mjs";
44
44
  import {
45
45
  PineconeProvider
@@ -325,11 +325,15 @@ 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 cleanKeywordQuery = (text) => {
329
+ return text.toLowerCase().replace(/\b(show|me|the|product|cards|of|category|find|list|browse|what|are|display)\b/g, "").trim().replace(/\s+/g, " & ");
330
+ };
328
331
  const queryPromises = this.tables.map(async (table) => {
329
332
  try {
330
333
  let sqlQuery = "";
331
334
  let params = [];
332
335
  if (queryText) {
336
+ const cleanedText = cleanKeywordQuery(queryText) || queryText;
333
337
  const hasEntityHints = entityHints.length > 0;
334
338
  const exactNameScoreExpr = hasEntityHints ? `+ (
335
339
  SELECT COALESCE(MAX(
@@ -342,13 +346,13 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
342
346
  sqlQuery = `
343
347
  SELECT *,
344
348
  (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
349
+ COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
350
+ ((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
351
  FROM "${table}" t
348
352
  ORDER BY hybrid_score DESC
349
353
  LIMIT 50
350
354
  `;
351
- params = [vectorLiteral, queryText];
355
+ params = [vectorLiteral, cleanedText];
352
356
  } else {
353
357
  sqlQuery = `
354
358
  SELECT *,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.7.3",
3
+ "version": "1.7.5",
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",
@@ -32,8 +32,8 @@ function sanitizeJson(raw: string): string {
32
32
  },
33
33
  );
34
34
 
35
- // 2. Strip JS-style single-line comments
36
- s = s.replace(/\/\/[^\n]*/g, '');
35
+ // 2. Strip JS-style comments while preserving strings (e.g. URLs)
36
+ s = s.replace(/("(?:\\.|[^\\"])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (m, g1) => g1 || '');
37
37
 
38
38
  // 3. Quote bare (unquoted) object keys
39
39
  s = s.replace(/([{,]\s*)([A-Za-z_$][A-Za-z0-9_$]*)\s*:/g, '$1"$2":');
@@ -204,11 +204,14 @@ function isLikelyUiOnlyMessage(text: string): boolean {
204
204
  // ─── Tiny helpers ─────────────────────────────────────────────────────────────
205
205
 
206
206
  function resolveImage(data: Record<string, unknown>): string | undefined {
207
+ const isPlaceholder = (val: unknown) =>
208
+ typeof val === 'string' && (val === '...' || val === '' || val.toLowerCase() === 'null' || val.toLowerCase() === 'undefined');
209
+
207
210
  for (const key of ['image', 'img', 'thumbnail']) {
208
211
  const v = data[key];
209
- if (typeof v === 'string' && v) return v;
212
+ if (typeof v === 'string' && v && !isPlaceholder(v)) return v;
210
213
  }
211
- if (Array.isArray(data.images) && typeof data.images[0] === 'string') {
214
+ if (Array.isArray(data.images) && typeof data.images[0] === 'string' && !isPlaceholder(data.images[0])) {
212
215
  return data.images[0];
213
216
  }
214
217
  return undefined;
@@ -378,14 +381,46 @@ interface UIConfig {
378
381
  rows?: Record<string, unknown>[];
379
382
  products?: Record<string, unknown>[];
380
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
+ };
381
400
  }
382
401
 
383
- interface RawUIConfig extends Omit<UIConfig, 'data' | 'items' | 'rows' | 'products' | 'results'> {
402
+ interface RawUIConfig extends Omit<UIConfig, 'data' | 'items' | 'rows' | 'products' | 'results' | 'carousel' | 'chart' | 'table'> {
384
403
  data: Array<Record<string, unknown> | unknown[]>;
385
404
  items?: Array<Record<string, unknown> | unknown[]>;
386
405
  rows?: Array<Record<string, unknown> | unknown[]>;
387
406
  products?: Array<Record<string, unknown> | unknown[]>;
388
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
+ };
389
424
  }
390
425
 
391
426
  function normalizeTabularRows(
@@ -417,6 +452,13 @@ function normalizeTabularRows(
417
452
  }
418
453
 
419
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
420
462
  if (Array.isArray(config.data) && config.data.length > 0) return config.data;
421
463
  if (Array.isArray(config.items) && config.items.length > 0) return config.items;
422
464
  if (Array.isArray(config.products) && config.products.length > 0) return config.products;
@@ -442,6 +484,17 @@ function UIDispatcher({ rawContent, primaryColor, accentColor, isStreaming, onAd
442
484
  .trim();
443
485
  const parsed = JSON.parse(sanitizeJson(extractLikelyJsonBlock(clean)));
444
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
+
445
498
  const resolvedRows = resolveStructuredRows(config);
446
499
 
447
500
  // ─── Intent Healing & Auto-Detection ───────────────────────────────────