@retrivora-ai/rag-engine 1.9.3 → 1.9.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.
Files changed (69) hide show
  1. package/README.md +59 -7
  2. package/dist/{ILLMProvider-Bw2A28nU.d.mts → ILLMProvider-Bhk6zJOK.d.mts} +43 -7
  3. package/dist/{ILLMProvider-Bw2A28nU.d.ts → ILLMProvider-Bhk6zJOK.d.ts} +43 -7
  4. package/dist/handlers/index.d.mts +2 -2
  5. package/dist/handlers/index.d.ts +2 -2
  6. package/dist/handlers/index.js +737 -237
  7. package/dist/handlers/index.mjs +736 -237
  8. package/dist/index-B9J_XEh0.d.ts +187 -0
  9. package/dist/index-BJ4cd-t5.d.mts +187 -0
  10. package/dist/{index-B70ZLkfG.d.mts → index-Bu7T6xgr.d.ts} +20 -3
  11. package/dist/{index-DVu-mkAM.d.ts → index-C3SVtPYg.d.mts} +20 -3
  12. package/dist/index.css +237 -10
  13. package/dist/index.d.mts +13 -5
  14. package/dist/index.d.ts +13 -5
  15. package/dist/index.js +365 -164
  16. package/dist/index.mjs +350 -158
  17. package/dist/server.d.mts +15 -94
  18. package/dist/server.d.ts +15 -94
  19. package/dist/server.js +850 -239
  20. package/dist/server.mjs +839 -238
  21. package/package.json +2 -4
  22. package/src/app/api/chat/route.ts +2 -2
  23. package/src/app/api/health/route.ts +3 -2
  24. package/src/app/api/ingest/route.ts +3 -2
  25. package/src/app/api/suggestions/route.ts +3 -2
  26. package/src/app/api/upload/route.ts +3 -2
  27. package/src/app/constants.tsx +168 -148
  28. package/src/app/layout.tsx +5 -18
  29. package/src/app/types.ts +17 -17
  30. package/src/components/ChatWidget.tsx +3 -1
  31. package/src/components/ChatWindow.tsx +5 -1
  32. package/src/components/DocViewer.tsx +71 -5
  33. package/src/components/Documentation.tsx +74 -11
  34. package/src/components/MessageBubble.tsx +39 -2
  35. package/src/components/ThinkingBlock.tsx +75 -0
  36. package/src/components/VisualizationRenderer.tsx +27 -1
  37. package/src/components/constants.tsx +275 -0
  38. package/src/config/RagConfig.ts +47 -0
  39. package/src/config/constants.ts +1 -0
  40. package/src/config/serverConfig.ts +25 -0
  41. package/src/core/ConfigResolver.ts +73 -22
  42. package/src/core/ConfigValidator.ts +2 -1
  43. package/src/core/Pipeline.ts +226 -68
  44. package/src/core/ProviderRegistry.ts +16 -7
  45. package/src/core/QueryProcessor.ts +38 -4
  46. package/src/core/Retrivora.ts +91 -0
  47. package/src/core/VectorPlugin.ts +62 -8
  48. package/src/exceptions/index.ts +111 -0
  49. package/src/handlers/index.ts +73 -0
  50. package/src/hooks/useRagChat.ts +30 -5
  51. package/src/index.ts +27 -1
  52. package/src/lib/plugin.ts +24 -0
  53. package/src/llm/LLMFactory.ts +8 -4
  54. package/src/llm/providers/AnthropicProvider.ts +70 -20
  55. package/src/llm/providers/GeminiProvider.ts +14 -15
  56. package/src/llm/providers/OllamaProvider.ts +13 -16
  57. package/src/llm/providers/OpenAIProvider.ts +9 -14
  58. package/src/llm/providers/UniversalLLMAdapter.ts +5 -5
  59. package/src/llm/utils.ts +46 -0
  60. package/src/providers/vectordb/MongoDBProvider.ts +9 -4
  61. package/src/providers/vectordb/MultiTablePostgresProvider.ts +45 -13
  62. package/src/rag/EntityExtractor.ts +2 -2
  63. package/src/rag/Reranker.ts +9 -16
  64. package/src/server.ts +30 -1
  65. package/src/types/chat.ts +9 -0
  66. package/src/types/props.ts +38 -1
  67. package/src/utils/UITransformer.ts +73 -4
  68. package/dist/DocumentChunker-Dh9TvmGG.d.mts +0 -45
  69. package/dist/DocumentChunker-Dh9TvmGG.d.ts +0 -45
