@retrivora-ai/rag-engine 1.7.5 → 1.7.7

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.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sseFrame,
40
40
  sseMetaFrame,
41
41
  sseTextFrame
42
- } from "./chunk-QOO37S2Z.mjs";
42
+ } from "./chunk-UHGYLTTY.mjs";
43
43
  import "./chunk-YLTMFW4M.mjs";
44
44
  import {
45
45
  PineconeProvider
@@ -325,15 +325,21 @@ 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, " & ");
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 "";
330
336
  };
337
+ const dynamicKeywordQuery = getDynamicKeywordQuery();
331
338
  const queryPromises = this.tables.map(async (table) => {
332
339
  try {
333
340
  let sqlQuery = "";
334
341
  let params = [];
335
342
  if (queryText) {
336
- const cleanedText = cleanKeywordQuery(queryText) || queryText;
337
343
  const hasEntityHints = entityHints.length > 0;
338
344
  const exactNameScoreExpr = hasEntityHints ? `+ (
339
345
  SELECT COALESCE(MAX(
@@ -352,7 +358,7 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
352
358
  ORDER BY hybrid_score DESC
353
359
  LIMIT 50
354
360
  `;
355
- params = [vectorLiteral, cleanedText];
361
+ params = [vectorLiteral, dynamicKeywordQuery];
356
362
  } else {
357
363
  sqlQuery = `
358
364
  SELECT *,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.7.5",
3
+ "version": "1.7.7",
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",
@@ -9,6 +9,7 @@ import { ChatViewportSize } from '../types/props';
9
9
  import { SourceCard } from './SourceCard';
10
10
  import { ProductCarousel } from './ProductCarousel';
11
11
  import { DynamicChart } from './DynamicChart';
12
+ import { VisualizationRenderer } from './VisualizationRenderer';
12
13
 
13
14
  // ─── JSON sanitization ────────────────────────────────────────────────────────
14
15
  // Order matters: each step is a targeted fix, no step undoes a previous one.
@@ -988,6 +989,13 @@ export function MessageBubble({
988
989
  </ReactMarkdown>
989
990
  )}
990
991
 
992
+ {/* Automated UI Fallback (if LLM failed to output structured block but UITransformer found data) */}
993
+ {!isUser && !structuredContent.payload && message.uiTransformation && (
994
+ <div className="mt-4 border-t border-slate-100 dark:border-white/5 pt-4">
995
+ <VisualizationRenderer data={message.uiTransformation} />
996
+ </div>
997
+ )}
998
+
991
999
  {isStreaming && message.content && (
992
1000
  <span className="inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" />
993
1001
  )}
@@ -13,6 +13,7 @@ import { BatchProcessor, BatchOptions } from './BatchProcessor';
13
13
  import { EmbeddingStrategyResolver } from '../config/EmbeddingStrategy';
14
14
  import { QueryProcessor } from './QueryProcessor';
15
15
  import { IngestDocument, ChatResponse, VectorMatch, GraphSearchResult, RetrievalResult, UpsertDocument } from '../types';
16
+ import { UITransformer } from '../utils/UITransformer';
16
17
 
17
18
  // ─── LRU Embedding Cache ───────────────────────────────────────────────────────
18
19
 
@@ -203,11 +204,14 @@ Classify the query into ONE of the following intents:
203
204
 
204
205
  ## 5. DATA RULES
205
206
 
206
- - NEVER hallucinate fields
207
+ - NEVER hallucinate fields or data
207
208
  - ONLY use retrieved vector DB data
208
209
  - NO placeholders ("...", "N/A")
209
210
  - If image/link missing → REMOVE FIELD
210
211
  - Aggregate BEFORE rendering chart
212
+ - IF NO DATA IS FOUND in the retrieved context:
213
+ → DO NOT output a \`\`\`ui\`\`\` block
214
+ → Instead, state clearly that no relevant products or data were found.
211
215
 
212
216
  ---
213
217
 
@@ -227,6 +231,8 @@ Classify the query into ONE of the following intents:
227
231
  - ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block
228
232
  - JSON must be VALID
229
233
  - No explanation inside block
234
+ - NEVER use ASCII charts, Markdown tables, or text-based drawings for data.
235
+ - If data is tabular or statistical, ALWAYS use the \`\`\`ui\`\`\` block.
230
236
 
231
237
  ---
232
238
 
@@ -559,8 +565,18 @@ User: "distribution of products across categories"
559
565
  yield reply;
560
566
  }
561
567
 
562
- // Yield retrieval metadata at the end so UI can show sources
563
- yield { reply: '', sources, graphData };
568
+ // 6. Automated UI Fallback (UITransformer)
569
+ // This provides a secondary, rule-based visualization frame in case the LLM
570
+ // fails to adhere to the strict protocol or outputs a text fallback.
571
+ const uiTransformation = UITransformer.transform(question, sources);
572
+
573
+ // Yield retrieval metadata and automated UI transformation at the end
574
+ yield {
575
+ reply: '',
576
+ sources,
577
+ graphData,
578
+ ui_transformation: uiTransformation
579
+ } as ChatResponse;
564
580
 
565
581
  } catch (error) {
566
582
  throw new Error(`[Pipeline] Stream failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -27,7 +27,8 @@ import { useStoredMessages } from './useStoredMessages';
27
27
  interface SseTextFrame { type: 'text'; text: string }
28
28
  interface SseMetaFrame { type: 'metadata'; sources?: VectorMatch[] }
29
29
  interface SseErrorFrame { type: 'error'; error: string }
30
- type SseFrame = SseTextFrame | SseMetaFrame | SseErrorFrame;
30
+ interface SseUiFrame { type: 'ui_transformation'; data: any }
31
+ type SseFrame = SseTextFrame | SseMetaFrame | SseErrorFrame | SseUiFrame;
31
32
 
32
33
  /**
33
34
  * Parse all complete SSE frames from a raw chunk string.
@@ -124,6 +125,7 @@ export function useRagChat(
124
125
  const decoder = new TextDecoder();
125
126
  let assistantContent = '';
126
127
  let sources: VectorMatch[] = [];
128
+ let uiTransformation: any = null;
127
129
  // Buffer for partial SSE frames that arrive split across reads
128
130
  let buffer = '';
129
131
 
@@ -157,6 +159,8 @@ export function useRagChat(
157
159
  assistantContent += frame.text;
158
160
  } else if (frame.type === 'metadata') {
159
161
  sources = frame.sources ?? [];
162
+ } else if (frame.type === 'ui_transformation') {
163
+ uiTransformation = frame.data;
160
164
  } else if (frame.type === 'error') {
161
165
  setError(frame.error || 'Stream error');
162
166
  }
@@ -166,7 +170,12 @@ export function useRagChat(
166
170
  setMessages((prev) =>
167
171
  prev.map((msg) =>
168
172
  msg.id === assistantMessageId
169
- ? { ...msg, content: assistantContent, sources: sources.length > 0 ? sources : msg.sources }
173
+ ? {
174
+ ...msg,
175
+ content: assistantContent,
176
+ sources: sources.length > 0 ? sources : msg.sources,
177
+ uiTransformation: uiTransformation || (msg as any).uiTransformation
178
+ }
170
179
  : msg
171
180
  )
172
181
  );
@@ -185,6 +194,7 @@ export function useRagChat(
185
194
  role: 'assistant',
186
195
  content: assistantContent,
187
196
  sources: sources.length > 0 ? sources : undefined,
197
+ uiTransformation: uiTransformation || undefined,
188
198
  createdAt: new Date().toISOString(),
189
199
  };
190
200
  onReply?.(finalAssistantMessage);
@@ -151,22 +151,34 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
151
151
  console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
152
152
  console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(', ')}]`);
153
153
 
154
- // Helper to clean conversational filler for better keyword matching
155
- const cleanKeywordQuery = (text: string) => {
156
- return text
157
- .toLowerCase()
158
- .replace(/\b(show|me|the|product|cards|of|category|find|list|browse|what|are|display)\b/g, '')
159
- .trim()
160
- .replace(/\s+/g, ' & '); // Join remaining words with AND for tsquery
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 '';
161
172
  };
162
173
 
174
+ const dynamicKeywordQuery = getDynamicKeywordQuery();
175
+
163
176
  const queryPromises = this.tables.map(async (table) => {
164
177
  try {
165
178
  let sqlQuery = '';
166
179
  let params: unknown[] = [];
167
180
 
168
181
  if (queryText) {
169
- const cleanedText = cleanKeywordQuery(queryText) || queryText;
170
182
  const hasEntityHints = entityHints.length > 0;
171
183
  const exactNameScoreExpr = hasEntityHints
172
184
  ? `+ (
@@ -189,7 +201,7 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
189
201
  ORDER BY hybrid_score DESC
190
202
  LIMIT 50
191
203
  `;
192
- params = [vectorLiteral, cleanedText];
204
+ params = [vectorLiteral, dynamicKeywordQuery];
193
205
  } else {
194
206
  // Fallback to Vector-only Search
195
207
  sqlQuery = `
package/src/types/chat.ts CHANGED
@@ -10,6 +10,7 @@ export interface ChatMessage {
10
10
  export interface RagMessage extends ChatMessage {
11
11
  id: string;
12
12
  sources?: VectorMatch[];
13
+ uiTransformation?: any;
13
14
  createdAt: string;
14
15
  }
15
16