@retrivora-ai/rag-engine 1.8.1 → 1.8.3
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/{ILLMProvider-BfRgI1Xh.d.mts → ILLMProvider-BOJFz3Na.d.mts} +47 -1
- package/dist/{ILLMProvider-BfRgI1Xh.d.ts → ILLMProvider-BOJFz3Na.d.ts} +47 -1
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +509 -162
- package/dist/handlers/index.mjs +5849 -15
- package/dist/{index-1Z4GuYBi.d.ts → index-BwpcaziY.d.ts} +4 -2
- package/dist/{index-BV0z5mb6.d.mts → index-D3V9Et2M.d.mts} +4 -2
- package/dist/index.d.mts +30 -16
- package/dist/index.d.ts +30 -16
- package/dist/index.js +1999 -1210
- package/dist/index.mjs +2041 -1205
- package/dist/server.d.mts +21 -25
- package/dist/server.d.ts +21 -25
- package/dist/server.js +536 -163
- package/dist/server.mjs +5923 -67
- package/package.json +9 -7
- package/src/app/api/upload/route.ts +4 -0
- package/src/app/constants.tsx +2 -2
- package/src/app/globals.css +35 -11
- package/src/app/page.tsx +12 -321
- package/src/components/AmbientBackground.tsx +29 -0
- package/src/components/ArchitectureCard.tsx +17 -0
- package/src/components/ArchitectureCardsSection.tsx +15 -0
- package/src/components/ChatWidget.tsx +0 -1
- package/src/components/ChatWindow.tsx +32 -0
- package/src/components/CodeViewer.tsx +51 -0
- package/src/components/ConfigProvider.tsx +1 -0
- package/src/components/DocViewer.tsx +37 -0
- package/src/components/DocumentUpload.tsx +44 -1
- package/src/components/Documentation.tsx +58 -0
- package/src/components/DynamicChart.tsx +27 -2
- package/src/components/Hero.tsx +59 -0
- package/src/components/HourglassLoader.tsx +87 -0
- package/src/components/Lifecycle.tsx +37 -0
- package/src/components/MarkdownComponents.tsx +143 -0
- package/src/components/MessageBubble.tsx +91 -1012
- package/src/components/Navbar.tsx +55 -0
- package/src/components/ObservabilityPanel.tsx +374 -0
- package/src/components/ProductCard.tsx +3 -1
- package/src/components/UIDispatcher.tsx +341 -0
- package/src/components/VisualizationRenderer.tsx +48 -26
- package/src/config/ConfigBuilder.ts +38 -1
- package/src/core/LangChainAgent.ts +1 -4
- package/src/core/Pipeline.ts +209 -86
- package/src/core/QueryProcessor.ts +65 -0
- package/src/handlers/index.ts +72 -12
- package/src/hooks/useRagChat.ts +19 -9
- package/src/index.ts +9 -1
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +150 -64
- package/src/rag/Reranker.ts +99 -6
- package/src/types/chat.ts +2 -0
- package/src/types/index.ts +52 -0
- package/src/types/props.ts +9 -1
- package/src/utils/ProductExtractor.ts +347 -0
- package/src/utils/UITransformer.ts +4 -53
- package/src/utils/synonyms.ts +78 -0
- package/dist/ChromaDBProvider-MIDOR4FW.mjs +0 -8
- package/dist/MilvusProvider-U7SKC27V.mjs +0 -8
- package/dist/MongoDBProvider-YNKC7EJ6.mjs +0 -8
- package/dist/MultiTablePostgresProvider-YY7LPNJK.mjs +0 -8
- package/dist/PineconeProvider-QZNRKTN2.mjs +0 -8
- package/dist/QdrantProvider-RLJTNGPY.mjs +0 -8
- package/dist/RedisProvider-SR65SCKV.mjs +0 -8
- package/dist/SimpleGraphProvider-SLOXO4M7.mjs +0 -62
- package/dist/UniversalVectorProvider-IN67OS56.mjs +0 -9
- package/dist/WeaviateProvider-5FWDFITI.mjs +0 -8
- package/dist/chunk-5AJ4XHLW.mjs +0 -201
- package/dist/chunk-5YGUXK7Z.mjs +0 -80
- package/dist/chunk-BFYLQYQU.mjs +0 -3985
- package/dist/chunk-CFVEZTBJ.mjs +0 -102
- package/dist/chunk-IMP6FUCY.mjs +0 -30
- package/dist/chunk-LR3VMDVK.mjs +0 -157
- package/dist/chunk-M6JSPGAR.mjs +0 -117
- package/dist/chunk-PSFPZXHX.mjs +0 -245
- package/dist/chunk-R3RGUMHE.mjs +0 -218
- package/dist/chunk-U55XRW3U.mjs +0 -96
- package/dist/chunk-VUQJVIJT.mjs +0 -148
- package/dist/chunk-X4TOT24V.mjs +0 -89
- package/dist/chunk-YLTMFW4M.mjs +0 -49
package/dist/handlers/index.js
CHANGED
|
@@ -347,7 +347,7 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
347
347
|
init_BaseVectorProvider();
|
|
348
348
|
MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
349
349
|
constructor(config) {
|
|
350
|
-
var _a, _b, _c
|
|
350
|
+
var _a, _b, _c;
|
|
351
351
|
super(config);
|
|
352
352
|
const opts = config.options || {};
|
|
353
353
|
if (!opts.connectionString) {
|
|
@@ -355,37 +355,37 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
355
355
|
}
|
|
356
356
|
this.connectionString = opts.connectionString;
|
|
357
357
|
this.dimensions = (_a = opts.dimensions) != null ? _a : 768;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (this.tables.length === 0) {
|
|
361
|
-
console.warn(
|
|
362
|
-
"[MultiTablePostgresProvider] No tables configured. Set VECTOR_DB_TABLES as a comma-separated list of table names or pass options.tables."
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
const rawSearchFields = (_e = (_d = opts.searchFields) != null ? _d : process.env.VECTOR_DB_SEARCH_FIELDS) != null ? _e : ["name", "product_name", "productname", "title"];
|
|
358
|
+
this.tables = [];
|
|
359
|
+
const rawSearchFields = (_c = (_b = opts.searchFields) != null ? _b : process.env.VECTOR_DB_SEARCH_FIELDS) != null ? _c : ["name", "product_name", "productname", "title"];
|
|
366
360
|
this.searchFields = typeof rawSearchFields === "string" ? rawSearchFields.split(",").map((f) => f.trim()).filter(Boolean) : rawSearchFields;
|
|
361
|
+
this.uploadTable = opts.uploadTable || "document_chunks";
|
|
367
362
|
}
|
|
368
363
|
async initialize() {
|
|
369
364
|
this.pool = new import_pg.Pool({ connectionString: this.connectionString });
|
|
370
365
|
const client = await this.pool.connect();
|
|
371
366
|
try {
|
|
367
|
+
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
368
|
+
await client.query(`
|
|
369
|
+
CREATE TABLE IF NOT EXISTS ${this.uploadTable} (
|
|
370
|
+
id TEXT PRIMARY KEY,
|
|
371
|
+
namespace TEXT NOT NULL DEFAULT '',
|
|
372
|
+
content TEXT NOT NULL,
|
|
373
|
+
metadata JSONB,
|
|
374
|
+
embedding VECTOR(${this.dimensions})
|
|
375
|
+
)
|
|
376
|
+
`);
|
|
377
|
+
await client.query(`
|
|
378
|
+
CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
|
|
379
|
+
ON ${this.uploadTable}
|
|
380
|
+
USING hnsw (embedding vector_cosine_ops)
|
|
381
|
+
`);
|
|
372
382
|
const res = await client.query(`
|
|
373
383
|
SELECT table_name
|
|
374
384
|
FROM information_schema.columns
|
|
375
385
|
WHERE column_name = 'embedding'
|
|
376
386
|
AND table_schema = 'public'
|
|
377
387
|
`);
|
|
378
|
-
|
|
379
|
-
if (this.tables.length === 0) {
|
|
380
|
-
this.tables = discoveredTables;
|
|
381
|
-
} else {
|
|
382
|
-
const staticTables = [...this.tables];
|
|
383
|
-
this.tables = staticTables.filter((t) => discoveredTables.includes(t));
|
|
384
|
-
if (this.tables.length < staticTables.length) {
|
|
385
|
-
const missing = staticTables.filter((t) => !discoveredTables.includes(t));
|
|
386
|
-
console.warn(`[MultiTablePostgresProvider] Some configured tables were skipped (missing 'embedding' column): ${missing.join(", ")}`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
388
|
+
this.tables = res.rows.map((r) => r.table_name);
|
|
389
389
|
if (this.tables.length === 0) {
|
|
390
390
|
console.warn('[MultiTablePostgresProvider] No tables with "embedding" columns found in the database.');
|
|
391
391
|
} else {
|
|
@@ -398,27 +398,106 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
398
398
|
}
|
|
399
399
|
}
|
|
400
400
|
/**
|
|
401
|
-
* Upsert
|
|
402
|
-
* searching across pre-existing tables with varying schemas.
|
|
401
|
+
* Upsert a document by dynamically provisioning a table and columns.
|
|
403
402
|
*/
|
|
404
|
-
async upsert(
|
|
405
|
-
|
|
406
|
-
"[MultiTablePostgresProvider] upsert() is not supported in multi-table mode. Please use the standard PostgreSQLProvider for single-table managed indices."
|
|
407
|
-
);
|
|
403
|
+
async upsert(doc, namespace = "") {
|
|
404
|
+
await this.batchUpsert([doc], namespace);
|
|
408
405
|
}
|
|
409
406
|
/**
|
|
410
|
-
* Batch upsert
|
|
407
|
+
* Batch upsert documents by dynamically provisioning tables and columns based on CSV headers.
|
|
411
408
|
*/
|
|
412
|
-
async batchUpsert(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
409
|
+
async batchUpsert(docs, namespace = "") {
|
|
410
|
+
var _a;
|
|
411
|
+
if (docs.length === 0) return;
|
|
412
|
+
const docsByFile = {};
|
|
413
|
+
for (const doc of docs) {
|
|
414
|
+
const fileName = ((_a = doc.metadata) == null ? void 0 : _a.fileName) || this.uploadTable;
|
|
415
|
+
if (!docsByFile[fileName]) docsByFile[fileName] = [];
|
|
416
|
+
docsByFile[fileName].push(doc);
|
|
417
|
+
}
|
|
418
|
+
const client = await this.pool.connect();
|
|
419
|
+
try {
|
|
420
|
+
await client.query("BEGIN");
|
|
421
|
+
for (const [fileName, fileDocs] of Object.entries(docsByFile)) {
|
|
422
|
+
let tableName = fileName;
|
|
423
|
+
if (tableName.toLowerCase().endsWith(".csv")) {
|
|
424
|
+
tableName = tableName.slice(0, -4);
|
|
425
|
+
}
|
|
426
|
+
tableName = tableName.replace(/[^a-z0-9_]/gi, "_").toLowerCase() || this.uploadTable;
|
|
427
|
+
const firstMeta = fileDocs[0].metadata || {};
|
|
428
|
+
const systemKeys = ["fileName", "fileSize", "fileType", "uploadedAt", "dimension", "chunkIndex", "id", "namespace", "content", "metadata", "embedding"];
|
|
429
|
+
const csvHeaders = Object.keys(firstMeta).filter((k) => !systemKeys.includes(k) && !systemKeys.includes(k.toLowerCase()));
|
|
430
|
+
const columnDefs = csvHeaders.map((h) => `"${h}" TEXT`).join(",\n ");
|
|
431
|
+
const createTableSql = `
|
|
432
|
+
CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
433
|
+
id TEXT PRIMARY KEY,
|
|
434
|
+
${columnDefs ? columnDefs + "," : ""}
|
|
435
|
+
namespace TEXT NOT NULL DEFAULT '',
|
|
436
|
+
content TEXT NOT NULL,
|
|
437
|
+
metadata JSONB,
|
|
438
|
+
embedding VECTOR(${this.dimensions})
|
|
439
|
+
)
|
|
440
|
+
`;
|
|
441
|
+
await client.query(createTableSql);
|
|
442
|
+
await client.query(`
|
|
443
|
+
CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
|
|
444
|
+
ON "${tableName}"
|
|
445
|
+
USING hnsw (embedding vector_cosine_ops)
|
|
446
|
+
`);
|
|
447
|
+
if (!this.tables.includes(tableName)) {
|
|
448
|
+
this.tables.push(tableName);
|
|
449
|
+
}
|
|
450
|
+
const BATCH_SIZE = 50;
|
|
451
|
+
for (let i = 0; i < fileDocs.length; i += BATCH_SIZE) {
|
|
452
|
+
const batch = fileDocs.slice(i, i + BATCH_SIZE);
|
|
453
|
+
const values = [];
|
|
454
|
+
const headerColumns = csvHeaders.map((h) => `"${h}"`).join(", ");
|
|
455
|
+
const allColumns = `id, ${headerColumns ? headerColumns + ", " : ""}namespace, content, metadata, embedding`;
|
|
456
|
+
const valuePlaceholders = batch.map((doc, idx) => {
|
|
457
|
+
var _a2, _b;
|
|
458
|
+
const offset = idx * (5 + csvHeaders.length);
|
|
459
|
+
values.push(doc.id);
|
|
460
|
+
for (const h of csvHeaders) {
|
|
461
|
+
values.push(String(((_a2 = doc.metadata) == null ? void 0 : _a2[h]) || ""));
|
|
462
|
+
}
|
|
463
|
+
values.push(namespace, doc.content, JSON.stringify((_b = doc.metadata) != null ? _b : {}), `[${doc.vector.join(",")}]`);
|
|
464
|
+
const docPlaceholders = [];
|
|
465
|
+
for (let j = 1; j <= 5 + csvHeaders.length; j++) {
|
|
466
|
+
if (j === 5 + csvHeaders.length) {
|
|
467
|
+
docPlaceholders.push(`$${offset + j}::vector`);
|
|
468
|
+
} else {
|
|
469
|
+
docPlaceholders.push(`$${offset + j}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return `(${docPlaceholders.join(", ")})`;
|
|
473
|
+
}).join(", ");
|
|
474
|
+
const conflictUpdates = csvHeaders.map((h) => `"${h}" = EXCLUDED."${h}"`).join(",\n ");
|
|
475
|
+
const query = `
|
|
476
|
+
INSERT INTO "${tableName}" (${allColumns})
|
|
477
|
+
VALUES ${valuePlaceholders}
|
|
478
|
+
ON CONFLICT (id) DO UPDATE
|
|
479
|
+
SET ${conflictUpdates ? conflictUpdates + "," : ""}
|
|
480
|
+
namespace = EXCLUDED.namespace,
|
|
481
|
+
content = EXCLUDED.content,
|
|
482
|
+
metadata = EXCLUDED.metadata,
|
|
483
|
+
embedding = EXCLUDED.embedding
|
|
484
|
+
`;
|
|
485
|
+
await client.query(query, values);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
await client.query("COMMIT");
|
|
489
|
+
} catch (error) {
|
|
490
|
+
await client.query("ROLLBACK");
|
|
491
|
+
throw error;
|
|
492
|
+
} finally {
|
|
493
|
+
client.release();
|
|
494
|
+
}
|
|
416
495
|
}
|
|
417
496
|
/**
|
|
418
497
|
* Query all configured tables and merge results, sorted by cosine similarity score.
|
|
419
498
|
*/
|
|
420
499
|
async query(vector, topK, _namespace, _filter) {
|
|
421
|
-
var _a
|
|
500
|
+
var _a;
|
|
422
501
|
if (!this.pool) {
|
|
423
502
|
throw new Error("[MultiTablePostgresProvider] Provider not initialized. Call initialize() first.");
|
|
424
503
|
}
|
|
@@ -510,22 +589,16 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
510
589
|
for (const tableResults of resultsArray) {
|
|
511
590
|
allResults.push(...tableResults);
|
|
512
591
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
const finalSorted = balancedResults.sort((a, b) => b.score - a.score);
|
|
525
|
-
if (finalSorted.length > 0) {
|
|
526
|
-
console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
|
|
527
|
-
console.log(`[MultiTablePostgresProvider] Final top match from "${(_c = (_b = finalSorted[0].metadata) == null ? void 0 : _b.source_table) != null ? _c : "unknown"}" with score ${finalSorted[0].score.toFixed(4)}`);
|
|
528
|
-
}
|
|
592
|
+
allResults.sort((a, b) => b.score - a.score);
|
|
593
|
+
if (allResults.length === 0) return [];
|
|
594
|
+
const bestMatchTable = (_a = allResults[0].metadata) == null ? void 0 : _a.source_table;
|
|
595
|
+
const finalSorted = allResults.filter((res) => {
|
|
596
|
+
var _a2;
|
|
597
|
+
return ((_a2 = res.metadata) == null ? void 0 : _a2.source_table) === bestMatchTable;
|
|
598
|
+
});
|
|
599
|
+
console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
|
|
600
|
+
console.log(`[MultiTablePostgresProvider] Final top match from "${bestMatchTable}" with score ${finalSorted[0].score.toFixed(4)}`);
|
|
601
|
+
console.log(`[MultiTablePostgresProvider] Restricting recommendations strictly to table: "${bestMatchTable}"`);
|
|
529
602
|
return finalSorted.slice(0, Math.max(topK, 15));
|
|
530
603
|
}
|
|
531
604
|
async delete(_id, _namespace) {
|
|
@@ -1640,6 +1713,7 @@ __export(handlers_exports, {
|
|
|
1640
1713
|
sseErrorFrame: () => sseErrorFrame,
|
|
1641
1714
|
sseFrame: () => sseFrame,
|
|
1642
1715
|
sseMetaFrame: () => sseMetaFrame,
|
|
1716
|
+
sseObservabilityFrame: () => sseObservabilityFrame,
|
|
1643
1717
|
sseTextFrame: () => sseTextFrame,
|
|
1644
1718
|
sseUIFrame: () => sseUIFrame
|
|
1645
1719
|
});
|
|
@@ -3306,11 +3380,70 @@ Text to extract from:
|
|
|
3306
3380
|
// src/rag/Reranker.ts
|
|
3307
3381
|
var Reranker = class {
|
|
3308
3382
|
/**
|
|
3309
|
-
* Re-ranks matches based on a secondary relevance score.
|
|
3310
|
-
* In a production environment, this would call a Cross-Encoder model.
|
|
3311
|
-
* Here we implement a placeholder that filters by score and limits count.
|
|
3383
|
+
* Re-ranks matches based on a secondary relevance score using an LLM.
|
|
3312
3384
|
*/
|
|
3313
|
-
async rerank(matches, query, limit = 5) {
|
|
3385
|
+
async rerank(matches, query, limit = 5, llm) {
|
|
3386
|
+
if (!llm || matches.length <= 1) {
|
|
3387
|
+
const keywords = query.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/).filter((w) => w.length > 2);
|
|
3388
|
+
if (keywords.length === 0) {
|
|
3389
|
+
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
3390
|
+
}
|
|
3391
|
+
const scoredMatches = matches.map((match) => {
|
|
3392
|
+
const contentLower = match.content.toLowerCase();
|
|
3393
|
+
let keywordScore = 0;
|
|
3394
|
+
keywords.forEach((keyword) => {
|
|
3395
|
+
if (contentLower.includes(keyword)) {
|
|
3396
|
+
keywordScore += 1;
|
|
3397
|
+
}
|
|
3398
|
+
});
|
|
3399
|
+
const normalizedKeywordScore = keywordScore / keywords.length;
|
|
3400
|
+
const combinedScore = match.score + normalizedKeywordScore * 0.5;
|
|
3401
|
+
return __spreadProps(__spreadValues({}, match), {
|
|
3402
|
+
score: combinedScore
|
|
3403
|
+
});
|
|
3404
|
+
});
|
|
3405
|
+
return scoredMatches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
3406
|
+
}
|
|
3407
|
+
try {
|
|
3408
|
+
const topN = matches.slice(0, 10);
|
|
3409
|
+
const prompt = `You are a relevance ranking expert.
|
|
3410
|
+
Given the following user query and a list of retrieved document chunks, rank the chunks by relevance to the query.
|
|
3411
|
+
Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
|
|
3412
|
+
Use the indices provided in brackets like [0], [1], etc.
|
|
3413
|
+
Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
|
|
3414
|
+
|
|
3415
|
+
Query: "${query}"
|
|
3416
|
+
|
|
3417
|
+
Documents:
|
|
3418
|
+
${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}`;
|
|
3419
|
+
const response = await llm.chat(
|
|
3420
|
+
[{ role: "user", content: prompt }],
|
|
3421
|
+
"",
|
|
3422
|
+
{ temperature: 0, maxTokens: 50 }
|
|
3423
|
+
);
|
|
3424
|
+
const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
|
|
3425
|
+
const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
|
|
3426
|
+
if (rankedIndices.length > 0) {
|
|
3427
|
+
const rankedMatches = [];
|
|
3428
|
+
const usedIndices = /* @__PURE__ */ new Set();
|
|
3429
|
+
for (const index of rankedIndices) {
|
|
3430
|
+
if (index >= 0 && index < topN.length && !usedIndices.has(index)) {
|
|
3431
|
+
rankedMatches.push(topN[index]);
|
|
3432
|
+
usedIndices.add(index);
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
for (let i = 0; i < topN.length; i++) {
|
|
3436
|
+
if (!usedIndices.has(i)) {
|
|
3437
|
+
rankedMatches.push(topN[i]);
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
const rest = matches.slice(10);
|
|
3441
|
+
rankedMatches.push(...rest);
|
|
3442
|
+
return rankedMatches.slice(0, limit);
|
|
3443
|
+
}
|
|
3444
|
+
} catch (error) {
|
|
3445
|
+
console.warn("[Reranker] LLM re-ranking failed, falling back to score-based sorting:", error);
|
|
3446
|
+
}
|
|
3314
3447
|
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
3315
3448
|
}
|
|
3316
3449
|
};
|
|
@@ -3370,7 +3503,7 @@ var LangChainAgent = class {
|
|
|
3370
3503
|
*/
|
|
3371
3504
|
async initialize(chatModel) {
|
|
3372
3505
|
try {
|
|
3373
|
-
const [{ DynamicTool },
|
|
3506
|
+
const [{ DynamicTool }, , { createAgent }] = await Promise.all([
|
|
3374
3507
|
import("@langchain/core/tools"),
|
|
3375
3508
|
import("@langchain/core/messages"),
|
|
3376
3509
|
import("langchain")
|
|
@@ -3420,7 +3553,6 @@ When presenting structured data, statistics, or comparisons, decide if it is bes
|
|
|
3420
3553
|
tools: [searchTool],
|
|
3421
3554
|
systemPrompt: finalSystemPrompt
|
|
3422
3555
|
});
|
|
3423
|
-
void HumanMessage;
|
|
3424
3556
|
} catch (error) {
|
|
3425
3557
|
const isMissing = error instanceof Error && error.message.includes("Cannot find module");
|
|
3426
3558
|
const hint = isMissing ? " Make sure 'langchain' and '@langchain/core' are installed:\n npm install langchain @langchain/core" : "";
|
|
@@ -3898,8 +4030,129 @@ var QueryProcessor = class {
|
|
|
3898
4030
|
if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
|
|
3899
4031
|
return filter;
|
|
3900
4032
|
}
|
|
4033
|
+
/**
|
|
4034
|
+
* Determines the retrieval strategy based on the query content.
|
|
4035
|
+
*/
|
|
4036
|
+
static async determineRetrievalStrategy(query, llm, customGraphKeywords, customVectorKeywords) {
|
|
4037
|
+
if (llm) {
|
|
4038
|
+
try {
|
|
4039
|
+
const prompt = `You are a routing expert in a RAG system.
|
|
4040
|
+
Given the user query, determine which retrieval mechanism is needed:
|
|
4041
|
+
- 'vector': For semantic search, finding specific documents or content based on similarity.
|
|
4042
|
+
- 'graph': For finding relationships between entities, multi-hop questions, or network structures.
|
|
4043
|
+
- 'both': If the query requires both specific content and relationship/structural analysis.
|
|
4044
|
+
|
|
4045
|
+
Query: "${query}"
|
|
4046
|
+
|
|
4047
|
+
Return ONLY 'vector', 'graph', or 'both'. No explanation.`;
|
|
4048
|
+
const response = await llm.chat(
|
|
4049
|
+
[{ role: "user", content: prompt }],
|
|
4050
|
+
"",
|
|
4051
|
+
{ temperature: 0, maxTokens: 10 }
|
|
4052
|
+
);
|
|
4053
|
+
const cleanResponse = response.trim().toLowerCase();
|
|
4054
|
+
if (cleanResponse.includes("vector")) return "vector";
|
|
4055
|
+
if (cleanResponse.includes("graph")) return "graph";
|
|
4056
|
+
if (cleanResponse.includes("both")) return "both";
|
|
4057
|
+
} catch (error) {
|
|
4058
|
+
console.warn("[QueryProcessor] LLM strategy classification failed, falling back to keywords:", error);
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
const normalized = query.toLowerCase();
|
|
4062
|
+
const graphKeywords = customGraphKeywords || [
|
|
4063
|
+
"relationship",
|
|
4064
|
+
"connect",
|
|
4065
|
+
"link",
|
|
4066
|
+
"network",
|
|
4067
|
+
"friend",
|
|
4068
|
+
"colleague",
|
|
4069
|
+
"manager",
|
|
4070
|
+
"hierarchy",
|
|
4071
|
+
"multi-hop",
|
|
4072
|
+
"shortest path",
|
|
4073
|
+
"between",
|
|
4074
|
+
"related to",
|
|
4075
|
+
"associated with",
|
|
4076
|
+
"belongs to",
|
|
4077
|
+
"part of"
|
|
4078
|
+
];
|
|
4079
|
+
const hasGraphKeyword = graphKeywords.some((kw) => normalized.includes(kw));
|
|
4080
|
+
const vectorKeywords = customVectorKeywords || [
|
|
4081
|
+
"find",
|
|
4082
|
+
"search",
|
|
4083
|
+
"tell me about",
|
|
4084
|
+
"what is",
|
|
4085
|
+
"how to",
|
|
4086
|
+
"documents about"
|
|
4087
|
+
];
|
|
4088
|
+
const hasVectorKeyword = vectorKeywords.some((kw) => normalized.includes(kw));
|
|
4089
|
+
if (hasGraphKeyword && !hasVectorKeyword) {
|
|
4090
|
+
return "graph";
|
|
4091
|
+
} else if (hasGraphKeyword && hasVectorKeyword) {
|
|
4092
|
+
return "both";
|
|
4093
|
+
}
|
|
4094
|
+
return "vector";
|
|
4095
|
+
}
|
|
3901
4096
|
};
|
|
3902
4097
|
|
|
4098
|
+
// src/utils/synonyms.ts
|
|
4099
|
+
var FIELD_SYNONYMS = {
|
|
4100
|
+
name: ["product", "item", "title", "label", "heading", "subject", "product name", "item name"],
|
|
4101
|
+
price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
|
|
4102
|
+
brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
|
|
4103
|
+
image: [
|
|
4104
|
+
"imageUrl",
|
|
4105
|
+
"thumbnail",
|
|
4106
|
+
"img",
|
|
4107
|
+
"url",
|
|
4108
|
+
"photo",
|
|
4109
|
+
"picture",
|
|
4110
|
+
"media",
|
|
4111
|
+
"image_url",
|
|
4112
|
+
"main_image",
|
|
4113
|
+
"product_image",
|
|
4114
|
+
"thumb"
|
|
4115
|
+
],
|
|
4116
|
+
stock: [
|
|
4117
|
+
"inventory",
|
|
4118
|
+
"quantity",
|
|
4119
|
+
"count",
|
|
4120
|
+
"availability",
|
|
4121
|
+
"stock_level",
|
|
4122
|
+
"inStock",
|
|
4123
|
+
"is_available",
|
|
4124
|
+
"in stock",
|
|
4125
|
+
"status"
|
|
4126
|
+
],
|
|
4127
|
+
description: ["summary", "content", "body", "text", "info", "details"],
|
|
4128
|
+
link: ["url", "href", "product_url", "page_url", "link"]
|
|
4129
|
+
};
|
|
4130
|
+
function resolveMetadataValue(meta, uiKey) {
|
|
4131
|
+
var _a;
|
|
4132
|
+
const synonyms = (_a = FIELD_SYNONYMS[uiKey]) != null ? _a : [];
|
|
4133
|
+
const keys = Object.keys(meta);
|
|
4134
|
+
const systemKeys = ["namespace", "filename", "filesize", "filetype", "docid", "uploadedat", "dimension", "chunkindex"];
|
|
4135
|
+
let match = keys.find((k) => k.toLowerCase() === uiKey.toLowerCase());
|
|
4136
|
+
if (match !== void 0) return meta[match];
|
|
4137
|
+
match = keys.find((k) => synonyms.map((s) => s.toLowerCase()).includes(k.toLowerCase()));
|
|
4138
|
+
if (match !== void 0) return meta[match];
|
|
4139
|
+
const isBlacklisted = (kl) => {
|
|
4140
|
+
return systemKeys.includes(kl) || kl.startsWith("option1") || kl.startsWith("option2") || kl.startsWith("option3");
|
|
4141
|
+
};
|
|
4142
|
+
match = keys.find((k) => {
|
|
4143
|
+
const kl = k.toLowerCase();
|
|
4144
|
+
if (isBlacklisted(kl)) return false;
|
|
4145
|
+
return kl.includes(uiKey.toLowerCase());
|
|
4146
|
+
});
|
|
4147
|
+
if (match !== void 0) return meta[match];
|
|
4148
|
+
match = keys.find((k) => {
|
|
4149
|
+
const kl = k.toLowerCase();
|
|
4150
|
+
if (isBlacklisted(kl)) return false;
|
|
4151
|
+
return synonyms.some((sk) => kl.includes(sk.toLowerCase()) || sk.toLowerCase().includes(kl));
|
|
4152
|
+
});
|
|
4153
|
+
return match !== void 0 ? meta[match] : void 0;
|
|
4154
|
+
}
|
|
4155
|
+
|
|
3903
4156
|
// src/utils/UITransformer.ts
|
|
3904
4157
|
var UITransformer = class {
|
|
3905
4158
|
/**
|
|
@@ -3967,28 +4220,6 @@ var UITransformer = class {
|
|
|
3967
4220
|
data: pieData
|
|
3968
4221
|
};
|
|
3969
4222
|
}
|
|
3970
|
-
/**
|
|
3971
|
-
* Transform data to bar chart format
|
|
3972
|
-
*/
|
|
3973
|
-
static transformToBarChart(data) {
|
|
3974
|
-
const categories = this.detectCategories(data);
|
|
3975
|
-
const categoryData = this.aggregateByCategory(data, categories);
|
|
3976
|
-
const barData = Object.entries(categoryData).map(([category, value]) => {
|
|
3977
|
-
const { inStockCount, outOfStockCount } = this.calculateStockCounts(category, data);
|
|
3978
|
-
return {
|
|
3979
|
-
category,
|
|
3980
|
-
value,
|
|
3981
|
-
inStockCount,
|
|
3982
|
-
outOfStockCount
|
|
3983
|
-
};
|
|
3984
|
-
});
|
|
3985
|
-
return {
|
|
3986
|
-
type: "bar_chart",
|
|
3987
|
-
title: "Comparison by Category",
|
|
3988
|
-
description: `Comparing ${categories.length} categories`,
|
|
3989
|
-
data: barData
|
|
3990
|
-
};
|
|
3991
|
-
}
|
|
3992
4223
|
/**
|
|
3993
4224
|
* Transform data to line chart format
|
|
3994
4225
|
*/
|
|
@@ -4267,15 +4498,7 @@ var UITransformer = class {
|
|
|
4267
4498
|
const trainedKey = trainedSchema[uiKey];
|
|
4268
4499
|
if (trainedKey && meta[trainedKey] !== void 0) return meta[trainedKey];
|
|
4269
4500
|
}
|
|
4270
|
-
|
|
4271
|
-
const searchKeys = [uiKey, ...synonyms].map((k) => k.toLowerCase());
|
|
4272
|
-
const foundDirectKey = Object.keys(meta).find((k) => searchKeys.includes(k.toLowerCase()));
|
|
4273
|
-
if (foundDirectKey) return meta[foundDirectKey];
|
|
4274
|
-
const foundPartialKey = Object.keys(meta).find((k) => {
|
|
4275
|
-
const keyLow = k.toLowerCase();
|
|
4276
|
-
return searchKeys.some((sk) => keyLow.includes(sk) || sk.includes(keyLow));
|
|
4277
|
-
});
|
|
4278
|
-
return foundPartialKey ? meta[foundPartialKey] : void 0;
|
|
4501
|
+
return resolveMetadataValue(meta, uiKey);
|
|
4279
4502
|
}
|
|
4280
4503
|
static extractProductInfo(item, config, trainedSchema) {
|
|
4281
4504
|
const meta = item.metadata || {};
|
|
@@ -4345,7 +4568,7 @@ var UITransformer = class {
|
|
|
4345
4568
|
data.forEach((item) => {
|
|
4346
4569
|
const meta = item.metadata || {};
|
|
4347
4570
|
const itemCategory = meta.category || meta.type || "Other";
|
|
4348
|
-
if (
|
|
4571
|
+
if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
|
|
4349
4572
|
result[itemCategory]++;
|
|
4350
4573
|
} else {
|
|
4351
4574
|
result["Other"] = (result["Other"] || 0) + 1;
|
|
@@ -4566,18 +4789,6 @@ IMPORTANT:
|
|
|
4566
4789
|
return JSON.stringify(partial, null, 2) + "\n// ... truncated";
|
|
4567
4790
|
}
|
|
4568
4791
|
};
|
|
4569
|
-
/**
|
|
4570
|
-
* Central dictionary of common synonyms for UI properties.
|
|
4571
|
-
* This allows the system to be schema-agnostic by guessing field names.
|
|
4572
|
-
*/
|
|
4573
|
-
UITransformer.SYNONYMS = {
|
|
4574
|
-
name: ["product", "item", "title", "label", "heading", "subject"],
|
|
4575
|
-
price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
|
|
4576
|
-
brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
|
|
4577
|
-
image: ["imageUrl", "thumbnail", "img", "url", "photo", "picture", "media", "image_url", "main_image", "product_image", "thumb"],
|
|
4578
|
-
stock: ["inventory", "quantity", "count", "availability", "stock_level", "inStock", "is_available"],
|
|
4579
|
-
description: ["summary", "content", "body", "text", "info", "details"]
|
|
4580
|
-
};
|
|
4581
4792
|
|
|
4582
4793
|
// src/utils/SchemaMapper.ts
|
|
4583
4794
|
var SchemaMapper = class {
|
|
@@ -4698,6 +4909,57 @@ var LRUEmbeddingCache = class {
|
|
|
4698
4909
|
return this.cache.size;
|
|
4699
4910
|
}
|
|
4700
4911
|
};
|
|
4912
|
+
function estimateTokens(text) {
|
|
4913
|
+
return Math.ceil(text.length / 4);
|
|
4914
|
+
}
|
|
4915
|
+
var MODEL_COST_PER_1K = {
|
|
4916
|
+
"gpt-4o": { input: 25e-4, output: 0.01 },
|
|
4917
|
+
"gpt-4o-mini": { input: 15e-5, output: 6e-4 },
|
|
4918
|
+
"gpt-4-turbo": { input: 0.01, output: 0.03 },
|
|
4919
|
+
"gpt-3.5-turbo": { input: 5e-4, output: 15e-4 },
|
|
4920
|
+
"claude-3-5-sonnet": { input: 3e-3, output: 0.015 },
|
|
4921
|
+
"claude-3-haiku": { input: 25e-5, output: 125e-5 },
|
|
4922
|
+
"gemini-1.5-flash": { input: 75e-6, output: 3e-4 },
|
|
4923
|
+
"gemini-1.5-pro": { input: 125e-5, output: 5e-3 }
|
|
4924
|
+
};
|
|
4925
|
+
function estimateCostUsd(promptTokens, completionTokens, model) {
|
|
4926
|
+
if (!model) return void 0;
|
|
4927
|
+
const key = Object.keys(MODEL_COST_PER_1K).find((k) => model.toLowerCase().includes(k));
|
|
4928
|
+
if (!key) return void 0;
|
|
4929
|
+
const prices = MODEL_COST_PER_1K[key];
|
|
4930
|
+
return promptTokens / 1e3 * prices.input + completionTokens / 1e3 * prices.output;
|
|
4931
|
+
}
|
|
4932
|
+
async function scoreHallucination(llm, answer, context) {
|
|
4933
|
+
const maxContextChars = 3e3;
|
|
4934
|
+
const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
|
|
4935
|
+
const prompt = `You are an AI quality checker. Given the CONTEXT retrieved from a knowledge base and the ANSWER generated from it, rate how well-grounded the answer is.
|
|
4936
|
+
|
|
4937
|
+
CONTEXT:
|
|
4938
|
+
${truncatedContext}
|
|
4939
|
+
|
|
4940
|
+
ANSWER:
|
|
4941
|
+
${answer}
|
|
4942
|
+
|
|
4943
|
+
Return ONLY a valid JSON object with no markdown fences:
|
|
4944
|
+
{"score": <float 0-1>, "reason": "<one sentence>"}
|
|
4945
|
+
|
|
4946
|
+
Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
|
|
4947
|
+
try {
|
|
4948
|
+
const raw = await llm.chat(
|
|
4949
|
+
[{ role: "user", content: prompt }],
|
|
4950
|
+
"",
|
|
4951
|
+
{ temperature: 0, maxTokens: 120 }
|
|
4952
|
+
);
|
|
4953
|
+
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
4954
|
+
if (!jsonMatch) return void 0;
|
|
4955
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
4956
|
+
if (typeof parsed.score === "number" && typeof parsed.reason === "string") {
|
|
4957
|
+
return { score: Math.min(1, Math.max(0, parsed.score)), reason: parsed.reason };
|
|
4958
|
+
}
|
|
4959
|
+
} catch (e) {
|
|
4960
|
+
}
|
|
4961
|
+
return void 0;
|
|
4962
|
+
}
|
|
4701
4963
|
var Pipeline = class {
|
|
4702
4964
|
constructor(config) {
|
|
4703
4965
|
this.config = config;
|
|
@@ -4724,8 +4986,7 @@ var Pipeline = class {
|
|
|
4724
4986
|
async initialize() {
|
|
4725
4987
|
var _a;
|
|
4726
4988
|
if (this.initialised) return;
|
|
4727
|
-
const chartInstruction = `
|
|
4728
|
-
You are a helpful product assistant. Use the provided context to answer questions accurately.
|
|
4989
|
+
const chartInstruction = `You are a helpful product assistant. Use the provided context to answer questions accurately.
|
|
4729
4990
|
|
|
4730
4991
|
### UI STYLE RULES (CRITICAL):
|
|
4731
4992
|
- NEVER generate markdown tables. If you do, the UI will break.
|
|
@@ -4739,7 +5000,6 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4739
5000
|
- Do NOT try to format product lists as tables.
|
|
4740
5001
|
`;
|
|
4741
5002
|
this.config.llm.systemPrompt = chartInstruction;
|
|
4742
|
-
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
4743
5003
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
4744
5004
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
4745
5005
|
this.config.llm,
|
|
@@ -4761,7 +5021,6 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4761
5021
|
}
|
|
4762
5022
|
/**
|
|
4763
5023
|
* Ingest documents with automatic chunking, embedding, and batch upsert.
|
|
4764
|
-
* Handles retries for transient failures.
|
|
4765
5024
|
*/
|
|
4766
5025
|
async ingest(documents, namespace) {
|
|
4767
5026
|
await this.initialize();
|
|
@@ -4778,10 +5037,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4778
5037
|
metadata: chunk.metadata
|
|
4779
5038
|
}));
|
|
4780
5039
|
const totalProcessed = await this.processUpserts(upsertDocs, ns);
|
|
4781
|
-
results.push({
|
|
4782
|
-
docId: doc.docId,
|
|
4783
|
-
chunksIngested: totalProcessed
|
|
4784
|
-
});
|
|
5040
|
+
results.push({ docId: doc.docId, chunksIngested: totalProcessed });
|
|
4785
5041
|
if (this.graphDB && this.entityExtractor) {
|
|
4786
5042
|
await this.processGraphIngestion(doc.docId, chunks);
|
|
4787
5043
|
}
|
|
@@ -4792,23 +5048,18 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4792
5048
|
}
|
|
4793
5049
|
return results;
|
|
4794
5050
|
}
|
|
4795
|
-
/**
|
|
4796
|
-
* Step 1: Chunk the document content.
|
|
4797
|
-
*/
|
|
5051
|
+
/** Step 1: Chunk the document content. */
|
|
4798
5052
|
async prepareChunks(doc) {
|
|
4799
5053
|
var _a, _b;
|
|
4800
5054
|
if (this.llamaIngestor) {
|
|
4801
|
-
return
|
|
5055
|
+
return this.llamaIngestor.chunk(doc.content, {
|
|
4802
5056
|
docId: doc.docId,
|
|
4803
5057
|
metadata: doc.metadata,
|
|
4804
5058
|
chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
|
|
4805
5059
|
chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
|
|
4806
5060
|
});
|
|
4807
5061
|
}
|
|
4808
|
-
return this.chunker.chunk(doc.content, {
|
|
4809
|
-
docId: doc.docId,
|
|
4810
|
-
metadata: doc.metadata
|
|
4811
|
-
});
|
|
5062
|
+
return this.chunker.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata });
|
|
4812
5063
|
}
|
|
4813
5064
|
/**
|
|
4814
5065
|
* Step 2: Generate embeddings for chunks with retry logic.
|
|
@@ -4832,9 +5083,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4832
5083
|
}
|
|
4833
5084
|
return vectors;
|
|
4834
5085
|
}
|
|
4835
|
-
/**
|
|
4836
|
-
* Step 3: Upsert chunks to vector database with retry logic.
|
|
4837
|
-
*/
|
|
5086
|
+
/** Step 3: Upsert chunks to vector database with retry logic. */
|
|
4838
5087
|
async processUpserts(upsertDocs, namespace) {
|
|
4839
5088
|
const upsertBatchOptions = {
|
|
4840
5089
|
batchSize: 100,
|
|
@@ -4851,14 +5100,10 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4851
5100
|
}
|
|
4852
5101
|
return upsertResult.totalProcessed;
|
|
4853
5102
|
}
|
|
4854
|
-
/**
|
|
4855
|
-
* Step 4: Optional graph-based entity extraction and ingestion.
|
|
4856
|
-
*/
|
|
5103
|
+
/** Step 4: Optional graph-based entity extraction and ingestion. */
|
|
4857
5104
|
async processGraphIngestion(docId, chunks) {
|
|
4858
|
-
console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
|
|
4859
5105
|
const extractionOptions = {
|
|
4860
5106
|
batchSize: 2,
|
|
4861
|
-
// Low concurrency for LLM extraction
|
|
4862
5107
|
maxRetries: 1,
|
|
4863
5108
|
initialDelayMs: 500
|
|
4864
5109
|
};
|
|
@@ -4871,7 +5116,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4871
5116
|
if (nodes.length > 0) await this.graphDB.addNodes(nodes);
|
|
4872
5117
|
if (edges.length > 0) await this.graphDB.addEdges(edges);
|
|
4873
5118
|
} catch (err) {
|
|
4874
|
-
console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
|
|
5119
|
+
console.warn(`[Pipeline] Entity extraction failed for chunk in doc ${docId}:`, err);
|
|
4875
5120
|
}
|
|
4876
5121
|
}
|
|
4877
5122
|
},
|
|
@@ -4882,7 +5127,6 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4882
5127
|
var _a;
|
|
4883
5128
|
await this.initialize();
|
|
4884
5129
|
if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
|
|
4885
|
-
console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
|
|
4886
5130
|
const agentReply = await this.agent.run(question, history);
|
|
4887
5131
|
return { reply: agentReply, sources: [] };
|
|
4888
5132
|
}
|
|
@@ -4891,6 +5135,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4891
5135
|
let sources = [];
|
|
4892
5136
|
let graphData;
|
|
4893
5137
|
let uiTransformation;
|
|
5138
|
+
let trace;
|
|
4894
5139
|
try {
|
|
4895
5140
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
4896
5141
|
const chunk = temp.value;
|
|
@@ -4900,6 +5145,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4900
5145
|
if ("sources" in chunk) sources = chunk.sources;
|
|
4901
5146
|
if ("graphData" in chunk) graphData = chunk.graphData;
|
|
4902
5147
|
if ("ui_transformation" in chunk) uiTransformation = chunk.ui_transformation;
|
|
5148
|
+
if ("trace" in chunk) trace = chunk.trace;
|
|
4903
5149
|
}
|
|
4904
5150
|
}
|
|
4905
5151
|
} catch (temp) {
|
|
@@ -4912,41 +5158,51 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4912
5158
|
throw error[0];
|
|
4913
5159
|
}
|
|
4914
5160
|
}
|
|
4915
|
-
return { reply, sources, graphData, ui_transformation: uiTransformation };
|
|
5161
|
+
return { reply, sources, graphData, ui_transformation: uiTransformation, trace };
|
|
4916
5162
|
}
|
|
4917
5163
|
/**
|
|
4918
5164
|
* High-performance streaming RAG flow.
|
|
4919
|
-
* Yields text chunks first, then the retrieval metadata at the end.
|
|
5165
|
+
* Yields text chunks first, then the retrieval metadata + observability trace at the end.
|
|
4920
5166
|
*/
|
|
4921
5167
|
askStream(_0) {
|
|
4922
5168
|
return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
|
|
4923
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
5169
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
4924
5170
|
yield new __await(this.initialize());
|
|
4925
5171
|
const ns = namespace != null ? namespace : this.config.projectId;
|
|
4926
5172
|
const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
|
|
4927
5173
|
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
|
|
5174
|
+
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
5175
|
+
const requestStart = performance.now();
|
|
4928
5176
|
try {
|
|
4929
5177
|
let searchQuery = question;
|
|
5178
|
+
let rewrittenQuery;
|
|
4930
5179
|
if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
|
|
4931
5180
|
searchQuery = yield new __await(this.rewriteQuery(question, history));
|
|
5181
|
+
rewrittenQuery = searchQuery !== question ? searchQuery : void 0;
|
|
4932
5182
|
}
|
|
4933
5183
|
const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
|
|
4934
5184
|
const filter = QueryProcessor.buildQueryFilter(question, hints);
|
|
4935
|
-
|
|
5185
|
+
const embedStart = performance.now();
|
|
4936
5186
|
const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
|
|
4937
5187
|
namespace: ns,
|
|
4938
5188
|
topK: topK * 2,
|
|
4939
5189
|
filter
|
|
4940
5190
|
}));
|
|
5191
|
+
const retrieveEnd = performance.now();
|
|
5192
|
+
const embedMs = retrieveEnd - embedStart;
|
|
5193
|
+
const retrieveMs = retrieveEnd - embedStart;
|
|
5194
|
+
const rerankStart = performance.now();
|
|
4941
5195
|
let sources = rawSources.filter((m) => m.score >= scoreThreshold);
|
|
4942
5196
|
if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
|
|
4943
5197
|
sources = yield new __await(this.reranker.rerank(sources, question, topK));
|
|
4944
5198
|
} else {
|
|
4945
5199
|
sources = sources.slice(0, topK);
|
|
4946
5200
|
}
|
|
5201
|
+
const rerankMs = performance.now() - rerankStart;
|
|
4947
5202
|
let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
|
|
4948
5203
|
${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
|
|
4949
5204
|
if (graphData && graphData.nodes.length > 0) {
|
|
5205
|
+
console.log(`[Graph Retrieval] Found ${graphData.nodes.length} relevant entities.`);
|
|
4950
5206
|
const graphContext = graphData.nodes.map(
|
|
4951
5207
|
(n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
|
|
4952
5208
|
).join("\n");
|
|
@@ -4962,6 +5218,10 @@ ${context}`;
|
|
|
4962
5218
|
const hardenedHistory = [...history];
|
|
4963
5219
|
const userQuestion = { role: "user", content: question + restrictionSuffix };
|
|
4964
5220
|
const messages = [...hardenedHistory, userQuestion];
|
|
5221
|
+
const systemPrompt = (_h = this.config.llm.systemPrompt) != null ? _h : "";
|
|
5222
|
+
const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
5223
|
+
let fullReply = "";
|
|
5224
|
+
const generateStart = performance.now();
|
|
4965
5225
|
if (this.llmProvider.chatStream) {
|
|
4966
5226
|
const stream = this.llmProvider.chatStream(messages, context);
|
|
4967
5227
|
if (!stream) {
|
|
@@ -4970,6 +5230,7 @@ ${context}`;
|
|
|
4970
5230
|
try {
|
|
4971
5231
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
4972
5232
|
const chunk = temp.value;
|
|
5233
|
+
fullReply += chunk;
|
|
4973
5234
|
yield chunk;
|
|
4974
5235
|
}
|
|
4975
5236
|
} catch (temp) {
|
|
@@ -4984,15 +5245,58 @@ ${context}`;
|
|
|
4984
5245
|
}
|
|
4985
5246
|
} else {
|
|
4986
5247
|
const reply = yield new __await(this.llmProvider.chat(messages, context));
|
|
5248
|
+
fullReply = reply;
|
|
4987
5249
|
yield reply;
|
|
4988
5250
|
}
|
|
5251
|
+
const generateMs = performance.now() - generateStart;
|
|
5252
|
+
const totalMs = performance.now() - requestStart;
|
|
5253
|
+
const latency = {
|
|
5254
|
+
embedMs: Math.round(embedMs),
|
|
5255
|
+
retrieveMs: Math.round(retrieveMs),
|
|
5256
|
+
rerankMs: ((_i = this.config.rag) == null ? void 0 : _i.useReranking) ? Math.round(rerankMs) : void 0,
|
|
5257
|
+
generateMs: Math.round(generateMs),
|
|
5258
|
+
totalMs: Math.round(totalMs)
|
|
5259
|
+
};
|
|
5260
|
+
const promptText = systemPrompt + "\n" + context + "\n" + userPrompt;
|
|
5261
|
+
const promptTokens = estimateTokens(promptText);
|
|
5262
|
+
const completionTokens = estimateTokens(fullReply);
|
|
5263
|
+
const tokens = {
|
|
5264
|
+
promptTokens,
|
|
5265
|
+
completionTokens,
|
|
5266
|
+
totalTokens: promptTokens + completionTokens,
|
|
5267
|
+
estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
|
|
5268
|
+
};
|
|
5269
|
+
const hallucinationResult = yield new __await(scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0));
|
|
4989
5270
|
const trainedSchema = yield new __await(trainingPromise);
|
|
4990
|
-
const uiTransformation = yield new __await(this.generateUiTransformation(question, sources,
|
|
5271
|
+
const uiTransformation = yield new __await(this.generateUiTransformation(question, sources, trainedSchema));
|
|
5272
|
+
const trace = {
|
|
5273
|
+
requestId,
|
|
5274
|
+
query: question,
|
|
5275
|
+
rewrittenQuery,
|
|
5276
|
+
systemPrompt,
|
|
5277
|
+
userPrompt: question + restrictionSuffix,
|
|
5278
|
+
chunks: sources.map((s) => {
|
|
5279
|
+
var _a2;
|
|
5280
|
+
return {
|
|
5281
|
+
id: s.id,
|
|
5282
|
+
score: s.score,
|
|
5283
|
+
content: s.content,
|
|
5284
|
+
metadata: (_a2 = s.metadata) != null ? _a2 : {},
|
|
5285
|
+
namespace: ns
|
|
5286
|
+
};
|
|
5287
|
+
}),
|
|
5288
|
+
latency,
|
|
5289
|
+
tokens,
|
|
5290
|
+
hallucinationScore: hallucinationResult == null ? void 0 : hallucinationResult.score,
|
|
5291
|
+
hallucinationReason: hallucinationResult == null ? void 0 : hallucinationResult.reason,
|
|
5292
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5293
|
+
};
|
|
4991
5294
|
yield {
|
|
4992
5295
|
reply: "",
|
|
4993
5296
|
sources,
|
|
4994
5297
|
graphData,
|
|
4995
|
-
ui_transformation: uiTransformation
|
|
5298
|
+
ui_transformation: uiTransformation,
|
|
5299
|
+
trace
|
|
4996
5300
|
};
|
|
4997
5301
|
} catch (error2) {
|
|
4998
5302
|
throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
@@ -5003,7 +5307,7 @@ ${context}`;
|
|
|
5003
5307
|
* Universal retrieval method combining all enabled providers.
|
|
5004
5308
|
* Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
|
|
5005
5309
|
*/
|
|
5006
|
-
async generateUiTransformation(question, sources,
|
|
5310
|
+
async generateUiTransformation(question, sources, trainedSchema) {
|
|
5007
5311
|
if (!sources || sources.length === 0) {
|
|
5008
5312
|
return UITransformer.transform(question, sources, this.config, trainedSchema);
|
|
5009
5313
|
}
|
|
@@ -5020,23 +5324,24 @@ ${context}`;
|
|
|
5020
5324
|
const topK = (_b = options.topK) != null ? _b : 5;
|
|
5021
5325
|
const cacheKey = `${ns}::${query}`;
|
|
5022
5326
|
let queryVector = this.embeddingCache.get(cacheKey);
|
|
5327
|
+
const strategy = await QueryProcessor.determineRetrievalStrategy(query, this.llmProvider);
|
|
5328
|
+
console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
|
|
5023
5329
|
const [retrievedVector, graphData] = await Promise.all([
|
|
5024
|
-
|
|
5025
|
-
|
|
5330
|
+
// Only embed if we need vector search (strategy is 'vector' or 'both')
|
|
5331
|
+
strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
|
|
5332
|
+
// Only query graph if we need graph search (strategy is 'graph' or 'both')
|
|
5333
|
+
(strategy === "graph" || strategy === "both") && this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
|
|
5026
5334
|
]);
|
|
5027
|
-
if (!queryVector) {
|
|
5335
|
+
if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
|
|
5028
5336
|
this.embeddingCache.set(cacheKey, retrievedVector);
|
|
5029
5337
|
queryVector = retrievedVector;
|
|
5030
5338
|
}
|
|
5031
|
-
const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
|
|
5339
|
+
const sources = (strategy === "vector" || strategy === "both") && queryVector && queryVector.length > 0 ? await this.vectorDB.query(queryVector, topK, ns, options.filter) : [];
|
|
5032
5340
|
return { sources, graphData };
|
|
5033
5341
|
}
|
|
5034
|
-
/**
|
|
5035
|
-
* Rewrite the user query for better retrieval performance.
|
|
5036
|
-
*/
|
|
5342
|
+
/** Rewrite the user query for better retrieval performance. */
|
|
5037
5343
|
async rewriteQuery(question, history) {
|
|
5038
|
-
const prompt = `
|
|
5039
|
-
Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
|
|
5344
|
+
const prompt = `Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
|
|
5040
5345
|
Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
|
|
5041
5346
|
|
|
5042
5347
|
History:
|
|
@@ -5054,26 +5359,19 @@ Optimized Search Query:`;
|
|
|
5054
5359
|
);
|
|
5055
5360
|
return rewrite.trim() || question;
|
|
5056
5361
|
}
|
|
5057
|
-
/**
|
|
5058
|
-
* Generate 3-5 short, relevant questions based on the vector database content.
|
|
5059
|
-
*/
|
|
5362
|
+
/** Generate 3-5 short, relevant questions based on the vector database content. */
|
|
5060
5363
|
async getSuggestions(query, namespace) {
|
|
5061
|
-
if (!query || query.trim().length < 3)
|
|
5062
|
-
return [];
|
|
5063
|
-
}
|
|
5364
|
+
if (!query || query.trim().length < 3) return [];
|
|
5064
5365
|
await this.initialize();
|
|
5065
5366
|
const ns = namespace != null ? namespace : this.config.projectId;
|
|
5066
5367
|
try {
|
|
5067
|
-
const { sources } = await this.retrieve(query, { namespace: ns, topK:
|
|
5068
|
-
if (sources.length === 0)
|
|
5069
|
-
return [];
|
|
5070
|
-
}
|
|
5368
|
+
const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
|
|
5369
|
+
if (sources.length === 0) return [];
|
|
5071
5370
|
const context = sources.map((s) => s.content).join("\n\n---\n\n");
|
|
5072
|
-
const prompt = `
|
|
5073
|
-
Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
|
|
5371
|
+
const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
|
|
5074
5372
|
Focus on questions that can be answered by the context.
|
|
5075
5373
|
Keep each question under 10 words and make them very specific to the content.
|
|
5076
|
-
Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
|
|
5374
|
+
Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3", "Question 4", "Question 5"].
|
|
5077
5375
|
|
|
5078
5376
|
Context:
|
|
5079
5377
|
${context}
|
|
@@ -5090,7 +5388,7 @@ Suggestions:`;
|
|
|
5090
5388
|
if (match) {
|
|
5091
5389
|
const suggestions = JSON.parse(match[0]);
|
|
5092
5390
|
if (Array.isArray(suggestions)) {
|
|
5093
|
-
return suggestions.map((s) => String(s)).slice(0,
|
|
5391
|
+
return suggestions.map((s) => String(s)).slice(0, 5);
|
|
5094
5392
|
}
|
|
5095
5393
|
}
|
|
5096
5394
|
} catch (error) {
|
|
@@ -5347,6 +5645,11 @@ function sseMetaFrame(meta) {
|
|
|
5347
5645
|
function sseUIFrame(uiTransformation) {
|
|
5348
5646
|
return `data: ${JSON.stringify({ type: "ui_transformation", data: uiTransformation })}
|
|
5349
5647
|
|
|
5648
|
+
`;
|
|
5649
|
+
}
|
|
5650
|
+
function sseObservabilityFrame(trace) {
|
|
5651
|
+
return `data: ${JSON.stringify({ type: "observability", data: trace })}
|
|
5652
|
+
|
|
5350
5653
|
`;
|
|
5351
5654
|
}
|
|
5352
5655
|
function sseErrorFrame(message) {
|
|
@@ -5413,6 +5716,9 @@ function createStreamHandler(configOrPlugin) {
|
|
|
5413
5716
|
enqueue(sseMetaFrame(chunk));
|
|
5414
5717
|
const responseChunk = chunk;
|
|
5415
5718
|
const sources = (responseChunk == null ? void 0 : responseChunk.sources) || [];
|
|
5719
|
+
if (responseChunk == null ? void 0 : responseChunk.trace) {
|
|
5720
|
+
enqueue(sseObservabilityFrame(responseChunk.trace));
|
|
5721
|
+
}
|
|
5416
5722
|
if (sources.length > 0) {
|
|
5417
5723
|
try {
|
|
5418
5724
|
const llmProvider = (_a = plugin.getLLMProvider) == null ? void 0 : _a.call(plugin);
|
|
@@ -5494,24 +5800,64 @@ function createUploadHandler(configOrPlugin) {
|
|
|
5494
5800
|
const formData = await req.formData();
|
|
5495
5801
|
const files = formData.getAll("files");
|
|
5496
5802
|
const namespace = formData.get("namespace") || void 0;
|
|
5803
|
+
const dimensionRaw = formData.get("dimension");
|
|
5804
|
+
const dimension = dimensionRaw ? parseInt(dimensionRaw, 10) : void 0;
|
|
5497
5805
|
if (!files || files.length === 0) {
|
|
5498
5806
|
return import_server.NextResponse.json({ error: "No files provided" }, { status: 400 });
|
|
5499
5807
|
}
|
|
5500
|
-
const documents =
|
|
5501
|
-
|
|
5808
|
+
const documents = [];
|
|
5809
|
+
for (const file of files) {
|
|
5810
|
+
if (file.name.toLowerCase().endsWith(".csv") || file.type === "text/csv") {
|
|
5811
|
+
const text = await file.text();
|
|
5812
|
+
const Papa = await import("papaparse");
|
|
5813
|
+
const parsed = Papa.default.parse(text, { header: true, skipEmptyLines: true });
|
|
5814
|
+
if (parsed.data && parsed.data.length > 0) {
|
|
5815
|
+
let i = 0;
|
|
5816
|
+
let lastRowData = null;
|
|
5817
|
+
for (const row of parsed.data) {
|
|
5818
|
+
i++;
|
|
5819
|
+
const rowData = row;
|
|
5820
|
+
const groupingKey = Object.keys(rowData)[0];
|
|
5821
|
+
if (lastRowData && groupingKey && rowData[groupingKey] && rowData[groupingKey] === lastRowData[groupingKey]) {
|
|
5822
|
+
for (const key of Object.keys(rowData)) {
|
|
5823
|
+
if (!rowData[key] && lastRowData[key]) {
|
|
5824
|
+
rowData[key] = lastRowData[key];
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
}
|
|
5828
|
+
lastRowData = __spreadValues({}, rowData);
|
|
5829
|
+
const contentParts = [];
|
|
5830
|
+
for (const [key, val] of Object.entries(rowData)) {
|
|
5831
|
+
if (key && val) {
|
|
5832
|
+
contentParts.push(`${key}: ${val}`);
|
|
5833
|
+
}
|
|
5834
|
+
}
|
|
5835
|
+
documents.push({
|
|
5836
|
+
docId: `${file.name}-row-${i}`,
|
|
5837
|
+
content: contentParts.join(", "),
|
|
5838
|
+
metadata: __spreadValues(__spreadValues({
|
|
5839
|
+
fileName: file.name,
|
|
5840
|
+
fileSize: file.size,
|
|
5841
|
+
fileType: file.type,
|
|
5842
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5843
|
+
}, dimension ? { dimension } : {}), rowData)
|
|
5844
|
+
});
|
|
5845
|
+
}
|
|
5846
|
+
}
|
|
5847
|
+
} else {
|
|
5502
5848
|
const content = await DocumentParser.parse(file, file.name, file.type);
|
|
5503
|
-
|
|
5849
|
+
documents.push({
|
|
5504
5850
|
docId: file.name,
|
|
5505
5851
|
content,
|
|
5506
|
-
metadata: {
|
|
5852
|
+
metadata: __spreadValues({
|
|
5507
5853
|
fileName: file.name,
|
|
5508
5854
|
fileSize: file.size,
|
|
5509
5855
|
fileType: file.type,
|
|
5510
5856
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5511
|
-
}
|
|
5512
|
-
};
|
|
5513
|
-
}
|
|
5514
|
-
|
|
5857
|
+
}, dimension ? { dimension } : {})
|
|
5858
|
+
});
|
|
5859
|
+
}
|
|
5860
|
+
}
|
|
5515
5861
|
const results = await plugin.ingest(documents, namespace);
|
|
5516
5862
|
return import_server.NextResponse.json({ message: "Upload successful", results });
|
|
5517
5863
|
} catch (err) {
|
|
@@ -5548,6 +5894,7 @@ function createSuggestionsHandler(configOrPlugin) {
|
|
|
5548
5894
|
sseErrorFrame,
|
|
5549
5895
|
sseFrame,
|
|
5550
5896
|
sseMetaFrame,
|
|
5897
|
+
sseObservabilityFrame,
|
|
5551
5898
|
sseTextFrame,
|
|
5552
5899
|
sseUIFrame
|
|
5553
5900
|
});
|