@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.
- package/README.md +59 -7
- package/dist/{ILLMProvider-Bw2A28nU.d.mts → ILLMProvider-Bhk6zJOK.d.mts} +43 -7
- package/dist/{ILLMProvider-Bw2A28nU.d.ts → ILLMProvider-Bhk6zJOK.d.ts} +43 -7
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +737 -237
- package/dist/handlers/index.mjs +736 -237
- package/dist/index-B9J_XEh0.d.ts +187 -0
- package/dist/index-BJ4cd-t5.d.mts +187 -0
- package/dist/{index-B70ZLkfG.d.mts → index-Bu7T6xgr.d.ts} +20 -3
- package/dist/{index-DVu-mkAM.d.ts → index-C3SVtPYg.d.mts} +20 -3
- package/dist/index.css +237 -10
- package/dist/index.d.mts +13 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +365 -164
- package/dist/index.mjs +350 -158
- package/dist/server.d.mts +15 -94
- package/dist/server.d.ts +15 -94
- package/dist/server.js +850 -239
- package/dist/server.mjs +839 -238
- package/package.json +2 -4
- package/src/app/api/chat/route.ts +2 -2
- package/src/app/api/health/route.ts +3 -2
- package/src/app/api/ingest/route.ts +3 -2
- package/src/app/api/suggestions/route.ts +3 -2
- package/src/app/api/upload/route.ts +3 -2
- package/src/app/constants.tsx +168 -148
- package/src/app/layout.tsx +5 -18
- package/src/app/types.ts +17 -17
- package/src/components/ChatWidget.tsx +3 -1
- package/src/components/ChatWindow.tsx +5 -1
- package/src/components/DocViewer.tsx +71 -5
- package/src/components/Documentation.tsx +74 -11
- package/src/components/MessageBubble.tsx +39 -2
- package/src/components/ThinkingBlock.tsx +75 -0
- package/src/components/VisualizationRenderer.tsx +27 -1
- package/src/components/constants.tsx +275 -0
- package/src/config/RagConfig.ts +47 -0
- package/src/config/constants.ts +1 -0
- package/src/config/serverConfig.ts +25 -0
- package/src/core/ConfigResolver.ts +73 -22
- package/src/core/ConfigValidator.ts +2 -1
- package/src/core/Pipeline.ts +226 -68
- package/src/core/ProviderRegistry.ts +16 -7
- package/src/core/QueryProcessor.ts +38 -4
- package/src/core/Retrivora.ts +91 -0
- package/src/core/VectorPlugin.ts +62 -8
- package/src/exceptions/index.ts +111 -0
- package/src/handlers/index.ts +73 -0
- package/src/hooks/useRagChat.ts +30 -5
- package/src/index.ts +27 -1
- package/src/lib/plugin.ts +24 -0
- package/src/llm/LLMFactory.ts +8 -4
- package/src/llm/providers/AnthropicProvider.ts +70 -20
- package/src/llm/providers/GeminiProvider.ts +14 -15
- package/src/llm/providers/OllamaProvider.ts +13 -16
- package/src/llm/providers/OpenAIProvider.ts +9 -14
- package/src/llm/providers/UniversalLLMAdapter.ts +5 -5
- package/src/llm/utils.ts +46 -0
- package/src/providers/vectordb/MongoDBProvider.ts +9 -4
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +45 -13
- package/src/rag/EntityExtractor.ts +2 -2
- package/src/rag/Reranker.ts +9 -16
- package/src/server.ts +30 -1
- package/src/types/chat.ts +9 -0
- package/src/types/props.ts +38 -1
- package/src/utils/UITransformer.ts +73 -4
- package/dist/DocumentChunker-Dh9TvmGG.d.mts +0 -45
- 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:
|
|
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 };
|