@retrivora-ai/rag-engine 1.7.6 → 1.7.8

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.
@@ -3775,65 +3775,410 @@ var QueryProcessor = class {
3775
3775
  }
3776
3776
  };
3777
3777
 
3778
- // src/core/Pipeline.ts
3779
- var LRUEmbeddingCache = class {
3780
- constructor(maxSize = 500) {
3781
- this.cache = /* @__PURE__ */ new Map();
3782
- this.maxSize = maxSize;
3783
- }
3784
- get(key) {
3785
- const value = this.cache.get(key);
3786
- if (value !== void 0) {
3787
- this.cache.delete(key);
3788
- this.cache.set(key, value);
3778
+ // src/utils/UITransformer.ts
3779
+ var UITransformer = class {
3780
+ /**
3781
+ * Main transformation method
3782
+ * Analyzes user query and retrieved data to determine the best visualization format
3783
+ *
3784
+ * @param userQuery - The original user question/query
3785
+ * @param retrievedData - Vector database retrieval results
3786
+ * @returns Structured JSON response for UI rendering
3787
+ */
3788
+ static transform(userQuery, retrievedData) {
3789
+ if (!retrievedData || retrievedData.length === 0) {
3790
+ return this.createTextResponse("No data available", "No relevant data found for your query.");
3791
+ }
3792
+ const analysis = this.analyzeData(userQuery, retrievedData);
3793
+ switch (analysis.suggestedType) {
3794
+ case "product_carousel":
3795
+ return this.transformToProductCarousel(retrievedData);
3796
+ case "pie_chart":
3797
+ return this.transformToPieChart(retrievedData);
3798
+ case "bar_chart":
3799
+ return this.transformToBarChart(retrievedData);
3800
+ case "line_chart":
3801
+ return this.transformToLineChart(retrievedData);
3802
+ case "table":
3803
+ return this.transformToTable(retrievedData);
3804
+ default:
3805
+ return this.transformToText(retrievedData);
3789
3806
  }
3790
- return value;
3791
3807
  }
3792
- set(key, value) {
3793
- if (this.cache.has(key)) {
3794
- this.cache.delete(key);
3795
- } else if (this.cache.size >= this.maxSize) {
3796
- const oldestKey = this.cache.keys().next().value;
3797
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
3808
+ /**
3809
+ * Analyzes the data and query to suggest the best visualization type
3810
+ */
3811
+ static analyzeData(query, data) {
3812
+ const queryLower = query.toLowerCase();
3813
+ const hasProducts = data.some(
3814
+ (item) => this.isProductData(item)
3815
+ );
3816
+ const hasTimeSeries = data.some(
3817
+ (item) => this.isTimeSeriesData(item)
3818
+ );
3819
+ const hasCategories = this.detectCategories(data).length > 0;
3820
+ const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
3821
+ const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
3822
+ const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
3823
+ if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
3824
+ return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
3798
3825
  }
3799
- this.cache.set(key, value);
3826
+ if (isTrendQuery && hasTimeSeries) {
3827
+ return { suggestedType: "line_chart", confidence: 0.9, hasProducts, hasTimeSeries, hasCategories };
3828
+ }
3829
+ if (isDistributionQuery && hasCategories) {
3830
+ return { suggestedType: "pie_chart", confidence: 0.85, hasProducts, hasTimeSeries, hasCategories };
3831
+ }
3832
+ if (isComparisonQuery && hasCategories && data.length > 2) {
3833
+ return { suggestedType: "bar_chart", confidence: 0.8, hasProducts, hasTimeSeries, hasCategories };
3834
+ }
3835
+ if (data.length > 5 && this.hasMultipleFields(data)) {
3836
+ return { suggestedType: "table", confidence: 0.75, hasProducts, hasTimeSeries, hasCategories };
3837
+ }
3838
+ return { suggestedType: "text", confidence: 0.5, hasProducts, hasTimeSeries, hasCategories };
3800
3839
  }
3801
- clear() {
3802
- this.cache.clear();
3840
+ /**
3841
+ * Transform data to product carousel format
3842
+ */
3843
+ static transformToProductCarousel(data) {
3844
+ const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
3845
+ return {
3846
+ type: "product_carousel",
3847
+ title: "Recommended Products",
3848
+ description: `Found ${products.length} relevant products`,
3849
+ data: products
3850
+ };
3803
3851
  }
3804
- get size() {
3805
- return this.cache.size;
3852
+ /**
3853
+ * Transform data to pie chart format
3854
+ */
3855
+ static transformToPieChart(data) {
3856
+ const categories = this.detectCategories(data);
3857
+ const categoryData = this.aggregateByCategory(data, categories);
3858
+ const pieData = Object.entries(categoryData).map(([label, count]) => ({
3859
+ label,
3860
+ value: count,
3861
+ inStock: this.checkStockStatus(label, data)
3862
+ }));
3863
+ return {
3864
+ type: "pie_chart",
3865
+ title: "Distribution by Category",
3866
+ description: `Showing breakdown across ${categories.length} categories`,
3867
+ data: pieData
3868
+ };
3806
3869
  }
3807
- };
3808
- var Pipeline = class {
3809
- constructor(config) {
3810
- this.config = config;
3811
- /** LRU-bounded cache: avoids re-embedding identical queries within the same process. */
3812
- this.embeddingCache = new LRUEmbeddingCache(500);
3813
- this.initialised = false;
3814
- var _a, _b, _c, _d, _e;
3815
- this.chunker = new DocumentChunker(
3816
- (_b = (_a = config.rag) == null ? void 0 : _a.chunkSize) != null ? _b : 1e3,
3817
- (_d = (_c = config.rag) == null ? void 0 : _c.chunkOverlap) != null ? _d : 200
3870
+ /**
3871
+ * Transform data to bar chart format
3872
+ */
3873
+ static transformToBarChart(data) {
3874
+ const categories = this.detectCategories(data);
3875
+ const categoryData = this.aggregateByCategory(data, categories);
3876
+ const barData = Object.entries(categoryData).map(([category, value]) => ({
3877
+ category,
3878
+ value,
3879
+ inStock: this.checkStockStatus(category, data)
3880
+ }));
3881
+ return {
3882
+ type: "bar_chart",
3883
+ title: "Comparison by Category",
3884
+ description: `Comparing ${categories.length} categories`,
3885
+ data: barData
3886
+ };
3887
+ }
3888
+ /**
3889
+ * Transform data to line chart format
3890
+ */
3891
+ static transformToLineChart(data) {
3892
+ const timePoints = this.extractTimeSeriesData(data);
3893
+ const lineData = timePoints.map((point) => ({
3894
+ timestamp: point.timestamp,
3895
+ value: point.value,
3896
+ label: point.label
3897
+ }));
3898
+ return {
3899
+ type: "line_chart",
3900
+ title: "Trend Over Time",
3901
+ description: `Showing ${lineData.length} data points`,
3902
+ data: lineData
3903
+ };
3904
+ }
3905
+ /**
3906
+ * Transform data to table format
3907
+ */
3908
+ static transformToTable(data) {
3909
+ const columns = this.extractTableColumns(data);
3910
+ const rows = data.map((item) => this.extractTableRow(item, columns));
3911
+ const tableData = {
3912
+ columns,
3913
+ rows
3914
+ };
3915
+ return {
3916
+ type: "table",
3917
+ title: "Detailed Results",
3918
+ description: `Showing ${data.length} results`,
3919
+ data: tableData
3920
+ };
3921
+ }
3922
+ /**
3923
+ * Transform data to text format (fallback)
3924
+ */
3925
+ static transformToText(data) {
3926
+ const textContent = data.map((item) => item.content).join("\n\n");
3927
+ return this.createTextResponse(
3928
+ "Search Results",
3929
+ textContent,
3930
+ `Found ${data.length} relevant results`
3818
3931
  );
3819
- if (((_e = config.rag) == null ? void 0 : _e.chunkingStrategy) === "llamaindex") {
3820
- this.llamaIngestor = new LlamaIndexIngestor();
3932
+ }
3933
+ /**
3934
+ * Helper: Create text response
3935
+ */
3936
+ static createTextResponse(title, content, description) {
3937
+ return {
3938
+ type: "text",
3939
+ title,
3940
+ description,
3941
+ data: { content }
3942
+ };
3943
+ }
3944
+ /**
3945
+ * Helper: Check if data item is product-related
3946
+ */
3947
+ static isProductData(item) {
3948
+ const content = (item.content || "").toLowerCase();
3949
+ const productKeywords = ["product", "price", "stock", "item", "sku", "brand", "model"];
3950
+ return productKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
3951
+ (k) => ["name", "price", "product", "sku"].includes(k.toLowerCase())
3952
+ );
3953
+ }
3954
+ /**
3955
+ * Helper: Check if data contains time series
3956
+ */
3957
+ static isTimeSeriesData(item) {
3958
+ const content = (item.content || "").toLowerCase();
3959
+ const timeKeywords = ["date", "time", "year", "month", "week", "day", "hour", "trend", "historical"];
3960
+ return timeKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
3961
+ (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
3962
+ );
3963
+ }
3964
+ /**
3965
+ * Helper: Extract product information from vector match
3966
+ */
3967
+ static extractProductInfo(item) {
3968
+ const meta = item.metadata || {};
3969
+ if (meta.name || meta.product) {
3970
+ return {
3971
+ id: item.id,
3972
+ name: meta.name || meta.product,
3973
+ price: meta.price,
3974
+ image: meta.image || meta.imageUrl,
3975
+ brand: meta.brand,
3976
+ description: item.content,
3977
+ inStock: this.determineStockStatus(item)
3978
+ };
3821
3979
  }
3822
- this.reranker = new Reranker();
3980
+ const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
3981
+ const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
3982
+ if (nameMatch) {
3983
+ return {
3984
+ id: item.id,
3985
+ name: nameMatch[1],
3986
+ price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
3987
+ description: item.content,
3988
+ inStock: this.determineStockStatus(item)
3989
+ };
3990
+ }
3991
+ return null;
3823
3992
  }
3824
- async initialize() {
3825
- var _a, _b;
3826
- if (this.initialised) return;
3827
- const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
3828
- const chartInstruction = `
3829
-
3830
- ${CHART_MARKER}
3831
- ### UNIVERSAL UI PROTOCOL V7 (STRICT MODE)
3832
-
3833
- You are responsible for returning a SINGLE structured UI block when visualization is required.
3834
-
3835
- ---
3836
-
3993
+ /**
3994
+ * Helper: Detect categories in data
3995
+ */
3996
+ static detectCategories(data) {
3997
+ const categories = /* @__PURE__ */ new Set();
3998
+ data.forEach((item) => {
3999
+ const meta = item.metadata || {};
4000
+ if (meta.category) {
4001
+ categories.add(String(meta.category));
4002
+ }
4003
+ if (meta.type) {
4004
+ categories.add(String(meta.type));
4005
+ }
4006
+ if (meta.tag) {
4007
+ const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4008
+ tags.forEach((t) => categories.add(String(t)));
4009
+ }
4010
+ const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
4011
+ if (categoryMatch) {
4012
+ categories.add(categoryMatch[1].trim());
4013
+ }
4014
+ });
4015
+ return Array.from(categories);
4016
+ }
4017
+ /**
4018
+ * Helper: Aggregate data by category
4019
+ */
4020
+ static aggregateByCategory(data, categories) {
4021
+ const result = {};
4022
+ categories.forEach((cat) => {
4023
+ result[cat] = 0;
4024
+ });
4025
+ data.forEach((item) => {
4026
+ const meta = item.metadata || {};
4027
+ const itemCategory = meta.category || meta.type || "Other";
4028
+ if (result.hasOwnProperty(itemCategory)) {
4029
+ result[itemCategory]++;
4030
+ } else {
4031
+ result["Other"] = (result["Other"] || 0) + 1;
4032
+ }
4033
+ });
4034
+ return result;
4035
+ }
4036
+ /**
4037
+ * Helper: Extract time series data
4038
+ */
4039
+ static extractTimeSeriesData(data) {
4040
+ return data.map((item) => {
4041
+ const meta = item.metadata || {};
4042
+ return {
4043
+ timestamp: meta.timestamp || meta.date || (/* @__PURE__ */ new Date()).toISOString(),
4044
+ value: meta.value || item.score || 0,
4045
+ label: meta.label || item.content.substring(0, 50)
4046
+ };
4047
+ });
4048
+ }
4049
+ /**
4050
+ * Helper: Extract table columns
4051
+ */
4052
+ static extractTableColumns(data) {
4053
+ const columnSet = /* @__PURE__ */ new Set();
4054
+ columnSet.add("Content");
4055
+ data.forEach((item) => {
4056
+ Object.keys(item.metadata || {}).forEach((key) => {
4057
+ columnSet.add(key.charAt(0).toUpperCase() + key.slice(1));
4058
+ });
4059
+ });
4060
+ return Array.from(columnSet);
4061
+ }
4062
+ /**
4063
+ * Helper: Extract table row
4064
+ */
4065
+ static extractTableRow(item, columns) {
4066
+ const meta = item.metadata || {};
4067
+ return columns.map((col) => {
4068
+ if (col === "Content") {
4069
+ return item.content.substring(0, 100);
4070
+ }
4071
+ const metaKey = col.charAt(0).toLowerCase() + col.slice(1);
4072
+ const value = meta[metaKey];
4073
+ return value !== void 0 ? String(value) : "";
4074
+ });
4075
+ }
4076
+ /**
4077
+ * Helper: Check stock status
4078
+ */
4079
+ static checkStockStatus(category, data) {
4080
+ const item = data.find((d) => {
4081
+ const meta = d.metadata || {};
4082
+ return meta.category === category || meta.type === category;
4083
+ });
4084
+ return this.determineStockStatus(item || {});
4085
+ }
4086
+ /**
4087
+ * Helper: Determine if item is in stock
4088
+ */
4089
+ static determineStockStatus(item) {
4090
+ const meta = item.metadata || {};
4091
+ if (meta.inStock !== void 0) {
4092
+ return Boolean(meta.inStock);
4093
+ }
4094
+ if (meta.stock !== void 0) {
4095
+ return Boolean(meta.stock);
4096
+ }
4097
+ if (meta.available !== void 0) {
4098
+ return Boolean(meta.available);
4099
+ }
4100
+ const content = (item.content || "").toLowerCase();
4101
+ if (content.includes("out of stock") || content.includes("unavailable")) {
4102
+ return false;
4103
+ }
4104
+ if (content.includes("in stock") || content.includes("available")) {
4105
+ return true;
4106
+ }
4107
+ return true;
4108
+ }
4109
+ /**
4110
+ * Helper: Check if data has multiple fields
4111
+ */
4112
+ static hasMultipleFields(data) {
4113
+ const fieldCount = /* @__PURE__ */ new Set();
4114
+ data.forEach((item) => {
4115
+ Object.keys(item.metadata || {}).forEach((key) => {
4116
+ fieldCount.add(key);
4117
+ });
4118
+ });
4119
+ return fieldCount.size > 2;
4120
+ }
4121
+ };
4122
+
4123
+ // src/core/Pipeline.ts
4124
+ var LRUEmbeddingCache = class {
4125
+ constructor(maxSize = 500) {
4126
+ this.cache = /* @__PURE__ */ new Map();
4127
+ this.maxSize = maxSize;
4128
+ }
4129
+ get(key) {
4130
+ const value = this.cache.get(key);
4131
+ if (value !== void 0) {
4132
+ this.cache.delete(key);
4133
+ this.cache.set(key, value);
4134
+ }
4135
+ return value;
4136
+ }
4137
+ set(key, value) {
4138
+ if (this.cache.has(key)) {
4139
+ this.cache.delete(key);
4140
+ } else if (this.cache.size >= this.maxSize) {
4141
+ const oldestKey = this.cache.keys().next().value;
4142
+ if (oldestKey !== void 0) this.cache.delete(oldestKey);
4143
+ }
4144
+ this.cache.set(key, value);
4145
+ }
4146
+ clear() {
4147
+ this.cache.clear();
4148
+ }
4149
+ get size() {
4150
+ return this.cache.size;
4151
+ }
4152
+ };
4153
+ var Pipeline = class {
4154
+ constructor(config) {
4155
+ this.config = config;
4156
+ /** LRU-bounded cache: avoids re-embedding identical queries within the same process. */
4157
+ this.embeddingCache = new LRUEmbeddingCache(500);
4158
+ this.initialised = false;
4159
+ var _a, _b, _c, _d, _e;
4160
+ this.chunker = new DocumentChunker(
4161
+ (_b = (_a = config.rag) == null ? void 0 : _a.chunkSize) != null ? _b : 1e3,
4162
+ (_d = (_c = config.rag) == null ? void 0 : _c.chunkOverlap) != null ? _d : 200
4163
+ );
4164
+ if (((_e = config.rag) == null ? void 0 : _e.chunkingStrategy) === "llamaindex") {
4165
+ this.llamaIngestor = new LlamaIndexIngestor();
4166
+ }
4167
+ this.reranker = new Reranker();
4168
+ }
4169
+ async initialize() {
4170
+ var _a, _b;
4171
+ if (this.initialised) return;
4172
+ const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
4173
+ const chartInstruction = `
4174
+
4175
+ ${CHART_MARKER}
4176
+ ### UNIVERSAL UI PROTOCOL V7 (STRICT MODE)
4177
+
4178
+ You are responsible for returning a SINGLE structured UI block when visualization is required.
4179
+
4180
+ ---
4181
+
3837
4182
  ## 1. INTENT CLASSIFICATION (MANDATORY)
3838
4183
 
3839
4184
  Classify the query into ONE of the following intents:
@@ -3953,6 +4298,8 @@ Classify the query into ONE of the following intents:
3953
4298
  - ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block
3954
4299
  - JSON must be VALID
3955
4300
  - No explanation inside block
4301
+ - NEVER use ASCII charts, Markdown tables, or text-based drawings for data.
4302
+ - If data is tabular or statistical, ALWAYS use the \`\`\`ui\`\`\` block.
3956
4303
 
3957
4304
  ---
3958
4305
 
@@ -4008,914 +4355,575 @@ User: "distribution of products across categories"
4008
4355
  { "label": "Electronics", "value": 10 }
4009
4356
  ]
4010
4357
  },
4011
- "insights": ["Beauty dominates inventory"]
4012
- }
4013
- \`\`\`
4014
- `;
4015
- if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
4016
- let cleanPrompt = this.config.llm.systemPrompt || "";
4017
- cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
4018
- cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
4019
- this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
4020
- }
4021
- console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
4022
- this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
4023
- const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
4024
- this.config.llm,
4025
- this.config.embedding
4026
- );
4027
- this.llmProvider = llmProvider;
4028
- this.embeddingProvider = embeddingProvider;
4029
- if (this.config.graphDb) {
4030
- this.graphDB = await ProviderRegistry.createGraphProvider(this.config.graphDb);
4031
- await this.graphDB.initialize();
4032
- this.entityExtractor = new EntityExtractor(this.llmProvider);
4033
- }
4034
- await this.vectorDB.initialize();
4035
- if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
4036
- this.agent = new LangChainAgent(this, this.config);
4037
- await this.agent.initialize(this.llmProvider);
4038
- }
4039
- this.initialised = true;
4040
- }
4041
- /**
4042
- * Ingest documents with automatic chunking, embedding, and batch upsert.
4043
- * Handles retries for transient failures.
4044
- */
4045
- async ingest(documents, namespace) {
4046
- await this.initialize();
4047
- const ns = namespace != null ? namespace : this.config.projectId;
4048
- const results = [];
4049
- for (const doc of documents) {
4050
- try {
4051
- const chunks = await this.prepareChunks(doc);
4052
- const vectors = await this.processEmbeddings(chunks);
4053
- const upsertDocs = chunks.map((chunk, i) => ({
4054
- id: chunk.id,
4055
- vector: vectors[i],
4056
- content: chunk.content,
4057
- metadata: chunk.metadata
4058
- }));
4059
- const totalProcessed = await this.processUpserts(upsertDocs, ns);
4060
- results.push({
4061
- docId: doc.docId,
4062
- chunksIngested: totalProcessed
4063
- });
4064
- if (this.graphDB && this.entityExtractor) {
4065
- await this.processGraphIngestion(doc.docId, chunks);
4066
- }
4067
- } catch (error) {
4068
- console.error(`[Pipeline] Failed to ingest document ${doc.docId}:`, error);
4069
- results.push({ docId: doc.docId, chunksIngested: 0 });
4070
- }
4071
- }
4072
- return results;
4073
- }
4074
- /**
4075
- * Step 1: Chunk the document content.
4076
- */
4077
- async prepareChunks(doc) {
4078
- var _a, _b;
4079
- if (this.llamaIngestor) {
4080
- return await this.llamaIngestor.chunk(doc.content, {
4081
- docId: doc.docId,
4082
- metadata: doc.metadata,
4083
- chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
4084
- chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
4085
- });
4086
- }
4087
- return this.chunker.chunk(doc.content, {
4088
- docId: doc.docId,
4089
- metadata: doc.metadata
4090
- });
4091
- }
4092
- /**
4093
- * Step 2: Generate embeddings for chunks with retry logic.
4094
- * Uses batchEmbed when available for efficiency; falls back to sequential embedding.
4095
- */
4096
- async processEmbeddings(chunks) {
4097
- const embedBatchOptions = {
4098
- batchSize: 50,
4099
- maxRetries: 3,
4100
- initialDelayMs: 100
4101
- };
4102
- const vectors = await BatchProcessor.mapWithRetry(
4103
- chunks.map((c) => c.content),
4104
- (text) => this.embeddingProvider.embed(text, { taskType: "document" }),
4105
- embedBatchOptions
4106
- );
4107
- if (vectors.length !== chunks.length) {
4108
- throw new Error(
4109
- `[Pipeline] Embedding mismatch: got ${vectors.length} vectors for ${chunks.length} chunks. Check embedding provider logs for individual chunk failures.`
4110
- );
4111
- }
4112
- return vectors;
4113
- }
4114
- /**
4115
- * Step 3: Upsert chunks to vector database with retry logic.
4116
- */
4117
- async processUpserts(upsertDocs, namespace) {
4118
- const upsertBatchOptions = {
4119
- batchSize: 100,
4120
- maxRetries: 3,
4121
- initialDelayMs: 100
4122
- };
4123
- const upsertResult = await BatchProcessor.processBatch(
4124
- upsertDocs,
4125
- (batch) => this.vectorDB.batchUpsert(batch, namespace),
4126
- upsertBatchOptions
4127
- );
4128
- if (upsertResult.errors.length > 0) {
4129
- console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed`);
4130
- }
4131
- return upsertResult.totalProcessed;
4132
- }
4133
- /**
4134
- * Step 4: Optional graph-based entity extraction and ingestion.
4135
- */
4136
- async processGraphIngestion(docId, chunks) {
4137
- console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
4138
- const extractionOptions = {
4139
- batchSize: 2,
4140
- // Low concurrency for LLM extraction
4141
- maxRetries: 1,
4142
- initialDelayMs: 500
4143
- };
4144
- await BatchProcessor.processBatch(
4145
- chunks,
4146
- async (batch) => {
4147
- for (const chunk of batch) {
4148
- try {
4149
- const { nodes, edges } = await this.entityExtractor.extract(chunk.content);
4150
- if (nodes.length > 0) await this.graphDB.addNodes(nodes);
4151
- if (edges.length > 0) await this.graphDB.addEdges(edges);
4152
- } catch (err) {
4153
- console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
4154
- }
4155
- }
4156
- },
4157
- extractionOptions
4158
- );
4159
- }
4160
- async ask(question, history = [], namespace) {
4161
- var _a;
4162
- await this.initialize();
4163
- if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
4164
- console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
4165
- const agentReply = await this.agent.run(question, history);
4166
- return { reply: agentReply, sources: [] };
4167
- }
4168
- const stream = this.askStream(question, history, namespace);
4169
- let reply = "";
4170
- let sources = [];
4171
- let graphData;
4172
- try {
4173
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
4174
- const chunk = temp.value;
4175
- if (typeof chunk === "string") {
4176
- reply += chunk;
4177
- } else if ("sources" in chunk) {
4178
- sources = chunk.sources;
4179
- graphData = chunk.graphData;
4180
- }
4181
- }
4182
- } catch (temp) {
4183
- error = [temp];
4184
- } finally {
4185
- try {
4186
- more && (temp = iter.return) && await temp.call(iter);
4187
- } finally {
4188
- if (error)
4189
- throw error[0];
4190
- }
4191
- }
4192
- return { reply, sources, graphData };
4193
- }
4194
- /**
4195
- * High-performance streaming RAG flow.
4196
- * Yields text chunks first, then the retrieval metadata at the end.
4197
- */
4198
- askStream(_0) {
4199
- return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
4200
- var _a, _b, _c, _d, _e, _f, _g;
4201
- yield new __await(this.initialize());
4202
- const ns = namespace != null ? namespace : this.config.projectId;
4203
- const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
4204
- const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
4205
- try {
4206
- let searchQuery = question;
4207
- if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
4208
- searchQuery = yield new __await(this.rewriteQuery(question, history));
4209
- }
4210
- const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
4211
- const filter = QueryProcessor.buildQueryFilter(question, hints);
4212
- console.log(`[Pipeline] \u{1F9E9} Extracted filters:`, JSON.stringify(filter));
4213
- const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
4214
- namespace: ns,
4215
- topK: topK * 2,
4216
- filter
4217
- }));
4218
- let sources = rawSources.filter((m) => m.score >= scoreThreshold);
4219
- if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
4220
- sources = yield new __await(this.reranker.rerank(sources, question, topK));
4221
- } else {
4222
- sources = sources.slice(0, topK);
4223
- }
4224
- let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
4225
- ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
4226
- if (graphData && graphData.nodes.length > 0) {
4227
- const graphContext = graphData.nodes.map(
4228
- (n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
4229
- ).join("\n");
4230
- context = `GRAPH KNOWLEDGE:
4231
- ${graphContext}
4232
-
4233
- VECTOR CONTEXT:
4234
- ${context}`;
4235
- }
4236
- const messages = [...history, { role: "user", content: question }];
4237
- if (this.llmProvider.chatStream) {
4238
- const stream = this.llmProvider.chatStream(messages, context);
4239
- if (!stream) {
4240
- throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
4241
- }
4242
- try {
4243
- for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
4244
- const chunk = temp.value;
4245
- yield chunk;
4246
- }
4247
- } catch (temp) {
4248
- error = [temp];
4249
- } finally {
4250
- try {
4251
- more && (temp = iter.return) && (yield new __await(temp.call(iter)));
4252
- } finally {
4253
- if (error)
4254
- throw error[0];
4255
- }
4256
- }
4257
- } else {
4258
- const reply = yield new __await(this.llmProvider.chat(messages, context));
4259
- yield reply;
4260
- }
4261
- yield { reply: "", sources, graphData };
4262
- } catch (error2) {
4263
- throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
4264
- }
4265
- });
4266
- }
4267
- /**
4268
- * Universal retrieval method combining all enabled providers.
4269
- * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
4270
- */
4271
- async retrieve(query, options) {
4272
- var _a, _b, _c;
4273
- const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
4274
- const topK = (_b = options.topK) != null ? _b : 5;
4275
- const cacheKey = `${ns}::${query}`;
4276
- let queryVector = this.embeddingCache.get(cacheKey);
4277
- const [retrievedVector, graphData] = await Promise.all([
4278
- queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }),
4279
- this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
4280
- ]);
4281
- if (!queryVector) {
4282
- this.embeddingCache.set(cacheKey, retrievedVector);
4283
- queryVector = retrievedVector;
4284
- }
4285
- const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
4286
- return { sources, graphData };
4287
- }
4288
- /**
4289
- * Rewrite the user query for better retrieval performance.
4290
- */
4291
- async rewriteQuery(question, history) {
4292
- const prompt = `
4293
- Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
4294
- Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
4295
-
4296
- History:
4297
- ${history.map((m) => `${m.role}: ${m.content}`).join("\n")}
4298
-
4299
- New Question: ${question}
4300
-
4301
- Optimized Search Query:`;
4302
- const rewrite = await this.llmProvider.chat(
4303
- [
4304
- { role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
4305
- { role: "user", content: prompt }
4306
- ],
4307
- ""
4308
- );
4309
- return rewrite.trim() || question;
4310
- }
4311
- /**
4312
- * Generate 3-5 short, relevant questions based on the vector database content.
4313
- */
4314
- async getSuggestions(query, namespace) {
4315
- if (!query || query.trim().length < 3) {
4316
- return [];
4317
- }
4318
- await this.initialize();
4319
- const ns = namespace != null ? namespace : this.config.projectId;
4320
- try {
4321
- const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
4322
- if (sources.length === 0) {
4323
- return [];
4324
- }
4325
- const context = sources.map((s) => s.content).join("\n\n---\n\n");
4326
- const prompt = `
4327
- Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
4328
- Focus on questions that can be answered by the context.
4329
- Keep each question under 10 words and make them very specific to the content.
4330
- Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
4331
-
4332
- Context:
4333
- ${context}
4334
-
4335
- Suggestions:`;
4336
- const response = await this.llmProvider.chat(
4337
- [
4338
- { role: "system", content: "You are a helpful assistant that generates search suggestions." },
4339
- { role: "user", content: prompt }
4340
- ],
4341
- ""
4342
- );
4343
- const match = response.match(/\[[\s\S]*\]/);
4344
- if (match) {
4345
- const suggestions = JSON.parse(match[0]);
4346
- if (Array.isArray(suggestions)) {
4347
- return suggestions.map((s) => String(s)).slice(0, 3);
4348
- }
4349
- }
4350
- } catch (error) {
4351
- console.error("[Pipeline] Failed to generate suggestions:", error);
4352
- }
4353
- return [];
4354
- }
4355
- };
4356
-
4357
- // src/core/ProviderHealthCheck.ts
4358
- var ProviderHealthCheck = class {
4359
- /**
4360
- * Validates vector database configuration before initialization.
4361
- */
4362
- static async checkVectorProvider(config) {
4363
- const timestamp = Date.now();
4364
- const configErrors = await ConfigValidator.validate({
4365
- projectId: "health-check",
4366
- vectorDb: config,
4367
- llm: { provider: "openai", model: "gpt-4o", apiKey: "none" },
4368
- embedding: { provider: "openai", model: "text-embedding-3-small", apiKey: "none" }
4369
- });
4370
- const vectorDbErrors = configErrors.filter((e) => e.field.startsWith("vectorDb"));
4371
- if (vectorDbErrors.length > 0) {
4372
- return {
4373
- healthy: false,
4374
- provider: config.provider,
4375
- error: `Configuration validation failed: ${vectorDbErrors.map((e) => e.message).join("; ")}`,
4376
- timestamp
4377
- };
4378
- }
4379
- try {
4380
- const checker = await ProviderRegistry.getVectorHealthChecker(config.provider);
4381
- if (checker) {
4382
- return await checker.check(config);
4383
- }
4384
- return await this.fallbackVectorHealthCheck(config, timestamp);
4385
- } catch (error) {
4386
- return {
4387
- healthy: false,
4388
- provider: config.provider,
4389
- error: error instanceof Error ? error.message : String(error),
4390
- timestamp
4391
- };
4392
- }
4393
- }
4394
- static async fallbackVectorHealthCheck(config, timestamp) {
4395
- const opts = config.options || {};
4396
- const baseUrl = opts.baseUrl || opts.uri;
4397
- if (baseUrl && baseUrl.startsWith("http")) {
4398
- try {
4399
- const response = await fetch(`${baseUrl.replace(/\/$/, "")}/health`);
4400
- return {
4401
- healthy: response.ok,
4402
- provider: config.provider,
4403
- error: response.ok ? void 0 : `Health check failed: ${response.status}`,
4404
- timestamp
4405
- };
4406
- } catch (e) {
4407
- return { healthy: false, provider: config.provider, error: "Provider unreachable", timestamp };
4408
- }
4409
- }
4410
- return {
4411
- healthy: true,
4412
- provider: config.provider,
4413
- error: "Pluggable health check not implemented; basic reachability assumed.",
4414
- timestamp
4415
- };
4416
- }
4417
- /**
4418
- * Validates LLM provider configuration.
4419
- */
4420
- static async checkLLMProvider(config) {
4421
- const timestamp = Date.now();
4422
- try {
4423
- const checker = LLMFactory.getHealthChecker(config.provider);
4424
- if (checker) {
4425
- return await checker.check(config);
4426
- }
4427
- return {
4428
- healthy: true,
4429
- provider: config.provider,
4430
- error: "Pluggable health check not implemented; basic reachability assumed.",
4431
- timestamp
4432
- };
4433
- } catch (error) {
4434
- return {
4435
- healthy: false,
4436
- provider: config.provider,
4437
- error: error instanceof Error ? error.message : String(error),
4438
- timestamp
4439
- };
4440
- }
4441
- }
4442
- /**
4443
- * Runs comprehensive health checks on all configured providers in parallel.
4444
- */
4445
- static async checkAll(vectorDbConfig, llmConfig, embeddingConfig) {
4446
- const [vectorDb, llm, embedding] = await Promise.all([
4447
- this.checkVectorProvider(vectorDbConfig),
4448
- this.checkLLMProvider(llmConfig),
4449
- embeddingConfig ? this.checkLLMProvider(embeddingConfig) : Promise.resolve(void 0)
4450
- ]);
4451
- return {
4452
- vectorDb,
4453
- llm,
4454
- embedding,
4455
- allHealthy: vectorDb.healthy && llm.healthy && (!embedding || embedding.healthy)
4456
- };
4457
- }
4458
- };
4459
-
4460
- // src/core/VectorPlugin.ts
4461
- var VectorPlugin = class {
4462
- constructor(hostConfig) {
4463
- this.config = ConfigResolver.resolve(hostConfig);
4464
- this.validationPromise = ConfigValidator.validateAndThrow(this.config);
4465
- this.pipeline = new Pipeline(this.config);
4466
- }
4467
- /**
4468
- * Get the current resolved configuration.
4469
- */
4470
- getConfig() {
4471
- return this.config;
4472
- }
4473
- /**
4474
- * Perform pre-flight health checks on all configured providers.
4475
- * Useful to verify connectivity before running operations.
4476
- *
4477
- * @returns Health status for vector DB, LLM, and embedding providers
4478
- */
4479
- async checkHealth() {
4480
- return ProviderHealthCheck.checkAll(
4481
- this.config.vectorDb,
4482
- this.config.llm,
4483
- this.config.embedding
4484
- );
4485
- }
4486
- /**
4487
- * Run a chat query.
4488
- */
4489
- async chat(message, history = [], namespace) {
4490
- await this.validationPromise;
4491
- return this.pipeline.ask(message, history, namespace);
4492
- }
4493
- /**
4494
- * Run a streaming chat query.
4495
- */
4496
- chatStream(_0) {
4497
- return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
4498
- yield new __await(this.validationPromise);
4499
- yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
4500
- });
4501
- }
4502
- /**
4503
- * Ingest documents into the vector database.
4504
- */
4505
- async ingest(documents, namespace) {
4506
- await this.validationPromise;
4507
- return this.pipeline.ingest(documents, namespace);
4508
- }
4509
- /**
4510
- * Get auto-suggestions based on a query prefix.
4511
- */
4512
- async getSuggestions(query, namespace) {
4513
- await this.validationPromise;
4514
- return this.pipeline.getSuggestions(query, namespace);
4515
- }
4516
- };
4517
-
4518
- // src/utils/DocumentParser.ts
4519
- var DocumentParser = class {
4520
- /**
4521
- * Extract text from a File or Buffer based on its type.
4522
- */
4523
- static async parse(file, fileName, mimeType) {
4524
- var _a;
4525
- const extension = (_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase();
4526
- if (extension === "txt" || extension === "md" || mimeType === "text/plain" || mimeType === "text/markdown") {
4527
- return this.readAsText(file);
4528
- }
4529
- if (extension === "json" || mimeType === "application/json") {
4530
- const text = await this.readAsText(file);
4531
- try {
4532
- const obj = JSON.parse(text);
4533
- return JSON.stringify(obj, null, 2);
4534
- } catch (e) {
4535
- return text;
4536
- }
4537
- }
4538
- if (extension === "csv" || mimeType === "text/csv") {
4539
- return this.readAsText(file);
4540
- }
4541
- if (extension === "pdf" || mimeType === "application/pdf") {
4542
- try {
4543
- const pdf = await import("pdf-parse");
4544
- const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
4545
- const data = await pdf.default(buffer);
4546
- return data.text;
4547
- } catch (err) {
4548
- console.warn('[DocumentParser] PDF parsing failed. Make sure "pdf-parse" is installed.', err);
4549
- return `[PDF Parsing Error for ${fileName}]`;
4550
- }
4551
- }
4552
- if (extension === "docx" || mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
4553
- try {
4554
- const mammoth = await import("mammoth");
4555
- const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
4556
- const result = await mammoth.extractRawText({ buffer });
4557
- return result.value;
4558
- } catch (err) {
4559
- console.warn('[DocumentParser] DOCX parsing failed. Make sure "mammoth" is installed.', err);
4560
- return `[DOCX Parsing Error for ${fileName}]`;
4561
- }
4358
+ "insights": ["Beauty dominates inventory"]
4359
+ }
4360
+ \`\`\`
4361
+ `;
4362
+ if (!((_a = this.config.llm.systemPrompt) == null ? void 0 : _a.includes(CHART_MARKER))) {
4363
+ let cleanPrompt = this.config.llm.systemPrompt || "";
4364
+ cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
4365
+ cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
4366
+ this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
4562
4367
  }
4563
- try {
4564
- return await this.readAsText(file);
4565
- } catch (e) {
4566
- throw new Error(`[DocumentParser] Unsupported file format: ${fileName} (${mimeType})`);
4368
+ console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
4369
+ this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
4370
+ const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
4371
+ this.config.llm,
4372
+ this.config.embedding
4373
+ );
4374
+ this.llmProvider = llmProvider;
4375
+ this.embeddingProvider = embeddingProvider;
4376
+ if (this.config.graphDb) {
4377
+ this.graphDB = await ProviderRegistry.createGraphProvider(this.config.graphDb);
4378
+ await this.graphDB.initialize();
4379
+ this.entityExtractor = new EntityExtractor(this.llmProvider);
4567
4380
  }
4568
- }
4569
- static async readAsText(file) {
4570
- if (Buffer.isBuffer(file)) {
4571
- return file.toString("utf-8");
4381
+ await this.vectorDB.initialize();
4382
+ if (((_b = this.config.rag) == null ? void 0 : _b.architecture) === "agentic") {
4383
+ this.agent = new LangChainAgent(this, this.config);
4384
+ await this.agent.initialize(this.llmProvider);
4572
4385
  }
4573
- return await file.text();
4386
+ this.initialised = true;
4574
4387
  }
4575
- };
4576
-
4577
- // src/utils/UITransformer.ts
4578
- var UITransformer = class {
4579
4388
  /**
4580
- * Main transformation method
4581
- * Analyzes user query and retrieved data to determine the best visualization format
4582
- *
4583
- * @param userQuery - The original user question/query
4584
- * @param retrievedData - Vector database retrieval results
4585
- * @returns Structured JSON response for UI rendering
4389
+ * Ingest documents with automatic chunking, embedding, and batch upsert.
4390
+ * Handles retries for transient failures.
4586
4391
  */
4587
- static transform(userQuery, retrievedData) {
4588
- if (!retrievedData || retrievedData.length === 0) {
4589
- return this.createTextResponse("No data available", "No relevant data found for your query.");
4590
- }
4591
- const analysis = this.analyzeData(userQuery, retrievedData);
4592
- switch (analysis.suggestedType) {
4593
- case "product_carousel":
4594
- return this.transformToProductCarousel(retrievedData);
4595
- case "pie_chart":
4596
- return this.transformToPieChart(retrievedData);
4597
- case "bar_chart":
4598
- return this.transformToBarChart(retrievedData);
4599
- case "line_chart":
4600
- return this.transformToLineChart(retrievedData);
4601
- case "table":
4602
- return this.transformToTable(retrievedData);
4603
- default:
4604
- return this.transformToText(retrievedData);
4392
+ async ingest(documents, namespace) {
4393
+ await this.initialize();
4394
+ const ns = namespace != null ? namespace : this.config.projectId;
4395
+ const results = [];
4396
+ for (const doc of documents) {
4397
+ try {
4398
+ const chunks = await this.prepareChunks(doc);
4399
+ const vectors = await this.processEmbeddings(chunks);
4400
+ const upsertDocs = chunks.map((chunk, i) => ({
4401
+ id: chunk.id,
4402
+ vector: vectors[i],
4403
+ content: chunk.content,
4404
+ metadata: chunk.metadata
4405
+ }));
4406
+ const totalProcessed = await this.processUpserts(upsertDocs, ns);
4407
+ results.push({
4408
+ docId: doc.docId,
4409
+ chunksIngested: totalProcessed
4410
+ });
4411
+ if (this.graphDB && this.entityExtractor) {
4412
+ await this.processGraphIngestion(doc.docId, chunks);
4413
+ }
4414
+ } catch (error) {
4415
+ console.error(`[Pipeline] Failed to ingest document ${doc.docId}:`, error);
4416
+ results.push({ docId: doc.docId, chunksIngested: 0 });
4417
+ }
4605
4418
  }
4419
+ return results;
4606
4420
  }
4607
4421
  /**
4608
- * Analyzes the data and query to suggest the best visualization type
4422
+ * Step 1: Chunk the document content.
4609
4423
  */
4610
- static analyzeData(query, data) {
4611
- const queryLower = query.toLowerCase();
4612
- const hasProducts = data.some(
4613
- (item) => this.isProductData(item)
4614
- );
4615
- const hasTimeSeries = data.some(
4616
- (item) => this.isTimeSeriesData(item)
4617
- );
4618
- const hasCategories = this.detectCategories(data).length > 0;
4619
- const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
4620
- const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
4621
- const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
4622
- if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
4623
- return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
4624
- }
4625
- if (isTrendQuery && hasTimeSeries) {
4626
- return { suggestedType: "line_chart", confidence: 0.9, hasProducts, hasTimeSeries, hasCategories };
4627
- }
4628
- if (isDistributionQuery && hasCategories) {
4629
- return { suggestedType: "pie_chart", confidence: 0.85, hasProducts, hasTimeSeries, hasCategories };
4630
- }
4631
- if (isComparisonQuery && hasCategories && data.length > 2) {
4632
- return { suggestedType: "bar_chart", confidence: 0.8, hasProducts, hasTimeSeries, hasCategories };
4633
- }
4634
- if (data.length > 5 && this.hasMultipleFields(data)) {
4635
- return { suggestedType: "table", confidence: 0.75, hasProducts, hasTimeSeries, hasCategories };
4424
+ async prepareChunks(doc) {
4425
+ var _a, _b;
4426
+ if (this.llamaIngestor) {
4427
+ return await this.llamaIngestor.chunk(doc.content, {
4428
+ docId: doc.docId,
4429
+ metadata: doc.metadata,
4430
+ chunkSize: (_a = this.config.rag) == null ? void 0 : _a.chunkSize,
4431
+ chunkOverlap: (_b = this.config.rag) == null ? void 0 : _b.chunkOverlap
4432
+ });
4636
4433
  }
4637
- return { suggestedType: "text", confidence: 0.5, hasProducts, hasTimeSeries, hasCategories };
4434
+ return this.chunker.chunk(doc.content, {
4435
+ docId: doc.docId,
4436
+ metadata: doc.metadata
4437
+ });
4638
4438
  }
4639
4439
  /**
4640
- * Transform data to product carousel format
4440
+ * Step 2: Generate embeddings for chunks with retry logic.
4441
+ * Uses batchEmbed when available for efficiency; falls back to sequential embedding.
4641
4442
  */
4642
- static transformToProductCarousel(data) {
4643
- const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
4644
- return {
4645
- type: "product_carousel",
4646
- title: "Recommended Products",
4647
- description: `Found ${products.length} relevant products`,
4648
- data: products
4443
+ async processEmbeddings(chunks) {
4444
+ const embedBatchOptions = {
4445
+ batchSize: 50,
4446
+ maxRetries: 3,
4447
+ initialDelayMs: 100
4649
4448
  };
4449
+ const vectors = await BatchProcessor.mapWithRetry(
4450
+ chunks.map((c) => c.content),
4451
+ (text) => this.embeddingProvider.embed(text, { taskType: "document" }),
4452
+ embedBatchOptions
4453
+ );
4454
+ if (vectors.length !== chunks.length) {
4455
+ throw new Error(
4456
+ `[Pipeline] Embedding mismatch: got ${vectors.length} vectors for ${chunks.length} chunks. Check embedding provider logs for individual chunk failures.`
4457
+ );
4458
+ }
4459
+ return vectors;
4650
4460
  }
4651
4461
  /**
4652
- * Transform data to pie chart format
4462
+ * Step 3: Upsert chunks to vector database with retry logic.
4653
4463
  */
4654
- static transformToPieChart(data) {
4655
- const categories = this.detectCategories(data);
4656
- const categoryData = this.aggregateByCategory(data, categories);
4657
- const pieData = Object.entries(categoryData).map(([label, count]) => ({
4658
- label,
4659
- value: count,
4660
- inStock: this.checkStockStatus(label, data)
4661
- }));
4662
- return {
4663
- type: "pie_chart",
4664
- title: "Distribution by Category",
4665
- description: `Showing breakdown across ${categories.length} categories`,
4666
- data: pieData
4464
+ async processUpserts(upsertDocs, namespace) {
4465
+ const upsertBatchOptions = {
4466
+ batchSize: 100,
4467
+ maxRetries: 3,
4468
+ initialDelayMs: 100
4667
4469
  };
4470
+ const upsertResult = await BatchProcessor.processBatch(
4471
+ upsertDocs,
4472
+ (batch) => this.vectorDB.batchUpsert(batch, namespace),
4473
+ upsertBatchOptions
4474
+ );
4475
+ if (upsertResult.errors.length > 0) {
4476
+ console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed`);
4477
+ }
4478
+ return upsertResult.totalProcessed;
4668
4479
  }
4669
4480
  /**
4670
- * Transform data to bar chart format
4481
+ * Step 4: Optional graph-based entity extraction and ingestion.
4671
4482
  */
4672
- static transformToBarChart(data) {
4673
- const categories = this.detectCategories(data);
4674
- const categoryData = this.aggregateByCategory(data, categories);
4675
- const barData = Object.entries(categoryData).map(([category, value]) => ({
4676
- category,
4677
- value,
4678
- inStock: this.checkStockStatus(category, data)
4679
- }));
4680
- return {
4681
- type: "bar_chart",
4682
- title: "Comparison by Category",
4683
- description: `Comparing ${categories.length} categories`,
4684
- data: barData
4483
+ async processGraphIngestion(docId, chunks) {
4484
+ console.log(`[Pipeline] Extracting entities for doc ${docId} (${chunks.length} chunks)...`);
4485
+ const extractionOptions = {
4486
+ batchSize: 2,
4487
+ // Low concurrency for LLM extraction
4488
+ maxRetries: 1,
4489
+ initialDelayMs: 500
4685
4490
  };
4491
+ await BatchProcessor.processBatch(
4492
+ chunks,
4493
+ async (batch) => {
4494
+ for (const chunk of batch) {
4495
+ try {
4496
+ const { nodes, edges } = await this.entityExtractor.extract(chunk.content);
4497
+ if (nodes.length > 0) await this.graphDB.addNodes(nodes);
4498
+ if (edges.length > 0) await this.graphDB.addEdges(edges);
4499
+ } catch (err) {
4500
+ console.warn(`[Pipeline] Entity extraction failed for chunk:`, err);
4501
+ }
4502
+ }
4503
+ },
4504
+ extractionOptions
4505
+ );
4506
+ }
4507
+ async ask(question, history = [], namespace) {
4508
+ var _a;
4509
+ await this.initialize();
4510
+ if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic" && this.agent) {
4511
+ console.log("[Pipeline] \u{1F916} Executing in Agentic Mode...");
4512
+ const agentReply = await this.agent.run(question, history);
4513
+ return { reply: agentReply, sources: [] };
4514
+ }
4515
+ const stream = this.askStream(question, history, namespace);
4516
+ let reply = "";
4517
+ let sources = [];
4518
+ let graphData;
4519
+ try {
4520
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
4521
+ const chunk = temp.value;
4522
+ if (typeof chunk === "string") {
4523
+ reply += chunk;
4524
+ } else if ("sources" in chunk) {
4525
+ sources = chunk.sources;
4526
+ graphData = chunk.graphData;
4527
+ }
4528
+ }
4529
+ } catch (temp) {
4530
+ error = [temp];
4531
+ } finally {
4532
+ try {
4533
+ more && (temp = iter.return) && await temp.call(iter);
4534
+ } finally {
4535
+ if (error)
4536
+ throw error[0];
4537
+ }
4538
+ }
4539
+ return { reply, sources, graphData };
4686
4540
  }
4687
4541
  /**
4688
- * Transform data to line chart format
4542
+ * High-performance streaming RAG flow.
4543
+ * Yields text chunks first, then the retrieval metadata at the end.
4689
4544
  */
4690
- static transformToLineChart(data) {
4691
- const timePoints = this.extractTimeSeriesData(data);
4692
- const lineData = timePoints.map((point) => ({
4693
- timestamp: point.timestamp,
4694
- value: point.value,
4695
- label: point.label
4696
- }));
4697
- return {
4698
- type: "line_chart",
4699
- title: "Trend Over Time",
4700
- description: `Showing ${lineData.length} data points`,
4701
- data: lineData
4702
- };
4545
+ askStream(_0) {
4546
+ return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
4547
+ var _a, _b, _c, _d, _e, _f, _g;
4548
+ yield new __await(this.initialize());
4549
+ const ns = namespace != null ? namespace : this.config.projectId;
4550
+ const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
4551
+ const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
4552
+ try {
4553
+ let searchQuery = question;
4554
+ if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
4555
+ searchQuery = yield new __await(this.rewriteQuery(question, history));
4556
+ }
4557
+ const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
4558
+ const filter = QueryProcessor.buildQueryFilter(question, hints);
4559
+ console.log(`[Pipeline] \u{1F9E9} Extracted filters:`, JSON.stringify(filter));
4560
+ const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
4561
+ namespace: ns,
4562
+ topK: topK * 2,
4563
+ filter
4564
+ }));
4565
+ let sources = rawSources.filter((m) => m.score >= scoreThreshold);
4566
+ if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
4567
+ sources = yield new __await(this.reranker.rerank(sources, question, topK));
4568
+ } else {
4569
+ sources = sources.slice(0, topK);
4570
+ }
4571
+ let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
4572
+ ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
4573
+ if (graphData && graphData.nodes.length > 0) {
4574
+ const graphContext = graphData.nodes.map(
4575
+ (n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
4576
+ ).join("\n");
4577
+ context = `GRAPH KNOWLEDGE:
4578
+ ${graphContext}
4579
+
4580
+ VECTOR CONTEXT:
4581
+ ${context}`;
4582
+ }
4583
+ const messages = [...history, { role: "user", content: question }];
4584
+ if (this.llmProvider.chatStream) {
4585
+ const stream = this.llmProvider.chatStream(messages, context);
4586
+ if (!stream) {
4587
+ throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
4588
+ }
4589
+ try {
4590
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
4591
+ const chunk = temp.value;
4592
+ yield chunk;
4593
+ }
4594
+ } catch (temp) {
4595
+ error = [temp];
4596
+ } finally {
4597
+ try {
4598
+ more && (temp = iter.return) && (yield new __await(temp.call(iter)));
4599
+ } finally {
4600
+ if (error)
4601
+ throw error[0];
4602
+ }
4603
+ }
4604
+ } else {
4605
+ const reply = yield new __await(this.llmProvider.chat(messages, context));
4606
+ yield reply;
4607
+ }
4608
+ const uiTransformation = UITransformer.transform(question, sources);
4609
+ yield {
4610
+ reply: "",
4611
+ sources,
4612
+ graphData,
4613
+ ui_transformation: uiTransformation
4614
+ };
4615
+ } catch (error2) {
4616
+ throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
4617
+ }
4618
+ });
4703
4619
  }
4704
4620
  /**
4705
- * Transform data to table format
4621
+ * Universal retrieval method combining all enabled providers.
4622
+ * Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
4706
4623
  */
4707
- static transformToTable(data) {
4708
- const columns = this.extractTableColumns(data);
4709
- const rows = data.map((item) => this.extractTableRow(item, columns));
4710
- const tableData = {
4711
- columns,
4712
- rows
4713
- };
4714
- return {
4715
- type: "table",
4716
- title: "Detailed Results",
4717
- description: `Showing ${data.length} results`,
4718
- data: tableData
4719
- };
4624
+ async retrieve(query, options) {
4625
+ var _a, _b, _c;
4626
+ const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
4627
+ const topK = (_b = options.topK) != null ? _b : 5;
4628
+ const cacheKey = `${ns}::${query}`;
4629
+ let queryVector = this.embeddingCache.get(cacheKey);
4630
+ const [retrievedVector, graphData] = await Promise.all([
4631
+ queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }),
4632
+ this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
4633
+ ]);
4634
+ if (!queryVector) {
4635
+ this.embeddingCache.set(cacheKey, retrievedVector);
4636
+ queryVector = retrievedVector;
4637
+ }
4638
+ const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
4639
+ return { sources, graphData };
4720
4640
  }
4721
4641
  /**
4722
- * Transform data to text format (fallback)
4642
+ * Rewrite the user query for better retrieval performance.
4723
4643
  */
4724
- static transformToText(data) {
4725
- const textContent = data.map((item) => item.content).join("\n\n");
4726
- return this.createTextResponse(
4727
- "Search Results",
4728
- textContent,
4729
- `Found ${data.length} relevant results`
4644
+ async rewriteQuery(question, history) {
4645
+ const prompt = `
4646
+ Given the following conversation history and a new question, rewrite the question to be a better search query for a vector database.
4647
+ Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.
4648
+
4649
+ History:
4650
+ ${history.map((m) => `${m.role}: ${m.content}`).join("\n")}
4651
+
4652
+ New Question: ${question}
4653
+
4654
+ Optimized Search Query:`;
4655
+ const rewrite = await this.llmProvider.chat(
4656
+ [
4657
+ { role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
4658
+ { role: "user", content: prompt }
4659
+ ],
4660
+ ""
4730
4661
  );
4662
+ return rewrite.trim() || question;
4731
4663
  }
4732
4664
  /**
4733
- * Helper: Create text response
4665
+ * Generate 3-5 short, relevant questions based on the vector database content.
4734
4666
  */
4735
- static createTextResponse(title, content, description) {
4736
- return {
4737
- type: "text",
4738
- title,
4739
- description,
4740
- data: { content }
4741
- };
4667
+ async getSuggestions(query, namespace) {
4668
+ if (!query || query.trim().length < 3) {
4669
+ return [];
4670
+ }
4671
+ await this.initialize();
4672
+ const ns = namespace != null ? namespace : this.config.projectId;
4673
+ try {
4674
+ const { sources } = await this.retrieve(query, { namespace: ns, topK: 3 });
4675
+ if (sources.length === 0) {
4676
+ return [];
4677
+ }
4678
+ const context = sources.map((s) => s.content).join("\n\n---\n\n");
4679
+ const prompt = `
4680
+ Based on the following snippets from a document, what are 3 short, relevant questions a user might ask?
4681
+ Focus on questions that can be answered by the context.
4682
+ Keep each question under 10 words and make them very specific to the content.
4683
+ Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
4684
+
4685
+ Context:
4686
+ ${context}
4687
+
4688
+ Suggestions:`;
4689
+ const response = await this.llmProvider.chat(
4690
+ [
4691
+ { role: "system", content: "You are a helpful assistant that generates search suggestions." },
4692
+ { role: "user", content: prompt }
4693
+ ],
4694
+ ""
4695
+ );
4696
+ const match = response.match(/\[[\s\S]*\]/);
4697
+ if (match) {
4698
+ const suggestions = JSON.parse(match[0]);
4699
+ if (Array.isArray(suggestions)) {
4700
+ return suggestions.map((s) => String(s)).slice(0, 3);
4701
+ }
4702
+ }
4703
+ } catch (error) {
4704
+ console.error("[Pipeline] Failed to generate suggestions:", error);
4705
+ }
4706
+ return [];
4742
4707
  }
4708
+ };
4709
+
4710
+ // src/core/ProviderHealthCheck.ts
4711
+ var ProviderHealthCheck = class {
4743
4712
  /**
4744
- * Helper: Check if data item is product-related
4713
+ * Validates vector database configuration before initialization.
4745
4714
  */
4746
- static isProductData(item) {
4747
- const content = (item.content || "").toLowerCase();
4748
- const productKeywords = ["product", "price", "stock", "item", "sku", "brand", "model"];
4749
- return productKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
4750
- (k) => ["name", "price", "product", "sku"].includes(k.toLowerCase())
4751
- );
4715
+ static async checkVectorProvider(config) {
4716
+ const timestamp = Date.now();
4717
+ const configErrors = await ConfigValidator.validate({
4718
+ projectId: "health-check",
4719
+ vectorDb: config,
4720
+ llm: { provider: "openai", model: "gpt-4o", apiKey: "none" },
4721
+ embedding: { provider: "openai", model: "text-embedding-3-small", apiKey: "none" }
4722
+ });
4723
+ const vectorDbErrors = configErrors.filter((e) => e.field.startsWith("vectorDb"));
4724
+ if (vectorDbErrors.length > 0) {
4725
+ return {
4726
+ healthy: false,
4727
+ provider: config.provider,
4728
+ error: `Configuration validation failed: ${vectorDbErrors.map((e) => e.message).join("; ")}`,
4729
+ timestamp
4730
+ };
4731
+ }
4732
+ try {
4733
+ const checker = await ProviderRegistry.getVectorHealthChecker(config.provider);
4734
+ if (checker) {
4735
+ return await checker.check(config);
4736
+ }
4737
+ return await this.fallbackVectorHealthCheck(config, timestamp);
4738
+ } catch (error) {
4739
+ return {
4740
+ healthy: false,
4741
+ provider: config.provider,
4742
+ error: error instanceof Error ? error.message : String(error),
4743
+ timestamp
4744
+ };
4745
+ }
4752
4746
  }
4753
- /**
4754
- * Helper: Check if data contains time series
4755
- */
4756
- static isTimeSeriesData(item) {
4757
- const content = (item.content || "").toLowerCase();
4758
- const timeKeywords = ["date", "time", "year", "month", "week", "day", "hour", "trend", "historical"];
4759
- return timeKeywords.some((kw) => content.includes(kw)) || Object.keys(item.metadata || {}).some(
4760
- (k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
4761
- );
4747
+ static async fallbackVectorHealthCheck(config, timestamp) {
4748
+ const opts = config.options || {};
4749
+ const baseUrl = opts.baseUrl || opts.uri;
4750
+ if (baseUrl && baseUrl.startsWith("http")) {
4751
+ try {
4752
+ const response = await fetch(`${baseUrl.replace(/\/$/, "")}/health`);
4753
+ return {
4754
+ healthy: response.ok,
4755
+ provider: config.provider,
4756
+ error: response.ok ? void 0 : `Health check failed: ${response.status}`,
4757
+ timestamp
4758
+ };
4759
+ } catch (e) {
4760
+ return { healthy: false, provider: config.provider, error: "Provider unreachable", timestamp };
4761
+ }
4762
+ }
4763
+ return {
4764
+ healthy: true,
4765
+ provider: config.provider,
4766
+ error: "Pluggable health check not implemented; basic reachability assumed.",
4767
+ timestamp
4768
+ };
4762
4769
  }
4763
4770
  /**
4764
- * Helper: Extract product information from vector match
4771
+ * Validates LLM provider configuration.
4765
4772
  */
4766
- static extractProductInfo(item) {
4767
- const meta = item.metadata || {};
4768
- if (meta.name || meta.product) {
4773
+ static async checkLLMProvider(config) {
4774
+ const timestamp = Date.now();
4775
+ try {
4776
+ const checker = LLMFactory.getHealthChecker(config.provider);
4777
+ if (checker) {
4778
+ return await checker.check(config);
4779
+ }
4769
4780
  return {
4770
- id: item.id,
4771
- name: meta.name || meta.product,
4772
- price: meta.price,
4773
- image: meta.image || meta.imageUrl,
4774
- brand: meta.brand,
4775
- description: item.content,
4776
- inStock: this.determineStockStatus(item)
4781
+ healthy: true,
4782
+ provider: config.provider,
4783
+ error: "Pluggable health check not implemented; basic reachability assumed.",
4784
+ timestamp
4777
4785
  };
4778
- }
4779
- const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
4780
- const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
4781
- if (nameMatch) {
4786
+ } catch (error) {
4782
4787
  return {
4783
- id: item.id,
4784
- name: nameMatch[1],
4785
- price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
4786
- description: item.content,
4787
- inStock: this.determineStockStatus(item)
4788
+ healthy: false,
4789
+ provider: config.provider,
4790
+ error: error instanceof Error ? error.message : String(error),
4791
+ timestamp
4788
4792
  };
4789
4793
  }
4790
- return null;
4791
4794
  }
4792
4795
  /**
4793
- * Helper: Detect categories in data
4796
+ * Runs comprehensive health checks on all configured providers in parallel.
4794
4797
  */
4795
- static detectCategories(data) {
4796
- const categories = /* @__PURE__ */ new Set();
4797
- data.forEach((item) => {
4798
- const meta = item.metadata || {};
4799
- if (meta.category) {
4800
- categories.add(String(meta.category));
4801
- }
4802
- if (meta.type) {
4803
- categories.add(String(meta.type));
4804
- }
4805
- if (meta.tag) {
4806
- const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4807
- tags.forEach((t) => categories.add(String(t)));
4808
- }
4809
- const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
4810
- if (categoryMatch) {
4811
- categories.add(categoryMatch[1].trim());
4812
- }
4813
- });
4814
- return Array.from(categories);
4798
+ static async checkAll(vectorDbConfig, llmConfig, embeddingConfig) {
4799
+ const [vectorDb, llm, embedding] = await Promise.all([
4800
+ this.checkVectorProvider(vectorDbConfig),
4801
+ this.checkLLMProvider(llmConfig),
4802
+ embeddingConfig ? this.checkLLMProvider(embeddingConfig) : Promise.resolve(void 0)
4803
+ ]);
4804
+ return {
4805
+ vectorDb,
4806
+ llm,
4807
+ embedding,
4808
+ allHealthy: vectorDb.healthy && llm.healthy && (!embedding || embedding.healthy)
4809
+ };
4810
+ }
4811
+ };
4812
+
4813
+ // src/core/VectorPlugin.ts
4814
+ var VectorPlugin = class {
4815
+ constructor(hostConfig) {
4816
+ this.config = ConfigResolver.resolve(hostConfig);
4817
+ this.validationPromise = ConfigValidator.validateAndThrow(this.config);
4818
+ this.pipeline = new Pipeline(this.config);
4815
4819
  }
4816
4820
  /**
4817
- * Helper: Aggregate data by category
4821
+ * Get the current resolved configuration.
4818
4822
  */
4819
- static aggregateByCategory(data, categories) {
4820
- const result = {};
4821
- categories.forEach((cat) => {
4822
- result[cat] = 0;
4823
- });
4824
- data.forEach((item) => {
4825
- const meta = item.metadata || {};
4826
- const itemCategory = meta.category || meta.type || "Other";
4827
- if (result.hasOwnProperty(itemCategory)) {
4828
- result[itemCategory]++;
4829
- } else {
4830
- result["Other"] = (result["Other"] || 0) + 1;
4831
- }
4832
- });
4833
- return result;
4823
+ getConfig() {
4824
+ return this.config;
4834
4825
  }
4835
4826
  /**
4836
- * Helper: Extract time series data
4827
+ * Perform pre-flight health checks on all configured providers.
4828
+ * Useful to verify connectivity before running operations.
4829
+ *
4830
+ * @returns Health status for vector DB, LLM, and embedding providers
4837
4831
  */
4838
- static extractTimeSeriesData(data) {
4839
- return data.map((item) => {
4840
- const meta = item.metadata || {};
4841
- return {
4842
- timestamp: meta.timestamp || meta.date || (/* @__PURE__ */ new Date()).toISOString(),
4843
- value: meta.value || item.score || 0,
4844
- label: meta.label || item.content.substring(0, 50)
4845
- };
4846
- });
4832
+ async checkHealth() {
4833
+ return ProviderHealthCheck.checkAll(
4834
+ this.config.vectorDb,
4835
+ this.config.llm,
4836
+ this.config.embedding
4837
+ );
4847
4838
  }
4848
4839
  /**
4849
- * Helper: Extract table columns
4840
+ * Run a chat query.
4850
4841
  */
4851
- static extractTableColumns(data) {
4852
- const columnSet = /* @__PURE__ */ new Set();
4853
- columnSet.add("Content");
4854
- data.forEach((item) => {
4855
- Object.keys(item.metadata || {}).forEach((key) => {
4856
- columnSet.add(key.charAt(0).toUpperCase() + key.slice(1));
4857
- });
4858
- });
4859
- return Array.from(columnSet);
4842
+ async chat(message, history = [], namespace) {
4843
+ await this.validationPromise;
4844
+ return this.pipeline.ask(message, history, namespace);
4860
4845
  }
4861
4846
  /**
4862
- * Helper: Extract table row
4847
+ * Run a streaming chat query.
4863
4848
  */
4864
- static extractTableRow(item, columns) {
4865
- const meta = item.metadata || {};
4866
- return columns.map((col) => {
4867
- if (col === "Content") {
4868
- return item.content.substring(0, 100);
4869
- }
4870
- const metaKey = col.charAt(0).toLowerCase() + col.slice(1);
4871
- const value = meta[metaKey];
4872
- return value !== void 0 ? String(value) : "";
4849
+ chatStream(_0) {
4850
+ return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
4851
+ yield new __await(this.validationPromise);
4852
+ yield* __yieldStar(this.pipeline.askStream(message, history, namespace));
4873
4853
  });
4874
4854
  }
4875
4855
  /**
4876
- * Helper: Check stock status
4856
+ * Ingest documents into the vector database.
4877
4857
  */
4878
- static checkStockStatus(category, data) {
4879
- const item = data.find((d) => {
4880
- const meta = d.metadata || {};
4881
- return meta.category === category || meta.type === category;
4882
- });
4883
- return this.determineStockStatus(item || {});
4858
+ async ingest(documents, namespace) {
4859
+ await this.validationPromise;
4860
+ return this.pipeline.ingest(documents, namespace);
4884
4861
  }
4885
4862
  /**
4886
- * Helper: Determine if item is in stock
4863
+ * Get auto-suggestions based on a query prefix.
4887
4864
  */
4888
- static determineStockStatus(item) {
4889
- const meta = item.metadata || {};
4890
- if (meta.inStock !== void 0) {
4891
- return Boolean(meta.inStock);
4865
+ async getSuggestions(query, namespace) {
4866
+ await this.validationPromise;
4867
+ return this.pipeline.getSuggestions(query, namespace);
4868
+ }
4869
+ };
4870
+
4871
+ // src/utils/DocumentParser.ts
4872
+ var DocumentParser = class {
4873
+ /**
4874
+ * Extract text from a File or Buffer based on its type.
4875
+ */
4876
+ static async parse(file, fileName, mimeType) {
4877
+ var _a;
4878
+ const extension = (_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase();
4879
+ if (extension === "txt" || extension === "md" || mimeType === "text/plain" || mimeType === "text/markdown") {
4880
+ return this.readAsText(file);
4892
4881
  }
4893
- if (meta.stock !== void 0) {
4894
- return Boolean(meta.stock);
4882
+ if (extension === "json" || mimeType === "application/json") {
4883
+ const text = await this.readAsText(file);
4884
+ try {
4885
+ const obj = JSON.parse(text);
4886
+ return JSON.stringify(obj, null, 2);
4887
+ } catch (e) {
4888
+ return text;
4889
+ }
4895
4890
  }
4896
- if (meta.available !== void 0) {
4897
- return Boolean(meta.available);
4891
+ if (extension === "csv" || mimeType === "text/csv") {
4892
+ return this.readAsText(file);
4898
4893
  }
4899
- const content = (item.content || "").toLowerCase();
4900
- if (content.includes("out of stock") || content.includes("unavailable")) {
4901
- return false;
4894
+ if (extension === "pdf" || mimeType === "application/pdf") {
4895
+ try {
4896
+ const pdf = await import("pdf-parse");
4897
+ const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
4898
+ const data = await pdf.default(buffer);
4899
+ return data.text;
4900
+ } catch (err) {
4901
+ console.warn('[DocumentParser] PDF parsing failed. Make sure "pdf-parse" is installed.', err);
4902
+ return `[PDF Parsing Error for ${fileName}]`;
4903
+ }
4902
4904
  }
4903
- if (content.includes("in stock") || content.includes("available")) {
4904
- return true;
4905
+ if (extension === "docx" || mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
4906
+ try {
4907
+ const mammoth = await import("mammoth");
4908
+ const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
4909
+ const result = await mammoth.extractRawText({ buffer });
4910
+ return result.value;
4911
+ } catch (err) {
4912
+ console.warn('[DocumentParser] DOCX parsing failed. Make sure "mammoth" is installed.', err);
4913
+ return `[DOCX Parsing Error for ${fileName}]`;
4914
+ }
4915
+ }
4916
+ try {
4917
+ return await this.readAsText(file);
4918
+ } catch (e) {
4919
+ throw new Error(`[DocumentParser] Unsupported file format: ${fileName} (${mimeType})`);
4905
4920
  }
4906
- return true;
4907
4921
  }
4908
- /**
4909
- * Helper: Check if data has multiple fields
4910
- */
4911
- static hasMultipleFields(data) {
4912
- const fieldCount = /* @__PURE__ */ new Set();
4913
- data.forEach((item) => {
4914
- Object.keys(item.metadata || {}).forEach((key) => {
4915
- fieldCount.add(key);
4916
- });
4917
- });
4918
- return fieldCount.size > 2;
4922
+ static async readAsText(file) {
4923
+ if (Buffer.isBuffer(file)) {
4924
+ return file.toString("utf-8");
4925
+ }
4926
+ return await file.text();
4919
4927
  }
4920
4928
  };
4921
4929