@retrivora-ai/rag-engine 1.8.1 → 1.8.2

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.
Files changed (55) hide show
  1. package/dist/{ILLMProvider-BfRgI1Xh.d.mts → ILLMProvider-BOJFz3Na.d.mts} +47 -1
  2. package/dist/{ILLMProvider-BfRgI1Xh.d.ts → ILLMProvider-BOJFz3Na.d.ts} +47 -1
  3. package/dist/{MultiTablePostgresProvider-YY7LPNJK.mjs → MultiTablePostgresProvider-ZLGSKTJR.mjs} +1 -1
  4. package/dist/chunk-ICKRMZQK.mjs +76 -0
  5. package/dist/{chunk-BFYLQYQU.mjs → chunk-LZVVLSDN.mjs} +192 -100
  6. package/dist/{chunk-R3RGUMHE.mjs → chunk-OZFBG4BA.mjs} +121 -48
  7. package/dist/handlers/index.d.mts +2 -2
  8. package/dist/handlers/index.d.ts +2 -2
  9. package/dist/handlers/index.js +368 -147
  10. package/dist/handlers/index.mjs +4 -1
  11. package/dist/{index-BV0z5mb6.d.mts → index-BwpcaziY.d.ts} +4 -2
  12. package/dist/{index-1Z4GuYBi.d.ts → index-D3V9Et2M.d.mts} +4 -2
  13. package/dist/index.d.mts +23 -3
  14. package/dist/index.d.ts +23 -3
  15. package/dist/index.js +1143 -790
  16. package/dist/index.mjs +1065 -770
  17. package/dist/server.d.mts +15 -25
  18. package/dist/server.d.ts +15 -25
  19. package/dist/server.js +366 -147
  20. package/dist/server.mjs +3 -2
  21. package/package.json +4 -2
  22. package/src/app/api/upload/route.ts +4 -0
  23. package/src/app/constants.tsx +2 -2
  24. package/src/app/page.tsx +12 -321
  25. package/src/components/AmbientBackground.tsx +29 -0
  26. package/src/components/ArchitectureCard.tsx +17 -0
  27. package/src/components/ArchitectureCardsSection.tsx +15 -0
  28. package/src/components/ChatWindow.tsx +32 -0
  29. package/src/components/CodeViewer.tsx +51 -0
  30. package/src/components/ConfigProvider.tsx +1 -0
  31. package/src/components/DocViewer.tsx +37 -0
  32. package/src/components/DocumentUpload.tsx +44 -1
  33. package/src/components/Documentation.tsx +58 -0
  34. package/src/components/DynamicChart.tsx +27 -2
  35. package/src/components/Hero.tsx +59 -0
  36. package/src/components/HourglassLoader.tsx +87 -0
  37. package/src/components/Lifecycle.tsx +37 -0
  38. package/src/components/MarkdownComponents.tsx +140 -0
  39. package/src/components/MessageBubble.tsx +88 -1010
  40. package/src/components/Navbar.tsx +55 -0
  41. package/src/components/ObservabilityPanel.tsx +374 -0
  42. package/src/components/ProductCard.tsx +3 -1
  43. package/src/components/UIDispatcher.tsx +344 -0
  44. package/src/components/VisualizationRenderer.tsx +48 -26
  45. package/src/core/Pipeline.ts +186 -76
  46. package/src/handlers/index.ts +72 -12
  47. package/src/hooks/useRagChat.ts +19 -9
  48. package/src/index.ts +9 -1
  49. package/src/providers/vectordb/MultiTablePostgresProvider.ts +150 -64
  50. package/src/types/chat.ts +2 -0
  51. package/src/types/index.ts +52 -0
  52. package/src/types/props.ts +9 -1
  53. package/src/utils/ProductExtractor.ts +347 -0
  54. package/src/utils/UITransformer.ts +4 -53
  55. package/src/utils/synonyms.ts +78 -0
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, _d, _e;
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
- const rawTables = (_c = (_b = opts.tables) != null ? _b : process.env.VECTOR_DB_TABLES) != null ? _c : "";
359
- this.tables = typeof rawTables === "string" ? rawTables.split(",").map((t) => t.trim()).filter(Boolean) : rawTables;
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
- const discoveredTables = res.rows.map((r) => r.table_name);
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 is not supported for MultiTablePostgresProvider as it's designed for
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(_doc, _namespace) {
405
- throw new Error(
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 is not supported for MultiTablePostgresProvider.
407
+ * Batch upsert documents by dynamically provisioning tables and columns based on CSV headers.
411
408
  */
412
- async batchUpsert(_docs, _namespace) {
413
- throw new Error(
414
- "[MultiTablePostgresProvider] batchUpsert() is not supported in multi-table mode."
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, _b, _c;
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
- const resultsByTable = {};
514
- for (const res of allResults) {
515
- const table = (_a = res.metadata) == null ? void 0 : _a.source_table;
516
- if (!resultsByTable[table]) resultsByTable[table] = [];
517
- resultsByTable[table].push(res);
518
- }
519
- const balancedResults = [];
520
- const tables = Object.keys(resultsByTable);
521
- for (const table of tables) {
522
- balancedResults.push(...resultsByTable[table].slice(0, 3));
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) {
@@ -3978,6 +4051,64 @@ var QueryProcessor = class {
3978
4051
  }
3979
4052
  };
3980
4053
 
4054
+ // src/utils/synonyms.ts
4055
+ var FIELD_SYNONYMS = {
4056
+ name: ["product", "item", "title", "label", "heading", "subject", "product name", "item name"],
4057
+ price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
4058
+ brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
4059
+ image: [
4060
+ "imageUrl",
4061
+ "thumbnail",
4062
+ "img",
4063
+ "url",
4064
+ "photo",
4065
+ "picture",
4066
+ "media",
4067
+ "image_url",
4068
+ "main_image",
4069
+ "product_image",
4070
+ "thumb"
4071
+ ],
4072
+ stock: [
4073
+ "inventory",
4074
+ "quantity",
4075
+ "count",
4076
+ "availability",
4077
+ "stock_level",
4078
+ "inStock",
4079
+ "is_available",
4080
+ "in stock",
4081
+ "status"
4082
+ ],
4083
+ description: ["summary", "content", "body", "text", "info", "details"],
4084
+ link: ["url", "href", "product_url", "page_url", "link"]
4085
+ };
4086
+ function resolveMetadataValue(meta, uiKey) {
4087
+ var _a;
4088
+ const synonyms = (_a = FIELD_SYNONYMS[uiKey]) != null ? _a : [];
4089
+ const keys = Object.keys(meta);
4090
+ const systemKeys = ["namespace", "filename", "filesize", "filetype", "docid", "uploadedat", "dimension", "chunkindex"];
4091
+ let match = keys.find((k) => k.toLowerCase() === uiKey.toLowerCase());
4092
+ if (match !== void 0) return meta[match];
4093
+ match = keys.find((k) => synonyms.map((s) => s.toLowerCase()).includes(k.toLowerCase()));
4094
+ if (match !== void 0) return meta[match];
4095
+ const isBlacklisted = (kl) => {
4096
+ return systemKeys.includes(kl) || kl.startsWith("option1") || kl.startsWith("option2") || kl.startsWith("option3");
4097
+ };
4098
+ match = keys.find((k) => {
4099
+ const kl = k.toLowerCase();
4100
+ if (isBlacklisted(kl)) return false;
4101
+ return kl.includes(uiKey.toLowerCase());
4102
+ });
4103
+ if (match !== void 0) return meta[match];
4104
+ match = keys.find((k) => {
4105
+ const kl = k.toLowerCase();
4106
+ if (isBlacklisted(kl)) return false;
4107
+ return synonyms.some((sk) => kl.includes(sk.toLowerCase()) || sk.toLowerCase().includes(kl));
4108
+ });
4109
+ return match !== void 0 ? meta[match] : void 0;
4110
+ }
4111
+
3981
4112
  // src/utils/UITransformer.ts
3982
4113
  var UITransformer = class {
3983
4114
  /**
@@ -4045,28 +4176,6 @@ var UITransformer = class {
4045
4176
  data: pieData
4046
4177
  };
4047
4178
  }
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
4179
  /**
4071
4180
  * Transform data to line chart format
4072
4181
  */
@@ -4345,15 +4454,7 @@ var UITransformer = class {
4345
4454
  const trainedKey = trainedSchema[uiKey];
4346
4455
  if (trainedKey && meta[trainedKey] !== void 0) return meta[trainedKey];
4347
4456
  }
4348
- const synonyms = this.SYNONYMS[uiKey] || [];
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;
4457
+ return resolveMetadataValue(meta, uiKey);
4357
4458
  }
4358
4459
  static extractProductInfo(item, config, trainedSchema) {
4359
4460
  const meta = item.metadata || {};
@@ -4423,7 +4524,7 @@ var UITransformer = class {
4423
4524
  data.forEach((item) => {
4424
4525
  const meta = item.metadata || {};
4425
4526
  const itemCategory = meta.category || meta.type || "Other";
4426
- if (result.hasOwnProperty(itemCategory)) {
4527
+ if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
4427
4528
  result[itemCategory]++;
4428
4529
  } else {
4429
4530
  result["Other"] = (result["Other"] || 0) + 1;
@@ -4644,18 +4745,6 @@ IMPORTANT:
4644
4745
  return JSON.stringify(partial, null, 2) + "\n// ... truncated";
4645
4746
  }
4646
4747
  };
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
4748
 
4660
4749
  // src/utils/SchemaMapper.ts
4661
4750
  var SchemaMapper = class {
@@ -4776,6 +4865,57 @@ var LRUEmbeddingCache = class {
4776
4865
  return this.cache.size;
4777
4866
  }
4778
4867
  };
4868
+ function estimateTokens(text) {
4869
+ return Math.ceil(text.length / 4);
4870
+ }
4871
+ var MODEL_COST_PER_1K = {
4872
+ "gpt-4o": { input: 25e-4, output: 0.01 },
4873
+ "gpt-4o-mini": { input: 15e-5, output: 6e-4 },
4874
+ "gpt-4-turbo": { input: 0.01, output: 0.03 },
4875
+ "gpt-3.5-turbo": { input: 5e-4, output: 15e-4 },
4876
+ "claude-3-5-sonnet": { input: 3e-3, output: 0.015 },
4877
+ "claude-3-haiku": { input: 25e-5, output: 125e-5 },
4878
+ "gemini-1.5-flash": { input: 75e-6, output: 3e-4 },
4879
+ "gemini-1.5-pro": { input: 125e-5, output: 5e-3 }
4880
+ };
4881
+ function estimateCostUsd(promptTokens, completionTokens, model) {
4882
+ if (!model) return void 0;
4883
+ const key = Object.keys(MODEL_COST_PER_1K).find((k) => model.toLowerCase().includes(k));
4884
+ if (!key) return void 0;
4885
+ const prices = MODEL_COST_PER_1K[key];
4886
+ return promptTokens / 1e3 * prices.input + completionTokens / 1e3 * prices.output;
4887
+ }
4888
+ async function scoreHallucination(llm, answer, context) {
4889
+ const maxContextChars = 3e3;
4890
+ const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
4891
+ 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.
4892
+
4893
+ CONTEXT:
4894
+ ${truncatedContext}
4895
+
4896
+ ANSWER:
4897
+ ${answer}
4898
+
4899
+ Return ONLY a valid JSON object with no markdown fences:
4900
+ {"score": <float 0-1>, "reason": "<one sentence>"}
4901
+
4902
+ Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
4903
+ try {
4904
+ const raw = await llm.chat(
4905
+ [{ role: "user", content: prompt }],
4906
+ "",
4907
+ { temperature: 0, maxTokens: 120 }
4908
+ );
4909
+ const jsonMatch = raw.match(/\{[\s\S]*\}/);
4910
+ if (!jsonMatch) return void 0;
4911
+ const parsed = JSON.parse(jsonMatch[0]);
4912
+ if (typeof parsed.score === "number" && typeof parsed.reason === "string") {
4913
+ return { score: Math.min(1, Math.max(0, parsed.score)), reason: parsed.reason };
4914
+ }
4915
+ } catch (e) {
4916
+ }
4917
+ return void 0;
4918
+ }
4779
4919
  var Pipeline = class {
4780
4920
  constructor(config) {
4781
4921
  this.config = config;
@@ -4802,8 +4942,7 @@ var Pipeline = class {
4802
4942
  async initialize() {
4803
4943
  var _a;
4804
4944
  if (this.initialised) return;
4805
- const chartInstruction = `
4806
- You are a helpful product assistant. Use the provided context to answer questions accurately.
4945
+ const chartInstruction = `You are a helpful product assistant. Use the provided context to answer questions accurately.
4807
4946
 
4808
4947
  ### UI STYLE RULES (CRITICAL):
4809
4948
  - NEVER generate markdown tables. If you do, the UI will break.
@@ -4817,7 +4956,6 @@ You are a helpful product assistant. Use the provided context to answer question
4817
4956
  - Do NOT try to format product lists as tables.
4818
4957
  `;
4819
4958
  this.config.llm.systemPrompt = chartInstruction;
4820
- console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
4821
4959
  this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
4822
4960
  const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
4823
4961
  this.config.llm,
@@ -4839,7 +4977,6 @@ You are a helpful product assistant. Use the provided context to answer question
4839
4977
  }
4840
4978
  /**
4841
4979
  * Ingest documents with automatic chunking, embedding, and batch upsert.
4842
- * Handles retries for transient failures.
4843
4980
  */
4844
4981
  async ingest(documents, namespace) {
4845
4982
  await this.initialize();
@@ -4856,10 +4993,7 @@ You are a helpful product assistant. Use the provided context to answer question
4856
4993
  metadata: chunk.metadata
4857
4994
  }));
4858
4995
  const totalProcessed = await this.processUpserts(upsertDocs, ns);
4859
- results.push({
4860
- docId: doc.docId,
4861
- chunksIngested: totalProcessed
4862
- });
4996
+ results.push({ docId: doc.docId, chunksIngested: totalProcessed });
4863
4997
  if (this.graphDB && this.entityExtractor) {
4864
4998
  await this.processGraphIngestion(doc.docId, chunks);
4865
4999
  }
@@ -4870,23 +5004,18 @@ You are a helpful product assistant. Use the provided context to answer question
4870
5004
  }
4871
5005
  return results;
4872
5006
  }
4873
- /**
4874
- * Step 1: Chunk the document content.
4875
- */
5007
+ /** Step 1: Chunk the document content. */
4876
5008
  async prepareChunks(doc) {
4877
5009
  var _a, _b;
4878
5010
  if (this.llamaIngestor) {
4879
- return await this.llamaIngestor.chunk(doc.content, {
5011
+ return this.llamaIngestor.chunk(doc.content, {
4880
5012
  docId: doc.docId,
4881
5013
  metadata: doc.metadata,
4882
5014
  chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
4883
5015
  chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
4884
5016
  });
4885
5017
  }
4886
- return this.chunker.chunk(doc.content, {
4887
- docId: doc.docId,
4888
- metadata: doc.metadata
4889
- });
5018
+ return this.chunker.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata });
4890
5019
  }
4891
5020
  /**
4892
5021
  * Step 2: Generate embeddings for chunks with retry logic.
@@ -4910,9 +5039,7 @@ You are a helpful product assistant. Use the provided context to answer question
4910
5039
  }
4911
5040
  return vectors;
4912
5041
  }
4913
- /**
4914
- * Step 3: Upsert chunks to vector database with retry logic.
4915
- */
5042
+ /** Step 3: Upsert chunks to vector database with retry logic. */
4916
5043
  async processUpserts(upsertDocs, namespace) {
4917
5044
  const upsertBatchOptions = {
4918
5045
  batchSize: 100,
@@ -4929,14 +5056,10 @@ You are a helpful product assistant. Use the provided context to answer question
4929
5056
  }
4930
5057
  return upsertResult.totalProcessed;
4931
5058
  }
4932
- /**
4933
- * Step 4: Optional graph-based entity extraction and ingestion.
4934
- */
5059
+ /** Step 4: Optional graph-based entity extraction and ingestion. */
4935
5060
  async processGraphIngestion(docId, chunks) {
4936
- console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
4937
5061
  const extractionOptions = {
4938
5062
  batchSize: 2,
4939
- // Low concurrency for LLM extraction
4940
5063
  maxRetries: 1,
4941
5064
  initialDelayMs: 500
4942
5065
  };
@@ -4949,7 +5072,7 @@ You are a helpful product assistant. Use the provided context to answer question
4949
5072
  if (nodes.length > 0) await this.graphDB.addNodes(nodes);
4950
5073
  if (edges.length > 0) await this.graphDB.addEdges(edges);
4951
5074
  } catch (err) {
4952
- console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
5075
+ console.warn(`[Pipeline] Entity extraction failed for chunk in doc ${docId}:`, err);
4953
5076
  }
4954
5077
  }
4955
5078
  },
@@ -4960,7 +5083,6 @@ You are a helpful product assistant. Use the provided context to answer question
4960
5083
  var _a;
4961
5084
  await this.initialize();
4962
5085
  if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
4963
- console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
4964
5086
  const agentReply = await this.agent.run(question, history);
4965
5087
  return { reply: agentReply, sources: [] };
4966
5088
  }
@@ -4969,6 +5091,7 @@ You are a helpful product assistant. Use the provided context to answer question
4969
5091
  let sources = [];
4970
5092
  let graphData;
4971
5093
  let uiTransformation;
5094
+ let trace;
4972
5095
  try {
4973
5096
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
4974
5097
  const chunk = temp.value;
@@ -4978,6 +5101,7 @@ You are a helpful product assistant. Use the provided context to answer question
4978
5101
  if ("sources" in chunk) sources = chunk.sources;
4979
5102
  if ("graphData" in chunk) graphData = chunk.graphData;
4980
5103
  if ("ui_transformation" in chunk) uiTransformation = chunk.ui_transformation;
5104
+ if ("trace" in chunk) trace = chunk.trace;
4981
5105
  }
4982
5106
  }
4983
5107
  } catch (temp) {
@@ -4990,38 +5114,47 @@ You are a helpful product assistant. Use the provided context to answer question
4990
5114
  throw error[0];
4991
5115
  }
4992
5116
  }
4993
- return { reply, sources, graphData, ui_transformation: uiTransformation };
5117
+ return { reply, sources, graphData, ui_transformation: uiTransformation, trace };
4994
5118
  }
4995
5119
  /**
4996
5120
  * High-performance streaming RAG flow.
4997
- * Yields text chunks first, then the retrieval metadata at the end.
5121
+ * Yields text chunks first, then the retrieval metadata + observability trace at the end.
4998
5122
  */
4999
5123
  askStream(_0) {
5000
5124
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
5001
- var _a, _b, _c, _d, _e, _f, _g;
5125
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
5002
5126
  yield new __await(this.initialize());
5003
5127
  const ns = namespace != null ? namespace : this.config.projectId;
5004
5128
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
5005
5129
  const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
5130
+ const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
5131
+ const requestStart = performance.now();
5006
5132
  try {
5007
5133
  let searchQuery = question;
5134
+ let rewrittenQuery;
5008
5135
  if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
5009
5136
  searchQuery = yield new __await(this.rewriteQuery(question, history));
5137
+ rewrittenQuery = searchQuery !== question ? searchQuery : void 0;
5010
5138
  }
5011
5139
  const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
5012
5140
  const filter = QueryProcessor.buildQueryFilter(question, hints);
5013
- console.log(`[Pipeline] \u{1F9E9} Extracted filters:`, JSON.stringify(filter));
5141
+ const embedStart = performance.now();
5014
5142
  const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
5015
5143
  namespace: ns,
5016
5144
  topK: topK * 2,
5017
5145
  filter
5018
5146
  }));
5147
+ const retrieveEnd = performance.now();
5148
+ const embedMs = retrieveEnd - embedStart;
5149
+ const retrieveMs = retrieveEnd - embedStart;
5150
+ const rerankStart = performance.now();
5019
5151
  let sources = rawSources.filter((m) => m.score >= scoreThreshold);
5020
5152
  if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
5021
5153
  sources = yield new __await(this.reranker.rerank(sources, question, topK));
5022
5154
  } else {
5023
5155
  sources = sources.slice(0, topK);
5024
5156
  }
5157
+ const rerankMs = performance.now() - rerankStart;
5025
5158
  let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
5026
5159
  ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
5027
5160
  if (graphData && graphData.nodes.length > 0) {
@@ -5040,6 +5173,10 @@ ${context}`;
5040
5173
  const hardenedHistory = [...history];
5041
5174
  const userQuestion = { role: "user", content: question + restrictionSuffix };
5042
5175
  const messages = [...hardenedHistory, userQuestion];
5176
+ const systemPrompt = (_h = this.config.llm.systemPrompt) != null ? _h : "";
5177
+ const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
5178
+ let fullReply = "";
5179
+ const generateStart = performance.now();
5043
5180
  if (this.llmProvider.chatStream) {
5044
5181
  const stream = this.llmProvider.chatStream(messages, context);
5045
5182
  if (!stream) {
@@ -5048,6 +5185,7 @@ ${context}`;
5048
5185
  try {
5049
5186
  for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
5050
5187
  const chunk = temp.value;
5188
+ fullReply += chunk;
5051
5189
  yield chunk;
5052
5190
  }
5053
5191
  } catch (temp) {
@@ -5062,15 +5200,58 @@ ${context}`;
5062
5200
  }
5063
5201
  } else {
5064
5202
  const reply = yield new __await(this.llmProvider.chat(messages, context));
5203
+ fullReply = reply;
5065
5204
  yield reply;
5066
5205
  }
5206
+ const generateMs = performance.now() - generateStart;
5207
+ const totalMs = performance.now() - requestStart;
5208
+ const latency = {
5209
+ embedMs: Math.round(embedMs),
5210
+ retrieveMs: Math.round(retrieveMs),
5211
+ rerankMs: ((_i = this.config.rag) == null ? void 0 : _i.useReranking) ? Math.round(rerankMs) : void 0,
5212
+ generateMs: Math.round(generateMs),
5213
+ totalMs: Math.round(totalMs)
5214
+ };
5215
+ const promptText = systemPrompt + "\n" + context + "\n" + userPrompt;
5216
+ const promptTokens = estimateTokens(promptText);
5217
+ const completionTokens = estimateTokens(fullReply);
5218
+ const tokens = {
5219
+ promptTokens,
5220
+ completionTokens,
5221
+ totalTokens: promptTokens + completionTokens,
5222
+ estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
5223
+ };
5224
+ const hallucinationResult = yield new __await(scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0));
5067
5225
  const trainedSchema = yield new __await(trainingPromise);
5068
5226
  const uiTransformation = yield new __await(this.generateUiTransformation(question, sources, context, trainedSchema));
5227
+ const trace = {
5228
+ requestId,
5229
+ query: question,
5230
+ rewrittenQuery,
5231
+ systemPrompt,
5232
+ userPrompt: question + restrictionSuffix,
5233
+ chunks: sources.map((s) => {
5234
+ var _a2;
5235
+ return {
5236
+ id: s.id,
5237
+ score: s.score,
5238
+ content: s.content,
5239
+ metadata: (_a2 = s.metadata) != null ? _a2 : {},
5240
+ namespace: ns
5241
+ };
5242
+ }),
5243
+ latency,
5244
+ tokens,
5245
+ hallucinationScore: hallucinationResult == null ? void 0 : hallucinationResult.score,
5246
+ hallucinationReason: hallucinationResult == null ? void 0 : hallucinationResult.reason,
5247
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5248
+ };
5069
5249
  yield {
5070
5250
  reply: "",
5071
5251
  sources,
5072
5252
  graphData,
5073
- ui_transformation: uiTransformation
5253
+ ui_transformation: uiTransformation,
5254
+ trace
5074
5255
  };
5075
5256
  } catch (error2) {
5076
5257
  throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
@@ -5109,12 +5290,9 @@ ${context}`;
5109
5290
  const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
5110
5291
  return { sources, graphData };
5111
5292
  }
5112
- /**
5113
- * Rewrite the user query for better retrieval performance.
5114
- */
5293
+ /** Rewrite the user query for better retrieval performance. */
5115
5294
  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.
5295
+ 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
5296
  Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
5119
5297
 
5120
5298
  History:
@@ -5132,23 +5310,16 @@ Optimized Search Query:`;
5132
5310
  );
5133
5311
  return rewrite.trim() || question;
5134
5312
  }
5135
- /**
5136
- * Generate 3-5 short, relevant questions based on the vector database content.
5137
- */
5313
+ /** Generate 3-5 short, relevant questions based on the vector database content. */
5138
5314
  async getSuggestions(query, namespace) {
5139
- if (!query || query.trim().length < 3) {
5140
- return [];
5141
- }
5315
+ if (!query || query.trim().length < 3) return [];
5142
5316
  await this.initialize();
5143
5317
  const ns = namespace != null ? namespace : this.config.projectId;
5144
5318
  try {
5145
5319
  const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
5146
- if (sources.length === 0) {
5147
- return [];
5148
- }
5320
+ if (sources.length === 0) return [];
5149
5321
  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?
5322
+ const prompt = `Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
5152
5323
  Focus on questions that can be answered by the context.
5153
5324
  Keep each question under 10 words and make them very specific to the content.
5154
5325
  Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
@@ -5808,6 +5979,11 @@ function sseMetaFrame(meta) {
5808
5979
  function sseUIFrame(uiTransformation) {
5809
5980
  return `data: ${JSON.stringify({ type: "ui_transformation", data: uiTransformation })}
5810
5981
 
5982
+ `;
5983
+ }
5984
+ function sseObservabilityFrame(trace) {
5985
+ return `data: ${JSON.stringify({ type: "observability", data: trace })}
5986
+
5811
5987
  `;
5812
5988
  }
5813
5989
  function sseErrorFrame(message) {
@@ -5874,6 +6050,9 @@ function createStreamHandler(configOrPlugin) {
5874
6050
  enqueue(sseMetaFrame(chunk));
5875
6051
  const responseChunk = chunk;
5876
6052
  const sources = (responseChunk == null ? void 0 : responseChunk.sources) || [];
6053
+ if (responseChunk == null ? void 0 : responseChunk.trace) {
6054
+ enqueue(sseObservabilityFrame(responseChunk.trace));
6055
+ }
5877
6056
  if (sources.length > 0) {
5878
6057
  try {
5879
6058
  const llmProvider = (_a = plugin.getLLMProvider) == null ? void 0 : _a.call(plugin);
@@ -5955,24 +6134,64 @@ function createUploadHandler(configOrPlugin) {
5955
6134
  const formData = await req.formData();
5956
6135
  const files = formData.getAll("files");
5957
6136
  const namespace = formData.get("namespace") || void 0;
6137
+ const dimensionRaw = formData.get("dimension");
6138
+ const dimension = dimensionRaw ? parseInt(dimensionRaw, 10) : void 0;
5958
6139
  if (!files || files.length === 0) {
5959
6140
  return import_server.NextResponse.json({ error: "No files provided" }, { status: 400 });
5960
6141
  }
5961
- const documents = await Promise.all(
5962
- files.map(async (file) => {
6142
+ const documents = [];
6143
+ for (const file of files) {
6144
+ if (file.name.toLowerCase().endsWith(".csv") || file.type === "text/csv") {
6145
+ const text = await file.text();
6146
+ const Papa = await import("papaparse");
6147
+ const parsed = Papa.default.parse(text, { header: true, skipEmptyLines: true });
6148
+ if (parsed.data && parsed.data.length > 0) {
6149
+ let i = 0;
6150
+ let lastRowData = null;
6151
+ for (const row of parsed.data) {
6152
+ i++;
6153
+ const rowData = row;
6154
+ const groupingKey = Object.keys(rowData)[0];
6155
+ if (lastRowData && groupingKey && rowData[groupingKey] && rowData[groupingKey] === lastRowData[groupingKey]) {
6156
+ for (const key of Object.keys(rowData)) {
6157
+ if (!rowData[key] && lastRowData[key]) {
6158
+ rowData[key] = lastRowData[key];
6159
+ }
6160
+ }
6161
+ }
6162
+ lastRowData = __spreadValues({}, rowData);
6163
+ const contentParts = [];
6164
+ for (const [key, val] of Object.entries(rowData)) {
6165
+ if (key && val) {
6166
+ contentParts.push(`${key}: ${val}`);
6167
+ }
6168
+ }
6169
+ documents.push({
6170
+ docId: `${file.name}-row-${i}`,
6171
+ content: contentParts.join(", "),
6172
+ metadata: __spreadValues(__spreadValues({
6173
+ fileName: file.name,
6174
+ fileSize: file.size,
6175
+ fileType: file.type,
6176
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
6177
+ }, dimension ? { dimension } : {}), rowData)
6178
+ });
6179
+ }
6180
+ }
6181
+ } else {
5963
6182
  const content = await DocumentParser.parse(file, file.name, file.type);
5964
- return {
6183
+ documents.push({
5965
6184
  docId: file.name,
5966
6185
  content,
5967
- metadata: {
6186
+ metadata: __spreadValues({
5968
6187
  fileName: file.name,
5969
6188
  fileSize: file.size,
5970
6189
  fileType: file.type,
5971
6190
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
5972
- }
5973
- };
5974
- })
5975
- );
6191
+ }, dimension ? { dimension } : {})
6192
+ });
6193
+ }
6194
+ }
5976
6195
  const results = await plugin.ingest(documents, namespace);
5977
6196
  return import_server.NextResponse.json({ message: "Upload successful", results });
5978
6197
  } catch (err) {