@retrivora-ai/rag-engine 2.2.4 → 2.2.6
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.d.mts +1 -1
- package/dist/handlers/index.d.ts +1 -1
- package/dist/handlers/index.js +147 -843
- package/dist/handlers/index.mjs +147 -843
- package/dist/{index-C6ehmP0b.d.ts → index-B5TTZWkx.d.ts} +6 -6
- package/dist/{index-DFc_Ll9z.d.mts → index-DvbtNz7m.d.mts} +6 -6
- package/dist/index.css +119 -0
- package/dist/index.js +277 -173
- package/dist/index.mjs +279 -174
- package/dist/server.d.mts +3 -4
- package/dist/server.d.ts +3 -4
- package/dist/server.js +147 -843
- package/dist/server.mjs +147 -843
- package/package.json +3 -1
- package/src/components/ChatWidget.tsx +75 -6
- package/src/components/ChatWindow.tsx +91 -13
- package/src/config/UniversalProfiles.ts +1 -2
- package/src/config/serverConfig.ts +5 -3
- package/src/core/ConfigFetcher.ts +1 -1
- package/src/core/Pipeline.ts +24 -27
- package/src/core/VectorPlugin.ts +2 -2
- package/src/handlers/index.ts +11 -14
- package/src/hooks/useRagChat.ts +9 -1
- package/src/index.css +119 -0
- package/src/llm/providers/UniversalLLMAdapter.ts +53 -104
- package/src/utils/SchemaMapper.ts +107 -62
- package/src/utils/UITransformer.ts +22 -22
|
@@ -13,13 +13,18 @@ export interface SchemaMap {
|
|
|
13
13
|
/**
|
|
14
14
|
* SchemaMapper — Uses AI to "train" the plugin on unknown database schemas.
|
|
15
15
|
* It maps arbitrary metadata keys to standard UI properties.
|
|
16
|
+
*
|
|
17
|
+
* Design notes:
|
|
18
|
+
* - Bypasses ILLMProvider.chat() entirely to avoid injecting the full chat
|
|
19
|
+
* system prompt (which can exceed Groq's free-tier TPM limit).
|
|
20
|
+
* - Deduplicates concurrent in-flight training calls for the same schema.
|
|
21
|
+
* - Falls back to heuristics on any failure (non-blocking).
|
|
16
22
|
*/
|
|
17
23
|
export class SchemaMapper {
|
|
18
24
|
private static cache = new Map<string, SchemaMap>();
|
|
25
|
+
/** In-flight deduplication — prevents concurrent identical LLM calls. */
|
|
26
|
+
private static pending = new Map<string, Promise<SchemaMap>>();
|
|
19
27
|
|
|
20
|
-
/**
|
|
21
|
-
* Descriptions of standard UI properties to help the AI map fields accurately.
|
|
22
|
-
*/
|
|
23
28
|
private static readonly TARGET_PROPERTIES: Record<string, string> = {
|
|
24
29
|
name: 'The primary title, name, or label of the item',
|
|
25
30
|
price: 'The numeric cost, price, MSRP, or amount',
|
|
@@ -32,74 +37,95 @@ export class SchemaMapper {
|
|
|
32
37
|
|
|
33
38
|
/**
|
|
34
39
|
* Trains the plugin on a set of keys.
|
|
35
|
-
*
|
|
40
|
+
* Results are cached in-process; concurrent calls for the same key set are
|
|
41
|
+
* deduplicated to a single LLM request.
|
|
36
42
|
*/
|
|
37
43
|
static async train(
|
|
38
44
|
llm: ILLMProvider,
|
|
39
45
|
projectId: string,
|
|
40
46
|
keys: string[]
|
|
41
47
|
): Promise<SchemaMap> {
|
|
42
|
-
const cacheKey = `${projectId}:${keys.sort().join(',')}`;
|
|
48
|
+
const cacheKey = `${projectId}:${keys.slice().sort().join(',')}`;
|
|
49
|
+
|
|
50
|
+
// 1. Return from in-process cache (fast path)
|
|
43
51
|
if (this.cache.has(cacheKey)) {
|
|
44
52
|
return this.cache.get(cacheKey)!;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
// 2. Deduplicate concurrent in-flight requests for the same schema
|
|
56
|
+
if (this.pending.has(cacheKey)) {
|
|
57
|
+
return this.pending.get(cacheKey)!;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const promise = this._doTrain(llm, cacheKey, keys);
|
|
61
|
+
this.pending.set(cacheKey, promise);
|
|
62
|
+
promise.finally(() => this.pending.delete(cacheKey));
|
|
63
|
+
return promise;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private static async _doTrain(
|
|
67
|
+
llm: ILLMProvider,
|
|
68
|
+
cacheKey: string,
|
|
69
|
+
keys: string[]
|
|
70
|
+
): Promise<SchemaMap> {
|
|
71
|
+
console.log(`[SchemaMapper] 🧠 Training on new schema keys: ${keys.join(', ')}`);
|
|
48
72
|
|
|
49
|
-
// Dynamically build the property list for the prompt
|
|
50
73
|
const propertyList = Object.entries(this.TARGET_PROPERTIES)
|
|
51
74
|
.map(([prop, desc]) => `- ${prop} (${desc})`)
|
|
52
75
|
.join('\n');
|
|
53
76
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
"name": "Title",
|
|
66
|
-
"price": "Variant Price",
|
|
67
|
-
"brand": "Vendor",
|
|
68
|
-
"image": "Image Src",
|
|
69
|
-
"stock": "Variant Inventory Qty"
|
|
70
|
-
}
|
|
71
|
-
`;
|
|
77
|
+
// Compact prompt — no chat system prompt injected.
|
|
78
|
+
const messages = [
|
|
79
|
+
{ role: 'system', content: 'You are a database schema expert. Respond ONLY with valid JSON.' },
|
|
80
|
+
{
|
|
81
|
+
role: 'user',
|
|
82
|
+
content:
|
|
83
|
+
`Keys: [${keys.join(', ')}]\n\nMap to:\n${propertyList}\n\n` +
|
|
84
|
+
`Return a JSON object like {"name":"Title","price":"Price",...}. Omit unmapped properties.`,
|
|
85
|
+
},
|
|
86
|
+
];
|
|
72
87
|
|
|
73
88
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
89
|
+
// Use a direct fetch against the resolved base URL + auth headers,
|
|
90
|
+
// bypassing ILLMProvider.chat() which injects the full chat system prompt.
|
|
91
|
+
const baseUrl: string = (llm as any)?.baseUrl ?? '';
|
|
92
|
+
const apiKey: string = (llm as any)?.apiKey ?? '';
|
|
93
|
+
const model: string = (llm as any)?.model ?? 'llama-3.1-8b-instant';
|
|
94
|
+
|
|
95
|
+
let responseText: string | undefined;
|
|
96
|
+
|
|
97
|
+
if (baseUrl) {
|
|
98
|
+
const endpoint = `${baseUrl.replace(/\/$/, '')}/chat/completions`;
|
|
99
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
100
|
+
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
|
|
101
|
+
|
|
102
|
+
const res = await fetch(endpoint, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers,
|
|
105
|
+
body: JSON.stringify({
|
|
106
|
+
model,
|
|
107
|
+
messages,
|
|
108
|
+
max_tokens: 256, // Only needs a small JSON object back
|
|
109
|
+
temperature: 0,
|
|
110
|
+
stream: false,
|
|
111
|
+
}),
|
|
112
|
+
signal: AbortSignal.timeout(8000),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (res.ok) {
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
responseText =
|
|
118
|
+
data?.choices?.[0]?.message?.content ??
|
|
119
|
+
data?.choices?.[0]?.delta?.content ??
|
|
120
|
+
undefined;
|
|
121
|
+
} else {
|
|
122
|
+
console.warn(`[SchemaMapper] LLM returned ${res.status}, falling back to heuristics.`);
|
|
97
123
|
}
|
|
124
|
+
}
|
|
98
125
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const mapping = JSON.parse(cleanJson) as SchemaMap;
|
|
126
|
+
if (responseText) {
|
|
127
|
+
const mapping = this._parseJson(responseText);
|
|
128
|
+
if (mapping) {
|
|
103
129
|
this.cache.set(cacheKey, mapping);
|
|
104
130
|
return mapping;
|
|
105
131
|
}
|
|
@@ -108,24 +134,43 @@ Example:
|
|
|
108
134
|
console.warn('[SchemaMapper] AI training failed, falling back to heuristics:', error);
|
|
109
135
|
}
|
|
110
136
|
|
|
137
|
+
// Store empty result so we don't keep retrying on the same schema
|
|
138
|
+
this.cache.set(cacheKey, {});
|
|
111
139
|
return {};
|
|
112
140
|
}
|
|
113
141
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
142
|
+
private static _parseJson(text: string): SchemaMap | null {
|
|
143
|
+
const startIdx = text.indexOf('{');
|
|
144
|
+
if (startIdx === -1) return null;
|
|
145
|
+
let braceCount = 0;
|
|
146
|
+
let endIdx = -1;
|
|
147
|
+
for (let i = startIdx; i < text.length; i++) {
|
|
148
|
+
if (text[i] === '{') braceCount++;
|
|
149
|
+
else if (text[i] === '}') {
|
|
150
|
+
braceCount--;
|
|
151
|
+
if (braceCount === 0) { endIdx = i; break; }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (endIdx === -1) return null;
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(this._sanitizeJson(text.substring(startIdx, endIdx + 1))) as SchemaMap;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private static _sanitizeJson(s: string): string {
|
|
118
163
|
return s
|
|
119
|
-
.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '')
|
|
120
|
-
.replace(/[\u201C\u201D\u2018\u2019]/g, '"')
|
|
121
|
-
.replace(/'/g, '"')
|
|
122
|
-
.replace(/([{,]\s*)([a-zA-Z0-9_]+?)\s*:/g, '$1"$2":')
|
|
123
|
-
.replace(/,\s*([}\]])/g, '$1')
|
|
164
|
+
.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '')
|
|
165
|
+
.replace(/[\u201C\u201D\u2018\u2019]/g, '"')
|
|
166
|
+
.replace(/'/g, '"')
|
|
167
|
+
.replace(/([{,]\s*)([a-zA-Z0-9_]+?)\s*:/g, '$1"$2":')
|
|
168
|
+
.replace(/,\s*([}\]])/g, '$1')
|
|
124
169
|
.trim();
|
|
125
170
|
}
|
|
126
171
|
|
|
127
172
|
static getCached(projectId: string, keys: string[]): SchemaMap | undefined {
|
|
128
|
-
const cacheKey = `${projectId}:${keys.sort().join(',')}`;
|
|
173
|
+
const cacheKey = `${projectId}:${keys.slice().sort().join(',')}`;
|
|
129
174
|
return this.cache.get(cacheKey);
|
|
130
175
|
}
|
|
131
176
|
}
|
|
@@ -128,16 +128,16 @@ 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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
});
|
|
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
141
|
|
|
142
142
|
const resolvedIntent = intent ?? this.detectIntentHeuristic(userQuery);
|
|
143
143
|
|
|
@@ -642,16 +642,16 @@ RULES:
|
|
|
642
642
|
const fallbackData: BarChartData[] = barData.length > 0
|
|
643
643
|
? barData
|
|
644
644
|
: data.slice(0, 12).map((item, index) => {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
645
|
+
const meta = item.metadata || {};
|
|
646
|
+
const label = String(
|
|
647
|
+
this.getDynamicVal(meta, 'name') ??
|
|
648
|
+
meta.label ??
|
|
649
|
+
meta.title ??
|
|
650
|
+
`Result ${index + 1}`,
|
|
651
|
+
);
|
|
652
|
+
const value = this.extractNumericValue(meta) ?? item.score ?? 0;
|
|
653
|
+
return { category: label, value: Number(value) };
|
|
654
|
+
});
|
|
655
655
|
|
|
656
656
|
return {
|
|
657
657
|
type: horizontal ? 'horizontal_bar' : 'bar_chart',
|
|
@@ -724,8 +724,8 @@ RULES:
|
|
|
724
724
|
const numericField = this.selectNumericField(profile, query);
|
|
725
725
|
const values = numericField
|
|
726
726
|
? profile.records
|
|
727
|
-
|
|
728
|
-
|
|
727
|
+
.map(record => this.toFiniteNumber(record.fields[numericField.key]))
|
|
728
|
+
.filter((value): value is number => value !== null)
|
|
729
729
|
: [];
|
|
730
730
|
|
|
731
731
|
const value = operation === 'count' || values.length === 0
|