@retrivora-ai/rag-engine 1.8.4 → 1.8.5
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/LICENSE.txt +21 -0
- package/dist/{ILLMProvider-BOJFz3Na.d.mts → ILLMProvider-CbUtvWAW.d.mts} +2 -0
- package/dist/{ILLMProvider-BOJFz3Na.d.ts → ILLMProvider-CbUtvWAW.d.ts} +2 -0
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +100 -52
- package/dist/handlers/index.mjs +100 -52
- package/dist/{index-BwpcaziY.d.ts → index-DgzcnqZs.d.ts} +1 -1
- package/dist/{index-D3V9Et2M.d.mts → index-c_y_5qdw.d.mts} +1 -1
- package/dist/index.css +27 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -4
- package/dist/index.mjs +18 -4
- package/dist/server.d.mts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/server.js +108 -55
- package/dist/server.mjs +108 -55
- package/package.json +1 -1
- package/src/app/globals.css +97 -0
- package/src/components/ChatWidget.tsx +23 -10
- package/src/config/RagConfig.ts +2 -0
- package/src/providers/vectordb/ChromaDBProvider.ts +4 -0
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +18 -5
- package/src/providers/vectordb/PostgreSQLProvider.ts +14 -3
- package/src/providers/vectordb/QdrantProvider.ts +4 -1
- package/src/utils/UITransformer.ts +99 -51
package/dist/server.js
CHANGED
|
@@ -374,10 +374,12 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
374
374
|
embedding VECTOR(${this.dimensions})
|
|
375
375
|
)
|
|
376
376
|
`);
|
|
377
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
378
|
+
const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
|
|
377
379
|
await client.query(`
|
|
378
380
|
CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
|
|
379
381
|
ON ${this.uploadTable}
|
|
380
|
-
USING hnsw (embedding
|
|
382
|
+
USING hnsw (embedding ${indexOps})
|
|
381
383
|
`);
|
|
382
384
|
const res = await client.query(`
|
|
383
385
|
SELECT table_name
|
|
@@ -439,10 +441,12 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
439
441
|
)
|
|
440
442
|
`;
|
|
441
443
|
await client.query(createTableSql);
|
|
444
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
445
|
+
const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
|
|
442
446
|
await client.query(`
|
|
443
447
|
CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
|
|
444
448
|
ON "${tableName}"
|
|
445
|
-
USING hnsw (embedding
|
|
449
|
+
USING hnsw (embedding ${indexOps})
|
|
446
450
|
`);
|
|
447
451
|
if (!this.tables.includes(tableName)) {
|
|
448
452
|
this.tables.push(tableName);
|
|
@@ -524,6 +528,8 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
524
528
|
try {
|
|
525
529
|
let sqlQuery = "";
|
|
526
530
|
let params = [];
|
|
531
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
532
|
+
const scoreExpr = metric === "euclidean" ? `1 / (1 + (embedding <-> $1::vector))` : metric === "dotproduct" ? `(embedding <#> $1::vector) * -1` : `1 - (embedding <=> $1::vector)`;
|
|
527
533
|
if (queryText) {
|
|
528
534
|
const hasEntityHints = entityHints.length > 0;
|
|
529
535
|
const exactNameScoreExpr = hasEntityHints ? `+ (
|
|
@@ -536,9 +542,9 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
536
542
|
) * 3.0` : "";
|
|
537
543
|
sqlQuery = `
|
|
538
544
|
SELECT *,
|
|
539
|
-
(
|
|
545
|
+
(${scoreExpr}) AS vector_score,
|
|
540
546
|
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
541
|
-
((
|
|
547
|
+
((${scoreExpr}) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
542
548
|
FROM "${table}" t
|
|
543
549
|
ORDER BY hybrid_score DESC
|
|
544
550
|
LIMIT 50
|
|
@@ -547,7 +553,7 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
547
553
|
} else {
|
|
548
554
|
sqlQuery = `
|
|
549
555
|
SELECT *,
|
|
550
|
-
(
|
|
556
|
+
(${scoreExpr}) AS hybrid_score
|
|
551
557
|
FROM "${table}" t
|
|
552
558
|
ORDER BY hybrid_score DESC
|
|
553
559
|
LIMIT 50
|
|
@@ -1021,11 +1027,13 @@ var init_QdrantProvider = __esm({
|
|
|
1021
1027
|
if (import_axios4.default.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
|
|
1022
1028
|
const opts = this.config.options;
|
|
1023
1029
|
const dimensionsForCreate = opts.dimensions || 1536;
|
|
1030
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
1031
|
+
const distance = metric === "euclidean" ? "Euclid" : metric === "dotproduct" ? "Dot" : "Cosine";
|
|
1024
1032
|
console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensionsForCreate})...`);
|
|
1025
1033
|
await this.http.put(`/collections/${this.indexName}`, {
|
|
1026
1034
|
vectors: {
|
|
1027
1035
|
size: dimensionsForCreate,
|
|
1028
|
-
distance
|
|
1036
|
+
distance
|
|
1029
1037
|
}
|
|
1030
1038
|
});
|
|
1031
1039
|
console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
|
|
@@ -1203,9 +1211,12 @@ var init_ChromaDBProvider = __esm({
|
|
|
1203
1211
|
console.log(`[ChromaDBProvider] \u2705 Collection "${this.indexName}" found (id: ${this.collectionId})`);
|
|
1204
1212
|
} catch (err) {
|
|
1205
1213
|
if (import_axios5.default.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
|
|
1214
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
1215
|
+
const space = metric === "euclidean" ? "l2" : metric === "dotproduct" ? "ip" : "cosine";
|
|
1206
1216
|
console.log(`[ChromaDBProvider] \u23F3 Collection "${this.indexName}" not found. Creating...`);
|
|
1207
1217
|
const { data } = await this.http.post("/api/v1/collections", {
|
|
1208
|
-
name: this.indexName
|
|
1218
|
+
name: this.indexName,
|
|
1219
|
+
metadata: { "hnsw:space": space }
|
|
1209
1220
|
});
|
|
1210
1221
|
this.collectionId = data.id;
|
|
1211
1222
|
console.log(`[ChromaDBProvider] \u2705 Created collection "${this.indexName}" (id: ${this.collectionId})`);
|
|
@@ -4242,18 +4253,20 @@ var UITransformer = class {
|
|
|
4242
4253
|
}
|
|
4243
4254
|
const isStockRequest = this.isStockQuery(userQuery);
|
|
4244
4255
|
const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
|
|
4245
|
-
const
|
|
4256
|
+
const groupByField = this.determineGroupByField(userQuery, filteredData);
|
|
4257
|
+
const categories = this.detectCategories(filteredData, groupByField);
|
|
4246
4258
|
const hasProducts = filteredData.some((item) => this.isProductData(item));
|
|
4247
4259
|
const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
|
|
4248
4260
|
const isTrendQuery = this.isTrendQuery(userQuery);
|
|
4249
4261
|
if (isTrendQuery && isTimeSeries) {
|
|
4250
4262
|
return this.transformToLineChart(filteredData);
|
|
4251
4263
|
}
|
|
4252
|
-
|
|
4264
|
+
const explicitChartRequest = this.isExplicitChartQuery(userQuery);
|
|
4265
|
+
if (hasProducts && !explicitChartRequest && !this.shouldShowCategoryChart(userQuery)) {
|
|
4253
4266
|
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
4254
4267
|
}
|
|
4255
|
-
if (categories.length > 1 && this.shouldShowCategoryChart(userQuery
|
|
4256
|
-
return this.transformToPieChart(filteredData);
|
|
4268
|
+
if (explicitChartRequest || categories.length > 1 && this.shouldShowCategoryChart(userQuery)) {
|
|
4269
|
+
return this.transformToPieChart(filteredData, groupByField);
|
|
4257
4270
|
}
|
|
4258
4271
|
if (hasProducts) {
|
|
4259
4272
|
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
@@ -4278,11 +4291,12 @@ var UITransformer = class {
|
|
|
4278
4291
|
/**
|
|
4279
4292
|
* Transform data to pie chart format
|
|
4280
4293
|
*/
|
|
4281
|
-
static transformToPieChart(data) {
|
|
4282
|
-
|
|
4283
|
-
|
|
4294
|
+
static transformToPieChart(data, groupByField) {
|
|
4295
|
+
let categories = this.detectCategories(data, groupByField);
|
|
4296
|
+
if (categories.length === 0) categories = ["All Data"];
|
|
4297
|
+
const categoryData = this.aggregateByCategory(data, categories, groupByField);
|
|
4284
4298
|
const pieData = Object.entries(categoryData).map(([label, count]) => {
|
|
4285
|
-
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
|
|
4299
|
+
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data, groupByField);
|
|
4286
4300
|
return {
|
|
4287
4301
|
label,
|
|
4288
4302
|
value: count,
|
|
@@ -4292,7 +4306,7 @@ var UITransformer = class {
|
|
|
4292
4306
|
});
|
|
4293
4307
|
return {
|
|
4294
4308
|
type: "pie_chart",
|
|
4295
|
-
title:
|
|
4309
|
+
title: `Distribution by ${groupByField.charAt(0).toUpperCase() + groupByField.slice(1)}`,
|
|
4296
4310
|
description: `Showing breakdown across ${categories.length} categories`,
|
|
4297
4311
|
data: pieData
|
|
4298
4312
|
};
|
|
@@ -4515,10 +4529,11 @@ var UITransformer = class {
|
|
|
4515
4529
|
});
|
|
4516
4530
|
return hasTimeKeyword || hasValidDateValue;
|
|
4517
4531
|
}
|
|
4518
|
-
static
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4532
|
+
static isExplicitChartQuery(query) {
|
|
4533
|
+
const normalized = query.toLowerCase();
|
|
4534
|
+
return normalized.includes("pie chart") || normalized.includes("piechart") || normalized.includes("bar chart") || normalized.includes("barchart") || normalized.includes("radar chart");
|
|
4535
|
+
}
|
|
4536
|
+
static shouldShowCategoryChart(query) {
|
|
4522
4537
|
const normalized = query.toLowerCase();
|
|
4523
4538
|
const chartKeywords = [
|
|
4524
4539
|
"distribution",
|
|
@@ -4531,7 +4546,11 @@ var UITransformer = class {
|
|
|
4531
4546
|
"segmentation",
|
|
4532
4547
|
"split",
|
|
4533
4548
|
"category breakdown",
|
|
4534
|
-
"category distribution"
|
|
4549
|
+
"category distribution",
|
|
4550
|
+
"pie chart",
|
|
4551
|
+
"piechart",
|
|
4552
|
+
"bar chart",
|
|
4553
|
+
"barchart"
|
|
4535
4554
|
];
|
|
4536
4555
|
return chartKeywords.some((keyword) => normalized.includes(keyword));
|
|
4537
4556
|
}
|
|
@@ -4607,44 +4626,67 @@ var UITransformer = class {
|
|
|
4607
4626
|
return null;
|
|
4608
4627
|
}
|
|
4609
4628
|
/**
|
|
4610
|
-
* Helper:
|
|
4629
|
+
* Helper: Dynamically determine what field to group by based on user query
|
|
4630
|
+
*/
|
|
4631
|
+
static determineGroupByField(query, data) {
|
|
4632
|
+
const normalized = query.toLowerCase();
|
|
4633
|
+
const match = normalized.match(/(?:by|across|per|based on)\s+([a-zA-Z]+)/i);
|
|
4634
|
+
let targetField = "category";
|
|
4635
|
+
if (match && match[1]) {
|
|
4636
|
+
let field = match[1].toLowerCase();
|
|
4637
|
+
if (field.endsWith("ies")) field = field.replace(/ies$/, "y");
|
|
4638
|
+
else if (field.endsWith("s") && field !== "status") field = field.slice(0, -1);
|
|
4639
|
+
const stopWords = ["the", "all", "different", "various", "some", "these", "those"];
|
|
4640
|
+
if (!stopWords.includes(field)) {
|
|
4641
|
+
targetField = field;
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
const hasTargetField = data.some((d) => d.metadata && d.metadata[targetField] !== void 0);
|
|
4645
|
+
if (!hasTargetField && targetField === "category") {
|
|
4646
|
+
if (data.some((d) => d.metadata && d.metadata["type"] !== void 0)) return "type";
|
|
4647
|
+
if (data.some((d) => d.metadata && d.metadata["brand"] !== void 0)) return "brand";
|
|
4648
|
+
}
|
|
4649
|
+
return targetField;
|
|
4650
|
+
}
|
|
4651
|
+
/**
|
|
4652
|
+
* Helper: Detect grouping values dynamically
|
|
4611
4653
|
*/
|
|
4612
|
-
static detectCategories(data) {
|
|
4654
|
+
static detectCategories(data, groupByField) {
|
|
4613
4655
|
const categories = /* @__PURE__ */ new Set();
|
|
4614
4656
|
data.forEach((item) => {
|
|
4615
4657
|
const meta = item.metadata || {};
|
|
4616
|
-
if (meta
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
));
|
|
4629
|
-
contentCategories.forEach((category) => categories.add(category));
|
|
4630
|
-
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
4631
|
-
if (categoryMatch) {
|
|
4632
|
-
categories.add(categoryMatch[1].trim());
|
|
4658
|
+
if (meta[groupByField] !== void 0) {
|
|
4659
|
+
const val = meta[groupByField];
|
|
4660
|
+
if (Array.isArray(val)) val.forEach((v) => categories.add(String(v)));
|
|
4661
|
+
else categories.add(String(val));
|
|
4662
|
+
} else if (groupByField === "category") {
|
|
4663
|
+
if (meta.type) categories.add(String(meta.type));
|
|
4664
|
+
if (meta.tag) {
|
|
4665
|
+
const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
|
|
4666
|
+
tags.forEach((t) => categories.add(String(t)));
|
|
4667
|
+
}
|
|
4668
|
+
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
4669
|
+
if (categoryMatch) categories.add(categoryMatch[1].trim());
|
|
4633
4670
|
}
|
|
4634
4671
|
});
|
|
4635
4672
|
return Array.from(categories);
|
|
4636
4673
|
}
|
|
4637
4674
|
/**
|
|
4638
|
-
* Helper: Aggregate data
|
|
4675
|
+
* Helper: Aggregate data dynamically
|
|
4639
4676
|
*/
|
|
4640
|
-
static aggregateByCategory(data, categories) {
|
|
4677
|
+
static aggregateByCategory(data, categories, groupByField) {
|
|
4641
4678
|
const result = {};
|
|
4642
4679
|
categories.forEach((cat) => {
|
|
4643
4680
|
result[cat] = 0;
|
|
4644
4681
|
});
|
|
4645
4682
|
data.forEach((item) => {
|
|
4646
4683
|
const meta = item.metadata || {};
|
|
4647
|
-
|
|
4684
|
+
let itemCategory = "Other";
|
|
4685
|
+
if (meta[groupByField] !== void 0) {
|
|
4686
|
+
itemCategory = String(meta[groupByField]);
|
|
4687
|
+
} else if (groupByField === "category" && meta.type) {
|
|
4688
|
+
itemCategory = String(meta.type);
|
|
4689
|
+
}
|
|
4648
4690
|
if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
|
|
4649
4691
|
result[itemCategory]++;
|
|
4650
4692
|
} else {
|
|
@@ -4694,14 +4736,19 @@ var UITransformer = class {
|
|
|
4694
4736
|
});
|
|
4695
4737
|
}
|
|
4696
4738
|
/**
|
|
4697
|
-
* Helper: Calculate stock counts
|
|
4739
|
+
* Helper: Calculate stock counts dynamically
|
|
4698
4740
|
*/
|
|
4699
|
-
static calculateStockCounts(category, data) {
|
|
4741
|
+
static calculateStockCounts(category, data, groupByField) {
|
|
4700
4742
|
let inStock = 0;
|
|
4701
4743
|
let outOfStock = 0;
|
|
4702
4744
|
data.forEach((d) => {
|
|
4703
4745
|
const meta = d.metadata || {};
|
|
4704
|
-
|
|
4746
|
+
let itemCategory = "Other";
|
|
4747
|
+
if (meta[groupByField] !== void 0) {
|
|
4748
|
+
itemCategory = String(meta[groupByField]);
|
|
4749
|
+
} else if (groupByField === "category" && meta.type) {
|
|
4750
|
+
itemCategory = String(meta.type);
|
|
4751
|
+
}
|
|
4705
4752
|
if (itemCategory === category) {
|
|
4706
4753
|
if (this.determineStockStatus(d)) {
|
|
4707
4754
|
inStock++;
|
|
@@ -4826,12 +4873,13 @@ DATA SHAPE per type:
|
|
|
4826
4873
|
- text: { "content": "<prose answer>" }
|
|
4827
4874
|
|
|
4828
4875
|
DECISION RULES (follow strictly):
|
|
4829
|
-
1.
|
|
4830
|
-
2.
|
|
4831
|
-
3.
|
|
4832
|
-
4.
|
|
4833
|
-
5.
|
|
4834
|
-
6.
|
|
4876
|
+
1. IF the user explicitly asks for a "pie chart", "bar chart", or "table", you MUST output that exact type. Do not deviate from their request.
|
|
4877
|
+
2. bar_chart \u2192 comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
|
|
4878
|
+
3. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
|
|
4879
|
+
4. pie_chart \u2192 proportional breakdown or percentage distribution
|
|
4880
|
+
5. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes.
|
|
4881
|
+
6. table \u2192 multi-field structured records where each item has \u2265 3 attributes
|
|
4882
|
+
7. text \u2192 conversational or free-form answers where no chart adds value
|
|
4835
4883
|
|
|
4836
4884
|
IMPORTANT:
|
|
4837
4885
|
- Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
|
|
@@ -5989,10 +6037,12 @@ var PostgreSQLProvider = class extends BaseVectorProvider {
|
|
|
5989
6037
|
embedding VECTOR(${this.dimensions})
|
|
5990
6038
|
)
|
|
5991
6039
|
`);
|
|
6040
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
6041
|
+
const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
|
|
5992
6042
|
await client.query(`
|
|
5993
6043
|
CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
|
|
5994
6044
|
ON ${this.tableName}
|
|
5995
|
-
USING hnsw (embedding
|
|
6045
|
+
USING hnsw (embedding ${indexOps})
|
|
5996
6046
|
`);
|
|
5997
6047
|
} finally {
|
|
5998
6048
|
client.release();
|
|
@@ -6064,11 +6114,14 @@ var PostgreSQLProvider = class extends BaseVectorProvider {
|
|
|
6064
6114
|
try {
|
|
6065
6115
|
const efSearch = this.config.options.efSearch || Math.max(topK * 10, 40);
|
|
6066
6116
|
await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
|
|
6117
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
6118
|
+
const queryOp = metric === "euclidean" ? "<->" : metric === "dotproduct" ? "<#>" : "<=>";
|
|
6119
|
+
const scoreExpr = metric === "euclidean" ? `1 / (1 + (embedding <-> $1::vector))` : metric === "dotproduct" ? `(embedding <#> $1::vector) * -1` : `1 - (embedding <=> $1::vector)`;
|
|
6067
6120
|
const result = await client.query(
|
|
6068
|
-
`SELECT id, content, metadata,
|
|
6121
|
+
`SELECT id, content, metadata, ${scoreExpr} AS score
|
|
6069
6122
|
FROM ${this.tableName}
|
|
6070
6123
|
${whereClause}
|
|
6071
|
-
ORDER BY embedding
|
|
6124
|
+
ORDER BY embedding ${queryOp} $1::vector
|
|
6072
6125
|
LIMIT $2`,
|
|
6073
6126
|
params
|
|
6074
6127
|
);
|
package/dist/server.mjs
CHANGED
|
@@ -353,10 +353,12 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
353
353
|
embedding VECTOR(${this.dimensions})
|
|
354
354
|
)
|
|
355
355
|
`);
|
|
356
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
357
|
+
const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
|
|
356
358
|
await client.query(`
|
|
357
359
|
CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
|
|
358
360
|
ON ${this.uploadTable}
|
|
359
|
-
USING hnsw (embedding
|
|
361
|
+
USING hnsw (embedding ${indexOps})
|
|
360
362
|
`);
|
|
361
363
|
const res = await client.query(`
|
|
362
364
|
SELECT table_name
|
|
@@ -418,10 +420,12 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
418
420
|
)
|
|
419
421
|
`;
|
|
420
422
|
await client.query(createTableSql);
|
|
423
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
424
|
+
const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
|
|
421
425
|
await client.query(`
|
|
422
426
|
CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
|
|
423
427
|
ON "${tableName}"
|
|
424
|
-
USING hnsw (embedding
|
|
428
|
+
USING hnsw (embedding ${indexOps})
|
|
425
429
|
`);
|
|
426
430
|
if (!this.tables.includes(tableName)) {
|
|
427
431
|
this.tables.push(tableName);
|
|
@@ -503,6 +507,8 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
503
507
|
try {
|
|
504
508
|
let sqlQuery = "";
|
|
505
509
|
let params = [];
|
|
510
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
511
|
+
const scoreExpr = metric === "euclidean" ? `1 / (1 + (embedding <-> $1::vector))` : metric === "dotproduct" ? `(embedding <#> $1::vector) * -1` : `1 - (embedding <=> $1::vector)`;
|
|
506
512
|
if (queryText) {
|
|
507
513
|
const hasEntityHints = entityHints.length > 0;
|
|
508
514
|
const exactNameScoreExpr = hasEntityHints ? `+ (
|
|
@@ -515,9 +521,9 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
515
521
|
) * 3.0` : "";
|
|
516
522
|
sqlQuery = `
|
|
517
523
|
SELECT *,
|
|
518
|
-
(
|
|
524
|
+
(${scoreExpr}) AS vector_score,
|
|
519
525
|
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
520
|
-
((
|
|
526
|
+
((${scoreExpr}) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
521
527
|
FROM "${table}" t
|
|
522
528
|
ORDER BY hybrid_score DESC
|
|
523
529
|
LIMIT 50
|
|
@@ -526,7 +532,7 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
526
532
|
} else {
|
|
527
533
|
sqlQuery = `
|
|
528
534
|
SELECT *,
|
|
529
|
-
(
|
|
535
|
+
(${scoreExpr}) AS hybrid_score
|
|
530
536
|
FROM "${table}" t
|
|
531
537
|
ORDER BY hybrid_score DESC
|
|
532
538
|
LIMIT 50
|
|
@@ -1000,11 +1006,13 @@ var init_QdrantProvider = __esm({
|
|
|
1000
1006
|
if (axios4.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
|
|
1001
1007
|
const opts = this.config.options;
|
|
1002
1008
|
const dimensionsForCreate = opts.dimensions || 1536;
|
|
1009
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
1010
|
+
const distance = metric === "euclidean" ? "Euclid" : metric === "dotproduct" ? "Dot" : "Cosine";
|
|
1003
1011
|
console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensionsForCreate})...`);
|
|
1004
1012
|
await this.http.put(`/collections/${this.indexName}`, {
|
|
1005
1013
|
vectors: {
|
|
1006
1014
|
size: dimensionsForCreate,
|
|
1007
|
-
distance
|
|
1015
|
+
distance
|
|
1008
1016
|
}
|
|
1009
1017
|
});
|
|
1010
1018
|
console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
|
|
@@ -1182,9 +1190,12 @@ var init_ChromaDBProvider = __esm({
|
|
|
1182
1190
|
console.log(`[ChromaDBProvider] \u2705 Collection "${this.indexName}" found (id: ${this.collectionId})`);
|
|
1183
1191
|
} catch (err) {
|
|
1184
1192
|
if (axios5.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
|
|
1193
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
1194
|
+
const space = metric === "euclidean" ? "l2" : metric === "dotproduct" ? "ip" : "cosine";
|
|
1185
1195
|
console.log(`[ChromaDBProvider] \u23F3 Collection "${this.indexName}" not found. Creating...`);
|
|
1186
1196
|
const { data } = await this.http.post("/api/v1/collections", {
|
|
1187
|
-
name: this.indexName
|
|
1197
|
+
name: this.indexName,
|
|
1198
|
+
metadata: { "hnsw:space": space }
|
|
1188
1199
|
});
|
|
1189
1200
|
this.collectionId = data.id;
|
|
1190
1201
|
console.log(`[ChromaDBProvider] \u2705 Created collection "${this.indexName}" (id: ${this.collectionId})`);
|
|
@@ -4173,18 +4184,20 @@ var UITransformer = class {
|
|
|
4173
4184
|
}
|
|
4174
4185
|
const isStockRequest = this.isStockQuery(userQuery);
|
|
4175
4186
|
const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
|
|
4176
|
-
const
|
|
4187
|
+
const groupByField = this.determineGroupByField(userQuery, filteredData);
|
|
4188
|
+
const categories = this.detectCategories(filteredData, groupByField);
|
|
4177
4189
|
const hasProducts = filteredData.some((item) => this.isProductData(item));
|
|
4178
4190
|
const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
|
|
4179
4191
|
const isTrendQuery = this.isTrendQuery(userQuery);
|
|
4180
4192
|
if (isTrendQuery && isTimeSeries) {
|
|
4181
4193
|
return this.transformToLineChart(filteredData);
|
|
4182
4194
|
}
|
|
4183
|
-
|
|
4195
|
+
const explicitChartRequest = this.isExplicitChartQuery(userQuery);
|
|
4196
|
+
if (hasProducts && !explicitChartRequest && !this.shouldShowCategoryChart(userQuery)) {
|
|
4184
4197
|
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
4185
4198
|
}
|
|
4186
|
-
if (categories.length > 1 && this.shouldShowCategoryChart(userQuery
|
|
4187
|
-
return this.transformToPieChart(filteredData);
|
|
4199
|
+
if (explicitChartRequest || categories.length > 1 && this.shouldShowCategoryChart(userQuery)) {
|
|
4200
|
+
return this.transformToPieChart(filteredData, groupByField);
|
|
4188
4201
|
}
|
|
4189
4202
|
if (hasProducts) {
|
|
4190
4203
|
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
@@ -4209,11 +4222,12 @@ var UITransformer = class {
|
|
|
4209
4222
|
/**
|
|
4210
4223
|
* Transform data to pie chart format
|
|
4211
4224
|
*/
|
|
4212
|
-
static transformToPieChart(data) {
|
|
4213
|
-
|
|
4214
|
-
|
|
4225
|
+
static transformToPieChart(data, groupByField) {
|
|
4226
|
+
let categories = this.detectCategories(data, groupByField);
|
|
4227
|
+
if (categories.length === 0) categories = ["All Data"];
|
|
4228
|
+
const categoryData = this.aggregateByCategory(data, categories, groupByField);
|
|
4215
4229
|
const pieData = Object.entries(categoryData).map(([label, count]) => {
|
|
4216
|
-
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
|
|
4230
|
+
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data, groupByField);
|
|
4217
4231
|
return {
|
|
4218
4232
|
label,
|
|
4219
4233
|
value: count,
|
|
@@ -4223,7 +4237,7 @@ var UITransformer = class {
|
|
|
4223
4237
|
});
|
|
4224
4238
|
return {
|
|
4225
4239
|
type: "pie_chart",
|
|
4226
|
-
title:
|
|
4240
|
+
title: `Distribution by ${groupByField.charAt(0).toUpperCase() + groupByField.slice(1)}`,
|
|
4227
4241
|
description: `Showing breakdown across ${categories.length} categories`,
|
|
4228
4242
|
data: pieData
|
|
4229
4243
|
};
|
|
@@ -4446,10 +4460,11 @@ var UITransformer = class {
|
|
|
4446
4460
|
});
|
|
4447
4461
|
return hasTimeKeyword || hasValidDateValue;
|
|
4448
4462
|
}
|
|
4449
|
-
static
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4463
|
+
static isExplicitChartQuery(query) {
|
|
4464
|
+
const normalized = query.toLowerCase();
|
|
4465
|
+
return normalized.includes("pie chart") || normalized.includes("piechart") || normalized.includes("bar chart") || normalized.includes("barchart") || normalized.includes("radar chart");
|
|
4466
|
+
}
|
|
4467
|
+
static shouldShowCategoryChart(query) {
|
|
4453
4468
|
const normalized = query.toLowerCase();
|
|
4454
4469
|
const chartKeywords = [
|
|
4455
4470
|
"distribution",
|
|
@@ -4462,7 +4477,11 @@ var UITransformer = class {
|
|
|
4462
4477
|
"segmentation",
|
|
4463
4478
|
"split",
|
|
4464
4479
|
"category breakdown",
|
|
4465
|
-
"category distribution"
|
|
4480
|
+
"category distribution",
|
|
4481
|
+
"pie chart",
|
|
4482
|
+
"piechart",
|
|
4483
|
+
"bar chart",
|
|
4484
|
+
"barchart"
|
|
4466
4485
|
];
|
|
4467
4486
|
return chartKeywords.some((keyword) => normalized.includes(keyword));
|
|
4468
4487
|
}
|
|
@@ -4538,44 +4557,67 @@ var UITransformer = class {
|
|
|
4538
4557
|
return null;
|
|
4539
4558
|
}
|
|
4540
4559
|
/**
|
|
4541
|
-
* Helper:
|
|
4560
|
+
* Helper: Dynamically determine what field to group by based on user query
|
|
4561
|
+
*/
|
|
4562
|
+
static determineGroupByField(query, data) {
|
|
4563
|
+
const normalized = query.toLowerCase();
|
|
4564
|
+
const match = normalized.match(/(?:by|across|per|based on)\s+([a-zA-Z]+)/i);
|
|
4565
|
+
let targetField = "category";
|
|
4566
|
+
if (match && match[1]) {
|
|
4567
|
+
let field = match[1].toLowerCase();
|
|
4568
|
+
if (field.endsWith("ies")) field = field.replace(/ies$/, "y");
|
|
4569
|
+
else if (field.endsWith("s") && field !== "status") field = field.slice(0, -1);
|
|
4570
|
+
const stopWords = ["the", "all", "different", "various", "some", "these", "those"];
|
|
4571
|
+
if (!stopWords.includes(field)) {
|
|
4572
|
+
targetField = field;
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
const hasTargetField = data.some((d) => d.metadata && d.metadata[targetField] !== void 0);
|
|
4576
|
+
if (!hasTargetField && targetField === "category") {
|
|
4577
|
+
if (data.some((d) => d.metadata && d.metadata["type"] !== void 0)) return "type";
|
|
4578
|
+
if (data.some((d) => d.metadata && d.metadata["brand"] !== void 0)) return "brand";
|
|
4579
|
+
}
|
|
4580
|
+
return targetField;
|
|
4581
|
+
}
|
|
4582
|
+
/**
|
|
4583
|
+
* Helper: Detect grouping values dynamically
|
|
4542
4584
|
*/
|
|
4543
|
-
static detectCategories(data) {
|
|
4585
|
+
static detectCategories(data, groupByField) {
|
|
4544
4586
|
const categories = /* @__PURE__ */ new Set();
|
|
4545
4587
|
data.forEach((item) => {
|
|
4546
4588
|
const meta = item.metadata || {};
|
|
4547
|
-
if (meta
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
));
|
|
4560
|
-
contentCategories.forEach((category) => categories.add(category));
|
|
4561
|
-
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
4562
|
-
if (categoryMatch) {
|
|
4563
|
-
categories.add(categoryMatch[1].trim());
|
|
4589
|
+
if (meta[groupByField] !== void 0) {
|
|
4590
|
+
const val = meta[groupByField];
|
|
4591
|
+
if (Array.isArray(val)) val.forEach((v) => categories.add(String(v)));
|
|
4592
|
+
else categories.add(String(val));
|
|
4593
|
+
} else if (groupByField === "category") {
|
|
4594
|
+
if (meta.type) categories.add(String(meta.type));
|
|
4595
|
+
if (meta.tag) {
|
|
4596
|
+
const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
|
|
4597
|
+
tags.forEach((t) => categories.add(String(t)));
|
|
4598
|
+
}
|
|
4599
|
+
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
4600
|
+
if (categoryMatch) categories.add(categoryMatch[1].trim());
|
|
4564
4601
|
}
|
|
4565
4602
|
});
|
|
4566
4603
|
return Array.from(categories);
|
|
4567
4604
|
}
|
|
4568
4605
|
/**
|
|
4569
|
-
* Helper: Aggregate data
|
|
4606
|
+
* Helper: Aggregate data dynamically
|
|
4570
4607
|
*/
|
|
4571
|
-
static aggregateByCategory(data, categories) {
|
|
4608
|
+
static aggregateByCategory(data, categories, groupByField) {
|
|
4572
4609
|
const result = {};
|
|
4573
4610
|
categories.forEach((cat) => {
|
|
4574
4611
|
result[cat] = 0;
|
|
4575
4612
|
});
|
|
4576
4613
|
data.forEach((item) => {
|
|
4577
4614
|
const meta = item.metadata || {};
|
|
4578
|
-
|
|
4615
|
+
let itemCategory = "Other";
|
|
4616
|
+
if (meta[groupByField] !== void 0) {
|
|
4617
|
+
itemCategory = String(meta[groupByField]);
|
|
4618
|
+
} else if (groupByField === "category" && meta.type) {
|
|
4619
|
+
itemCategory = String(meta.type);
|
|
4620
|
+
}
|
|
4579
4621
|
if (Object.prototype.hasOwnProperty.call(result, itemCategory)) {
|
|
4580
4622
|
result[itemCategory]++;
|
|
4581
4623
|
} else {
|
|
@@ -4625,14 +4667,19 @@ var UITransformer = class {
|
|
|
4625
4667
|
});
|
|
4626
4668
|
}
|
|
4627
4669
|
/**
|
|
4628
|
-
* Helper: Calculate stock counts
|
|
4670
|
+
* Helper: Calculate stock counts dynamically
|
|
4629
4671
|
*/
|
|
4630
|
-
static calculateStockCounts(category, data) {
|
|
4672
|
+
static calculateStockCounts(category, data, groupByField) {
|
|
4631
4673
|
let inStock = 0;
|
|
4632
4674
|
let outOfStock = 0;
|
|
4633
4675
|
data.forEach((d) => {
|
|
4634
4676
|
const meta = d.metadata || {};
|
|
4635
|
-
|
|
4677
|
+
let itemCategory = "Other";
|
|
4678
|
+
if (meta[groupByField] !== void 0) {
|
|
4679
|
+
itemCategory = String(meta[groupByField]);
|
|
4680
|
+
} else if (groupByField === "category" && meta.type) {
|
|
4681
|
+
itemCategory = String(meta.type);
|
|
4682
|
+
}
|
|
4636
4683
|
if (itemCategory === category) {
|
|
4637
4684
|
if (this.determineStockStatus(d)) {
|
|
4638
4685
|
inStock++;
|
|
@@ -4757,12 +4804,13 @@ DATA SHAPE per type:
|
|
|
4757
4804
|
- text: { "content": "<prose answer>" }
|
|
4758
4805
|
|
|
4759
4806
|
DECISION RULES (follow strictly):
|
|
4760
|
-
1.
|
|
4761
|
-
2.
|
|
4762
|
-
3.
|
|
4763
|
-
4.
|
|
4764
|
-
5.
|
|
4765
|
-
6.
|
|
4807
|
+
1. IF the user explicitly asks for a "pie chart", "bar chart", or "table", you MUST output that exact type. Do not deviate from their request.
|
|
4808
|
+
2. bar_chart \u2192 comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
|
|
4809
|
+
3. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
|
|
4810
|
+
4. pie_chart \u2192 proportional breakdown or percentage distribution
|
|
4811
|
+
5. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes.
|
|
4812
|
+
6. table \u2192 multi-field structured records where each item has \u2265 3 attributes
|
|
4813
|
+
7. text \u2192 conversational or free-form answers where no chart adds value
|
|
4766
4814
|
|
|
4767
4815
|
IMPORTANT:
|
|
4768
4816
|
- Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
|
|
@@ -5920,10 +5968,12 @@ var PostgreSQLProvider = class extends BaseVectorProvider {
|
|
|
5920
5968
|
embedding VECTOR(${this.dimensions})
|
|
5921
5969
|
)
|
|
5922
5970
|
`);
|
|
5971
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
5972
|
+
const indexOps = metric === "euclidean" ? "vector_l2_ops" : metric === "dotproduct" ? "vector_ip_ops" : "vector_cosine_ops";
|
|
5923
5973
|
await client.query(`
|
|
5924
5974
|
CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
|
|
5925
5975
|
ON ${this.tableName}
|
|
5926
|
-
USING hnsw (embedding
|
|
5976
|
+
USING hnsw (embedding ${indexOps})
|
|
5927
5977
|
`);
|
|
5928
5978
|
} finally {
|
|
5929
5979
|
client.release();
|
|
@@ -5995,11 +6045,14 @@ var PostgreSQLProvider = class extends BaseVectorProvider {
|
|
|
5995
6045
|
try {
|
|
5996
6046
|
const efSearch = this.config.options.efSearch || Math.max(topK * 10, 40);
|
|
5997
6047
|
await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
|
|
6048
|
+
const metric = this.config.distanceMetric || "cosine";
|
|
6049
|
+
const queryOp = metric === "euclidean" ? "<->" : metric === "dotproduct" ? "<#>" : "<=>";
|
|
6050
|
+
const scoreExpr = metric === "euclidean" ? `1 / (1 + (embedding <-> $1::vector))` : metric === "dotproduct" ? `(embedding <#> $1::vector) * -1` : `1 - (embedding <=> $1::vector)`;
|
|
5998
6051
|
const result = await client.query(
|
|
5999
|
-
`SELECT id, content, metadata,
|
|
6052
|
+
`SELECT id, content, metadata, ${scoreExpr} AS score
|
|
6000
6053
|
FROM ${this.tableName}
|
|
6001
6054
|
${whereClause}
|
|
6002
|
-
ORDER BY embedding
|
|
6055
|
+
ORDER BY embedding ${queryOp} $1::vector
|
|
6003
6056
|
LIMIT $2`,
|
|
6004
6057
|
params
|
|
6005
6058
|
);
|