@retrivora-ai/rag-engine 2.0.6 → 2.0.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/handlers/index.js +167 -50
- package/dist/handlers/index.mjs +167 -50
- package/dist/index.js +79 -22
- package/dist/index.mjs +79 -22
- package/dist/server.js +167 -50
- package/dist/server.mjs +167 -50
- package/package.json +1 -1
- package/src/core/Pipeline.ts +51 -13
- package/src/core/QueryProcessor.ts +3 -3
- package/src/llm/providers/UniversalLLMAdapter.ts +17 -15
- package/src/rag/Reranker.ts +2 -2
- package/src/rendering/IntentClassifier.ts +1 -1
- package/src/rendering/RendererRegistry.ts +1 -1
- package/src/utils/ProductExtractor.ts +1 -1
- package/src/utils/UITransformer.ts +62 -17
|
@@ -96,9 +96,9 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
96
96
|
|
|
97
97
|
const isSelfHost = this.baseUrl.includes('retrivora.com') || this.baseUrl.includes('localhost');
|
|
98
98
|
if (isSelfHost || Boolean(process.env.VERCEL)) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
const _g = globalThis as any;
|
|
100
|
+
if (typeof _g.__retrivoraDispatchChat === 'function') {
|
|
101
|
+
try {
|
|
102
102
|
const res = await _g.__retrivoraDispatchChat({
|
|
103
103
|
model: this.model,
|
|
104
104
|
messages: formattedMessages as any,
|
|
@@ -109,9 +109,9 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
109
109
|
if (res?.response?.choices?.[0]?.message?.content) {
|
|
110
110
|
return res.response.choices[0].message.content;
|
|
111
111
|
}
|
|
112
|
+
} catch (dispatchErr) {
|
|
113
|
+
throw dispatchErr;
|
|
112
114
|
}
|
|
113
|
-
} catch {
|
|
114
|
-
/* proceed to HTTP fetch */
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -170,9 +170,9 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
170
170
|
let streamBody: ReadableStream<Uint8Array> | null = null;
|
|
171
171
|
|
|
172
172
|
if (isSelfHost || Boolean(process.env.VERCEL)) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
const _g = globalThis as any;
|
|
174
|
+
if (typeof _g.__retrivoraDispatchChat === 'function') {
|
|
175
|
+
try {
|
|
176
176
|
const res = await _g.__retrivoraDispatchChat({
|
|
177
177
|
model: this.model,
|
|
178
178
|
messages: formattedMessages as any,
|
|
@@ -187,9 +187,11 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
187
187
|
yield res.response.choices[0].message.content;
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
|
+
} catch (dispatchErr) {
|
|
191
|
+
// If in-process dispatch threw a real error (Rate limit, Auth, API error), rethrow it
|
|
192
|
+
// rather than silently falling back to external HTTP fetch which masks the error.
|
|
193
|
+
throw dispatchErr;
|
|
190
194
|
}
|
|
191
|
-
} catch {
|
|
192
|
-
/* proceed to HTTP fetch */
|
|
193
195
|
}
|
|
194
196
|
}
|
|
195
197
|
|
|
@@ -271,9 +273,9 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
271
273
|
|
|
272
274
|
const isSelfHost = this.baseUrl.includes('retrivora.com') || this.baseUrl.includes('localhost');
|
|
273
275
|
if (isSelfHost || Boolean(process.env.VERCEL)) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
276
|
+
const _g = globalThis as any;
|
|
277
|
+
if (typeof _g.__retrivoraDispatchEmbedding === 'function') {
|
|
278
|
+
try {
|
|
277
279
|
const res = await _g.__retrivoraDispatchEmbedding({
|
|
278
280
|
model: this.model,
|
|
279
281
|
input: text,
|
|
@@ -282,9 +284,9 @@ export class UniversalLLMAdapter implements ILLMProvider {
|
|
|
282
284
|
if (res?.data?.[0]?.embedding) {
|
|
283
285
|
return res.data[0].embedding;
|
|
284
286
|
}
|
|
287
|
+
} catch (dispatchErr) {
|
|
288
|
+
throw dispatchErr;
|
|
285
289
|
}
|
|
286
|
-
} catch {
|
|
287
|
-
/* proceed to HTTP fetch */
|
|
288
290
|
}
|
|
289
291
|
}
|
|
290
292
|
|
package/src/rag/Reranker.ts
CHANGED
|
@@ -25,7 +25,7 @@ export class Reranker {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const scoredMatches = matches.map(match => {
|
|
28
|
-
const contentLower = match
|
|
28
|
+
const contentLower = (match?.content ?? '').toLowerCase();
|
|
29
29
|
let keywordScore = 0;
|
|
30
30
|
|
|
31
31
|
keywords.forEach(keyword => {
|
|
@@ -57,7 +57,7 @@ export class Reranker {
|
|
|
57
57
|
const topN = matches.slice(0, 10);
|
|
58
58
|
|
|
59
59
|
const response = await llm.chat(
|
|
60
|
-
[{ role: 'user', content: `Query: "${query}"\n\nDocuments:\n${topN.map((m, i) => `[${i}] ${m
|
|
60
|
+
[{ role: 'user', content: `Query: "${query}"\n\nDocuments:\n${topN.map((m, i) => `[${i}] ${(m?.content ?? '').replace(/\n/g, ' ')}`).join('\n')}` }],
|
|
61
61
|
'',
|
|
62
62
|
{
|
|
63
63
|
systemPrompt: 'You are a relevance ranking expert. Given the user query and a list of retrieved document chunks, rank the chunks by relevance to the query. Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant. Use the indices provided in brackets like [0], [1], etc. Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.',
|
|
@@ -107,7 +107,7 @@ export class IntentClassifier {
|
|
|
107
107
|
numericFieldCount = numericKeys.size;
|
|
108
108
|
categoricalFieldCount = catKeys.size;
|
|
109
109
|
|
|
110
|
-
if (productKeyMatches >= 2 || docs.some(d => d
|
|
110
|
+
if (productKeyMatches >= 2 || docs.some(d => (d?.content ?? '').toLowerCase().includes('price') || (d?.content ?? '').toLowerCase().includes('brand'))) {
|
|
111
111
|
isProductLike = true;
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -13,7 +13,7 @@ export class TextRendererStrategy implements IRendererStrategy {
|
|
|
13
13
|
return { type: 'text', title, data: { content: data } };
|
|
14
14
|
}
|
|
15
15
|
const docs = Array.isArray(data) ? (data as VectorMatch[]) : [];
|
|
16
|
-
const text = docs.map(d => d
|
|
16
|
+
const text = docs.map(d => d?.content ?? '').join('\n\n');
|
|
17
17
|
return { type: 'text', title, data: { content: text || 'No detailed content available.' } };
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -216,7 +216,7 @@ export function extractProductsFromSources(sources: VectorMatch[] | undefined, i
|
|
|
216
216
|
|
|
217
217
|
return {
|
|
218
218
|
id: s.id,
|
|
219
|
-
name: name ?? s
|
|
219
|
+
name: name ?? (s?.content ?? '').split('\n')[0] ?? 'Unknown Product',
|
|
220
220
|
brand: brand,
|
|
221
221
|
price: price,
|
|
222
222
|
image: resolveImage(m),
|
|
@@ -128,6 +128,17 @@ export class UITransformer {
|
|
|
128
128
|
return this.createTextResponse('No data available', 'No relevant data found for your query.');
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
console.log('[UITransformer.transform] Processing retrievedData:', {
|
|
132
|
+
userQuery,
|
|
133
|
+
retrievedCount: retrievedData.length,
|
|
134
|
+
sample: retrievedData.slice(0, 2).map((item) => ({
|
|
135
|
+
id: item?.id,
|
|
136
|
+
contentType: typeof item?.content,
|
|
137
|
+
contentSnippet: String(item?.content ?? '').substring(0, 150),
|
|
138
|
+
metadataKeys: Object.keys(item?.metadata || {}),
|
|
139
|
+
})),
|
|
140
|
+
});
|
|
141
|
+
|
|
131
142
|
const resolvedIntent = intent ?? this.detectIntentHeuristic(userQuery);
|
|
132
143
|
|
|
133
144
|
const filteredData = resolvedIntent.filterInStockOnly
|
|
@@ -760,7 +771,7 @@ RULES:
|
|
|
760
771
|
type: 'radar_chart' as VisualizationType,
|
|
761
772
|
title: 'Product Comparison',
|
|
762
773
|
description: `Comparing ${data.length} items across ${radarData.length} attributes`,
|
|
763
|
-
data: radarData.length > 0 ? radarData : data.map(d => ({ attribute: d
|
|
774
|
+
data: radarData.length > 0 ? radarData : data.map(d => ({ attribute: (d?.content ?? '').substring(0, 40) })),
|
|
764
775
|
};
|
|
765
776
|
}
|
|
766
777
|
|
|
@@ -780,7 +791,7 @@ RULES:
|
|
|
780
791
|
private static transformToText(data: VectorMatch[]): UITransformationResponse {
|
|
781
792
|
return this.createTextResponse(
|
|
782
793
|
'Retrieved Context',
|
|
783
|
-
data.map(item => item
|
|
794
|
+
data.map(item => item?.content ?? '').join('\n\n'),
|
|
784
795
|
`Found ${data.length} relevant results`,
|
|
785
796
|
);
|
|
786
797
|
}
|
|
@@ -983,6 +994,7 @@ RULES:
|
|
|
983
994
|
|
|
984
995
|
|
|
985
996
|
private static isProductData(item: VectorMatch): boolean {
|
|
997
|
+
if (!item) return false;
|
|
986
998
|
const content = (item.content || '').toLowerCase();
|
|
987
999
|
const productKeywords = [
|
|
988
1000
|
'product', 'price', 'stock', 'sku', 'brand', 'msrp',
|
|
@@ -1297,7 +1309,7 @@ RULES:
|
|
|
1297
1309
|
return {
|
|
1298
1310
|
timestamp: (meta.timestamp ?? meta.date ?? new Date().toISOString()) as string | number,
|
|
1299
1311
|
value: (meta.value ?? item.score ?? 0) as number,
|
|
1300
|
-
label: (meta.label ?? item
|
|
1312
|
+
label: (meta.label ?? (item?.content ?? '').substring(0, 50)) as string,
|
|
1301
1313
|
};
|
|
1302
1314
|
});
|
|
1303
1315
|
}
|
|
@@ -1429,7 +1441,7 @@ RULES:
|
|
|
1429
1441
|
}
|
|
1430
1442
|
|
|
1431
1443
|
private static resolveTableCellValue(item: VectorMatch, column: string): string | number | boolean {
|
|
1432
|
-
if (column === 'Content') return item
|
|
1444
|
+
if (column === 'Content') return (item?.content ?? '').substring(0, 100);
|
|
1433
1445
|
|
|
1434
1446
|
const meta = item.metadata || {};
|
|
1435
1447
|
const normalizedColumn = this.normalizeComparableField(column);
|
|
@@ -1444,10 +1456,10 @@ RULES:
|
|
|
1444
1456
|
const aliasValue = this.resolveAliasedTableCell(meta, column);
|
|
1445
1457
|
if (aliasValue !== undefined) return this.toDisplayValue(aliasValue);
|
|
1446
1458
|
|
|
1447
|
-
const contentValue = this.extractContentFieldValue(item
|
|
1459
|
+
const contentValue = this.extractContentFieldValue(item?.content ?? '', column);
|
|
1448
1460
|
if (contentValue !== null) return contentValue;
|
|
1449
1461
|
|
|
1450
|
-
const aliasContentValue = this.extractAliasedContentFieldValue(item
|
|
1462
|
+
const aliasContentValue = this.extractAliasedContentFieldValue(item?.content ?? '', column);
|
|
1451
1463
|
return aliasContentValue ?? '';
|
|
1452
1464
|
}
|
|
1453
1465
|
|
|
@@ -1519,6 +1531,7 @@ RULES:
|
|
|
1519
1531
|
}
|
|
1520
1532
|
|
|
1521
1533
|
private static determineStockStatus(item: VectorMatch): boolean {
|
|
1534
|
+
if (!item) return true;
|
|
1522
1535
|
const meta = item.metadata || {};
|
|
1523
1536
|
const stockValue = resolveMetadataValue(meta, 'stock');
|
|
1524
1537
|
if (stockValue !== undefined) {
|
|
@@ -1583,42 +1596,74 @@ RULES:
|
|
|
1583
1596
|
config?: import('../config/RagConfig').RagConfig,
|
|
1584
1597
|
trainedSchema?: import('./SchemaMapper').SchemaMap,
|
|
1585
1598
|
): CarouselProduct | null {
|
|
1599
|
+
if (!item) return null;
|
|
1586
1600
|
const meta = item.metadata || {};
|
|
1601
|
+
const content = item.content || '';
|
|
1587
1602
|
const name = this.getDynamicVal(meta, 'name', config, trainedSchema);
|
|
1588
1603
|
const price = this.getDynamicVal(meta, 'price', config, trainedSchema);
|
|
1589
1604
|
const brand = this.getDynamicVal(meta, 'brand', config, trainedSchema);
|
|
1590
1605
|
const description = this.cleanProductDescription(
|
|
1591
|
-
this.extractProductDescriptionFromContent(
|
|
1606
|
+
this.extractProductDescriptionFromContent(content) ??
|
|
1592
1607
|
this.getProductDescriptionValue(meta, config, trainedSchema),
|
|
1593
1608
|
);
|
|
1594
1609
|
|
|
1595
1610
|
if (name || this.isProductData(item)) {
|
|
1596
1611
|
let finalName = name ? String(name) : undefined;
|
|
1612
|
+
if (!finalName && content) {
|
|
1613
|
+
const nameMatch = content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
|
|
1614
|
+
finalName = nameMatch ? nameMatch[1].trim() : content.split('\n')[0]?.substring(0, 60);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1597
1617
|
if (!finalName) {
|
|
1598
|
-
|
|
1599
|
-
|
|
1618
|
+
console.warn(
|
|
1619
|
+
`[UITransformer] Unable to read product name from metadata or content for item ${item.id}. Defaulting to 'Unnamed Product'.`,
|
|
1620
|
+
{ metadataKeys: Object.keys(meta), contentLength: content.length },
|
|
1621
|
+
);
|
|
1600
1622
|
}
|
|
1601
1623
|
|
|
1602
1624
|
let finalPrice: string | number | undefined =
|
|
1603
1625
|
typeof price === 'number' || typeof price === 'string' ? price : undefined;
|
|
1604
|
-
if (!finalPrice) {
|
|
1626
|
+
if (!finalPrice && content) {
|
|
1605
1627
|
const priceMatch =
|
|
1606
|
-
|
|
1607
|
-
|
|
1628
|
+
content.match(/(?:Price|Cost):\s*\$?([\d,.]+)/i) ||
|
|
1629
|
+
content.match(/\$\s*([\d,.]+)/);
|
|
1608
1630
|
if (priceMatch) finalPrice = priceMatch[1].replace(/,/g, '');
|
|
1609
1631
|
}
|
|
1610
1632
|
|
|
1633
|
+
if (finalPrice === undefined) {
|
|
1634
|
+
console.warn(
|
|
1635
|
+
`[UITransformer] Unable to read product price from metadata or content for item ${item.id}.`,
|
|
1636
|
+
{ metadataKeys: Object.keys(meta), contentSnippet: content.substring(0, 100) },
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1611
1640
|
const imageValue = this.getDynamicVal(meta, 'image', config, trainedSchema);
|
|
1641
|
+
if (!imageValue) {
|
|
1642
|
+
console.debug(
|
|
1643
|
+
`[UITransformer] Unable to read product image URL from metadata for item ${item.id}.`,
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
if (!description) {
|
|
1648
|
+
console.debug(
|
|
1649
|
+
`[UITransformer] Unable to read product description from metadata or content for item ${item.id}.`,
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1612
1652
|
|
|
1613
1653
|
return {
|
|
1614
1654
|
id: item.id,
|
|
1615
|
-
name: finalName,
|
|
1655
|
+
name: finalName || 'Unnamed Product',
|
|
1616
1656
|
price: finalPrice,
|
|
1617
1657
|
image: typeof imageValue === 'string' ? imageValue : undefined,
|
|
1618
1658
|
brand: brand ? String(brand) : undefined,
|
|
1619
1659
|
description,
|
|
1620
1660
|
inStock: this.determineStockStatus(item),
|
|
1621
1661
|
};
|
|
1662
|
+
} else {
|
|
1663
|
+
console.warn(
|
|
1664
|
+
`[UITransformer] Item ${item.id} skipped: unable to read name or identify as product data from metadata/content.`,
|
|
1665
|
+
{ metadataKeys: Object.keys(meta), contentSnippet: content.substring(0, 100) },
|
|
1666
|
+
);
|
|
1622
1667
|
}
|
|
1623
1668
|
|
|
1624
1669
|
return null;
|
|
@@ -1733,11 +1778,11 @@ RULES:
|
|
|
1733
1778
|
}
|
|
1734
1779
|
|
|
1735
1780
|
private static buildContextSummary(sources: VectorMatch[], maxChars = 6000): string {
|
|
1736
|
-
const items = sources.map((s, i) => ({
|
|
1781
|
+
const items = (sources || []).filter(Boolean).map((s, i) => ({
|
|
1737
1782
|
index: i + 1,
|
|
1738
|
-
content: s
|
|
1739
|
-
metadata: s
|
|
1740
|
-
score: s
|
|
1783
|
+
content: s?.content?.substring(0, 400) ?? '',
|
|
1784
|
+
metadata: s?.metadata ?? {},
|
|
1785
|
+
score: s?.score ?? 0,
|
|
1741
1786
|
}));
|
|
1742
1787
|
|
|
1743
1788
|
const full = JSON.stringify(items, null, 2);
|