@retrivora-ai/rag-engine 1.9.9 → 2.0.1
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 +111 -48
- package/dist/handlers/index.mjs +111 -48
- package/dist/{index-DXd29KMq.d.mts → index-DHsFLJXQ.d.mts} +1 -30
- package/dist/{index-D_bOdJML.d.ts → index-tzwc7UhY.d.ts} +1 -30
- package/dist/index.js +12 -3
- package/dist/index.mjs +12 -3
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +111 -48
- package/dist/server.mjs +111 -48
- package/package.json +1 -1
- package/src/components/ChatWindow.tsx +14 -3
- package/src/config/serverConfig.ts +15 -2
- package/src/core/Pipeline.ts +25 -20
- package/src/handlers/index.ts +92 -54
package/src/handlers/index.ts
CHANGED
|
@@ -145,6 +145,64 @@ function getOrCreatePlugin(configOrPlugin: Partial<RagConfig> | VectorPlugin | u
|
|
|
145
145
|
return _g[cacheKey];
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
// ─── Telemetry Reporter Helper ───────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Asynchronously sends telemetry payload to telemetryUrl when telemetry is enabled
|
|
152
|
+
* or when a licenseKey is set. Non-blocking.
|
|
153
|
+
*/
|
|
154
|
+
function reportTelemetry(
|
|
155
|
+
req: NextRequest,
|
|
156
|
+
plugin: VectorPlugin,
|
|
157
|
+
action: string,
|
|
158
|
+
status: 'success' | 'error',
|
|
159
|
+
details: string,
|
|
160
|
+
trace?: unknown
|
|
161
|
+
) {
|
|
162
|
+
try {
|
|
163
|
+
const config = plugin.getConfig();
|
|
164
|
+
const licenseKey = config.licenseKey || process.env.RAG_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY || process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY;
|
|
165
|
+
const telemetryConfig = config.telemetry;
|
|
166
|
+
|
|
167
|
+
const enabled = telemetryConfig?.enabled ?? Boolean(licenseKey);
|
|
168
|
+
const defaultUrl = process.env.NODE_ENV === 'development' && !process.env.TELEMETRY_URL && !process.env.NEXT_PUBLIC_TELEMETRY_URL
|
|
169
|
+
? 'http://localhost:3001/api/telemetry'
|
|
170
|
+
: 'https://retrivora.com/api/telemetry';
|
|
171
|
+
|
|
172
|
+
const telemetryUrl = telemetryConfig?.url || process.env.TELEMETRY_URL || process.env.NEXT_PUBLIC_TELEMETRY_URL || defaultUrl;
|
|
173
|
+
const host = req.headers.get('host') || 'localhost';
|
|
174
|
+
|
|
175
|
+
let absoluteUrl = telemetryUrl;
|
|
176
|
+
if (!telemetryUrl.startsWith('http')) {
|
|
177
|
+
const proto = req.headers.get('x-forwarded-proto') || 'http';
|
|
178
|
+
absoluteUrl = `${proto}://${host}${telemetryUrl}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const projectId = config.projectId || 'default';
|
|
182
|
+
|
|
183
|
+
fetch(absoluteUrl, {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
headers: {
|
|
186
|
+
'Content-Type': 'application/json',
|
|
187
|
+
'x-forwarded-for': req.headers.get('x-forwarded-for') || '',
|
|
188
|
+
'x-real-ip': req.headers.get('x-real-ip') || '',
|
|
189
|
+
},
|
|
190
|
+
body: JSON.stringify({
|
|
191
|
+
trace,
|
|
192
|
+
licenseKey,
|
|
193
|
+
projectId,
|
|
194
|
+
action,
|
|
195
|
+
status,
|
|
196
|
+
details,
|
|
197
|
+
}),
|
|
198
|
+
}).catch(err => {
|
|
199
|
+
console.warn('[Retrivora Telemetry] Async report warning:', err.message);
|
|
200
|
+
});
|
|
201
|
+
} catch {
|
|
202
|
+
/* non-blocking */
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
148
206
|
// ─── SSE helpers ──────────────────────────────────────────────────────────────
|
|
149
207
|
|
|
150
208
|
/**
|
|
@@ -194,17 +252,6 @@ const SSE_HEADERS: Record<string, string> = {
|
|
|
194
252
|
|
|
195
253
|
/**
|
|
196
254
|
* createChatHandler — factory that returns a Next.js App Router POST handler.
|
|
197
|
-
*
|
|
198
|
-
* Accepts either a full/partial `RagConfig` **or** a pre-built `VectorPlugin`
|
|
199
|
-
* instance (preferred for singleton/multi-tenant setups).
|
|
200
|
-
*
|
|
201
|
-
* @example
|
|
202
|
-
* // Option A — pass config (plugin created once at module load time)
|
|
203
|
-
* export const POST = createChatHandler(getRagConfig());
|
|
204
|
-
*
|
|
205
|
-
* // Option B — pass a pre-built plugin (most flexible)
|
|
206
|
-
* const plugin = new VectorPlugin(getRagConfig());
|
|
207
|
-
* export const POST = createChatHandler(plugin);
|
|
208
255
|
*/
|
|
209
256
|
export function createChatHandler(
|
|
210
257
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -261,6 +308,8 @@ export function createChatHandler(
|
|
|
261
308
|
trace: result.trace,
|
|
262
309
|
}).catch(err => console.warn('[createChatHandler] Failed to save assistant message:', err));
|
|
263
310
|
|
|
311
|
+
reportTelemetry(req, plugin, 'QUERY_GENERATION', 'success', `Tokens: ${result.trace?.tokens?.totalTokens || 0}, Latency: ${result.trace?.latency?.totalMs || 0}ms`, result.trace);
|
|
312
|
+
|
|
264
313
|
return NextResponse.json({
|
|
265
314
|
...result,
|
|
266
315
|
messageId: assistantMsgId,
|
|
@@ -268,6 +317,7 @@ export function createChatHandler(
|
|
|
268
317
|
});
|
|
269
318
|
} catch (err) {
|
|
270
319
|
const message = err instanceof Error ? err.message : 'Internal server error';
|
|
320
|
+
reportTelemetry(req, plugin, 'QUERY_GENERATION', 'error', message);
|
|
271
321
|
return NextResponse.json({ error: message }, { status: 500 });
|
|
272
322
|
}
|
|
273
323
|
};
|
|
@@ -275,25 +325,6 @@ export function createChatHandler(
|
|
|
275
325
|
|
|
276
326
|
/**
|
|
277
327
|
* createStreamHandler — factory for a streaming SSE Next.js App Router POST handler.
|
|
278
|
-
*
|
|
279
|
-
* Streams the assistant response as Server-Sent Events (SSE). Each frame is a
|
|
280
|
-
* JSON object with a `type` discriminant:
|
|
281
|
-
* - `{ type: "text", text: "..." }` — incremental text chunk
|
|
282
|
-
* - `{ type: "metadata", sources: [...] }` — retrieval metadata (last frame)
|
|
283
|
-
* - `{ type: "error", error: "..." }` — stream-level error
|
|
284
|
-
*
|
|
285
|
-
* @example
|
|
286
|
-
* // src/app/api/chat/route.ts
|
|
287
|
-
* export const POST = createStreamHandler(getRagConfig());
|
|
288
|
-
*
|
|
289
|
-
* // Client-side parsing in useRagChat.ts:
|
|
290
|
-
* // const lines = chunk.split('\n');
|
|
291
|
-
* // for (const line of lines) {
|
|
292
|
-
* // if (line.startsWith('data: ')) {
|
|
293
|
-
* // const frame = JSON.parse(line.slice(6));
|
|
294
|
-
* // if (frame.type === 'text') { ... }
|
|
295
|
-
* // }
|
|
296
|
-
* // }
|
|
297
328
|
*/
|
|
298
329
|
export function createStreamHandler(
|
|
299
330
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -311,7 +342,6 @@ export function createStreamHandler(
|
|
|
311
342
|
const rateLimited = checkRateLimit(req);
|
|
312
343
|
if (rateLimited) return rateLimited;
|
|
313
344
|
|
|
314
|
-
// Parse body first so we can return a normal JSON 400 before opening the stream
|
|
315
345
|
let body: {
|
|
316
346
|
message?: string;
|
|
317
347
|
history?: ChatMessage[];
|
|
@@ -380,11 +410,9 @@ export function createStreamHandler(
|
|
|
380
410
|
// Retrieval metadata object — always the final frame
|
|
381
411
|
enqueue(sseMetaFrame(chunk));
|
|
382
412
|
|
|
383
|
-
// ── LLM-driven visualization decision ─────────────────────────
|
|
384
413
|
const responseChunk = chunk as ChatResponse;
|
|
385
414
|
const sources = responseChunk?.sources || [];
|
|
386
415
|
|
|
387
|
-
// Emit observability trace if present
|
|
388
416
|
if (responseChunk?.trace) {
|
|
389
417
|
enqueue(sseObservabilityFrame(responseChunk.trace));
|
|
390
418
|
}
|
|
@@ -392,9 +420,6 @@ export function createStreamHandler(
|
|
|
392
420
|
let uiTransformation = responseChunk?.ui_transformation;
|
|
393
421
|
if (sources.length > 0) {
|
|
394
422
|
try {
|
|
395
|
-
// The pipeline already computed ui_transformation inside askStream().
|
|
396
|
-
// We ONLY fall back to the cheap heuristic transform (no LLM call) when
|
|
397
|
-
// the pipeline didn't produce one — never call analyzeAndDecide() here.
|
|
398
423
|
uiTransformation =
|
|
399
424
|
responseChunk?.ui_transformation ??
|
|
400
425
|
UITransformer.transform(message, sources, plugin.getConfig());
|
|
@@ -423,14 +448,16 @@ export function createStreamHandler(
|
|
|
423
448
|
uiTransformation,
|
|
424
449
|
trace: responseChunk?.trace,
|
|
425
450
|
}).catch(err => console.warn('[createStreamHandler] Failed to save assistant message:', err));
|
|
451
|
+
|
|
452
|
+
reportTelemetry(req, plugin, 'QUERY_GENERATION', 'success', `Tokens: ${responseChunk?.trace?.tokens?.totalTokens || 0}, Latency: ${responseChunk?.trace?.latency?.totalMs || 0}ms`, responseChunk?.trace);
|
|
426
453
|
}
|
|
427
454
|
}
|
|
428
455
|
} catch (streamError) {
|
|
429
|
-
// Do not log or enqueue errors if the client intentionally cancelled/aborted
|
|
430
456
|
if (isActive) {
|
|
431
457
|
const errorMessage =
|
|
432
458
|
streamError instanceof Error ? streamError.message : String(streamError);
|
|
433
459
|
console.error('[createStreamHandler] Stream error:', streamError);
|
|
460
|
+
reportTelemetry(req, plugin, 'QUERY_GENERATION', 'error', errorMessage);
|
|
434
461
|
try {
|
|
435
462
|
enqueue(sseErrorFrame(errorMessage));
|
|
436
463
|
} catch { /* silent */ }
|
|
@@ -480,9 +507,11 @@ export function createIngestHandler(
|
|
|
480
507
|
}
|
|
481
508
|
|
|
482
509
|
const results = await plugin.ingest(documents, namespace);
|
|
510
|
+
reportTelemetry(req, plugin, 'DOCUMENT_INGESTION', 'success', `Ingested ${documents.length} document(s)`);
|
|
483
511
|
return NextResponse.json({ results });
|
|
484
512
|
} catch (err) {
|
|
485
513
|
const message = err instanceof Error ? err.message : 'Internal server error';
|
|
514
|
+
reportTelemetry(req, plugin, 'DOCUMENT_INGESTION', 'error', message);
|
|
486
515
|
return NextResponse.json({ error: message }, { status: 500 });
|
|
487
516
|
}
|
|
488
517
|
};
|
|
@@ -551,38 +580,33 @@ export function createUploadHandler(
|
|
|
551
580
|
const text = await file.text();
|
|
552
581
|
const Papa = await import('papaparse');
|
|
553
582
|
const parsed = Papa.default.parse(text, { header: true, skipEmptyLines: true });
|
|
554
|
-
|
|
583
|
+
|
|
555
584
|
if (parsed.data && parsed.data.length > 0) {
|
|
556
585
|
let i = 0;
|
|
557
586
|
let lastRowData: Record<string, string> | null = null;
|
|
558
587
|
const csvCols = Object.keys(parsed.data[0] as Record<string, string>);
|
|
559
|
-
|
|
588
|
+
|
|
560
589
|
for (const row of parsed.data) {
|
|
561
590
|
i++;
|
|
562
591
|
const rowData = row as Record<string, string>;
|
|
563
|
-
|
|
564
|
-
// Dynamically determine the grouping key (typically the first column like ID or Handle)
|
|
565
592
|
const groupingKey = Object.keys(rowData)[0];
|
|
566
|
-
|
|
567
|
-
// Forward-fill ALL empty columns for variants belonging to the same group
|
|
593
|
+
|
|
568
594
|
if (lastRowData && groupingKey && rowData[groupingKey] && rowData[groupingKey] === lastRowData[groupingKey]) {
|
|
569
595
|
for (const key of Object.keys(rowData)) {
|
|
570
|
-
// Forward-fill if the current row lacks the field but the previous row has it
|
|
571
596
|
if (!rowData[key] && lastRowData[key]) {
|
|
572
597
|
rowData[key] = lastRowData[key];
|
|
573
598
|
}
|
|
574
599
|
}
|
|
575
600
|
}
|
|
576
|
-
// Save a copy of the filled rowData for the next iteration
|
|
577
601
|
lastRowData = { ...rowData };
|
|
578
|
-
|
|
602
|
+
|
|
579
603
|
const contentParts: string[] = [];
|
|
580
604
|
for (const [key, val] of Object.entries(rowData)) {
|
|
581
605
|
if (key && val) {
|
|
582
606
|
contentParts.push(`${key}: ${val}`);
|
|
583
607
|
}
|
|
584
608
|
}
|
|
585
|
-
|
|
609
|
+
|
|
586
610
|
documents.push({
|
|
587
611
|
docId: `${file.name}-row-${i}`,
|
|
588
612
|
content: contentParts.join(', '),
|
|
@@ -615,9 +639,11 @@ export function createUploadHandler(
|
|
|
615
639
|
}
|
|
616
640
|
|
|
617
641
|
const results = await plugin.ingest(documents, namespace);
|
|
642
|
+
reportTelemetry(req, plugin, 'DOCUMENT_INGESTION', 'success', `Uploaded & ingested ${files.length} file(s)`);
|
|
618
643
|
return NextResponse.json({ message: 'Upload successful', results });
|
|
619
644
|
} catch (err) {
|
|
620
645
|
const message = err instanceof Error ? err.message : 'Upload failed';
|
|
646
|
+
reportTelemetry(req, plugin, 'DOCUMENT_INGESTION', 'error', message);
|
|
621
647
|
return NextResponse.json({ error: message }, { status: 500 });
|
|
622
648
|
}
|
|
623
649
|
};
|
|
@@ -625,6 +651,7 @@ export function createUploadHandler(
|
|
|
625
651
|
|
|
626
652
|
/**
|
|
627
653
|
* createSuggestionsHandler — factory for the auto-suggestions endpoint.
|
|
654
|
+
* Supports both POST (JSON body) and GET (URL search parameters).
|
|
628
655
|
*/
|
|
629
656
|
export function createSuggestionsHandler(
|
|
630
657
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -633,25 +660,34 @@ export function createSuggestionsHandler(
|
|
|
633
660
|
const plugin = getOrCreatePlugin(configOrPlugin);
|
|
634
661
|
const onAuthorize = options?.onAuthorize;
|
|
635
662
|
|
|
636
|
-
return async function
|
|
663
|
+
return async function handler(req: NextRequest) {
|
|
637
664
|
const authResult = await checkAuth(req, onAuthorize);
|
|
638
665
|
if (authResult) return authResult as any;
|
|
639
666
|
|
|
640
667
|
try {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
668
|
+
let query = '';
|
|
669
|
+
let namespace: string | undefined = undefined;
|
|
670
|
+
|
|
671
|
+
if (req.method === 'POST') {
|
|
672
|
+
const body = await req.json().catch(() => ({}));
|
|
673
|
+
query = body.query || '';
|
|
674
|
+
namespace = body.namespace;
|
|
675
|
+
} else {
|
|
676
|
+
const url = new URL(req.url);
|
|
677
|
+
query = url.searchParams.get('query') || '';
|
|
678
|
+
namespace = url.searchParams.get('namespace') || undefined;
|
|
679
|
+
}
|
|
646
680
|
|
|
647
681
|
if (typeof query !== 'string') {
|
|
648
682
|
return NextResponse.json({ error: 'query is required' }, { status: 400 });
|
|
649
683
|
}
|
|
650
684
|
|
|
651
685
|
const suggestions = await plugin.getSuggestions(query, namespace);
|
|
686
|
+
reportTelemetry(req, plugin, 'AUTO_SUGGESTIONS', 'success', `Fetched ${suggestions.length} suggestions for: ${query.slice(0, 30)}`);
|
|
652
687
|
return NextResponse.json({ suggestions });
|
|
653
688
|
} catch (err) {
|
|
654
689
|
const message = err instanceof Error ? err.message : 'Internal server error';
|
|
690
|
+
reportTelemetry(req, plugin, 'AUTO_SUGGESTIONS', 'error', message);
|
|
655
691
|
return NextResponse.json({ error: message }, { status: 500 });
|
|
656
692
|
}
|
|
657
693
|
};
|
|
@@ -828,6 +864,8 @@ export function createRagHandler(
|
|
|
828
864
|
switch (segment) {
|
|
829
865
|
case 'health':
|
|
830
866
|
return healthHandler(req);
|
|
867
|
+
case 'suggestions':
|
|
868
|
+
return suggestionsHandler(req);
|
|
831
869
|
case 'history':
|
|
832
870
|
return historyHandler(req);
|
|
833
871
|
case 'feedback':
|
|
@@ -843,7 +881,7 @@ export function createRagHandler(
|
|
|
843
881
|
const resolvedParams = typeof context?.params?.then === 'function'
|
|
844
882
|
? await context.params
|
|
845
883
|
: context?.params;
|
|
846
|
-
|
|
884
|
+
|
|
847
885
|
const segments: string[] = resolvedParams?.retrivora || [];
|
|
848
886
|
return segments.join('/') || 'chat';
|
|
849
887
|
}
|