@retrivora-ai/rag-engine 1.7.5 → 1.7.7

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.
@@ -2211,65 +2211,410 @@ var QueryProcessor = class {
2211
2211
  }
2212
2212
  };
2213
2213
 
2214
- // src/core/Pipeline.ts
2215
- var LRUEmbeddingCache = class {
2216
- constructor(maxSize = 500) {
2217
- this.cache = /* @__PURE__ */ new Map();
2218
- this.maxSize = maxSize;
2219
- }
2220
- get(key) {
2221
- const value = this.cache.get(key);
2222
- if (value !== void 0) {
2223
- this.cache.delete(key);
2224
- this.cache.set(key, value);
2214
+ // src/utils/UITransformer.ts
2215
+ var UITransformer = class {
2216
+ /**
2217
+ * Main transformation method
2218
+ * Analyzes user query and retrieved data to determine the best visualization format
2219
+ *
2220
+ * @param userQuery - The original user question/query
2221
+ * @param retrievedData - Vector database retrieval results
2222
+ * @returns Structured JSON response for UI rendering
2223
+ */
2224
+ static transform(userQuery, retrievedData) {
2225
+ if (!retrievedData || retrievedData.length === 0) {
2226
+ return this.createTextResponse("No data available", "No relevant data found for your query.");
2227
+ }
2228
+ const analysis = this.analyzeData(userQuery, retrievedData);
2229
+ switch (analysis.suggestedType) {
2230
+ case "product_carousel":
2231
+ return this.transformToProductCarousel(retrievedData);
2232
+ case "pie_chart":
2233
+ return this.transformToPieChart(retrievedData);
2234
+ case "bar_chart":
2235
+ return this.transformToBarChart(retrievedData);
2236
+ case "line_chart":
2237
+ return this.transformToLineChart(retrievedData);
2238
+ case "table":
2239
+ return this.transformToTable(retrievedData);
2240
+ default:
2241
+ return this.transformToText(retrievedData);
2225
2242
  }
2226
- return value;
2227
2243
  }
2228
- set(key, value) {
2229
- if (this.cache.has(key)) {
2230
- this.cache.delete(key);
2231
- } else if (this.cache.size >= this.maxSize) {
2232
- const oldestKey = this.cache.keys().next().value;
2233
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
2244
+ /**
2245
+ * Analyzes the data and query to suggest the best visualization type
2246
+ */
2247
+ static analyzeData(query, data) {
2248
+ const queryLower = query.toLowerCase();
2249
+ const hasProducts = data.some(
2250
+ (item) => this.isProductData(item)
2251
+ );
2252
+ const hasTimeSeries = data.some(
2253
+ (item) => this.isTimeSeriesData(item)
2254
+ );
2255
+ const hasCategories = this.detectCategories(data).length > 0;
2256
+ const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
2257
+ const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
2258
+ const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
2259
+ if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
2260
+ return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
2234
2261
  }
2235
- this.cache.set(key, value);
2262
+ if (isTrendQuery && hasTimeSeries) {
2263
+ return { suggestedType: "line_chart", confidence: 0.9, hasProducts, hasTimeSeries, hasCategories };
2264
+ }
2265
+ if (isDistributionQuery && hasCategories) {
2266
+ return { suggestedType: "pie_chart", confidence: 0.85, hasProducts, hasTimeSeries, hasCategories };
2267
+ }
2268
+ if (isComparisonQuery && hasCategories && data.length > 2) {
2269
+ return { suggestedType: "bar_chart", confidence: 0.8, hasProducts, hasTimeSeries, hasCategories };
2270
+ }
2271
+ if (data.length > 5 && this.hasMultipleFields(data)) {
2272
+ return { suggestedType: "table", confidence: 0.75, hasProducts, hasTimeSeries, hasCategories };
2273
+ }
2274
+ return { suggestedType: "text", confidence: 0.5, hasProducts, hasTimeSeries, hasCategories };
2236
2275
  }
2237
- clear() {
2238
- this.cache.clear();
2276
+ /**
2277
+ * Transform data to product carousel format
2278
+ */
2279
+ static transformToProductCarousel(data) {
2280
+ const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
2281
+ return {
2282
+ type: "product_carousel",
2283
+ title: "Recommended Products",
2284
+ description: `Found ${products.length} relevant products`,
2285
+ data: products
2286
+ };
2239
2287
  }
2240
- get size() {
2241
- return this.cache.size;
2288
+ /**
2289
+ * Transform data to pie chart format
2290
+ */
2291
+ static transformToPieChart(data) {
2292
+ const categories = this.detectCategories(data);
2293
+ const categoryData = this.aggregateByCategory(data, categories);
2294
+ const pieData = Object.entries(categoryData).map(([label, count]) => ({
2295
+ label,
2296
+ value: count,
2297
+ inStock: this.checkStockStatus(label, data)
2298
+ }));
2299
+ return {
2300
+ type: "pie_chart",
2301
+ title: "Distribution by Category",
2302
+ description: `Showing breakdown across ${categories.length} categories`,
2303
+ data: pieData
2304
+ };
2242
2305
  }
2243
- };
2244
- var Pipeline = class {
2245
- constructor(config) {
2246
- this.config = config;
2247
- /** LRU-bounded cache: avoids re-embedding identical queries within the same process. */
2248
- this.embeddingCache = new LRUEmbeddingCache(500);
2249
- this.initialised = false;
2250
- var _a, _b, _c, _d, _e;
2251
- this.chunker = new DocumentChunker(
2252
- (_b = (_a = config.rag) == null ? void 0 : _a.chunkSize) != null ? _b : 1e3,
2253
- (_d = (_c = config.rag) == null ? void 0 : _c.chunkOverlap) != null ? _d : 200
2306
+ /**
2307
+ * Transform data to bar chart format
2308
+ */
2309
+ static transformToBarChart(data) {
2310
+ const categories = this.detectCategories(data);
2311
+ const categoryData = this.aggregateByCategory(data, categories);
2312
+ const barData = Object.entries(categoryData).map(([category, value]) => ({
2313
+ category,
2314
+ value,
2315
+ inStock: this.checkStockStatus(category, data)
2316
+ }));
2317
+ return {
2318
+ type: "bar_chart",
2319
+ title: "Comparison by Category",
2320
+ description: `Comparing ${categories.length} categories`,
2321
+ data: barData
2322
+ };
2323
+ }
2324
+ /**
2325
+ * Transform data to line chart format
2326
+ */
2327
+ static transformToLineChart(data) {
2328
+ const timePoints = this.extractTimeSeriesData(data);
2329
+ const lineData = timePoints.map((point) => ({
2330
+ timestamp: point.timestamp,
2331
+ value: point.value,
2332
+ label: point.label
2333
+ }));
2334
+ return {
2335
+ type: "line_chart",
2336
+ title: "Trend Over Time",
2337
+ description: `Showing ${lineData.length} data points`,
2338
+ data: lineData
2339
+ };
2340
+ }
2341
+ /**
2342
+ * Transform data to table format
2343
+ */
2344
+ static transformToTable(data) {
2345
+ const columns = this.extractTableColumns(data);
2346
+ const rows = data.map((item) => this.extractTableRow(item, columns));
2347
+ const tableData = {
2348
+ columns,
2349
+ rows
2350
+ };
2351
+ return {
2352
+ type: "table",
2353
+ title: "Detailed Results",
2354
+ description: `Showing ${data.length} results`,
2355
+ data: tableData
2356
+ };
2357
+ }
2358
+ /**
2359
+ * Transform data to text format (fallback)
2360
+ */
2361
+ static transformToText(data) {
2362
+ const textContent = data.map((item) => item.content).join("\n\n");
2363
+ return this.createTextResponse(
2364
+ "Search Results",
2365
+ textContent,
2366
+ `Found ${data.length} relevant results`
2254
2367
  );
2255
- if (((_e = config.rag) == null ? void 0 : _e.chunkingStrategy) === "llamaindex") {
2256
- this.llamaIngestor = new LlamaIndexIngestor();
2368
+ }
2369
+ /**
2370
+ * Helper: Create text response
2371
+ */
2372
+ static createTextResponse(title, content, description) {
2373
+ return {
2374
+ type: "text",
2375
+ title,
2376
+ description,
2377
+ data: { content }
2378
+ };
2379
+ }
2380
+ /**
2381
+ * Helper: Check if data item is product-related
2382
+ */
2383
+ static isProductData(item) {
2384
+ const content = (item.content || "").toLowerCase();
2385
+ const productKeywords = ["product", "price", "stock", "item", "sku", "brand", "model"];
2386
+ return productKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
2387
+ (k) => ["name", "price", "product", "sku"].includes(k.toLowerCase())
2388
+ );
2389
+ }
2390
+ /**
2391
+ * Helper: Check if data contains time series
2392
+ */
2393
+ static isTimeSeriesData(item) {
2394
+ const content = (item.content || "").toLowerCase();
2395
+ const timeKeywords = ["date", "time", "year", "month", "week", "day", "hour", "trend", "historical"];
2396
+ return timeKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
2397
+ (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
2398
+ );
2399
+ }
2400
+ /**
2401
+ * Helper: Extract product information from vector match
2402
+ */
2403
+ static extractProductInfo(item) {
2404
+ const meta = item.metadata || {};
2405
+ if (meta.name || meta.product) {
2406
+ return {
2407
+ id: item.id,
2408
+ name: meta.name || meta.product,
2409
+ price: meta.price,
2410
+ image: meta.image || meta.imageUrl,
2411
+ brand: meta.brand,
2412
+ description: item.content,
2413
+ inStock: this.determineStockStatus(item)
2414
+ };
2257
2415
  }
2258
- this.reranker = new Reranker();
2416
+ const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
2417
+ const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
2418
+ if (nameMatch) {
2419
+ return {
2420
+ id: item.id,
2421
+ name: nameMatch[1],
2422
+ price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
2423
+ description: item.content,
2424
+ inStock: this.determineStockStatus(item)
2425
+ };
2426
+ }
2427
+ return null;
2259
2428
  }
2260
- async initialize() {
2261
- var _a, _b;
2262
- if (this.initialised) return;
2263
- const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
2264
- const chartInstruction = `
2265
-
2266
- ${CHART_MARKER}
2267
- ### UNIVERSAL UI PROTOCOL V7 (STRICT MODE)
2268
-
2269
- You are responsible for returning a SINGLE structured UI block when visualization is required.
2270
-
2271
- ---
2272
-
2429
+ /**
2430
+ * Helper: Detect categories in data
2431
+ */
2432
+ static detectCategories(data) {
2433
+ const categories = /* @__PURE__ */ new Set();
2434
+ data.forEach((item) => {
2435
+ const meta = item.metadata || {};
2436
+ if (meta.category) {
2437
+ categories.add(String(meta.category));
2438
+ }
2439
+ if (meta.type) {
2440
+ categories.add(String(meta.type));
2441
+ }
2442
+ if (meta.tag) {
2443
+ const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
2444
+ tags.forEach((t) => categories.add(String(t)));
2445
+ }
2446
+ const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
2447
+ if (categoryMatch) {
2448
+ categories.add(categoryMatch[1].trim());
2449
+ }
2450
+ });
2451
+ return Array.from(categories);
2452
+ }
2453
+ /**
2454
+ * Helper: Aggregate data by category
2455
+ */
2456
+ static aggregateByCategory(data, categories) {
2457
+ const result = {};
2458
+ categories.forEach((cat) => {
2459
+ result[cat] = 0;
2460
+ });
2461
+ data.forEach((item) => {
2462
+ const meta = item.metadata || {};
2463
+ const itemCategory = meta.category || meta.type || "Other";
2464
+ if (result.hasOwnProperty(itemCategory)) {
2465
+ result[itemCategory]++;
2466
+ } else {
2467
+ result["Other"] = (result["Other"] || 0) + 1;
2468
+ }
2469
+ });
2470
+ return result;
2471
+ }
2472
+ /**
2473
+ * Helper: Extract time series data
2474
+ */
2475
+ static extractTimeSeriesData(data) {
2476
+ return data.map((item) => {
2477
+ const meta = item.metadata || {};
2478
+ return {
2479
+ timestamp: meta.timestamp || meta.date || (/* @__PURE__ */ new Date()).toISOString(),
2480
+ value: meta.value || item.score || 0,
2481
+ label: meta.label || item.content.substring(0, 50)
2482
+ };
2483
+ });
2484
+ }
2485
+ /**
2486
+ * Helper: Extract table columns
2487
+ */
2488
+ static extractTableColumns(data) {
2489
+ const columnSet = /* @__PURE__ */ new Set();
2490
+ columnSet.add("Content");
2491
+ data.forEach((item) => {
2492
+ Object.keys(item.metadata || {}).forEach((key) => {
2493
+ columnSet.add(key.charAt(0).toUpperCase() + key.slice(1));
2494
+ });
2495
+ });
2496
+ return Array.from(columnSet);
2497
+ }
2498
+ /**
2499
+ * Helper: Extract table row
2500
+ */
2501
+ static extractTableRow(item, columns) {
2502
+ const meta = item.metadata || {};
2503
+ return columns.map((col) => {
2504
+ if (col === "Content") {
2505
+ return item.content.substring(0, 100);
2506
+ }
2507
+ const metaKey = col.charAt(0).toLowerCase() + col.slice(1);
2508
+ const value = meta[metaKey];
2509
+ return value !== void 0 ? String(value) : "";
2510
+ });
2511
+ }
2512
+ /**
2513
+ * Helper: Check stock status
2514
+ */
2515
+ static checkStockStatus(category, data) {
2516
+ const item = data.find((d) => {
2517
+ const meta = d.metadata || {};
2518
+ return meta.category === category || meta.type === category;
2519
+ });
2520
+ return this.determineStockStatus(item || {});
2521
+ }
2522
+ /**
2523
+ * Helper: Determine if item is in stock
2524
+ */
2525
+ static determineStockStatus(item) {
2526
+ const meta = item.metadata || {};
2527
+ if (meta.inStock !== void 0) {
2528
+ return Boolean(meta.inStock);
2529
+ }
2530
+ if (meta.stock !== void 0) {
2531
+ return Boolean(meta.stock);
2532
+ }
2533
+ if (meta.available !== void 0) {
2534
+ return Boolean(meta.available);
2535
+ }
2536
+ const content = (item.content || "").toLowerCase();
2537
+ if (content.includes("out of stock") || content.includes("unavailable")) {
2538
+ return false;
2539
+ }
2540
+ if (content.includes("in stock") || content.includes("available")) {
2541
+ return true;
2542
+ }
2543
+ return true;
2544
+ }
2545
+ /**
2546
+ * Helper: Check if data has multiple fields
2547
+ */
2548
+ static hasMultipleFields(data) {
2549
+ const fieldCount = /* @__PURE__ */ new Set();
2550
+ data.forEach((item) => {
2551
+ Object.keys(item.metadata || {}).forEach((key) => {
2552
+ fieldCount.add(key);
2553
+ });
2554
+ });
2555
+ return fieldCount.size > 2;
2556
+ }
2557
+ };
2558
+
2559
+ // src/core/Pipeline.ts
2560
+ var LRUEmbeddingCache = class {
2561
+ constructor(maxSize = 500) {
2562
+ this.cache = /* @__PURE__ */ new Map();
2563
+ this.maxSize = maxSize;
2564
+ }
2565
+ get(key) {
2566
+ const value = this.cache.get(key);
2567
+ if (value !== void 0) {
2568
+ this.cache.delete(key);
2569
+ this.cache.set(key, value);
2570
+ }
2571
+ return value;
2572
+ }
2573
+ set(key, value) {
2574
+ if (this.cache.has(key)) {
2575
+ this.cache.delete(key);
2576
+ } else if (this.cache.size >= this.maxSize) {
2577
+ const oldestKey = this.cache.keys().next().value;
2578
+ if (oldestKey !== void 0) this.cache.delete(oldestKey);
2579
+ }
2580
+ this.cache.set(key, value);
2581
+ }
2582
+ clear() {
2583
+ this.cache.clear();
2584
+ }
2585
+ get size() {
2586
+ return this.cache.size;
2587
+ }
2588
+ };
2589
+ var Pipeline = class {
2590
+ constructor(config) {
2591
+ this.config = config;
2592
+ /** LRU-bounded cache: avoids re-embedding identical queries within the same process. */
2593
+ this.embeddingCache = new LRUEmbeddingCache(500);
2594
+ this.initialised = false;
2595
+ var _a, _b, _c, _d, _e;
2596
+ this.chunker = new DocumentChunker(
2597
+ (_b = (_a = config.rag) == null ? void 0 : _a.chunkSize) != null ? _b : 1e3,
2598
+ (_d = (_c = config.rag) == null ? void 0 : _c.chunkOverlap) != null ? _d : 200
2599
+ );
2600
+ if (((_e = config.rag) == null ? void 0 : _e.chunkingStrategy) === "llamaindex") {
2601
+ this.llamaIngestor = new LlamaIndexIngestor();
2602
+ }
2603
+ this.reranker = new Reranker();
2604
+ }
2605
+ async initialize() {
2606
+ var _a, _b;
2607
+ if (this.initialised) return;
2608
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
2609
+ const chartInstruction = `
2610
+
2611
+ ${CHART_MARKER}
2612
+ ### UNIVERSAL UI PROTOCOL V7 (STRICT MODE)
2613
+
2614
+ You are responsible for returning a SINGLE structured UI block when visualization is required.
2615
+
2616
+ ---
2617
+
2273
2618
  ## 1. INTENT CLASSIFICATION (MANDATORY)
2274
2619
 
2275
2620
  Classify the query into ONE of the following intents:
@@ -2362,11 +2707,14 @@ Classify the query into ONE of the following intents:
2362
2707
 
2363
2708
  ## 5. DATA RULES
2364
2709
 
2365
- - NEVER hallucinate fields
2710
+ - NEVER hallucinate fields or data
2366
2711
  - ONLY use retrieved vector DB data
2367
2712
  - NO placeholders ("...", "N/A")
2368
2713
  - If image/link missing \u2192 REMOVE FIELD
2369
2714
  - Aggregate BEFORE rendering chart
2715
+ - IF NO DATA IS FOUND in the retrieved context:
2716
+ \u2192 DO NOT output a \`\`\`ui\`\`\` block
2717
+ \u2192 Instead, state clearly that no relevant products or data were found.
2370
2718
 
2371
2719
  ---
2372
2720
 
@@ -2386,6 +2734,8 @@ Classify the query into ONE of the following intents:
2386
2734
  - ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block
2387
2735
  - JSON must be VALID
2388
2736
  - No explanation inside block
2737
+ - NEVER use ASCII charts, Markdown tables, or text-based drawings for data.
2738
+ - If data is tabular or statistical, ALWAYS use the \`\`\`ui\`\`\` block.
2389
2739
 
2390
2740
  ---
2391
2741
 
@@ -2441,914 +2791,575 @@ User: "distribution of products across categories"
2441
2791
  { "label": "Electronics", "value": 10 }
2442
2792
  ]
2443
2793
  },
2444
- "insights": ["Beauty dominates inventory"]
2445
- }
2446
- \`\`\`
2447
- `;
2448
- if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
2449
- let cleanPrompt = this.config.llm.systemPrompt || "";
2450
- cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
2451
- cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
2452
- this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
2453
- }
2454
- console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
2455
- this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
2456
- const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
2457
- this.config.llm,
2458
- this.config.embedding
2459
- );
2460
- this.llmProvider = llmProvider;
2461
- this.embeddingProvider = embeddingProvider;
2462
- if (this.config.graphDb) {
2463
- this.graphDB = await ProviderRegistry.createGraphProvider(this.config.graphDb);
2464
- await this.graphDB.initialize();
2465
- this.entityExtractor = new EntityExtractor(this.llmProvider);
2466
- }
2467
- await this.vectorDB.initialize();
2468
- if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
2469
- this.agent = new LangChainAgent(this, this.config);
2470
- await this.agent.initialize(this.llmProvider);
2471
- }
2472
- this.initialised = true;
2473
- }
2474
- /**
2475
- * Ingest documents with automatic chunking, embedding, and batch upsert.
2476
- * Handles retries for transient failures.
2477
- */
2478
- async ingest(documents, namespace) {
2479
- await this.initialize();
2480
- const ns = namespace != null ? namespace : this.config.projectId;
2481
- const results = [];
2482
- for (const doc of documents) {
2483
- try {
2484
- const chunks = await this.prepareChunks(doc);
2485
- const vectors = await this.processEmbeddings(chunks);
2486
- const upsertDocs = chunks.map((chunk, i) => ({
2487
- id: chunk.id,
2488
- vector: vectors[i],
2489
- content: chunk.content,
2490
- metadata: chunk.metadata
2491
- }));
2492
- const totalProcessed = await this.processUpserts(upsertDocs, ns);
2493
- results.push({
2494
- docId: doc.docId,
2495
- chunksIngested: totalProcessed
2496
- });
2497
- if (this.graphDB && this.entityExtractor) {
2498
- await this.processGraphIngestion(doc.docId, chunks);
2499
- }
2500
- } catch (error) {
2501
- console.error(`[Pipeline] Failed to ingest document ${doc.docId}:`, error);
2502
- results.push({ docId: doc.docId, chunksIngested: 0 });
2503
- }
2504
- }
2505
- return results;
2506
- }
2507
- /**
2508
- * Step 1: Chunk the document content.
2509
- */
2510
- async prepareChunks(doc) {
2511
- var _a, _b;
2512
- if (this.llamaIngestor) {
2513
- return await this.llamaIngestor.chunk(doc.content, {
2514
- docId: doc.docId,
2515
- metadata: doc.metadata,
2516
- chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
2517
- chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
2518
- });
2519
- }
2520
- return this.chunker.chunk(doc.content, {
2521
- docId: doc.docId,
2522
- metadata: doc.metadata
2523
- });
2524
- }
2525
- /**
2526
- * Step 2: Generate embeddings for chunks with retry logic.
2527
- * Uses batchEmbed when available for efficiency; falls back to sequential embedding.
2528
- */
2529
- async processEmbeddings(chunks) {
2530
- const embedBatchOptions = {
2531
- batchSize: 50,
2532
- maxRetries: 3,
2533
- initialDelayMs: 100
2534
- };
2535
- const vectors = await BatchProcessor.mapWithRetry(
2536
- chunks.map((c) => c.content),
2537
- (text) => this.embeddingProvider.embed(text, { taskType: "document" }),
2538
- embedBatchOptions
2539
- );
2540
- if (vectors.length !== chunks.length) {
2541
- throw new Error(
2542
- `[Pipeline] Embedding mismatch: got ${vectors.length} vectors for ${chunks.length} chunks. Check embedding provider logs for individual chunk failures.`
2543
- );
2544
- }
2545
- return vectors;
2546
- }
2547
- /**
2548
- * Step 3: Upsert chunks to vector database with retry logic.
2549
- */
2550
- async processUpserts(upsertDocs, namespace) {
2551
- const upsertBatchOptions = {
2552
- batchSize: 100,
2553
- maxRetries: 3,
2554
- initialDelayMs: 100
2555
- };
2556
- const upsertResult = await BatchProcessor.processBatch(
2557
- upsertDocs,
2558
- (batch) => this.vectorDB.batchUpsert(batch, namespace),
2559
- upsertBatchOptions
2560
- );
2561
- if (upsertResult.errors.length > 0) {
2562
- console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed`);
2563
- }
2564
- return upsertResult.totalProcessed;
2565
- }
2566
- /**
2567
- * Step 4: Optional graph-based entity extraction and ingestion.
2568
- */
2569
- async processGraphIngestion(docId, chunks) {
2570
- console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
2571
- const extractionOptions = {
2572
- batchSize: 2,
2573
- // Low concurrency for LLM extraction
2574
- maxRetries: 1,
2575
- initialDelayMs: 500
2576
- };
2577
- await BatchProcessor.processBatch(
2578
- chunks,
2579
- async (batch) => {
2580
- for (const chunk of batch) {
2581
- try {
2582
- const { nodes, edges } = await this.entityExtractor.extract(chunk.content);
2583
- if (nodes.length > 0) await this.graphDB.addNodes(nodes);
2584
- if (edges.length > 0) await this.graphDB.addEdges(edges);
2585
- } catch (err) {
2586
- console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
2587
- }
2588
- }
2589
- },
2590
- extractionOptions
2591
- );
2592
- }
2593
- async ask(question, history = [], namespace) {
2594
- var _a;
2595
- await this.initialize();
2596
- if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
2597
- console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
2598
- const agentReply = await this.agent.run(question, history);
2599
- return { reply: agentReply, sources: [] };
2600
- }
2601
- const stream = this.askStream(question, history, namespace);
2602
- let reply = "";
2603
- let sources = [];
2604
- let graphData;
2605
- try {
2606
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2607
- const chunk = temp.value;
2608
- if (typeof chunk === "string") {
2609
- reply += chunk;
2610
- } else if ("sources" in chunk) {
2611
- sources = chunk.sources;
2612
- graphData = chunk.graphData;
2613
- }
2614
- }
2615
- } catch (temp) {
2616
- error = [temp];
2617
- } finally {
2618
- try {
2619
- more && (temp = iter.return) && await temp.call(iter);
2620
- } finally {
2621
- if (error)
2622
- throw error[0];
2623
- }
2624
- }
2625
- return { reply, sources, graphData };
2626
- }
2627
- /**
2628
- * High-performance streaming RAG flow.
2629
- * Yields text chunks first, then the retrieval metadata at the end.
2630
- */
2631
- askStream(_0) {
2632
- return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
2633
- var _a, _b, _c, _d, _e, _f, _g;
2634
- yield new __await(this.initialize());
2635
- const ns = namespace != null ? namespace : this.config.projectId;
2636
- const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
2637
- const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
2638
- try {
2639
- let searchQuery = question;
2640
- if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
2641
- searchQuery = yield new __await(this.rewriteQuery(question, history));
2642
- }
2643
- const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
2644
- const filter = QueryProcessor.buildQueryFilter(question, hints);
2645
- console.log(`[Pipeline] \u{1F9E9} Extracted filters:`, JSON.stringify(filter));
2646
- const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
2647
- namespace: ns,
2648
- topK: topK * 2,
2649
- filter
2650
- }));
2651
- let sources = rawSources.filter((m) => m.score >= scoreThreshold);
2652
- if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
2653
- sources = yield new __await(this.reranker.rerank(sources, question, topK));
2654
- } else {
2655
- sources = sources.slice(0, topK);
2656
- }
2657
- let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
2658
- ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
2659
- if (graphData && graphData.nodes.length > 0) {
2660
- const graphContext = graphData.nodes.map(
2661
- (n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
2662
- ).join("\n");
2663
- context = `GRAPH KNOWLEDGE:
2664
- ${graphContext}
2665
-
2666
- VECTOR CONTEXT:
2667
- ${context}`;
2668
- }
2669
- const messages = [...history, { role: "user", content: question }];
2670
- if (this.llmProvider.chatStream) {
2671
- const stream = this.llmProvider.chatStream(messages, context);
2672
- if (!stream) {
2673
- throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
2674
- }
2675
- try {
2676
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
2677
- const chunk = temp.value;
2678
- yield chunk;
2679
- }
2680
- } catch (temp) {
2681
- error = [temp];
2682
- } finally {
2683
- try {
2684
- more && (temp = iter.return) && (yield new __await(temp.call(iter)));
2685
- } finally {
2686
- if (error)
2687
- throw error[0];
2688
- }
2689
- }
2690
- } else {
2691
- const reply = yield new __await(this.llmProvider.chat(messages, context));
2692
- yield reply;
2693
- }
2694
- yield { reply: "", sources, graphData };
2695
- } catch (error2) {
2696
- throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
2697
- }
2698
- });
2699
- }
2700
- /**
2701
- * Universal retrieval method combining all enabled providers.
2702
- * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
2703
- */
2704
- async retrieve(query, options) {
2705
- var _a, _b, _c;
2706
- const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
2707
- const topK = (_b = options.topK) != null ? _b : 5;
2708
- const cacheKey = `${ns}::${query}`;
2709
- let queryVector = this.embeddingCache.get(cacheKey);
2710
- const [retrievedVector, graphData] = await Promise.all([
2711
- queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }),
2712
- this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
2713
- ]);
2714
- if (!queryVector) {
2715
- this.embeddingCache.set(cacheKey, retrievedVector);
2716
- queryVector = retrievedVector;
2717
- }
2718
- const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
2719
- return { sources, graphData };
2720
- }
2721
- /**
2722
- * Rewrite the user query for better retrieval performance.
2723
- */
2724
- async rewriteQuery(question, history) {
2725
- const prompt = `
2726
- Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
2727
- Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
2728
-
2729
- History:
2730
- ${history.map((m) => `${m.role}: ${m.content}`).join("\n")}
2731
-
2732
- New Question: ${question}
2733
-
2734
- Optimized Search Query:`;
2735
- const rewrite = await this.llmProvider.chat(
2736
- [
2737
- { role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
2738
- { role: "user", content: prompt }
2739
- ],
2740
- ""
2741
- );
2742
- return rewrite.trim() || question;
2743
- }
2744
- /**
2745
- * Generate 3-5 short, relevant questions based on the vector database content.
2746
- */
2747
- async getSuggestions(query, namespace) {
2748
- if (!query || query.trim().length < 3) {
2749
- return [];
2750
- }
2751
- await this.initialize();
2752
- const ns = namespace != null ? namespace : this.config.projectId;
2753
- try {
2754
- const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
2755
- if (sources.length === 0) {
2756
- return [];
2757
- }
2758
- const context = sources.map((s) => s.content).join("\n\n---\n\n");
2759
- const prompt = `
2760
- Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
2761
- Focus on questions that can be answered by the context.
2762
- Keep each question under 10 words and make them very specific to the content.
2763
- Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
2764
-
2765
- Context:
2766
- ${context}
2767
-
2768
- Suggestions:`;
2769
- const response = await this.llmProvider.chat(
2770
- [
2771
- { role: "system", content: "You are a helpful assistant that generates search suggestions." },
2772
- { role: "user", content: prompt }
2773
- ],
2774
- ""
2775
- );
2776
- const match = response.match(/\[[\s\S]*\]/);
2777
- if (match) {
2778
- const suggestions = JSON.parse(match[0]);
2779
- if (Array.isArray(suggestions)) {
2780
- return suggestions.map((s) => String(s)).slice(0, 3);
2781
- }
2782
- }
2783
- } catch (error) {
2784
- console.error("[Pipeline] Failed to generate suggestions:", error);
2785
- }
2786
- return [];
2787
- }
2788
- };
2789
-
2790
- // src/core/ProviderHealthCheck.ts
2791
- var ProviderHealthCheck = class {
2792
- /**
2793
- * Validates vector database configuration before initialization.
2794
- */
2795
- static async checkVectorProvider(config) {
2796
- const timestamp = Date.now();
2797
- const configErrors = await ConfigValidator.validate({
2798
- projectId: "health-check",
2799
- vectorDb: config,
2800
- llm: { provider: "openai", model: "gpt-4o", apiKey: "none" },
2801
- embedding: { provider: "openai", model: "text-embedding-3-small", apiKey: "none" }
2802
- });
2803
- const vectorDbErrors = configErrors.filter((e) => e.field.startsWith("vectorDb"));
2804
- if (vectorDbErrors.length > 0) {
2805
- return {
2806
- healthy: false,
2807
- provider: config.provider,
2808
- error: `Configuration validation failed: ${vectorDbErrors.map((e) => e.message).join("; ")}`,
2809
- timestamp
2810
- };
2811
- }
2812
- try {
2813
- const checker = await ProviderRegistry.getVectorHealthChecker(config.provider);
2814
- if (checker) {
2815
- return await checker.check(config);
2816
- }
2817
- return await this.fallbackVectorHealthCheck(config, timestamp);
2818
- } catch (error) {
2819
- return {
2820
- healthy: false,
2821
- provider: config.provider,
2822
- error: error instanceof Error ? error.message : String(error),
2823
- timestamp
2824
- };
2825
- }
2826
- }
2827
- static async fallbackVectorHealthCheck(config, timestamp) {
2828
- const opts = config.options || {};
2829
- const baseUrl = opts.baseUrl || opts.uri;
2830
- if (baseUrl && baseUrl.startsWith("http")) {
2831
- try {
2832
- const response = await fetch(`${baseUrl.replace(/\/$/, "")}/health`);
2833
- return {
2834
- healthy: response.ok,
2835
- provider: config.provider,
2836
- error: response.ok ? void 0 : `Health check failed: ${response.status}`,
2837
- timestamp
2838
- };
2839
- } catch (e) {
2840
- return { healthy: false, provider: config.provider, error: "Provider unreachable", timestamp };
2841
- }
2842
- }
2843
- return {
2844
- healthy: true,
2845
- provider: config.provider,
2846
- error: "Pluggable health check not implemented; basic reachability assumed.",
2847
- timestamp
2848
- };
2849
- }
2850
- /**
2851
- * Validates LLM provider configuration.
2852
- */
2853
- static async checkLLMProvider(config) {
2854
- const timestamp = Date.now();
2855
- try {
2856
- const checker = LLMFactory.getHealthChecker(config.provider);
2857
- if (checker) {
2858
- return await checker.check(config);
2859
- }
2860
- return {
2861
- healthy: true,
2862
- provider: config.provider,
2863
- error: "Pluggable health check not implemented; basic reachability assumed.",
2864
- timestamp
2865
- };
2866
- } catch (error) {
2867
- return {
2868
- healthy: false,
2869
- provider: config.provider,
2870
- error: error instanceof Error ? error.message : String(error),
2871
- timestamp
2872
- };
2873
- }
2874
- }
2875
- /**
2876
- * Runs comprehensive health checks on all configured providers in parallel.
2877
- */
2878
- static async checkAll(vectorDbConfig, llmConfig, embeddingConfig) {
2879
- const [vectorDb, llm, embedding] = await Promise.all([
2880
- this.checkVectorProvider(vectorDbConfig),
2881
- this.checkLLMProvider(llmConfig),
2882
- embeddingConfig ? this.checkLLMProvider(embeddingConfig) : Promise.resolve(void 0)
2883
- ]);
2884
- return {
2885
- vectorDb,
2886
- llm,
2887
- embedding,
2888
- allHealthy: vectorDb.healthy && llm.healthy && (!embedding || embedding.healthy)
2889
- };
2890
- }
2891
- };
2892
-
2893
- // src/core/VectorPlugin.ts
2894
- var VectorPlugin = class {
2895
- constructor(hostConfig) {
2896
- this.config = ConfigResolver.resolve(hostConfig);
2897
- this.validationPromise = ConfigValidator.validateAndThrow(this.config);
2898
- this.pipeline = new Pipeline(this.config);
2899
- }
2900
- /**
2901
- * Get the current resolved configuration.
2902
- */
2903
- getConfig() {
2904
- return this.config;
2905
- }
2906
- /**
2907
- * Perform pre-flight health checks on all configured providers.
2908
- * Useful to verify connectivity before running operations.
2909
- *
2910
- * @returns Health status for vector DB, LLM, and embedding providers
2911
- */
2912
- async checkHealth() {
2913
- return ProviderHealthCheck.checkAll(
2914
- this.config.vectorDb,
2915
- this.config.llm,
2916
- this.config.embedding
2917
- );
2918
- }
2919
- /**
2920
- * Run a chat query.
2921
- */
2922
- async chat(message, history = [], namespace) {
2923
- await this.validationPromise;
2924
- return this.pipeline.ask(message, history, namespace);
2925
- }
2926
- /**
2927
- * Run a streaming chat query.
2928
- */
2929
- chatStream(_0) {
2930
- return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
2931
- yield new __await(this.validationPromise);
2932
- yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
2933
- });
2934
- }
2935
- /**
2936
- * Ingest documents into the vector database.
2937
- */
2938
- async ingest(documents, namespace) {
2939
- await this.validationPromise;
2940
- return this.pipeline.ingest(documents, namespace);
2941
- }
2942
- /**
2943
- * Get auto-suggestions based on a query prefix.
2944
- */
2945
- async getSuggestions(query, namespace) {
2946
- await this.validationPromise;
2947
- return this.pipeline.getSuggestions(query, namespace);
2948
- }
2949
- };
2950
-
2951
- // src/utils/DocumentParser.ts
2952
- var DocumentParser = class {
2953
- /**
2954
- * Extract text from a File or Buffer based on its type.
2955
- */
2956
- static async parse(file, fileName, mimeType) {
2957
- var _a;
2958
- const extension = (_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase();
2959
- if (extension === "txt" || extension === "md" || mimeType === "text/plain" || mimeType === "text/markdown") {
2960
- return this.readAsText(file);
2961
- }
2962
- if (extension === "json" || mimeType === "application/json") {
2963
- const text = await this.readAsText(file);
2964
- try {
2965
- const obj = JSON.parse(text);
2966
- return JSON.stringify(obj, null, 2);
2967
- } catch (e) {
2968
- return text;
2969
- }
2970
- }
2971
- if (extension === "csv" || mimeType === "text/csv") {
2972
- return this.readAsText(file);
2973
- }
2974
- if (extension === "pdf" || mimeType === "application/pdf") {
2975
- try {
2976
- const pdf = await import("pdf-parse");
2977
- const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
2978
- const data = await pdf.default(buffer);
2979
- return data.text;
2980
- } catch (err) {
2981
- console.warn('[DocumentParser] PDF parsing failed. Make sure "pdf-parse" is installed.', err);
2982
- return `[PDF Parsing Error for ${fileName}]`;
2983
- }
2984
- }
2985
- if (extension === "docx" || mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
2986
- try {
2987
- const mammoth = await import("mammoth");
2988
- const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
2989
- const result = await mammoth.extractRawText({ buffer });
2990
- return result.value;
2991
- } catch (err) {
2992
- console.warn('[DocumentParser] DOCX parsing failed. Make sure "mammoth" is installed.', err);
2993
- return `[DOCX Parsing Error for ${fileName}]`;
2994
- }
2794
+ "insights": ["Beauty dominates inventory"]
2795
+ }
2796
+ \`\`\`
2797
+ `;
2798
+ if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
2799
+ let cleanPrompt = this.config.llm.systemPrompt || "";
2800
+ cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
2801
+ cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
2802
+ this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
2995
2803
  }
2996
- try {
2997
- return await this.readAsText(file);
2998
- } catch (e) {
2999
- throw new Error(`[DocumentParser] Unsupported file format: ${fileName} (${mimeType})`);
2804
+ console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
2805
+ this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
2806
+ const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
2807
+ this.config.llm,
2808
+ this.config.embedding
2809
+ );
2810
+ this.llmProvider = llmProvider;
2811
+ this.embeddingProvider = embeddingProvider;
2812
+ if (this.config.graphDb) {
2813
+ this.graphDB = await ProviderRegistry.createGraphProvider(this.config.graphDb);
2814
+ await this.graphDB.initialize();
2815
+ this.entityExtractor = new EntityExtractor(this.llmProvider);
3000
2816
  }
3001
- }
3002
- static async readAsText(file) {
3003
- if (Buffer.isBuffer(file)) {
3004
- return file.toString("utf-8");
2817
+ await this.vectorDB.initialize();
2818
+ if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
2819
+ this.agent = new LangChainAgent(this, this.config);
2820
+ await this.agent.initialize(this.llmProvider);
3005
2821
  }
3006
- return await file.text();
2822
+ this.initialised = true;
3007
2823
  }
3008
- };
3009
-
3010
- // src/utils/UITransformer.ts
3011
- var UITransformer = class {
3012
2824
  /**
3013
- * Main transformation method
3014
- * Analyzes user query and retrieved data to determine the best visualization format
3015
- *
3016
- * @param userQuery - The original user question/query
3017
- * @param retrievedData - Vector database retrieval results
3018
- * @returns Structured JSON response for UI rendering
2825
+ * Ingest documents with automatic chunking, embedding, and batch upsert.
2826
+ * Handles retries for transient failures.
3019
2827
  */
3020
- static transform(userQuery, retrievedData) {
3021
- if (!retrievedData || retrievedData.length === 0) {
3022
- return this.createTextResponse("No data available", "No relevant data found for your query.");
3023
- }
3024
- const analysis = this.analyzeData(userQuery, retrievedData);
3025
- switch (analysis.suggestedType) {
3026
- case "product_carousel":
3027
- return this.transformToProductCarousel(retrievedData);
3028
- case "pie_chart":
3029
- return this.transformToPieChart(retrievedData);
3030
- case "bar_chart":
3031
- return this.transformToBarChart(retrievedData);
3032
- case "line_chart":
3033
- return this.transformToLineChart(retrievedData);
3034
- case "table":
3035
- return this.transformToTable(retrievedData);
3036
- default:
3037
- return this.transformToText(retrievedData);
2828
+ async ingest(documents, namespace) {
2829
+ await this.initialize();
2830
+ const ns = namespace != null ? namespace : this.config.projectId;
2831
+ const results = [];
2832
+ for (const doc of documents) {
2833
+ try {
2834
+ const chunks = await this.prepareChunks(doc);
2835
+ const vectors = await this.processEmbeddings(chunks);
2836
+ const upsertDocs = chunks.map((chunk, i) => ({
2837
+ id: chunk.id,
2838
+ vector: vectors[i],
2839
+ content: chunk.content,
2840
+ metadata: chunk.metadata
2841
+ }));
2842
+ const totalProcessed = await this.processUpserts(upsertDocs, ns);
2843
+ results.push({
2844
+ docId: doc.docId,
2845
+ chunksIngested: totalProcessed
2846
+ });
2847
+ if (this.graphDB && this.entityExtractor) {
2848
+ await this.processGraphIngestion(doc.docId, chunks);
2849
+ }
2850
+ } catch (error) {
2851
+ console.error(`[Pipeline] Failed to ingest document ${doc.docId}:`, error);
2852
+ results.push({ docId: doc.docId, chunksIngested: 0 });
2853
+ }
3038
2854
  }
2855
+ return results;
3039
2856
  }
3040
2857
  /**
3041
- * Analyzes the data and query to suggest the best visualization type
2858
+ * Step 1: Chunk the document content.
3042
2859
  */
3043
- static analyzeData(query, data) {
3044
- const queryLower = query.toLowerCase();
3045
- const hasProducts = data.some(
3046
- (item) => this.isProductData(item)
3047
- );
3048
- const hasTimeSeries = data.some(
3049
- (item) => this.isTimeSeriesData(item)
3050
- );
3051
- const hasCategories = this.detectCategories(data).length > 0;
3052
- const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
3053
- const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
3054
- const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
3055
- if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
3056
- return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
3057
- }
3058
- if (isTrendQuery && hasTimeSeries) {
3059
- return { suggestedType: "line_chart", confidence: 0.9, hasProducts, hasTimeSeries, hasCategories };
3060
- }
3061
- if (isDistributionQuery && hasCategories) {
3062
- return { suggestedType: "pie_chart", confidence: 0.85, hasProducts, hasTimeSeries, hasCategories };
3063
- }
3064
- if (isComparisonQuery && hasCategories && data.length > 2) {
3065
- return { suggestedType: "bar_chart", confidence: 0.8, hasProducts, hasTimeSeries, hasCategories };
3066
- }
3067
- if (data.length > 5 && this.hasMultipleFields(data)) {
3068
- return { suggestedType: "table", confidence: 0.75, hasProducts, hasTimeSeries, hasCategories };
2860
+ async prepareChunks(doc) {
2861
+ var _a, _b;
2862
+ if (this.llamaIngestor) {
2863
+ return await this.llamaIngestor.chunk(doc.content, {
2864
+ docId: doc.docId,
2865
+ metadata: doc.metadata,
2866
+ chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
2867
+ chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
2868
+ });
3069
2869
  }
3070
- return { suggestedType: "text", confidence: 0.5, hasProducts, hasTimeSeries, hasCategories };
2870
+ return this.chunker.chunk(doc.content, {
2871
+ docId: doc.docId,
2872
+ metadata: doc.metadata
2873
+ });
3071
2874
  }
3072
2875
  /**
3073
- * Transform data to product carousel format
2876
+ * Step 2: Generate embeddings for chunks with retry logic.
2877
+ * Uses batchEmbed when available for efficiency; falls back to sequential embedding.
3074
2878
  */
3075
- static transformToProductCarousel(data) {
3076
- const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
3077
- return {
3078
- type: "product_carousel",
3079
- title: "Recommended Products",
3080
- description: `Found ${products.length} relevant products`,
3081
- data: products
2879
+ async processEmbeddings(chunks) {
2880
+ const embedBatchOptions = {
2881
+ batchSize: 50,
2882
+ maxRetries: 3,
2883
+ initialDelayMs: 100
3082
2884
  };
2885
+ const vectors = await BatchProcessor.mapWithRetry(
2886
+ chunks.map((c) => c.content),
2887
+ (text) => this.embeddingProvider.embed(text, { taskType: "document" }),
2888
+ embedBatchOptions
2889
+ );
2890
+ if (vectors.length !== chunks.length) {
2891
+ throw new Error(
2892
+ `[Pipeline] Embedding mismatch: got ${vectors.length} vectors for ${chunks.length} chunks. Check embedding provider logs for individual chunk failures.`
2893
+ );
2894
+ }
2895
+ return vectors;
3083
2896
  }
3084
2897
  /**
3085
- * Transform data to pie chart format
2898
+ * Step 3: Upsert chunks to vector database with retry logic.
3086
2899
  */
3087
- static transformToPieChart(data) {
3088
- const categories = this.detectCategories(data);
3089
- const categoryData = this.aggregateByCategory(data, categories);
3090
- const pieData = Object.entries(categoryData).map(([label, count]) => ({
3091
- label,
3092
- value: count,
3093
- inStock: this.checkStockStatus(label, data)
3094
- }));
3095
- return {
3096
- type: "pie_chart",
3097
- title: "Distribution by Category",
3098
- description: `Showing breakdown across ${categories.length} categories`,
3099
- data: pieData
2900
+ async processUpserts(upsertDocs, namespace) {
2901
+ const upsertBatchOptions = {
2902
+ batchSize: 100,
2903
+ maxRetries: 3,
2904
+ initialDelayMs: 100
3100
2905
  };
2906
+ const upsertResult = await BatchProcessor.processBatch(
2907
+ upsertDocs,
2908
+ (batch) => this.vectorDB.batchUpsert(batch, namespace),
2909
+ upsertBatchOptions
2910
+ );
2911
+ if (upsertResult.errors.length > 0) {
2912
+ console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed`);
2913
+ }
2914
+ return upsertResult.totalProcessed;
3101
2915
  }
3102
2916
  /**
3103
- * Transform data to bar chart format
2917
+ * Step 4: Optional graph-based entity extraction and ingestion.
3104
2918
  */
3105
- static transformToBarChart(data) {
3106
- const categories = this.detectCategories(data);
3107
- const categoryData = this.aggregateByCategory(data, categories);
3108
- const barData = Object.entries(categoryData).map(([category, value]) => ({
3109
- category,
3110
- value,
3111
- inStock: this.checkStockStatus(category, data)
3112
- }));
3113
- return {
3114
- type: "bar_chart",
3115
- title: "Comparison by Category",
3116
- description: `Comparing ${categories.length} categories`,
3117
- data: barData
2919
+ async processGraphIngestion(docId, chunks) {
2920
+ console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
2921
+ const extractionOptions = {
2922
+ batchSize: 2,
2923
+ // Low concurrency for LLM extraction
2924
+ maxRetries: 1,
2925
+ initialDelayMs: 500
3118
2926
  };
2927
+ await BatchProcessor.processBatch(
2928
+ chunks,
2929
+ async (batch) => {
2930
+ for (const chunk of batch) {
2931
+ try {
2932
+ const { nodes, edges } = await this.entityExtractor.extract(chunk.content);
2933
+ if (nodes.length > 0) await this.graphDB.addNodes(nodes);
2934
+ if (edges.length > 0) await this.graphDB.addEdges(edges);
2935
+ } catch (err) {
2936
+ console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
2937
+ }
2938
+ }
2939
+ },
2940
+ extractionOptions
2941
+ );
2942
+ }
2943
+ async ask(question, history = [], namespace) {
2944
+ var _a;
2945
+ await this.initialize();
2946
+ if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
2947
+ console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
2948
+ const agentReply = await this.agent.run(question, history);
2949
+ return { reply: agentReply, sources: [] };
2950
+ }
2951
+ const stream = this.askStream(question, history, namespace);
2952
+ let reply = "";
2953
+ let sources = [];
2954
+ let graphData;
2955
+ try {
2956
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2957
+ const chunk = temp.value;
2958
+ if (typeof chunk === "string") {
2959
+ reply += chunk;
2960
+ } else if ("sources" in chunk) {
2961
+ sources = chunk.sources;
2962
+ graphData = chunk.graphData;
2963
+ }
2964
+ }
2965
+ } catch (temp) {
2966
+ error = [temp];
2967
+ } finally {
2968
+ try {
2969
+ more && (temp = iter.return) && await temp.call(iter);
2970
+ } finally {
2971
+ if (error)
2972
+ throw error[0];
2973
+ }
2974
+ }
2975
+ return { reply, sources, graphData };
3119
2976
  }
3120
2977
  /**
3121
- * Transform data to line chart format
2978
+ * High-performance streaming RAG flow.
2979
+ * Yields text chunks first, then the retrieval metadata at the end.
3122
2980
  */
3123
- static transformToLineChart(data) {
3124
- const timePoints = this.extractTimeSeriesData(data);
3125
- const lineData = timePoints.map((point) => ({
3126
- timestamp: point.timestamp,
3127
- value: point.value,
3128
- label: point.label
3129
- }));
3130
- return {
3131
- type: "line_chart",
3132
- title: "Trend Over Time",
3133
- description: `Showing ${lineData.length} data points`,
3134
- data: lineData
3135
- };
2981
+ askStream(_0) {
2982
+ return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
2983
+ var _a, _b, _c, _d, _e, _f, _g;
2984
+ yield new __await(this.initialize());
2985
+ const ns = namespace != null ? namespace : this.config.projectId;
2986
+ const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
2987
+ const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
2988
+ try {
2989
+ let searchQuery = question;
2990
+ if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
2991
+ searchQuery = yield new __await(this.rewriteQuery(question, history));
2992
+ }
2993
+ const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
2994
+ const filter = QueryProcessor.buildQueryFilter(question, hints);
2995
+ console.log(`[Pipeline] \u{1F9E9} Extracted filters:`, JSON.stringify(filter));
2996
+ const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
2997
+ namespace: ns,
2998
+ topK: topK * 2,
2999
+ filter
3000
+ }));
3001
+ let sources = rawSources.filter((m) => m.score >= scoreThreshold);
3002
+ if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
3003
+ sources = yield new __await(this.reranker.rerank(sources, question, topK));
3004
+ } else {
3005
+ sources = sources.slice(0, topK);
3006
+ }
3007
+ let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
3008
+ ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
3009
+ if (graphData && graphData.nodes.length > 0) {
3010
+ const graphContext = graphData.nodes.map(
3011
+ (n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
3012
+ ).join("\n");
3013
+ context = `GRAPH KNOWLEDGE:
3014
+ ${graphContext}
3015
+
3016
+ VECTOR CONTEXT:
3017
+ ${context}`;
3018
+ }
3019
+ const messages = [...history, { role: "user", content: question }];
3020
+ if (this.llmProvider.chatStream) {
3021
+ const stream = this.llmProvider.chatStream(messages, context);
3022
+ if (!stream) {
3023
+ throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
3024
+ }
3025
+ try {
3026
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
3027
+ const chunk = temp.value;
3028
+ yield chunk;
3029
+ }
3030
+ } catch (temp) {
3031
+ error = [temp];
3032
+ } finally {
3033
+ try {
3034
+ more && (temp = iter.return) && (yield new __await(temp.call(iter)));
3035
+ } finally {
3036
+ if (error)
3037
+ throw error[0];
3038
+ }
3039
+ }
3040
+ } else {
3041
+ const reply = yield new __await(this.llmProvider.chat(messages, context));
3042
+ yield reply;
3043
+ }
3044
+ const uiTransformation = UITransformer.transform(question, sources);
3045
+ yield {
3046
+ reply: "",
3047
+ sources,
3048
+ graphData,
3049
+ ui_transformation: uiTransformation
3050
+ };
3051
+ } catch (error2) {
3052
+ throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
3053
+ }
3054
+ });
3136
3055
  }
3137
3056
  /**
3138
- * Transform data to table format
3057
+ * Universal retrieval method combining all enabled providers.
3058
+ * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
3139
3059
  */
3140
- static transformToTable(data) {
3141
- const columns = this.extractTableColumns(data);
3142
- const rows = data.map((item) => this.extractTableRow(item, columns));
3143
- const tableData = {
3144
- columns,
3145
- rows
3146
- };
3147
- return {
3148
- type: "table",
3149
- title: "Detailed Results",
3150
- description: `Showing ${data.length} results`,
3151
- data: tableData
3152
- };
3060
+ async retrieve(query, options) {
3061
+ var _a, _b, _c;
3062
+ const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
3063
+ const topK = (_b = options.topK) != null ? _b : 5;
3064
+ const cacheKey = `${ns}::${query}`;
3065
+ let queryVector = this.embeddingCache.get(cacheKey);
3066
+ const [retrievedVector, graphData] = await Promise.all([
3067
+ queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }),
3068
+ this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
3069
+ ]);
3070
+ if (!queryVector) {
3071
+ this.embeddingCache.set(cacheKey, retrievedVector);
3072
+ queryVector = retrievedVector;
3073
+ }
3074
+ const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
3075
+ return { sources, graphData };
3153
3076
  }
3154
3077
  /**
3155
- * Transform data to text format (fallback)
3078
+ * Rewrite the user query for better retrieval performance.
3156
3079
  */
3157
- static transformToText(data) {
3158
- const textContent = data.map((item) => item.content).join("\n\n");
3159
- return this.createTextResponse(
3160
- "Search Results",
3161
- textContent,
3162
- `Found ${data.length} relevant results`
3080
+ async rewriteQuery(question, history) {
3081
+ const prompt = `
3082
+ Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
3083
+ Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
3084
+
3085
+ History:
3086
+ ${history.map((m) => `${m.role}: ${m.content}`).join("\n")}
3087
+
3088
+ New Question: ${question}
3089
+
3090
+ Optimized Search Query:`;
3091
+ const rewrite = await this.llmProvider.chat(
3092
+ [
3093
+ { role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
3094
+ { role: "user", content: prompt }
3095
+ ],
3096
+ ""
3163
3097
  );
3098
+ return rewrite.trim() || question;
3164
3099
  }
3165
3100
  /**
3166
- * Helper: Create text response
3101
+ * Generate 3-5 short, relevant questions based on the vector database content.
3167
3102
  */
3168
- static createTextResponse(title, content, description) {
3169
- return {
3170
- type: "text",
3171
- title,
3172
- description,
3173
- data: { content }
3174
- };
3103
+ async getSuggestions(query, namespace) {
3104
+ if (!query || query.trim().length < 3) {
3105
+ return [];
3106
+ }
3107
+ await this.initialize();
3108
+ const ns = namespace != null ? namespace : this.config.projectId;
3109
+ try {
3110
+ const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
3111
+ if (sources.length === 0) {
3112
+ return [];
3113
+ }
3114
+ const context = sources.map((s) => s.content).join("\n\n---\n\n");
3115
+ const prompt = `
3116
+ Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
3117
+ Focus on questions that can be answered by the context.
3118
+ Keep each question under 10 words and make them very specific to the content.
3119
+ Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
3120
+
3121
+ Context:
3122
+ ${context}
3123
+
3124
+ Suggestions:`;
3125
+ const response = await this.llmProvider.chat(
3126
+ [
3127
+ { role: "system", content: "You are a helpful assistant that generates search suggestions." },
3128
+ { role: "user", content: prompt }
3129
+ ],
3130
+ ""
3131
+ );
3132
+ const match = response.match(/\[[\s\S]*\]/);
3133
+ if (match) {
3134
+ const suggestions = JSON.parse(match[0]);
3135
+ if (Array.isArray(suggestions)) {
3136
+ return suggestions.map((s) => String(s)).slice(0, 3);
3137
+ }
3138
+ }
3139
+ } catch (error) {
3140
+ console.error("[Pipeline] Failed to generate suggestions:", error);
3141
+ }
3142
+ return [];
3175
3143
  }
3144
+ };
3145
+
3146
+ // src/core/ProviderHealthCheck.ts
3147
+ var ProviderHealthCheck = class {
3176
3148
  /**
3177
- * Helper: Check if data item is product-related
3149
+ * Validates vector database configuration before initialization.
3178
3150
  */
3179
- static isProductData(item) {
3180
- const content = (item.content || "").toLowerCase();
3181
- const productKeywords = ["product", "price", "stock", "item", "sku", "brand", "model"];
3182
- return productKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
3183
- (k) => ["name", "price", "product", "sku"].includes(k.toLowerCase())
3184
- );
3151
+ static async checkVectorProvider(config) {
3152
+ const timestamp = Date.now();
3153
+ const configErrors = await ConfigValidator.validate({
3154
+ projectId: "health-check",
3155
+ vectorDb: config,
3156
+ llm: { provider: "openai", model: "gpt-4o", apiKey: "none" },
3157
+ embedding: { provider: "openai", model: "text-embedding-3-small", apiKey: "none" }
3158
+ });
3159
+ const vectorDbErrors = configErrors.filter((e) => e.field.startsWith("vectorDb"));
3160
+ if (vectorDbErrors.length > 0) {
3161
+ return {
3162
+ healthy: false,
3163
+ provider: config.provider,
3164
+ error: `Configuration validation failed: ${vectorDbErrors.map((e) => e.message).join("; ")}`,
3165
+ timestamp
3166
+ };
3167
+ }
3168
+ try {
3169
+ const checker = await ProviderRegistry.getVectorHealthChecker(config.provider);
3170
+ if (checker) {
3171
+ return await checker.check(config);
3172
+ }
3173
+ return await this.fallbackVectorHealthCheck(config, timestamp);
3174
+ } catch (error) {
3175
+ return {
3176
+ healthy: false,
3177
+ provider: config.provider,
3178
+ error: error instanceof Error ? error.message : String(error),
3179
+ timestamp
3180
+ };
3181
+ }
3185
3182
  }
3186
- /**
3187
- * Helper: Check if data contains time series
3188
- */
3189
- static isTimeSeriesData(item) {
3190
- const content = (item.content || "").toLowerCase();
3191
- const timeKeywords = ["date", "time", "year", "month", "week", "day", "hour", "trend", "historical"];
3192
- return timeKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
3193
- (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
3194
- );
3183
+ static async fallbackVectorHealthCheck(config, timestamp) {
3184
+ const opts = config.options || {};
3185
+ const baseUrl = opts.baseUrl || opts.uri;
3186
+ if (baseUrl && baseUrl.startsWith("http")) {
3187
+ try {
3188
+ const response = await fetch(`${baseUrl.replace(/\/$/, "")}/health`);
3189
+ return {
3190
+ healthy: response.ok,
3191
+ provider: config.provider,
3192
+ error: response.ok ? void 0 : `Health check failed: ${response.status}`,
3193
+ timestamp
3194
+ };
3195
+ } catch (e) {
3196
+ return { healthy: false, provider: config.provider, error: "Provider unreachable", timestamp };
3197
+ }
3198
+ }
3199
+ return {
3200
+ healthy: true,
3201
+ provider: config.provider,
3202
+ error: "Pluggable health check not implemented; basic reachability assumed.",
3203
+ timestamp
3204
+ };
3195
3205
  }
3196
3206
  /**
3197
- * Helper: Extract product information from vector match
3207
+ * Validates LLM provider configuration.
3198
3208
  */
3199
- static extractProductInfo(item) {
3200
- const meta = item.metadata || {};
3201
- if (meta.name || meta.product) {
3209
+ static async checkLLMProvider(config) {
3210
+ const timestamp = Date.now();
3211
+ try {
3212
+ const checker = LLMFactory.getHealthChecker(config.provider);
3213
+ if (checker) {
3214
+ return await checker.check(config);
3215
+ }
3202
3216
  return {
3203
- id: item.id,
3204
- name: meta.name || meta.product,
3205
- price: meta.price,
3206
- image: meta.image || meta.imageUrl,
3207
- brand: meta.brand,
3208
- description: item.content,
3209
- inStock: this.determineStockStatus(item)
3217
+ healthy: true,
3218
+ provider: config.provider,
3219
+ error: "Pluggable health check not implemented; basic reachability assumed.",
3220
+ timestamp
3210
3221
  };
3211
- }
3212
- const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
3213
- const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
3214
- if (nameMatch) {
3222
+ } catch (error) {
3215
3223
  return {
3216
- id: item.id,
3217
- name: nameMatch[1],
3218
- price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
3219
- description: item.content,
3220
- inStock: this.determineStockStatus(item)
3224
+ healthy: false,
3225
+ provider: config.provider,
3226
+ error: error instanceof Error ? error.message : String(error),
3227
+ timestamp
3221
3228
  };
3222
3229
  }
3223
- return null;
3224
3230
  }
3225
3231
  /**
3226
- * Helper: Detect categories in data
3232
+ * Runs comprehensive health checks on all configured providers in parallel.
3227
3233
  */
3228
- static detectCategories(data) {
3229
- const categories = /* @__PURE__ */ new Set();
3230
- data.forEach((item) => {
3231
- const meta = item.metadata || {};
3232
- if (meta.category) {
3233
- categories.add(String(meta.category));
3234
- }
3235
- if (meta.type) {
3236
- categories.add(String(meta.type));
3237
- }
3238
- if (meta.tag) {
3239
- const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
3240
- tags.forEach((t) => categories.add(String(t)));
3241
- }
3242
- const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
3243
- if (categoryMatch) {
3244
- categories.add(categoryMatch[1].trim());
3245
- }
3246
- });
3247
- return Array.from(categories);
3234
+ static async checkAll(vectorDbConfig, llmConfig, embeddingConfig) {
3235
+ const [vectorDb, llm, embedding] = await Promise.all([
3236
+ this.checkVectorProvider(vectorDbConfig),
3237
+ this.checkLLMProvider(llmConfig),
3238
+ embeddingConfig ? this.checkLLMProvider(embeddingConfig) : Promise.resolve(void 0)
3239
+ ]);
3240
+ return {
3241
+ vectorDb,
3242
+ llm,
3243
+ embedding,
3244
+ allHealthy: vectorDb.healthy && llm.healthy && (!embedding || embedding.healthy)
3245
+ };
3246
+ }
3247
+ };
3248
+
3249
+ // src/core/VectorPlugin.ts
3250
+ var VectorPlugin = class {
3251
+ constructor(hostConfig) {
3252
+ this.config = ConfigResolver.resolve(hostConfig);
3253
+ this.validationPromise = ConfigValidator.validateAndThrow(this.config);
3254
+ this.pipeline = new Pipeline(this.config);
3248
3255
  }
3249
3256
  /**
3250
- * Helper: Aggregate data by category
3257
+ * Get the current resolved configuration.
3251
3258
  */
3252
- static aggregateByCategory(data, categories) {
3253
- const result = {};
3254
- categories.forEach((cat) => {
3255
- result[cat] = 0;
3256
- });
3257
- data.forEach((item) => {
3258
- const meta = item.metadata || {};
3259
- const itemCategory = meta.category || meta.type || "Other";
3260
- if (result.hasOwnProperty(itemCategory)) {
3261
- result[itemCategory]++;
3262
- } else {
3263
- result["Other"] = (result["Other"] || 0) + 1;
3264
- }
3265
- });
3266
- return result;
3259
+ getConfig() {
3260
+ return this.config;
3267
3261
  }
3268
3262
  /**
3269
- * Helper: Extract time series data
3263
+ * Perform pre-flight health checks on all configured providers.
3264
+ * Useful to verify connectivity before running operations.
3265
+ *
3266
+ * @returns Health status for vector DB, LLM, and embedding providers
3270
3267
  */
3271
- static extractTimeSeriesData(data) {
3272
- return data.map((item) => {
3273
- const meta = item.metadata || {};
3274
- return {
3275
- timestamp: meta.timestamp || meta.date || (/* @__PURE__ */ new Date()).toISOString(),
3276
- value: meta.value || item.score || 0,
3277
- label: meta.label || item.content.substring(0, 50)
3278
- };
3279
- });
3268
+ async checkHealth() {
3269
+ return ProviderHealthCheck.checkAll(
3270
+ this.config.vectorDb,
3271
+ this.config.llm,
3272
+ this.config.embedding
3273
+ );
3280
3274
  }
3281
3275
  /**
3282
- * Helper: Extract table columns
3276
+ * Run a chat query.
3283
3277
  */
3284
- static extractTableColumns(data) {
3285
- const columnSet = /* @__PURE__ */ new Set();
3286
- columnSet.add("Content");
3287
- data.forEach((item) => {
3288
- Object.keys(item.metadata || {}).forEach((key) => {
3289
- columnSet.add(key.charAt(0).toUpperCase() + key.slice(1));
3290
- });
3291
- });
3292
- return Array.from(columnSet);
3278
+ async chat(message, history = [], namespace) {
3279
+ await this.validationPromise;
3280
+ return this.pipeline.ask(message, history, namespace);
3293
3281
  }
3294
3282
  /**
3295
- * Helper: Extract table row
3283
+ * Run a streaming chat query.
3296
3284
  */
3297
- static extractTableRow(item, columns) {
3298
- const meta = item.metadata || {};
3299
- return columns.map((col) => {
3300
- if (col === "Content") {
3301
- return item.content.substring(0, 100);
3302
- }
3303
- const metaKey = col.charAt(0).toLowerCase() + col.slice(1);
3304
- const value = meta[metaKey];
3305
- return value !== void 0 ? String(value) : "";
3285
+ chatStream(_0) {
3286
+ return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
3287
+ yield new __await(this.validationPromise);
3288
+ yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
3306
3289
  });
3307
3290
  }
3308
3291
  /**
3309
- * Helper: Check stock status
3292
+ * Ingest documents into the vector database.
3310
3293
  */
3311
- static checkStockStatus(category, data) {
3312
- const item = data.find((d) => {
3313
- const meta = d.metadata || {};
3314
- return meta.category === category || meta.type === category;
3315
- });
3316
- return this.determineStockStatus(item || {});
3294
+ async ingest(documents, namespace) {
3295
+ await this.validationPromise;
3296
+ return this.pipeline.ingest(documents, namespace);
3317
3297
  }
3318
3298
  /**
3319
- * Helper: Determine if item is in stock
3299
+ * Get auto-suggestions based on a query prefix.
3320
3300
  */
3321
- static determineStockStatus(item) {
3322
- const meta = item.metadata || {};
3323
- if (meta.inStock !== void 0) {
3324
- return Boolean(meta.inStock);
3301
+ async getSuggestions(query, namespace) {
3302
+ await this.validationPromise;
3303
+ return this.pipeline.getSuggestions(query, namespace);
3304
+ }
3305
+ };
3306
+
3307
+ // src/utils/DocumentParser.ts
3308
+ var DocumentParser = class {
3309
+ /**
3310
+ * Extract text from a File or Buffer based on its type.
3311
+ */
3312
+ static async parse(file, fileName, mimeType) {
3313
+ var _a;
3314
+ const extension = (_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase();
3315
+ if (extension === "txt" || extension === "md" || mimeType === "text/plain" || mimeType === "text/markdown") {
3316
+ return this.readAsText(file);
3325
3317
  }
3326
- if (meta.stock !== void 0) {
3327
- return Boolean(meta.stock);
3318
+ if (extension === "json" || mimeType === "application/json") {
3319
+ const text = await this.readAsText(file);
3320
+ try {
3321
+ const obj = JSON.parse(text);
3322
+ return JSON.stringify(obj, null, 2);
3323
+ } catch (e) {
3324
+ return text;
3325
+ }
3328
3326
  }
3329
- if (meta.available !== void 0) {
3330
- return Boolean(meta.available);
3327
+ if (extension === "csv" || mimeType === "text/csv") {
3328
+ return this.readAsText(file);
3331
3329
  }
3332
- const content = (item.content || "").toLowerCase();
3333
- if (content.includes("out of stock") || content.includes("unavailable")) {
3334
- return false;
3330
+ if (extension === "pdf" || mimeType === "application/pdf") {
3331
+ try {
3332
+ const pdf = await import("pdf-parse");
3333
+ const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
3334
+ const data = await pdf.default(buffer);
3335
+ return data.text;
3336
+ } catch (err) {
3337
+ console.warn('[DocumentParser] PDF parsing failed. Make sure "pdf-parse" is installed.', err);
3338
+ return `[PDF Parsing Error for ${fileName}]`;
3339
+ }
3335
3340
  }
3336
- if (content.includes("in stock") || content.includes("available")) {
3337
- return true;
3341
+ if (extension === "docx" || mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
3342
+ try {
3343
+ const mammoth = await import("mammoth");
3344
+ const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
3345
+ const result = await mammoth.extractRawText({ buffer });
3346
+ return result.value;
3347
+ } catch (err) {
3348
+ console.warn('[DocumentParser] DOCX parsing failed. Make sure "mammoth" is installed.', err);
3349
+ return `[DOCX Parsing Error for ${fileName}]`;
3350
+ }
3351
+ }
3352
+ try {
3353
+ return await this.readAsText(file);
3354
+ } catch (e) {
3355
+ throw new Error(`[DocumentParser] Unsupported file format: ${fileName} (${mimeType})`);
3338
3356
  }
3339
- return true;
3340
3357
  }
3341
- /**
3342
- * Helper: Check if data has multiple fields
3343
- */
3344
- static hasMultipleFields(data) {
3345
- const fieldCount = /* @__PURE__ */ new Set();
3346
- data.forEach((item) => {
3347
- Object.keys(item.metadata || {}).forEach((key) => {
3348
- fieldCount.add(key);
3349
- });
3350
- });
3351
- return fieldCount.size > 2;
3358
+ static async readAsText(file) {
3359
+ if (Buffer.isBuffer(file)) {
3360
+ return file.toString("utf-8");
3361
+ }
3362
+ return await file.text();
3352
3363
  }
3353
3364
  };
3354
3365