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