@@ -217,7 +217,7 @@ export class UITransformer {
217
217
  // ── Step 1: Detect intent ───────────────────────────────────────────────
218
218
  let intent: QueryIntent;
219
219
  try {
220
- intent = await this.detectIntent(query, llm);
220
+ intent = await this.detectIntent(query, llm, sources);
221
221
  console.debug('[UITransformer] Detected intent:', intent);
222
222
  } catch (err) {
223
223
  console.warn('[UITransformer] Intent detection failed; using heuristic.', err);
@@ -239,11 +239,24 @@ export class UITransformer {
239
239
  const context = this.buildContextSummary(sources);
240
240
  const systemPrompt = this.buildVisualizationSystemPrompt();
241
241
 
242
+ const profile = this.profileData(sources);
243
+ const schemaContext = `
244
+ RETRIEVED DATA SCHEMA:
245
+ - Numeric fields (measures): ${profile.numericFields.map(f => `${f.key} (unique values: ${f.uniqueCount})`).join(', ') || 'None'}
246
+ - Category fields (dimensions): ${profile.categoricalFields.map(f => `${f.key} (unique values: ${f.uniqueCount})`).join(', ') || 'None'}
247
+ - Date fields (temporal): ${profile.dateFields.map(f => `${f.key} (unique values: ${f.uniqueCount})`).join(', ') || 'None'}
248
+ - Boolean fields: ${profile.booleanFields.map(f => `${f.key} (unique values: ${f.uniqueCount})`).join(', ') || 'None'}
249
+ - Text/Other fields: ${profile.fields.filter(f => f.kind === 'text').map(f => f.key).join(', ') || 'None'}
250
+ `;
251
+
242
252
  const userPrompt = [
243
253
  `USER QUESTION: ${query}`,
244
254
  '',
245
255
  `DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
246
256
  '',
257
+ `RETRIEVED DATA SCHEMA:`,
258
+ schemaContext,
259
+ '',
247
260
  'RETRIEVED DATA (JSON):',
248
261
  context,
249
262
  ].join('\n');
@@ -256,6 +269,26 @@ export class UITransformer {
256
269
 
257
270
  const parsed = this.parseTransformationResponse(rawResponse);
258
271
  if (parsed) {
272
+ // Validate suitability of selected chart type based on schema
273
+ let isCompatible = true;
274
+ if (parsed.type === 'line_chart' && (profile.dateFields.length === 0 || profile.numericFields.length === 0)) {
275
+ isCompatible = false;
276
+ } else if (
277
+ ['bar_chart', 'horizontal_bar', 'pie_chart', 'donut_chart'].includes(parsed.type) &&
278
+ (profile.categoricalFields.length === 0 && profile.numericFields.length === 0)
279
+ ) {
280
+ isCompatible = false;
281
+ } else if (parsed.type === 'scatter_plot' && profile.numericFields.length < 2) {
282
+ isCompatible = false;
283
+ } else if (parsed.type === 'metric_card' && profile.numericFields.length === 0) {
284
+ isCompatible = false;
285
+ }
286
+
287
+ if (!isCompatible) {
288
+ console.warn(`[UITransformer] LLM chose incompatible visualization type "${parsed.type}" for retrieved schema. Falling back to heuristic.`);
289
+ return this.transform(query, sources, undefined, undefined, intent);
290
+ }
291
+
259
292
  const intentAllowsTable =
260
293
  intent.wantsExplicitTable ||
261
294
  ['tabular', 'table', 'geographic'].includes(intent.visualizationHint);
@@ -295,7 +328,27 @@ export class UITransformer {
295
328
  * - The intent object can be reused across both the heuristic and LLM paths.
296
329
  * - It is easy to unit-test intent detection in isolation.
297
330
  */
298
- static async detectIntent(query: string, llm: ILLMProvider): Promise<QueryIntent> {
331
+ static async detectIntent(query: string, llm: ILLMProvider, sources?: VectorMatch[]): Promise<QueryIntent> {
332
+ let schemaProfileText = '';
333
+ if (sources && sources.length > 0) {
334
+ const profile = this.profileData(sources);
335
+ const hasProducts = sources.some(item => this.isProductData(item));
336
+ schemaProfileText = `
337
+ RETRIEVED DATA SCHEMA:
338
+ - Numeric fields (measures): ${profile.numericFields.map(f => `${f.key} (unique values: ${f.uniqueCount})`).join(', ') || 'None'}
339
+ - Category fields (dimensions): ${profile.categoricalFields.map(f => `${f.key} (unique values: ${f.uniqueCount})`).join(', ') || 'None'}
340
+ - Date fields (temporal): ${profile.dateFields.map(f => `${f.key} (unique values: ${f.uniqueCount})`).join(', ') || 'None'}
341
+ - Boolean fields: ${profile.booleanFields.map(f => `${f.key} (unique values: ${f.uniqueCount})`).join(', ') || 'None'}
342
+ - Text fields: ${profile.fields.filter(f => f.kind === 'text').map(f => f.key).join(', ') || 'None'}
343
+
344
+ Please choose visualizationHint and recommendedChart dynamically based on the available schema.
345
+ - Do NOT recommend "trend" or "line_chart" if there are no Date fields.
346
+ - Do NOT recommend numeric-dependent visualizations (like bar_chart, line_chart, pie_chart, scatter_plot, histogram, metric_card) if there are no Numeric fields. Fall back to tabular/table or text.
347
+ - If there are multiple metadata fields but no clear categories or numbers, "tabular"/"table" is a highly accurate default.
348
+ ${hasProducts ? `- Note: The retrieved data matches product/catalog structure. If the user query is looking for, exploring, searching, browsing, or listing products/items, you MUST recommend visualizationHint: "product_browse" and recommendedChart: "text".` : ''}
349
+ `;
350
+ }
351
+
299
352
  const systemPrompt = `You are an intent classifier for a product-search RAG system.
300
353
  Given a user query, return ONLY a valid JSON object with this exact shape — no prose, no markdown:
301
354
 
@@ -332,8 +385,10 @@ RULES:
332
385
  - isComparison: true if user compares, ranks, or contrasts entities or categories.
333
386
  - language: detect from the query text itself; default "en" if uncertain.`;
334
387
 
388
+ const userPrompt = `QUERY: ${query}${schemaProfileText ? `\n\n${schemaProfileText}` : ''}`;
389
+
335
390
  const rawResponse = await llm.chat(
336
- [{ role: 'user', content: `QUERY: ${query}` }],
391
+ [{ role: 'user', content: userPrompt }],
337
392
  '',
338
393
  { systemPrompt, temperature: 0 },
339
394
  );
@@ -894,14 +949,28 @@ RULES:
894
949
  return asksForRecords && (hasNumericPredicate || hasEntityTerms);
895
950
  }
896
951
 
952
+ /** @internal kept private for transform() internal logic */
897
953
  private static isProductQuery(query: string): boolean {
954
+ return UITransformer._productQueryTest(query);
955
+ }
956
+
957
+ /**
958
+ * Public entry-point for external callers (e.g. Pipeline)
959
+ * to check whether a query maps to product_browse without triggering an LLM call.
960
+ */
961
+ static isProductQueryPublic(query: string): boolean {
962
+ return UITransformer._productQueryTest(query);
963
+ }
964
+
965
+ private static _productQueryTest(query: string): boolean {
898
966
  const q = query.toLowerCase();
899
967
  const productTerms = /\b(product|products|item|items|sku|catalog|catalogue|price|prices|brand|model|stock|inventory|in stock|out of stock|buy|shop)\b/.test(q);
900
- const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list)\b/.test(q);
968
+ const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list|explore|exploring)\b/.test(q);
901
969
  const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
902
970
  return productTerms && productAction && !nonProductEntityTerms;
903
971
  }
904
972
 
973
+
905
974
  private static isProductData(item: VectorMatch): boolean {
906
975
  const content = (item.content || '').toLowerCase();
907
976
  const productKeywords = [
@@ -1,45 +0,0 @@
1
- /**
2
- * DocumentChunker — splits long text into overlapping chunks
3
- * suitable for vector embedding and retrieval.
4
- *
5
- * Strategy: sentence-aware sliding window
6
- * 1. Split on sentence boundaries
7
- * 2. Accumulate sentences into chunks up to `chunkSize` characters
8
- * 3. Carry over `chunkOverlap` characters from the previous chunk
9
- */
10
- interface Chunk {
11
- id: string;
12
- content: string;
13
- metadata?: Record<string, unknown>;
14
- }
15
- interface ChunkOptions {
16
- /** Target chunk size in characters (default 1000) */
17
- chunkSize?: number;
18
- /** Overlap between consecutive chunks in characters (default 200) */
19
- chunkOverlap?: number;
20
- /** Source document identifier used as ID prefix */
21
- docId?: string | number;
22
- /** Extra metadata to attach to every chunk */
23
- metadata?: Record<string, unknown>;
24
- /** Characters used to split text, in order of priority */
25
- separators?: string[];
26
- }
27
- declare class DocumentChunker {
28
- private readonly chunkSize;
29
- private readonly chunkOverlap;
30
- private readonly separators;
31
- constructor(chunkSize?: number, chunkOverlap?: number, separators?: string[]);
32
- /**
33
- * Split a single text string into overlapping chunks using a recursive strategy.
34
- * Preserves structural boundaries (Markdown headers) where possible.
35
- */
36
- chunk(text: string, options?: ChunkOptions): Chunk[];
37
- private recursiveSplit;
38
- chunkMany(documents: Array<{
39
- content: string;
40
- docId?: string | number;
41
- metadata?: Record<string, unknown>;
42
- }>): Chunk[];
43
- }
44
-
45
- export { type Chunk as C, DocumentChunker as D, type ChunkOptions as a };
@@ -1,45 +0,0 @@
1
- /**
2
- * DocumentChunker — splits long text into overlapping chunks
3
- * suitable for vector embedding and retrieval.
4
- *
5
- * Strategy: sentence-aware sliding window
6
- * 1. Split on sentence boundaries
7
- * 2. Accumulate sentences into chunks up to `chunkSize` characters
8
- * 3. Carry over `chunkOverlap` characters from the previous chunk
9
- */
10
- interface Chunk {
11
- id: string;
12
- content: string;
13
- metadata?: Record<string, unknown>;
14
- }
15
- interface ChunkOptions {
16
- /** Target chunk size in characters (default 1000) */
17
- chunkSize?: number;
18
- /** Overlap between consecutive chunks in characters (default 200) */
19
- chunkOverlap?: number;
20
- /** Source document identifier used as ID prefix */
21
- docId?: string | number;
22
- /** Extra metadata to attach to every chunk */
23
- metadata?: Record<string, unknown>;
24
- /** Characters used to split text, in order of priority */
25
- separators?: string[];
26
- }
27
- declare class DocumentChunker {
28
- private readonly chunkSize;
29
- private readonly chunkOverlap;
30
- private readonly separators;
31
- constructor(chunkSize?: number, chunkOverlap?: number, separators?: string[]);
32
- /**
33
- * Split a single text string into overlapping chunks using a recursive strategy.
34
- * Preserves structural boundaries (Markdown headers) where possible.
35
- */
36
- chunk(text: string, options?: ChunkOptions): Chunk[];
37
- private recursiveSplit;
38
- chunkMany(documents: Array<{
39
- content: string;
40
- docId?: string | number;
41
- metadata?: Record<string, unknown>;
42
- }>): Chunk[];
43
- }
44
-
45
- export { type Chunk as C, DocumentChunker as D, type ChunkOptions as a };