@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/server.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) {
|
|
@@ -3378,11 +3451,70 @@ Text to extract from:
|
|
|
3378
3451
|
// src/rag/Reranker.ts
|
|
3379
3452
|
var Reranker = class {
|
|
3380
3453
|
/**
|
|
3381
|
-
* Re-ranks matches based on a secondary relevance score.
|
|
3382
|
-
* In a production environment, this would call a Cross-Encoder model.
|
|
3383
|
-
* Here we implement a placeholder that filters by score and limits count.
|
|
3454
|
+
* Re-ranks matches based on a secondary relevance score using an LLM.
|
|
3384
3455
|
*/
|
|
3385
|
-
async rerank(matches, query, limit = 5) {
|
|
3456
|
+
async rerank(matches, query, limit = 5, llm) {
|
|
3457
|
+
if (!llm || matches.length <= 1) {
|
|
3458
|
+
const keywords = query.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/).filter((w) => w.length > 2);
|
|
3459
|
+
if (keywords.length === 0) {
|
|
3460
|
+
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
3461
|
+
}
|
|
3462
|
+
const scoredMatches = matches.map((match) => {
|
|
3463
|
+
const contentLower = match.content.toLowerCase();
|
|
3464
|
+
let keywordScore = 0;
|
|
3465
|
+
keywords.forEach((keyword) => {
|
|
3466
|
+
if (contentLower.includes(keyword)) {
|
|
3467
|
+
keywordScore += 1;
|
|
3468
|
+
}
|
|
3469
|
+
});
|
|
3470
|
+
const normalizedKeywordScore = keywordScore / keywords.length;
|
|
3471
|
+
const combinedScore = match.score + normalizedKeywordScore * 0.5;
|
|
3472
|
+
return __spreadProps(__spreadValues({}, match), {
|
|
3473
|
+
score: combinedScore
|
|
3474
|
+
});
|
|
3475
|
+
});
|
|
3476
|
+
return scoredMatches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
3477
|
+
}
|
|
3478
|
+
try {
|
|
3479
|
+
const topN = matches.slice(0, 10);
|
|
3480
|
+
const prompt = `You are a relevance ranking expert.
|
|
3481
|
+
Given the following user query and a list of retrieved document chunks, rank the chunks by relevance to the query.
|
|
3482
|
+
Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
|
|
3483
|
+
Use the indices provided in brackets like [0], [1], etc.
|
|
3484
|
+
Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
|
|
3485
|
+
|
|
3486
|
+
Query: "${query}"
|
|
3487
|
+
|
|
3488
|
+
Documents:
|
|
3489
|
+
${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}`;
|
|
3490
|
+
const response = await llm.chat(
|
|
3491
|
+
[{ role: "user", content: prompt }],
|
|
3492
|
+
"",
|
|
3493
|
+
{ temperature: 0, maxTokens: 50 }
|
|
3494
|
+
);
|
|
3495
|
+
const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
|
|
3496
|
+
const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
|
|
3497
|
+
if (rankedIndices.length > 0) {
|
|
3498
|
+
const rankedMatches = [];
|
|
3499
|
+
const usedIndices = /* @__PURE__ */ new Set();
|
|
3500
|
+
for (const index of rankedIndices) {
|
|
3501
|
+
if (index >= 0 && index < topN.length && !usedIndices.has(index)) {
|
|
3502
|
+
rankedMatches.push(topN[index]);
|
|
3503
|
+
usedIndices.add(index);
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
for (let i = 0; i < topN.length; i++) {
|
|
3507
|
+
if (!usedIndices.has(i)) {
|
|
3508
|
+
rankedMatches.push(topN[i]);
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
const rest = matches.slice(10);
|
|
3512
|
+
rankedMatches.push(...rest);
|
|
3513
|
+
return rankedMatches.slice(0, limit);
|
|
3514
|
+
}
|
|
3515
|
+
} catch (error) {
|
|
3516
|
+
console.warn("[Reranker] LLM re-ranking failed, falling back to score-based sorting:", error);
|
|
3517
|
+
}
|
|
3386
3518
|
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
3387
3519
|
}
|
|
3388
3520
|
};
|
|
@@ -3442,7 +3574,7 @@ var LangChainAgent = class {
|
|
|
3442
3574
|
*/
|
|
3443
3575
|
async initialize(chatModel) {
|
|
3444
3576
|
try {
|
|
3445
|
-
const [{ DynamicTool },
|
|
3577
|
+
const [{ DynamicTool }, , { createAgent }] = await Promise.all([
|
|
3446
3578
|
import("@langchain/core/tools"),
|
|
3447
3579
|
import("@langchain/core/messages"),
|
|
3448
3580
|
import("langchain")
|
|
@@ -3492,7 +3624,6 @@ When presenting structured data, statistics, or comparisons, decide if it is bes
|
|
|
3492
3624
|
tools: [searchTool],
|
|
3493
3625
|
systemPrompt: finalSystemPrompt
|
|
3494
3626
|
});
|
|
3495
|
-
void HumanMessage;
|
|
3496
3627
|
} catch (error) {
|
|
3497
3628
|
const isMissing = error instanceof Error && error.message.includes("Cannot find module");
|
|
3498
3629
|
const hint = isMissing ? " Make sure 'langchain' and '@langchain/core' are installed:\n npm install langchain @langchain/core" : "";
|
|
@@ -3976,7 +4107,128 @@ var QueryProcessor = class {
|
|
|
3976
4107
|
if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
|
|
3977
4108
|
return filter;
|
|
3978
4109
|
}
|
|
4110
|
+
/**
|
|
4111
|
+
* Determines the retrieval strategy based on the query content.
|
|
4112
|
+
*/
|
|
4113
|
+
static async determineRetrievalStrategy(query, llm, customGraphKeywords, customVectorKeywords) {
|
|
4114
|
+
if (llm) {
|
|
4115
|
+
try {
|
|
4116
|
+
const prompt = `You are a routing expert in a RAG system.
|
|
4117
|
+
Given the user query, determine which retrieval mechanism is needed:
|
|
4118
|
+
- 'vector': For semantic search, finding specific documents or content based on similarity.
|
|
4119
|
+
- 'graph': For finding relationships between entities, multi-hop questions, or network structures.
|
|
4120
|
+
- 'both': If the query requires both specific content and relationship/structural analysis.
|
|
4121
|
+
|
|
4122
|
+
Query: "${query}"
|
|
4123
|
+
|
|
4124
|
+
Return ONLY 'vector', 'graph', or 'both'. No explanation.`;
|
|
4125
|
+
const response = await llm.chat(
|
|
4126
|
+
[{ role: "user", content: prompt }],
|
|
4127
|
+
"",
|
|
4128
|
+
{ temperature: 0, maxTokens: 10 }
|
|
4129
|
+
);
|
|
4130
|
+
const cleanResponse = response.trim().toLowerCase();
|
|
4131
|
+
if (cleanResponse.includes("vector")) return "vector";
|
|
4132
|
+
if (cleanResponse.includes("graph")) return "graph";
|
|
4133
|
+
if (cleanResponse.includes("both")) return "both";
|
|
4134
|
+
} catch (error) {
|
|
4135
|
+
console.warn("[QueryProcessor] LLM strategy classification failed, falling back to keywords:", error);
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
const normalized = query.toLowerCase();
|
|
4139
|
+
const graphKeywords = customGraphKeywords || [
|
|
4140
|
+
"relationship",
|
|
4141
|
+
"connect",
|
|
4142
|
+
"link",
|
|
4143
|
+
"network",
|
|
4144
|
+
"friend",
|
|
4145
|
+
"colleague",
|
|
4146
|
+
"manager",
|
|
4147
|
+
"hierarchy",
|
|
4148
|
+
"multi-hop",
|
|
4149
|
+
"shortest path",
|
|
4150
|
+
"between",
|
|
4151
|
+
"related to",
|
|
4152
|
+
"associated with",
|
|
4153
|
+
"belongs to",
|
|
4154
|
+
"part of"
|
|
4155
|
+
];
|
|
4156
|
+
const hasGraphKeyword = graphKeywords.some((kw) => normalized.includes(kw));
|
|
4157
|
+
const vectorKeywords = customVectorKeywords || [
|
|
4158
|
+
"find",
|
|
4159
|
+
"search",
|
|
4160
|
+
"tell me about",
|
|
4161
|
+
"what is",
|
|
4162
|
+
"how to",
|
|
4163
|
+
"documents about"
|
|
4164
|
+
];
|
|
4165
|
+
const hasVectorKeyword = vectorKeywords.some((kw) => normalized.includes(kw));
|
|
4166
|
+
if (hasGraphKeyword && !hasVectorKeyword) {
|
|
4167
|
+
return "graph";
|
|
4168
|
+
} else if (hasGraphKeyword && hasVectorKeyword) {
|
|
4169
|
+
return "both";
|
|
4170
|
+
}
|
|
4171
|
+
return "vector";
|
|
4172
|
+
}
|
|
4173
|
+
};
|
|
4174
|
+
|
|
4175
|
+
// src/utils/synonyms.ts
|
|
4176
|
+
var FIELD_SYNONYMS = {
|
|
4177
|
+
name: ["product", "item", "title", "label", "heading", "subject", "product name", "item name"],
|
|
4178
|
+
price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
|
|
4179
|
+
brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
|
|
4180
|
+
image: [
|
|
4181
|
+
"imageUrl",
|
|
4182
|
+
"thumbnail",
|
|
4183
|
+
"img",
|
|
4184
|
+
"url",
|
|
4185
|
+
"photo",
|
|
4186
|
+
"picture",
|
|
4187
|
+
"media",
|
|
4188
|
+
"image_url",
|
|
4189
|
+
"main_image",
|
|
4190
|
+
"product_image",
|
|
4191
|
+
"thumb"
|
|
4192
|
+
],
|
|
4193
|
+
stock: [
|
|
4194
|
+
"inventory",
|
|
4195
|
+
"quantity",
|
|
4196
|
+
"count",
|
|
4197
|
+
"availability",
|
|
4198
|
+
"stock_level",
|
|
4199
|
+
"inStock",
|
|
4200
|
+
"is_available",
|
|
4201
|
+
"in stock",
|
|
4202
|
+
"status"
|
|
4203
|
+
],
|
|
4204
|
+
description: ["summary", "content", "body", "text", "info", "details"],
|
|
4205
|
+
link: ["url", "href", "product_url", "page_url", "link"]
|
|
3979
4206
|
};
|
|
4207
|
+
function resolveMetadataValue(meta, uiKey) {
|
|
4208
|
+
var _a;
|
|
4209
|
+
const synonyms = (_a = FIELD_SYNONYMS[uiKey]) != null ? _a : [];
|
|
4210
|
+
const keys = Object.keys(meta);
|
|
4211
|
+
const systemKeys = ["namespace", "filename", "filesize", "filetype", "docid", "uploadedat", "dimension", "chunkindex"];
|
|
4212
|
+
let match = keys.find((k) => k.toLowerCase() === uiKey.toLowerCase());
|
|
4213
|
+
if (match !== void 0) return meta[match];
|
|
4214
|
+
match = keys.find((k) => synonyms.map((s) => s.toLowerCase()).includes(k.toLowerCase()));
|
|
4215
|
+
if (match !== void 0) return meta[match];
|
|
4216
|
+
const isBlacklisted = (kl) => {
|
|
4217
|
+
return systemKeys.includes(kl) || kl.startsWith("option1") || kl.startsWith("option2") || kl.startsWith("option3");
|
|
4218
|
+
};
|
|
4219
|
+
match = keys.find((k) => {
|
|
4220
|
+
const kl = k.toLowerCase();
|
|
4221
|
+
if (isBlacklisted(kl)) return false;
|
|
4222
|
+
return kl.includes(uiKey.toLowerCase());
|
|
4223
|
+
});
|
|
4224
|
+
if (match !== void 0) return meta[match];
|
|
4225
|
+
match = keys.find((k) => {
|
|
4226
|
+
const kl = k.toLowerCase();
|
|
4227
|
+
if (isBlacklisted(kl)) return false;
|
|
4228
|
+
return synonyms.some((sk) => kl.includes(sk.toLowerCase()) || sk.toLowerCase().includes(kl));
|
|
4229
|
+
});
|
|
4230
|
+
return match !== void 0 ? meta[match] : void 0;
|
|
4231
|
+
}
|
|
3980
4232
|
|
|
3981
4233
|
// src/utils/UITransformer.ts
|
|
3982
4234
|
var UITransformer = class {
|
|
@@ -4045,28 +4297,6 @@ var UITransformer = class {
|
|
|
4045
4297
|
data: pieData
|
|
4046
4298
|
};
|
|
4047
4299
|
}
|
|
4048
|
-
/**
|
|
4049
|
-
* Transform data to bar chart format
|
|
4050
|
-
*/
|
|
4051
|
-
static transformToBarChart(data) {
|
|
4052
|
-
const categories = this.detectCategories(data);
|
|
4053
|
-
const categoryData = this.aggregateByCategory(data, categories);
|
|
4054
|
-
const barData = Object.entries(categoryData).map(([category, value]) => {
|
|
4055
|
-
const { inStockCount, outOfStockCount } = this.calculateStockCounts(category, data);
|
|
4056
|
-
return {
|
|
4057
|
-
category,
|
|
4058
|
-
value,
|
|
4059
|
-
inStockCount,
|
|
4060
|
-
outOfStockCount
|
|
4061
|
-
};
|
|
4062
|
-
});
|
|
4063
|
-
return {
|
|
4064
|
-
type: "bar_chart",
|
|
4065
|
-
title: "Comparison by Category",
|
|
4066
|
-
description: `Comparing ${categories.length} categories`,
|
|
4067
|
-
data: barData
|
|
4068
|
-
};
|
|
4069
|
-
}
|
|
4070
4300
|
/**
|
|
4071
4301
|
* Transform data to line chart format
|
|
4072
4302
|
*/
|
|
@@ -4345,15 +4575,7 @@ var UITransformer = class {
|
|
|
4345
4575
|
const trainedKey = trainedSchema[uiKey];
|
|
4346
4576
|
if (trainedKey && meta[trainedKey] !== void 0) return meta[trainedKey];
|
|
4347
4577
|
}
|
|
4348
|
-
|
|
4349
|
-
const searchKeys = [uiKey, ...synonyms].map((k) => k.toLowerCase());
|
|
4350
|
-
const foundDirectKey = Object.keys(meta).find((k) => searchKeys.includes(k.toLowerCase()));
|
|
4351
|
-
if (foundDirectKey) return meta[foundDirectKey];
|
|
4352
|
-
const foundPartialKey = Object.keys(meta).find((k) => {
|
|
4353
|
-
const keyLow = k.toLowerCase();
|
|
4354
|
-
return searchKeys.some((sk) => keyLow.includes(sk) || sk.includes(keyLow));
|
|
4355
|
-
});
|
|
4356
|
-
return foundPartialKey ? meta[foundPartialKey] : void 0;
|
|
4578
|
+
return resolveMetadataValue(meta, uiKey);
|
|
4357
4579
|
}
|
|
4358
4580
|
static extractProductInfo(item, config, trainedSchema) {
|
|
4359
4581
|
const meta = item.metadata || {};
|
|
@@ -4423,7 +4645,7 @@ var UITransformer = class {
|
|
|
4423
4645
|
data.forEach((item) => {
|
|
4424
4646
|
const meta = item.metadata || {};
|
|
4425
4647
|
const itemCategory = meta.category || meta.type || "Other";
|
|
4426
|
-
if (
|
|
4648
|
+
if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
|
|
4427
4649
|
result[itemCategory]++;
|
|
4428
4650
|
} else {
|
|
4429
4651
|
result["Other"] = (result["Other"] || 0) + 1;
|
|
@@ -4644,18 +4866,6 @@ IMPORTANT:
|
|
|
4644
4866
|
return JSON.stringify(partial, null, 2) + "\n// ... truncated";
|
|
4645
4867
|
}
|
|
4646
4868
|
};
|
|
4647
|
-
/**
|
|
4648
|
-
* Central dictionary of common synonyms for UI properties.
|
|
4649
|
-
* This allows the system to be schema-agnostic by guessing field names.
|
|
4650
|
-
*/
|
|
4651
|
-
UITransformer.SYNONYMS = {
|
|
4652
|
-
name: ["product", "item", "title", "label", "heading", "subject"],
|
|
4653
|
-
price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
|
|
4654
|
-
brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
|
|
4655
|
-
image: ["imageUrl", "thumbnail", "img", "url", "photo", "picture", "media", "image_url", "main_image", "product_image", "thumb"],
|
|
4656
|
-
stock: ["inventory", "quantity", "count", "availability", "stock_level", "inStock", "is_available"],
|
|
4657
|
-
description: ["summary", "content", "body", "text", "info", "details"]
|
|
4658
|
-
};
|
|
4659
4869
|
|
|
4660
4870
|
// src/utils/SchemaMapper.ts
|
|
4661
4871
|
var SchemaMapper = class {
|
|
@@ -4776,6 +4986,57 @@ var LRUEmbeddingCache = class {
|
|
|
4776
4986
|
return this.cache.size;
|
|
4777
4987
|
}
|
|
4778
4988
|
};
|
|
4989
|
+
function estimateTokens(text) {
|
|
4990
|
+
return Math.ceil(text.length / 4);
|
|
4991
|
+
}
|
|
4992
|
+
var MODEL_COST_PER_1K = {
|
|
4993
|
+
"gpt-4o": { input: 25e-4, output: 0.01 },
|
|
4994
|
+
"gpt-4o-mini": { input: 15e-5, output: 6e-4 },
|
|
4995
|
+
"gpt-4-turbo": { input: 0.01, output: 0.03 },
|
|
4996
|
+
"gpt-3.5-turbo": { input: 5e-4, output: 15e-4 },
|
|
4997
|
+
"claude-3-5-sonnet": { input: 3e-3, output: 0.015 },
|
|
4998
|
+
"claude-3-haiku": { input: 25e-5, output: 125e-5 },
|
|
4999
|
+
"gemini-1.5-flash": { input: 75e-6, output: 3e-4 },
|
|
5000
|
+
"gemini-1.5-pro": { input: 125e-5, output: 5e-3 }
|
|
5001
|
+
};
|
|
5002
|
+
function estimateCostUsd(promptTokens, completionTokens, model) {
|
|
5003
|
+
if (!model) return void 0;
|
|
5004
|
+
const key = Object.keys(MODEL_COST_PER_1K).find((k) => model.toLowerCase().includes(k));
|
|
5005
|
+
if (!key) return void 0;
|
|
5006
|
+
const prices = MODEL_COST_PER_1K[key];
|
|
5007
|
+
return promptTokens / 1e3 * prices.input + completionTokens / 1e3 * prices.output;
|
|
5008
|
+
}
|
|
5009
|
+
async function scoreHallucination(llm, answer, context) {
|
|
5010
|
+
const maxContextChars = 3e3;
|
|
5011
|
+
const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
|
|
5012
|
+
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.
|
|
5013
|
+
|
|
5014
|
+
CONTEXT:
|
|
5015
|
+
${truncatedContext}
|
|
5016
|
+
|
|
5017
|
+
ANSWER:
|
|
5018
|
+
${answer}
|
|
5019
|
+
|
|
5020
|
+
Return ONLY a valid JSON object with no markdown fences:
|
|
5021
|
+
{"score": <float 0-1>, "reason": "<one sentence>"}
|
|
5022
|
+
|
|
5023
|
+
Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
|
|
5024
|
+
try {
|
|
5025
|
+
const raw = await llm.chat(
|
|
5026
|
+
[{ role: "user", content: prompt }],
|
|
5027
|
+
"",
|
|
5028
|
+
{ temperature: 0, maxTokens: 120 }
|
|
5029
|
+
);
|
|
5030
|
+
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
5031
|
+
if (!jsonMatch) return void 0;
|
|
5032
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
5033
|
+
if (typeof parsed.score === "number" && typeof parsed.reason === "string") {
|
|
5034
|
+
return { score: Math.min(1, Math.max(0, parsed.score)), reason: parsed.reason };
|
|
5035
|
+
}
|
|
5036
|
+
} catch (e) {
|
|
5037
|
+
}
|
|
5038
|
+
return void 0;
|
|
5039
|
+
}
|
|
4779
5040
|
var Pipeline = class {
|
|
4780
5041
|
constructor(config) {
|
|
4781
5042
|
this.config = config;
|
|
@@ -4802,8 +5063,7 @@ var Pipeline = class {
|
|
|
4802
5063
|
async initialize() {
|
|
4803
5064
|
var _a;
|
|
4804
5065
|
if (this.initialised) return;
|
|
4805
|
-
const chartInstruction = `
|
|
4806
|
-
You are a helpful product assistant. Use the provided context to answer questions accurately.
|
|
5066
|
+
const chartInstruction = `You are a helpful product assistant. Use the provided context to answer questions accurately.
|
|
4807
5067
|
|
|
4808
5068
|
### UI STYLE RULES (CRITICAL):
|
|
4809
5069
|
- NEVER generate markdown tables. If you do, the UI will break.
|
|
@@ -4817,7 +5077,6 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4817
5077
|
- Do NOT try to format product lists as tables.
|
|
4818
5078
|
`;
|
|
4819
5079
|
this.config.llm.systemPrompt = chartInstruction;
|
|
4820
|
-
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
4821
5080
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
4822
5081
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
4823
5082
|
this.config.llm,
|
|
@@ -4839,7 +5098,6 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4839
5098
|
}
|
|
4840
5099
|
/**
|
|
4841
5100
|
* Ingest documents with automatic chunking, embedding, and batch upsert.
|
|
4842
|
-
* Handles retries for transient failures.
|
|
4843
5101
|
*/
|
|
4844
5102
|
async ingest(documents, namespace) {
|
|
4845
5103
|
await this.initialize();
|
|
@@ -4856,10 +5114,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4856
5114
|
metadata: chunk.metadata
|
|
4857
5115
|
}));
|
|
4858
5116
|
const totalProcessed = await this.processUpserts(upsertDocs, ns);
|
|
4859
|
-
results.push({
|
|
4860
|
-
docId: doc.docId,
|
|
4861
|
-
chunksIngested: totalProcessed
|
|
4862
|
-
});
|
|
5117
|
+
results.push({ docId: doc.docId, chunksIngested: totalProcessed });
|
|
4863
5118
|
if (this.graphDB && this.entityExtractor) {
|
|
4864
5119
|
await this.processGraphIngestion(doc.docId, chunks);
|
|
4865
5120
|
}
|
|
@@ -4870,23 +5125,18 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4870
5125
|
}
|
|
4871
5126
|
return results;
|
|
4872
5127
|
}
|
|
4873
|
-
/**
|
|
4874
|
-
* Step 1: Chunk the document content.
|
|
4875
|
-
*/
|
|
5128
|
+
/** Step 1: Chunk the document content. */
|
|
4876
5129
|
async prepareChunks(doc) {
|
|
4877
5130
|
var _a, _b;
|
|
4878
5131
|
if (this.llamaIngestor) {
|
|
4879
|
-
return
|
|
5132
|
+
return this.llamaIngestor.chunk(doc.content, {
|
|
4880
5133
|
docId: doc.docId,
|
|
4881
5134
|
metadata: doc.metadata,
|
|
4882
5135
|
chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
|
|
4883
5136
|
chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
|
|
4884
5137
|
});
|
|
4885
5138
|
}
|
|
4886
|
-
return this.chunker.chunk(doc.content, {
|
|
4887
|
-
docId: doc.docId,
|
|
4888
|
-
metadata: doc.metadata
|
|
4889
|
-
});
|
|
5139
|
+
return this.chunker.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata });
|
|
4890
5140
|
}
|
|
4891
5141
|
/**
|
|
4892
5142
|
* Step 2: Generate embeddings for chunks with retry logic.
|
|
@@ -4910,9 +5160,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4910
5160
|
}
|
|
4911
5161
|
return vectors;
|
|
4912
5162
|
}
|
|
4913
|
-
/**
|
|
4914
|
-
* Step 3: Upsert chunks to vector database with retry logic.
|
|
4915
|
-
*/
|
|
5163
|
+
/** Step 3: Upsert chunks to vector database with retry logic. */
|
|
4916
5164
|
async processUpserts(upsertDocs, namespace) {
|
|
4917
5165
|
const upsertBatchOptions = {
|
|
4918
5166
|
batchSize: 100,
|
|
@@ -4929,14 +5177,10 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4929
5177
|
}
|
|
4930
5178
|
return upsertResult.totalProcessed;
|
|
4931
5179
|
}
|
|
4932
|
-
/**
|
|
4933
|
-
* Step 4: Optional graph-based entity extraction and ingestion.
|
|
4934
|
-
*/
|
|
5180
|
+
/** Step 4: Optional graph-based entity extraction and ingestion. */
|
|
4935
5181
|
async processGraphIngestion(docId, chunks) {
|
|
4936
|
-
console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
|
|
4937
5182
|
const extractionOptions = {
|
|
4938
5183
|
batchSize: 2,
|
|
4939
|
-
// Low concurrency for LLM extraction
|
|
4940
5184
|
maxRetries: 1,
|
|
4941
5185
|
initialDelayMs: 500
|
|
4942
5186
|
};
|
|
@@ -4949,7 +5193,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4949
5193
|
if (nodes.length > 0) await this.graphDB.addNodes(nodes);
|
|
4950
5194
|
if (edges.length > 0) await this.graphDB.addEdges(edges);
|
|
4951
5195
|
} catch (err) {
|
|
4952
|
-
console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
|
|
5196
|
+
console.warn(`[Pipeline] Entity extraction failed for chunk in doc ${docId}:`, err);
|
|
4953
5197
|
}
|
|
4954
5198
|
}
|
|
4955
5199
|
},
|
|
@@ -4960,7 +5204,6 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4960
5204
|
var _a;
|
|
4961
5205
|
await this.initialize();
|
|
4962
5206
|
if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
|
|
4963
|
-
console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
|
|
4964
5207
|
const agentReply = await this.agent.run(question, history);
|
|
4965
5208
|
return { reply: agentReply, sources: [] };
|
|
4966
5209
|
}
|
|
@@ -4969,6 +5212,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4969
5212
|
let sources = [];
|
|
4970
5213
|
let graphData;
|
|
4971
5214
|
let uiTransformation;
|
|
5215
|
+
let trace;
|
|
4972
5216
|
try {
|
|
4973
5217
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
4974
5218
|
const chunk = temp.value;
|
|
@@ -4978,6 +5222,7 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4978
5222
|
if ("sources" in chunk) sources = chunk.sources;
|
|
4979
5223
|
if ("graphData" in chunk) graphData = chunk.graphData;
|
|
4980
5224
|
if ("ui_transformation" in chunk) uiTransformation = chunk.ui_transformation;
|
|
5225
|
+
if ("trace" in chunk) trace = chunk.trace;
|
|
4981
5226
|
}
|
|
4982
5227
|
}
|
|
4983
5228
|
} catch (temp) {
|
|
@@ -4990,41 +5235,51 @@ You are a helpful product assistant. Use the provided context to answer question
|
|
|
4990
5235
|
throw error[0];
|
|
4991
5236
|
}
|
|
4992
5237
|
}
|
|
4993
|
-
return { reply, sources, graphData, ui_transformation: uiTransformation };
|
|
5238
|
+
return { reply, sources, graphData, ui_transformation: uiTransformation, trace };
|
|
4994
5239
|
}
|
|
4995
5240
|
/**
|
|
4996
5241
|
* High-performance streaming RAG flow.
|
|
4997
|
-
* Yields text chunks first, then the retrieval metadata at the end.
|
|
5242
|
+
* Yields text chunks first, then the retrieval metadata + observability trace at the end.
|
|
4998
5243
|
*/
|
|
4999
5244
|
askStream(_0) {
|
|
5000
5245
|
return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
|
|
5001
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
5246
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
5002
5247
|
yield new __await(this.initialize());
|
|
5003
5248
|
const ns = namespace != null ? namespace : this.config.projectId;
|
|
5004
5249
|
const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
|
|
5005
5250
|
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
|
|
5251
|
+
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
5252
|
+
const requestStart = performance.now();
|
|
5006
5253
|
try {
|
|
5007
5254
|
let searchQuery = question;
|
|
5255
|
+
let rewrittenQuery;
|
|
5008
5256
|
if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
|
|
5009
5257
|
searchQuery = yield new __await(this.rewriteQuery(question, history));
|
|
5258
|
+
rewrittenQuery = searchQuery !== question ? searchQuery : void 0;
|
|
5010
5259
|
}
|
|
5011
5260
|
const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
|
|
5012
5261
|
const filter = QueryProcessor.buildQueryFilter(question, hints);
|
|
5013
|
-
|
|
5262
|
+
const embedStart = performance.now();
|
|
5014
5263
|
const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
|
|
5015
5264
|
namespace: ns,
|
|
5016
5265
|
topK: topK * 2,
|
|
5017
5266
|
filter
|
|
5018
5267
|
}));
|
|
5268
|
+
const retrieveEnd = performance.now();
|
|
5269
|
+
const embedMs = retrieveEnd - embedStart;
|
|
5270
|
+
const retrieveMs = retrieveEnd - embedStart;
|
|
5271
|
+
const rerankStart = performance.now();
|
|
5019
5272
|
let sources = rawSources.filter((m) => m.score >= scoreThreshold);
|
|
5020
5273
|
if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
|
|
5021
5274
|
sources = yield new __await(this.reranker.rerank(sources, question, topK));
|
|
5022
5275
|
} else {
|
|
5023
5276
|
sources = sources.slice(0, topK);
|
|
5024
5277
|
}
|
|
5278
|
+
const rerankMs = performance.now() - rerankStart;
|
|
5025
5279
|
let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
|
|
5026
5280
|
${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
|
|
5027
5281
|
if (graphData && graphData.nodes.length > 0) {
|
|
5282
|
+
console.log(`[Graph Retrieval] Found ${graphData.nodes.length} relevant entities.`);
|
|
5028
5283
|
const graphContext = graphData.nodes.map(
|
|
5029
5284
|
(n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
|
|
5030
5285
|
).join("\n");
|
|
@@ -5040,6 +5295,10 @@ ${context}`;
|
|
|
5040
5295
|
const hardenedHistory = [...history];
|
|
5041
5296
|
const userQuestion = { role: "user", content: question + restrictionSuffix };
|
|
5042
5297
|
const messages = [...hardenedHistory, userQuestion];
|
|
5298
|
+
const systemPrompt = (_h = this.config.llm.systemPrompt) != null ? _h : "";
|
|
5299
|
+
const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
5300
|
+
let fullReply = "";
|
|
5301
|
+
const generateStart = performance.now();
|
|
5043
5302
|
if (this.llmProvider.chatStream) {
|
|
5044
5303
|
const stream = this.llmProvider.chatStream(messages, context);
|
|
5045
5304
|
if (!stream) {
|
|
@@ -5048,6 +5307,7 @@ ${context}`;
|
|
|
5048
5307
|
try {
|
|
5049
5308
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
5050
5309
|
const chunk = temp.value;
|
|
5310
|
+
fullReply += chunk;
|
|
5051
5311
|
yield chunk;
|
|
5052
5312
|
}
|
|
5053
5313
|
} catch (temp) {
|
|
@@ -5062,15 +5322,58 @@ ${context}`;
|
|
|
5062
5322
|
}
|
|
5063
5323
|
} else {
|
|
5064
5324
|
const reply = yield new __await(this.llmProvider.chat(messages, context));
|
|
5325
|
+
fullReply = reply;
|
|
5065
5326
|
yield reply;
|
|
5066
5327
|
}
|
|
5328
|
+
const generateMs = performance.now() - generateStart;
|
|
5329
|
+
const totalMs = performance.now() - requestStart;
|
|
5330
|
+
const latency = {
|
|
5331
|
+
embedMs: Math.round(embedMs),
|
|
5332
|
+
retrieveMs: Math.round(retrieveMs),
|
|
5333
|
+
rerankMs: ((_i = this.config.rag) == null ? void 0 : _i.useReranking) ? Math.round(rerankMs) : void 0,
|
|
5334
|
+
generateMs: Math.round(generateMs),
|
|
5335
|
+
totalMs: Math.round(totalMs)
|
|
5336
|
+
};
|
|
5337
|
+
const promptText = systemPrompt + "\n" + context + "\n" + userPrompt;
|
|
5338
|
+
const promptTokens = estimateTokens(promptText);
|
|
5339
|
+
const completionTokens = estimateTokens(fullReply);
|
|
5340
|
+
const tokens = {
|
|
5341
|
+
promptTokens,
|
|
5342
|
+
completionTokens,
|
|
5343
|
+
totalTokens: promptTokens + completionTokens,
|
|
5344
|
+
estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
|
|
5345
|
+
};
|
|
5346
|
+
const hallucinationResult = yield new __await(scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0));
|
|
5067
5347
|
const trainedSchema = yield new __await(trainingPromise);
|
|
5068
|
-
const uiTransformation = yield new __await(this.generateUiTransformation(question, sources,
|
|
5348
|
+
const uiTransformation = yield new __await(this.generateUiTransformation(question, sources, trainedSchema));
|
|
5349
|
+
const trace = {
|
|
5350
|
+
requestId,
|
|
5351
|
+
query: question,
|
|
5352
|
+
rewrittenQuery,
|
|
5353
|
+
systemPrompt,
|
|
5354
|
+
userPrompt: question + restrictionSuffix,
|
|
5355
|
+
chunks: sources.map((s) => {
|
|
5356
|
+
var _a2;
|
|
5357
|
+
return {
|
|
5358
|
+
id: s.id,
|
|
5359
|
+
score: s.score,
|
|
5360
|
+
content: s.content,
|
|
5361
|
+
metadata: (_a2 = s.metadata) != null ? _a2 : {},
|
|
5362
|
+
namespace: ns
|
|
5363
|
+
};
|
|
5364
|
+
}),
|
|
5365
|
+
latency,
|
|
5366
|
+
tokens,
|
|
5367
|
+
hallucinationScore: hallucinationResult == null ? void 0 : hallucinationResult.score,
|
|
5368
|
+
hallucinationReason: hallucinationResult == null ? void 0 : hallucinationResult.reason,
|
|
5369
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5370
|
+
};
|
|
5069
5371
|
yield {
|
|
5070
5372
|
reply: "",
|
|
5071
5373
|
sources,
|
|
5072
5374
|
graphData,
|
|
5073
|
-
ui_transformation: uiTransformation
|
|
5375
|
+
ui_transformation: uiTransformation,
|
|
5376
|
+
trace
|
|
5074
5377
|
};
|
|
5075
5378
|
} catch (error2) {
|
|
5076
5379
|
throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
@@ -5081,7 +5384,7 @@ ${context}`;
|
|
|
5081
5384
|
* Universal retrieval method combining all enabled providers.
|
|
5082
5385
|
* Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
|
|
5083
5386
|
*/
|
|
5084
|
-
async generateUiTransformation(question, sources,
|
|
5387
|
+
async generateUiTransformation(question, sources, trainedSchema) {
|
|
5085
5388
|
if (!sources || sources.length === 0) {
|
|
5086
5389
|
return UITransformer.transform(question, sources, this.config, trainedSchema);
|
|
5087
5390
|
}
|
|
@@ -5098,23 +5401,24 @@ ${context}`;
|
|
|
5098
5401
|
const topK = (_b = options.topK) != null ? _b : 5;
|
|
5099
5402
|
const cacheKey = `${ns}::${query}`;
|
|
5100
5403
|
let queryVector = this.embeddingCache.get(cacheKey);
|
|
5404
|
+
const strategy = await QueryProcessor.determineRetrievalStrategy(query, this.llmProvider);
|
|
5405
|
+
console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
|
|
5101
5406
|
const [retrievedVector, graphData] = await Promise.all([
|
|
5102
|
-
|
|
5103
|
-
|
|
5407
|
+
// Only embed if we need vector search (strategy is 'vector' or 'both')
|
|
5408
|
+
strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
|
|
5409
|
+
// Only query graph if we need graph search (strategy is 'graph' or 'both')
|
|
5410
|
+
(strategy === "graph" || strategy === "both") && this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
|
|
5104
5411
|
]);
|
|
5105
|
-
if (!queryVector) {
|
|
5412
|
+
if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
|
|
5106
5413
|
this.embeddingCache.set(cacheKey, retrievedVector);
|
|
5107
5414
|
queryVector = retrievedVector;
|
|
5108
5415
|
}
|
|
5109
|
-
const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
|
|
5416
|
+
const sources = (strategy === "vector" || strategy === "both") && queryVector && queryVector.length > 0 ? await this.vectorDB.query(queryVector, topK, ns, options.filter) : [];
|
|
5110
5417
|
return { sources, graphData };
|
|
5111
5418
|
}
|
|
5112
|
-
/**
|
|
5113
|
-
* Rewrite the user query for better retrieval performance.
|
|
5114
|
-
*/
|
|
5419
|
+
/** Rewrite the user query for better retrieval performance. */
|
|
5115
5420
|
async rewriteQuery(question, history) {
|
|
5116
|
-
const prompt = `
|
|
5117
|
-
Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
|
|
5421
|
+
const prompt = `Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
|
|
5118
5422
|
Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
|
|
5119
5423
|
|
|
5120
5424
|
History:
|
|
@@ -5132,26 +5436,19 @@ Optimized Search Query:`;
|
|
|
5132
5436
|
);
|
|
5133
5437
|
return rewrite.trim() || question;
|
|
5134
5438
|
}
|
|
5135
|
-
/**
|
|
5136
|
-
* Generate 3-5 short, relevant questions based on the vector database content.
|
|
5137
|
-
*/
|
|
5439
|
+
/** Generate 3-5 short, relevant questions based on the vector database content. */
|
|
5138
5440
|
async getSuggestions(query, namespace) {
|
|
5139
|
-
if (!query || query.trim().length < 3)
|
|
5140
|
-
return [];
|
|
5141
|
-
}
|
|
5441
|
+
if (!query || query.trim().length < 3) return [];
|
|
5142
5442
|
await this.initialize();
|
|
5143
5443
|
const ns = namespace != null ? namespace : this.config.projectId;
|
|
5144
5444
|
try {
|
|
5145
|
-
const { sources } = await this.retrieve(query, { namespace: ns, topK:
|
|
5146
|
-
if (sources.length === 0)
|
|
5147
|
-
return [];
|
|
5148
|
-
}
|
|
5445
|
+
const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
|
|
5446
|
+
if (sources.length === 0) return [];
|
|
5149
5447
|
const context = sources.map((s) => s.content).join("\n\n---\n\n");
|
|
5150
|
-
const prompt = `
|
|
5151
|
-
Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
|
|
5448
|
+
const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
|
|
5152
5449
|
Focus on questions that can be answered by the context.
|
|
5153
5450
|
Keep each question under 10 words and make them very specific to the content.
|
|
5154
|
-
Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
|
|
5451
|
+
Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3", "Question 4", "Question 5"].
|
|
5155
5452
|
|
|
5156
5453
|
Context:
|
|
5157
5454
|
${context}
|
|
@@ -5168,7 +5465,7 @@ Suggestions:`;
|
|
|
5168
5465
|
if (match) {
|
|
5169
5466
|
const suggestions = JSON.parse(match[0]);
|
|
5170
5467
|
if (Array.isArray(suggestions)) {
|
|
5171
|
-
return suggestions.map((s) => String(s)).slice(0,
|
|
5468
|
+
return suggestions.map((s) => String(s)).slice(0, 5);
|
|
5172
5469
|
}
|
|
5173
5470
|
}
|
|
5174
5471
|
} catch (error) {
|
|
@@ -5410,6 +5707,20 @@ var ConfigBuilder = class {
|
|
|
5410
5707
|
}
|
|
5411
5708
|
return this;
|
|
5412
5709
|
}
|
|
5710
|
+
/**
|
|
5711
|
+
* Configure the graph database provider
|
|
5712
|
+
*/
|
|
5713
|
+
graphDb(provider, options) {
|
|
5714
|
+
if (provider === "auto") {
|
|
5715
|
+
this._graphDb = this._autoDetectGraphDb();
|
|
5716
|
+
} else {
|
|
5717
|
+
this._graphDb = {
|
|
5718
|
+
provider,
|
|
5719
|
+
options: options != null ? options : {}
|
|
5720
|
+
};
|
|
5721
|
+
}
|
|
5722
|
+
return this;
|
|
5723
|
+
}
|
|
5413
5724
|
/**
|
|
5414
5725
|
* Set RAG-specific pipeline parameters
|
|
5415
5726
|
*/
|
|
@@ -5443,13 +5754,18 @@ var ConfigBuilder = class {
|
|
|
5443
5754
|
${missing.join("\n ")}`
|
|
5444
5755
|
);
|
|
5445
5756
|
}
|
|
5757
|
+
const ragConfig = (_a = this._rag) != null ? _a : { chunkSize: 1e3, chunkOverlap: 200, topK: 5 };
|
|
5758
|
+
if (process.env.RAG_USE_GRAPH_RETRIEVAL === "true") {
|
|
5759
|
+
ragConfig.useGraphRetrieval = true;
|
|
5760
|
+
}
|
|
5446
5761
|
return __spreadValues({
|
|
5447
5762
|
projectId: this._projectId,
|
|
5448
5763
|
vectorDb: this._vectorDb,
|
|
5449
5764
|
llm: this._llm,
|
|
5450
5765
|
embedding: this._embedding,
|
|
5451
5766
|
ui: this._rag ? this._ui : void 0,
|
|
5452
|
-
rag:
|
|
5767
|
+
rag: ragConfig,
|
|
5768
|
+
graphDb: this._graphDb
|
|
5453
5769
|
}, this._ui ? { ui: this._ui } : {});
|
|
5454
5770
|
}
|
|
5455
5771
|
/**
|
|
@@ -5497,6 +5813,15 @@ var ConfigBuilder = class {
|
|
|
5497
5813
|
}
|
|
5498
5814
|
return { provider: "openai", model: "text-embedding-3-small", apiKey: process.env.OPENAI_API_KEY };
|
|
5499
5815
|
}
|
|
5816
|
+
_autoDetectGraphDb() {
|
|
5817
|
+
if (process.env.GRAPH_DB_PROVIDER) {
|
|
5818
|
+
return {
|
|
5819
|
+
provider: process.env.GRAPH_DB_PROVIDER,
|
|
5820
|
+
options: {}
|
|
5821
|
+
};
|
|
5822
|
+
}
|
|
5823
|
+
return void 0;
|
|
5824
|
+
}
|
|
5500
5825
|
};
|
|
5501
5826
|
var PRESETS = {
|
|
5502
5827
|
/** OpenAI + Pinecone: Production-ready cloud setup */
|
|
@@ -5808,6 +6133,11 @@ function sseMetaFrame(meta) {
|
|
|
5808
6133
|
function sseUIFrame(uiTransformation) {
|
|
5809
6134
|
return `data: ${JSON.stringify({ type: "ui_transformation", data: uiTransformation })}
|
|
5810
6135
|
|
|
6136
|
+
`;
|
|
6137
|
+
}
|
|
6138
|
+
function sseObservabilityFrame(trace) {
|
|
6139
|
+
return `data: ${JSON.stringify({ type: "observability", data: trace })}
|
|
6140
|
+
|
|
5811
6141
|
`;
|
|
5812
6142
|
}
|
|
5813
6143
|
function sseErrorFrame(message) {
|
|
@@ -5874,6 +6204,9 @@ function createStreamHandler(configOrPlugin) {
|
|
|
5874
6204
|
enqueue(sseMetaFrame(chunk));
|
|
5875
6205
|
const responseChunk = chunk;
|
|
5876
6206
|
const sources = (responseChunk == null ? void 0 : responseChunk.sources) || [];
|
|
6207
|
+
if (responseChunk == null ? void 0 : responseChunk.trace) {
|
|
6208
|
+
enqueue(sseObservabilityFrame(responseChunk.trace));
|
|
6209
|
+
}
|
|
5877
6210
|
if (sources.length > 0) {
|
|
5878
6211
|
try {
|
|
5879
6212
|
const llmProvider = (_a = plugin.getLLMProvider) == null ? void 0 : _a.call(plugin);
|
|
@@ -5955,24 +6288,64 @@ function createUploadHandler(configOrPlugin) {
|
|
|
5955
6288
|
const formData = await req.formData();
|
|
5956
6289
|
const files = formData.getAll("files");
|
|
5957
6290
|
const namespace = formData.get("namespace") || void 0;
|
|
6291
|
+
const dimensionRaw = formData.get("dimension");
|
|
6292
|
+
const dimension = dimensionRaw ? parseInt(dimensionRaw, 10) : void 0;
|
|
5958
6293
|
if (!files || files.length === 0) {
|
|
5959
6294
|
return import_server.NextResponse.json({ error: "No files provided" }, { status: 400 });
|
|
5960
6295
|
}
|
|
5961
|
-
const documents =
|
|
5962
|
-
|
|
6296
|
+
const documents = [];
|
|
6297
|
+
for (const file of files) {
|
|
6298
|
+
if (file.name.toLowerCase().endsWith(".csv") || file.type === "text/csv") {
|
|
6299
|
+
const text = await file.text();
|
|
6300
|
+
const Papa = await import("papaparse");
|
|
6301
|
+
const parsed = Papa.default.parse(text, { header: true, skipEmptyLines: true });
|
|
6302
|
+
if (parsed.data && parsed.data.length > 0) {
|
|
6303
|
+
let i = 0;
|
|
6304
|
+
let lastRowData = null;
|
|
6305
|
+
for (const row of parsed.data) {
|
|
6306
|
+
i++;
|
|
6307
|
+
const rowData = row;
|
|
6308
|
+
const groupingKey = Object.keys(rowData)[0];
|
|
6309
|
+
if (lastRowData && groupingKey && rowData[groupingKey] && rowData[groupingKey] === lastRowData[groupingKey]) {
|
|
6310
|
+
for (const key of Object.keys(rowData)) {
|
|
6311
|
+
if (!rowData[key] && lastRowData[key]) {
|
|
6312
|
+
rowData[key] = lastRowData[key];
|
|
6313
|
+
}
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
lastRowData = __spreadValues({}, rowData);
|
|
6317
|
+
const contentParts = [];
|
|
6318
|
+
for (const [key, val] of Object.entries(rowData)) {
|
|
6319
|
+
if (key && val) {
|
|
6320
|
+
contentParts.push(`${key}: ${val}`);
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
documents.push({
|
|
6324
|
+
docId: `${file.name}-row-${i}`,
|
|
6325
|
+
content: contentParts.join(", "),
|
|
6326
|
+
metadata: __spreadValues(__spreadValues({
|
|
6327
|
+
fileName: file.name,
|
|
6328
|
+
fileSize: file.size,
|
|
6329
|
+
fileType: file.type,
|
|
6330
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6331
|
+
}, dimension ? { dimension } : {}), rowData)
|
|
6332
|
+
});
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
} else {
|
|
5963
6336
|
const content = await DocumentParser.parse(file, file.name, file.type);
|
|
5964
|
-
|
|
6337
|
+
documents.push({
|
|
5965
6338
|
docId: file.name,
|
|
5966
6339
|
content,
|
|
5967
|
-
metadata: {
|
|
6340
|
+
metadata: __spreadValues({
|
|
5968
6341
|
fileName: file.name,
|
|
5969
6342
|
fileSize: file.size,
|
|
5970
6343
|
fileType: file.type,
|
|
5971
6344
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5972
|
-
}
|
|
5973
|
-
};
|
|
5974
|
-
}
|
|
5975
|
-
|
|
6345
|
+
}, dimension ? { dimension } : {})
|
|
6346
|
+
});
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
5976
6349
|
const results = await plugin.ingest(documents, namespace);
|
|
5977
6350
|
return import_server.NextResponse.json({ message: "Upload successful", results });
|
|
5978
6351
|
} catch (err) {
|