@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/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 vector_cosine_ops)
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 vector_cosine_ops)
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
- (1 - (embedding <=> $1::vector)) AS vector_score,
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
- ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
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
- (1 - (embedding <=> $1::vector)) AS hybrid_score
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: "Cosine"
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 categories = this.detectCategories(filteredData);
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
- if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
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, categories)) {
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
- const categories = this.detectCategories(data);
4283
- const categoryData = this.aggregateByCategory(data, categories);
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: "Distribution by Category",
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 shouldShowCategoryChart(query, categories) {
4519
- if (categories.length < 2) {
4520
- return false;
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: Detect categories in data
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.category) {
4617
- categories.add(String(meta.category));
4618
- }
4619
- if (meta.type) {
4620
- categories.add(String(meta.type));
4621
- }
4622
- if (meta.tag) {
4623
- const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4624
- tags.forEach((t) => categories.add(String(t)));
4625
- }
4626
- const contentCategories = Array.from(new Set(
4627
- Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm)).map((match) => match[1].trim()).filter(Boolean)
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 by category
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
- const itemCategory = meta.category || meta.type || "Other";
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 by category
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
- const itemCategory = meta.category || meta.type || "Other";
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. 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.
4830
- 2. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
4831
- 3. pie_chart \u2192 proportional breakdown or percentage distribution
4832
- 4. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes (e.g. comparing features, ratings, or characteristics of 2 specific products). If the user asks to "compare" products and the data contains multiple dimensions or ratings for them, you MUST use this.
4833
- 5. table \u2192 multi-field structured records where each item has \u2265 3 attributes
4834
- 6. text \u2192 conversational or free-form answers where no chart adds value
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 vector_cosine_ops)
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, 1 - (embedding <=> $1::vector) AS score
6121
+ `SELECT id, content, metadata, ${scoreExpr} AS score
6069
6122
  FROM ${this.tableName}
6070
6123
  ${whereClause}
6071
- ORDER BY embedding <=> $1::vector
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 vector_cosine_ops)
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 vector_cosine_ops)
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
- (1 - (embedding <=> $1::vector)) AS vector_score,
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
- ((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
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
- (1 - (embedding <=> $1::vector)) AS hybrid_score
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: "Cosine"
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 categories = this.detectCategories(filteredData);
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
- if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
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, categories)) {
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
- const categories = this.detectCategories(data);
4214
- const categoryData = this.aggregateByCategory(data, categories);
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: "Distribution by Category",
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 shouldShowCategoryChart(query, categories) {
4450
- if (categories.length < 2) {
4451
- return false;
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: Detect categories in data
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.category) {
4548
- categories.add(String(meta.category));
4549
- }
4550
- if (meta.type) {
4551
- categories.add(String(meta.type));
4552
- }
4553
- if (meta.tag) {
4554
- const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
4555
- tags.forEach((t) => categories.add(String(t)));
4556
- }
4557
- const contentCategories = Array.from(new Set(
4558
- Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm)).map((match) => match[1].trim()).filter(Boolean)
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 by category
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
- const itemCategory = meta.category || meta.type || "Other";
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 by category
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
- const itemCategory = meta.category || meta.type || "Other";
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. 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.
4761
- 2. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
4762
- 3. pie_chart \u2192 proportional breakdown or percentage distribution
4763
- 4. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes (e.g. comparing features, ratings, or characteristics of 2 specific products). If the user asks to "compare" products and the data contains multiple dimensions or ratings for them, you MUST use this.
4764
- 5. table \u2192 multi-field structured records where each item has \u2265 3 attributes
4765
- 6. text \u2192 conversational or free-form answers where no chart adds value
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 vector_cosine_ops)
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, 1 - (embedding <=> $1::vector) AS score
6052
+ `SELECT id, content, metadata, ${scoreExpr} AS score
6000
6053
  FROM ${this.tableName}
6001
6054
  ${whereClause}
6002
- ORDER BY embedding <=> $1::vector
6055
+ ORDER BY embedding ${queryOp} $1::vector
6003
6056
  LIMIT $2`,
6004
6057
  params
6005
6058
  );