@retrivora-ai/rag-engine 1.7.3 → 1.7.5
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/{chunk-M32ZXLTT.mjs → chunk-QOO37S2Z.mjs} +177 -53
- package/dist/handlers/index.js +172 -48
- package/dist/handlers/index.mjs +1 -1
- package/dist/index.js +17 -3
- package/dist/index.mjs +17 -3
- package/dist/server.js +179 -51
- package/dist/server.mjs +8 -4
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +58 -5
- package/src/core/Pipeline.ts +179 -53
- package/src/llm/providers/GeminiProvider.ts +7 -2
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +13 -3
package/src/core/Pipeline.ts
CHANGED
|
@@ -101,72 +101,198 @@ export class Pipeline {
|
|
|
101
101
|
|
|
102
102
|
// Augment system prompt with a Universal Visualization Protocol
|
|
103
103
|
// We use a unique marker to ensure this is only injected once but is ALWAYS present.
|
|
104
|
-
const CHART_MARKER = '<!--
|
|
105
|
-
const chartInstruction =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
104
|
+
const CHART_MARKER = '<!-- UI_PROTOCOL_V7 -->';
|
|
105
|
+
const chartInstruction = `
|
|
106
|
+
|
|
107
|
+
${CHART_MARKER}
|
|
108
|
+
### UNIVERSAL UI PROTOCOL V7 (STRICT MODE)
|
|
109
|
+
|
|
110
|
+
You are responsible for returning a SINGLE structured UI block when visualization is required.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 1. INTENT CLASSIFICATION (MANDATORY)
|
|
115
|
+
|
|
116
|
+
Classify the query into ONE of the following intents:
|
|
117
|
+
|
|
118
|
+
- "explore" → user wants to browse items (e.g., "show me products")
|
|
119
|
+
- "analyze" → user wants aggregation/distribution (e.g., "distribution of products")
|
|
120
|
+
- "compare" → user wants side-by-side comparison
|
|
121
|
+
- "detail" → user wants structured/tabular data
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 2. VIEW MAPPING (STRICT)
|
|
126
|
+
|
|
127
|
+
| Intent | View |
|
|
128
|
+
|----------|-----------|
|
|
129
|
+
| explore | carousel |
|
|
130
|
+
| analyze | chart |
|
|
131
|
+
| compare | table |
|
|
132
|
+
| detail | table |
|
|
133
|
+
|
|
134
|
+
⚠️ Override Rules:
|
|
135
|
+
- If query contains "distribution", "percentage", "ratio" → ALWAYS chart
|
|
136
|
+
- If query contains "show products", "list items" → ALWAYS carousel
|
|
137
|
+
- NEVER mix views
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 3. RESPONSE FORMAT (STRICT JSON ONLY INSIDE BLOCK)
|
|
142
|
+
|
|
143
|
+
\`\`\`ui
|
|
144
|
+
{
|
|
145
|
+
"view": "carousel" | "chart" | "table",
|
|
146
|
+
"title": "string",
|
|
147
|
+
"description": "string",
|
|
148
|
+
"metadata": {
|
|
149
|
+
"intent": "explore" | "analyze" | "compare" | "detail",
|
|
150
|
+
"confidence": 0.0-1.0
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// CHART ONLY
|
|
154
|
+
"chart": {
|
|
155
|
+
"type": "pie" | "bar" | "line",
|
|
156
|
+
"xKey": "label",
|
|
157
|
+
"yKeys": ["value"],
|
|
158
|
+
"data": []
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// TABLE ONLY
|
|
162
|
+
"table": {
|
|
163
|
+
"columns": [],
|
|
164
|
+
"rows": []
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// CAROUSEL ONLY
|
|
168
|
+
"carousel": {
|
|
169
|
+
"items": []
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
"insights": []
|
|
173
|
+
}
|
|
174
|
+
\`\`\`
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 4. DATA CONTRACT (CRITICAL)
|
|
179
|
+
|
|
180
|
+
### 🔹 CAROUSEL ITEM FORMAT
|
|
181
|
+
{
|
|
182
|
+
"id": "string",
|
|
183
|
+
"name": "string",
|
|
184
|
+
"brand": "string",
|
|
185
|
+
"price": number,
|
|
186
|
+
"image": "url",
|
|
187
|
+
"link": "url",
|
|
188
|
+
"inStock": boolean
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
### 🔹 CHART DATA FORMAT
|
|
192
|
+
{
|
|
193
|
+
"label": "string",
|
|
194
|
+
"value": number,
|
|
195
|
+
"inStock": boolean (optional)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
### 🔹 TABLE FORMAT
|
|
199
|
+
- columns MUST match row keys
|
|
200
|
+
- rows MUST be flat objects
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 5. DATA RULES
|
|
205
|
+
|
|
206
|
+
- NEVER hallucinate fields
|
|
207
|
+
- ONLY use retrieved vector DB data
|
|
208
|
+
- NO placeholders ("...", "N/A")
|
|
209
|
+
- If image/link missing → REMOVE FIELD
|
|
210
|
+
- Aggregate BEFORE rendering chart
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 6. SMART RULES (IMPORTANT)
|
|
215
|
+
|
|
216
|
+
- If user asks BOTH:
|
|
217
|
+
"show products AND distribution"
|
|
218
|
+
→ PRIORITIZE "explore" → carousel
|
|
219
|
+
|
|
220
|
+
- If dataset size > 20:
|
|
221
|
+
→ aggregate → chart or table
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 7. OUTPUT RULES
|
|
226
|
+
|
|
227
|
+
- ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block
|
|
228
|
+
- JSON must be VALID
|
|
229
|
+
- No explanation inside block
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 8. EXAMPLES
|
|
234
|
+
|
|
138
235
|
User: "show me beauty products"
|
|
139
|
-
|
|
236
|
+
|
|
140
237
|
\`\`\`ui
|
|
141
238
|
{
|
|
142
239
|
"view": "carousel",
|
|
143
240
|
"title": "Beauty Products",
|
|
144
|
-
"description": "Browse
|
|
145
|
-
"
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
241
|
+
"description": "Browse available beauty items",
|
|
242
|
+
"metadata": {
|
|
243
|
+
"intent": "explore",
|
|
244
|
+
"confidence": 0.95
|
|
245
|
+
},
|
|
246
|
+
"carousel": {
|
|
247
|
+
"items": [
|
|
248
|
+
{
|
|
249
|
+
"id": "1",
|
|
250
|
+
"name": "Mascara",
|
|
251
|
+
"brand": "Essence",
|
|
252
|
+
"price": 9.99,
|
|
253
|
+
"image": "https://example.com/mascara.jpg",
|
|
254
|
+
"link": "https://example.com/p1",
|
|
255
|
+
"inStock": true
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
"insights": ["Most products are affordable"]
|
|
149
260
|
}
|
|
150
261
|
\`\`\`
|
|
151
262
|
|
|
152
|
-
|
|
153
|
-
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
User: "distribution of products across categories"
|
|
266
|
+
|
|
154
267
|
\`\`\`ui
|
|
155
268
|
{
|
|
156
269
|
"view": "chart",
|
|
157
|
-
"title": "
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
270
|
+
"title": "Product Distribution",
|
|
271
|
+
"description": "Category-wise product distribution",
|
|
272
|
+
"metadata": {
|
|
273
|
+
"intent": "analyze",
|
|
274
|
+
"confidence": 0.98
|
|
275
|
+
},
|
|
276
|
+
"chart": {
|
|
277
|
+
"type": "pie",
|
|
278
|
+
"xKey": "label",
|
|
279
|
+
"yKeys": ["value"],
|
|
280
|
+
"data": [
|
|
281
|
+
{ "label": "Beauty", "value": 15 },
|
|
282
|
+
{ "label": "Electronics", "value": 10 }
|
|
283
|
+
]
|
|
284
|
+
},
|
|
285
|
+
"insights": ["Beauty dominates inventory"]
|
|
165
286
|
}
|
|
166
|
-
|
|
167
|
-
|
|
287
|
+
\`\`\`
|
|
288
|
+
`;
|
|
168
289
|
if (!this.config.llm.systemPrompt?.includes(CHART_MARKER)) {
|
|
169
|
-
|
|
290
|
+
// Clean up ANY existing protocol markers (V1-V7) to avoid prompt bloat
|
|
291
|
+
let cleanPrompt = this.config.llm.systemPrompt || '';
|
|
292
|
+
cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, '');
|
|
293
|
+
cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, '');
|
|
294
|
+
|
|
295
|
+
this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
|
|
170
296
|
}
|
|
171
297
|
|
|
172
298
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
@@ -128,7 +128,10 @@ export class GeminiProvider implements ILLMProvider {
|
|
|
128
128
|
},
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
const text = typeof (response as { text: unknown }).text === 'function'
|
|
132
|
+
? (response as { text: () => string }).text()
|
|
133
|
+
: (response.text ?? '');
|
|
134
|
+
return text;
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
async *chatStream(messages: ChatMessage[], context: string, options?: ChatOptions): AsyncIterable<string> {
|
|
@@ -162,7 +165,9 @@ export class GeminiProvider implements ILLMProvider {
|
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
for await (const chunk of stream) {
|
|
165
|
-
const text = chunk.text
|
|
168
|
+
const text = typeof (chunk as { text: unknown }).text === 'function'
|
|
169
|
+
? (chunk as { text: () => string }).text()
|
|
170
|
+
: (chunk as { text?: string }).text;
|
|
166
171
|
if (text) yield text;
|
|
167
172
|
}
|
|
168
173
|
}
|
|
@@ -151,12 +151,22 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
151
151
|
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
152
152
|
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(', ')}]`);
|
|
153
153
|
|
|
154
|
+
// Helper to clean conversational filler for better keyword matching
|
|
155
|
+
const cleanKeywordQuery = (text: string) => {
|
|
156
|
+
return text
|
|
157
|
+
.toLowerCase()
|
|
158
|
+
.replace(/\b(show|me|the|product|cards|of|category|find|list|browse|what|are|display)\b/g, '')
|
|
159
|
+
.trim()
|
|
160
|
+
.replace(/\s+/g, ' & '); // Join remaining words with AND for tsquery
|
|
161
|
+
};
|
|
162
|
+
|
|
154
163
|
const queryPromises = this.tables.map(async (table) => {
|
|
155
164
|
try {
|
|
156
165
|
let sqlQuery = '';
|
|
157
166
|
let params: unknown[] = [];
|
|
158
167
|
|
|
159
168
|
if (queryText) {
|
|
169
|
+
const cleanedText = cleanKeywordQuery(queryText) || queryText;
|
|
160
170
|
const hasEntityHints = entityHints.length > 0;
|
|
161
171
|
const exactNameScoreExpr = hasEntityHints
|
|
162
172
|
? `+ (
|
|
@@ -173,13 +183,13 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
|
|
|
173
183
|
sqlQuery = `
|
|
174
184
|
SELECT *,
|
|
175
185
|
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
176
|
-
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text),
|
|
177
|
-
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text),
|
|
186
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
187
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
178
188
|
FROM "${table}" t
|
|
179
189
|
ORDER BY hybrid_score DESC
|
|
180
190
|
LIMIT 50
|
|
181
191
|
`;
|
|
182
|
-
params = [vectorLiteral,
|
|
192
|
+
params = [vectorLiteral, cleanedText];
|
|
183
193
|
} else {
|
|
184
194
|
// Fallback to Vector-only Search
|
|
185
195
|
sqlQuery = `
|