@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.
@@ -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
- try {
100
- const _g = globalThis as any;
101
- if (typeof _g.__retrivoraDispatchChat === 'function') {
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
- try {
174
- const _g = globalThis as any;
175
- if (typeof _g.__retrivoraDispatchChat === 'function') {
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
- try {
275
- const _g = globalThis as any;
276
- if (typeof _g.__retrivoraDispatchEmbedding === 'function') {
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
 
@@ -25,7 +25,7 @@ export class Reranker {
25
25
  }
26
26
 
27
27
  const scoredMatches = matches.map(match => {
28
- const contentLower = match.content.toLowerCase();
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.content.replace(/\n/g, ' ')}`).join('\n')}` }],
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.content.toLowerCase().includes('price') || d.content.toLowerCase().includes('brand'))) {
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.content).join('\n\n');
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.content.split('\n')[0] ?? 'Unknown Product',
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.content.substring(0, 40) })),
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.content).join('\n\n'),
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.content.substring(0, 50)) as string,
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.content.substring(0, 100);
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.content, column);
1459
+ const contentValue = this.extractContentFieldValue(item?.content ?? '', column);
1448
1460
  if (contentValue !== null) return contentValue;
1449
1461
 
1450
- const aliasContentValue = this.extractAliasedContentFieldValue(item.content, column);
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(item.content) ??
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
- const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
1599
- finalName = nameMatch ? nameMatch[1].trim() : item.content.split('\n')[0].substring(0, 60);
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
- item.content.match(/(?:Price|Cost):\s*\$?([\d,.]+)/i) ||
1607
- item.content.match(/\$\s*([\d,.]+)/);
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.content?.substring(0, 400) ?? '',
1739
- metadata: s.metadata ?? {},
1740
- score: s.score ?? 0,
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);