@retrivora-ai/rag-engine 0.2.8 → 0.2.9

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.
@@ -1556,7 +1556,11 @@ var EmbeddingStrategyResolver = class {
1556
1556
  function normalizeHintValue(value) {
1557
1557
  return value.replace(/\s+/g, " ").trim();
1558
1558
  }
1559
+ function isLikelyPromptPhrase(value) {
1560
+ return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
1561
+ }
1559
1562
  function extractQueryFieldHints(question) {
1563
+ var _a, _b, _c;
1560
1564
  if (!question.trim()) return [];
1561
1565
  const hints = /* @__PURE__ */ new Map();
1562
1566
  const addHint = (value, field) => {
@@ -1573,6 +1577,17 @@ function extractQueryFieldHints(question) {
1573
1577
  for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
1574
1578
  addHint(match[1]);
1575
1579
  }
1580
+ const naturalQuestionPatterns = [
1581
+ /\b(?:what|which)\s+(?:is|are|was|were)\s+(?:the\s+)?([^?.!,]{1,60}?)\s+of\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
1582
+ /\b(?:who|what)\s+(?:is|are|was|were)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
1583
+ /\b(?:about|for|regarding)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi
1584
+ ];
1585
+ for (const pattern of naturalQuestionPatterns) {
1586
+ for (const match of question.matchAll(pattern)) {
1587
+ const value = (_a = match[2]) != null ? _a : match[1];
1588
+ if (value) addHint(value);
1589
+ }
1590
+ }
1576
1591
  const fieldPattern = `([^\\n:=?.!,]{1,60}?)`;
1577
1592
  const valuePattern = `([^\\n?.!,]{1,120}?)`;
1578
1593
  const fieldValuePatterns = [
@@ -1582,11 +1597,37 @@ function extractQueryFieldHints(question) {
1582
1597
  ];
1583
1598
  for (const pattern of fieldValuePatterns) {
1584
1599
  for (const match of question.matchAll(pattern)) {
1585
- addHint(match[2], match[1]);
1600
+ const field = normalizeHintValue((_b = match[1]) != null ? _b : "");
1601
+ const value = (_c = match[2]) != null ? _c : "";
1602
+ if (field && !isLikelyPromptPhrase(field)) {
1603
+ addHint(value, field);
1604
+ } else {
1605
+ addHint(value);
1606
+ }
1586
1607
  }
1587
1608
  }
1609
+ for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){1,3}\b/g)) {
1610
+ addHint(match[0]);
1611
+ }
1588
1612
  return [...hints.values()];
1589
1613
  }
1614
+ function buildQueryFilter(question, hints) {
1615
+ const filter = { metadata: {}, keywords: [], queryText: question };
1616
+ for (const hint of hints) {
1617
+ if (hint.field) {
1618
+ filter.metadata[hint.field] = hint.value;
1619
+ } else {
1620
+ filter.keywords.push(hint.value);
1621
+ }
1622
+ }
1623
+ for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
1624
+ const term = normalizeHintValue(match[0]);
1625
+ if (term && !filter.keywords.includes(term)) filter.keywords.push(term);
1626
+ }
1627
+ if (Object.keys(filter.metadata || {}).length === 0) delete filter.metadata;
1628
+ if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
1629
+ return filter;
1630
+ }
1590
1631
  var Pipeline = class {
1591
1632
  constructor(config) {
1592
1633
  this.initialised = false;
@@ -1675,10 +1716,9 @@ var Pipeline = class {
1675
1716
  try {
1676
1717
  const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
1677
1718
  const fieldHints = extractQueryFieldHints(question);
1678
- const rawMatches = await this.vectorDB.query(queryVector, topK, ns, {
1679
- __queryText: question,
1680
- __fieldHints: fieldHints
1681
- });
1719
+ const filter = buildQueryFilter(question, fieldHints);
1720
+ filter.__fieldHints = fieldHints;
1721
+ const rawMatches = await this.vectorDB.query(queryVector, topK, ns, filter);
1682
1722
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
1683
1723
  const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
1684
1724
  ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
@@ -2680,7 +2680,11 @@ var EmbeddingStrategyResolver = class {
2680
2680
  function normalizeHintValue(value) {
2681
2681
  return value.replace(/\s+/g, " ").trim();
2682
2682
  }
2683
+ function isLikelyPromptPhrase(value) {
2684
+ return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
2685
+ }
2683
2686
  function extractQueryFieldHints(question) {
2687
+ var _a, _b, _c;
2684
2688
  if (!question.trim()) return [];
2685
2689
  const hints = /* @__PURE__ */ new Map();
2686
2690
  const addHint = (value, field) => {
@@ -2697,6 +2701,17 @@ function extractQueryFieldHints(question) {
2697
2701
  for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
2698
2702
  addHint(match[1]);
2699
2703
  }
2704
+ const naturalQuestionPatterns = [
2705
+ /\b(?:what|which)\s+(?:is|are|was|were)\s+(?:the\s+)?([^?.!,]{1,60}?)\s+of\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
2706
+ /\b(?:who|what)\s+(?:is|are|was|were)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
2707
+ /\b(?:about|for|regarding)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi
2708
+ ];
2709
+ for (const pattern of naturalQuestionPatterns) {
2710
+ for (const match of question.matchAll(pattern)) {
2711
+ const value = (_a = match[2]) != null ? _a : match[1];
2712
+ if (value) addHint(value);
2713
+ }
2714
+ }
2700
2715
  const fieldPattern = `([^\\n:=?.!,]{1,60}?)`;
2701
2716
  const valuePattern = `([^\\n?.!,]{1,120}?)`;
2702
2717
  const fieldValuePatterns = [
@@ -2706,11 +2721,37 @@ function extractQueryFieldHints(question) {
2706
2721
  ];
2707
2722
  for (const pattern of fieldValuePatterns) {
2708
2723
  for (const match of question.matchAll(pattern)) {
2709
- addHint(match[2], match[1]);
2724
+ const field = normalizeHintValue((_b = match[1]) != null ? _b : "");
2725
+ const value = (_c = match[2]) != null ? _c : "";
2726
+ if (field && !isLikelyPromptPhrase(field)) {
2727
+ addHint(value, field);
2728
+ } else {
2729
+ addHint(value);
2730
+ }
2710
2731
  }
2711
2732
  }
2733
+ for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){1,3}\b/g)) {
2734
+ addHint(match[0]);
2735
+ }
2712
2736
  return [...hints.values()];
2713
2737
  }
2738
+ function buildQueryFilter(question, hints) {
2739
+ const filter = { metadata: {}, keywords: [], queryText: question };
2740
+ for (const hint of hints) {
2741
+ if (hint.field) {
2742
+ filter.metadata[hint.field] = hint.value;
2743
+ } else {
2744
+ filter.keywords.push(hint.value);
2745
+ }
2746
+ }
2747
+ for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
2748
+ const term = normalizeHintValue(match[0]);
2749
+ if (term && !filter.keywords.includes(term)) filter.keywords.push(term);
2750
+ }
2751
+ if (Object.keys(filter.metadata || {}).length === 0) delete filter.metadata;
2752
+ if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
2753
+ return filter;
2754
+ }
2714
2755
  var Pipeline = class {
2715
2756
  constructor(config) {
2716
2757
  this.initialised = false;
@@ -2799,10 +2840,9 @@ var Pipeline = class {
2799
2840
  try {
2800
2841
  const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
2801
2842
  const fieldHints = extractQueryFieldHints(question);
2802
- const rawMatches = await this.vectorDB.query(queryVector, topK, ns, {
2803
- __queryText: question,
2804
- __fieldHints: fieldHints
2805
- });
2843
+ const filter = buildQueryFilter(question, fieldHints);
2844
+ filter.__fieldHints = fieldHints;
2845
+ const rawMatches = await this.vectorDB.query(queryVector, topK, ns, filter);
2806
2846
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
2807
2847
  const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
2808
2848
  ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
@@ -3,7 +3,7 @@ import {
3
3
  createHealthHandler,
4
4
  createIngestHandler,
5
5
  createUploadHandler
6
- } from "../chunk-CYVPACA7.mjs";
6
+ } from "../chunk-5U2DHPIX.mjs";
7
7
  import "../chunk-EDLTMSNY.mjs";
8
8
  import "../chunk-FWCSY2DS.mjs";
9
9
  export {
package/dist/server.js CHANGED
@@ -2769,7 +2769,11 @@ var EmbeddingStrategyResolver = class {
2769
2769
  function normalizeHintValue(value) {
2770
2770
  return value.replace(/\s+/g, " ").trim();
2771
2771
  }
2772
+ function isLikelyPromptPhrase(value) {
2773
+ return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
2774
+ }
2772
2775
  function extractQueryFieldHints(question) {
2776
+ var _a, _b, _c;
2773
2777
  if (!question.trim()) return [];
2774
2778
  const hints = /* @__PURE__ */ new Map();
2775
2779
  const addHint = (value, field) => {
@@ -2786,6 +2790,17 @@ function extractQueryFieldHints(question) {
2786
2790
  for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
2787
2791
  addHint(match[1]);
2788
2792
  }
2793
+ const naturalQuestionPatterns = [
2794
+ /\b(?:what|which)\s+(?:is|are|was|were)\s+(?:the\s+)?([^?.!,]{1,60}?)\s+of\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
2795
+ /\b(?:who|what)\s+(?:is|are|was|were)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
2796
+ /\b(?:about|for|regarding)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi
2797
+ ];
2798
+ for (const pattern of naturalQuestionPatterns) {
2799
+ for (const match of question.matchAll(pattern)) {
2800
+ const value = (_a = match[2]) != null ? _a : match[1];
2801
+ if (value) addHint(value);
2802
+ }
2803
+ }
2789
2804
  const fieldPattern = `([^\\n:=?.!,]{1,60}?)`;
2790
2805
  const valuePattern = `([^\\n?.!,]{1,120}?)`;
2791
2806
  const fieldValuePatterns = [
@@ -2795,11 +2810,37 @@ function extractQueryFieldHints(question) {
2795
2810
  ];
2796
2811
  for (const pattern of fieldValuePatterns) {
2797
2812
  for (const match of question.matchAll(pattern)) {
2798
- addHint(match[2], match[1]);
2813
+ const field = normalizeHintValue((_b = match[1]) != null ? _b : "");
2814
+ const value = (_c = match[2]) != null ? _c : "";
2815
+ if (field && !isLikelyPromptPhrase(field)) {
2816
+ addHint(value, field);
2817
+ } else {
2818
+ addHint(value);
2819
+ }
2799
2820
  }
2800
2821
  }
2822
+ for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){1,3}\b/g)) {
2823
+ addHint(match[0]);
2824
+ }
2801
2825
  return [...hints.values()];
2802
2826
  }
2827
+ function buildQueryFilter(question, hints) {
2828
+ const filter = { metadata: {}, keywords: [], queryText: question };
2829
+ for (const hint of hints) {
2830
+ if (hint.field) {
2831
+ filter.metadata[hint.field] = hint.value;
2832
+ } else {
2833
+ filter.keywords.push(hint.value);
2834
+ }
2835
+ }
2836
+ for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
2837
+ const term = normalizeHintValue(match[0]);
2838
+ if (term && !filter.keywords.includes(term)) filter.keywords.push(term);
2839
+ }
2840
+ if (Object.keys(filter.metadata || {}).length === 0) delete filter.metadata;
2841
+ if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
2842
+ return filter;
2843
+ }
2803
2844
  var Pipeline = class {
2804
2845
  constructor(config) {
2805
2846
  this.initialised = false;
@@ -2888,10 +2929,9 @@ var Pipeline = class {
2888
2929
  try {
2889
2930
  const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
2890
2931
  const fieldHints = extractQueryFieldHints(question);
2891
- const rawMatches = await this.vectorDB.query(queryVector, topK, ns, {
2892
- __queryText: question,
2893
- __fieldHints: fieldHints
2894
- });
2932
+ const filter = buildQueryFilter(question, fieldHints);
2933
+ filter.__fieldHints = fieldHints;
2934
+ const rawMatches = await this.vectorDB.query(queryVector, topK, ns, filter);
2895
2935
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
2896
2936
  const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
2897
2937
  ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
package/dist/server.mjs CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  createIngestHandler,
35
35
  createUploadHandler,
36
36
  getRagConfig
37
- } from "./chunk-CYVPACA7.mjs";
37
+ } from "./chunk-5U2DHPIX.mjs";
38
38
  import "./chunk-EDLTMSNY.mjs";
39
39
  import {
40
40
  PineconeProvider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "MIT",
@@ -12,10 +12,20 @@ interface QueryFieldHint {
12
12
  value: string;
13
13
  }
14
14
 
15
+ interface QueryFilter {
16
+ metadata?: Record<string, string>;
17
+ keywords?: string[];
18
+ queryText?: string;
19
+ }
20
+
15
21
  function normalizeHintValue(value: string): string {
16
22
  return value.replace(/\s+/g, ' ').trim();
17
23
  }
18
24
 
25
+ function isLikelyPromptPhrase(value: string): boolean {
26
+ return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
27
+ }
28
+
19
29
  function extractQueryFieldHints(question: string): QueryFieldHint[] {
20
30
  if (!question.trim()) return [];
21
31
 
@@ -45,6 +55,19 @@ function extractQueryFieldHints(question: string): QueryFieldHint[] {
45
55
  addHint(match[1]);
46
56
  }
47
57
 
58
+ const naturalQuestionPatterns = [
59
+ /\b(?:what|which)\s+(?:is|are|was|were)\s+(?:the\s+)?([^?.!,]{1,60}?)\s+of\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
60
+ /\b(?:who|what)\s+(?:is|are|was|were)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
61
+ /\b(?:about|for|regarding)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
62
+ ];
63
+
64
+ for (const pattern of naturalQuestionPatterns) {
65
+ for (const match of question.matchAll(pattern)) {
66
+ const value = match[2] ?? match[1];
67
+ if (value) addHint(value);
68
+ }
69
+ }
70
+
48
71
  const fieldPattern = `([^\\n:=?.!,]{1,60}?)`;
49
72
  const valuePattern = `([^\\n?.!,]{1,120}?)`;
50
73
  const fieldValuePatterns = [
@@ -55,13 +78,50 @@ function extractQueryFieldHints(question: string): QueryFieldHint[] {
55
78
 
56
79
  for (const pattern of fieldValuePatterns) {
57
80
  for (const match of question.matchAll(pattern)) {
58
- addHint(match[2], match[1]);
81
+ const field = normalizeHintValue(match[1] ?? '');
82
+ const value = match[2] ?? '';
83
+
84
+ if (field && !isLikelyPromptPhrase(field)) {
85
+ addHint(value, field);
86
+ } else {
87
+ addHint(value);
88
+ }
59
89
  }
60
90
  }
61
91
 
92
+ for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){1,3}\b/g)) {
93
+ addHint(match[0]);
94
+ }
95
+
62
96
  return [...hints.values()];
63
97
  }
64
98
 
99
+ function buildQueryFilter(question: string, hints: QueryFieldHint[]): QueryFilter {
100
+ const filter: QueryFilter = { metadata: {}, keywords: [], queryText: question };
101
+
102
+ for (const hint of hints) {
103
+ if (hint.field) {
104
+ // prefer last-seen value for a field; providers may interpret filters differently
105
+ filter.metadata![hint.field] = hint.value;
106
+ } else {
107
+ // treat as keyword / named entity
108
+ filter.keywords!.push(hint.value);
109
+ }
110
+ }
111
+
112
+ // Also extract quoted phrases (if not already captured) as keywords
113
+ for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
114
+ const term = normalizeHintValue(match[0]);
115
+ if (term && !filter.keywords!.includes(term)) filter.keywords!.push(term);
116
+ }
117
+
118
+ // Remove empty metadata object when no fields present to keep filter minimal
119
+ if (Object.keys(filter.metadata || {}).length === 0) delete filter.metadata;
120
+ if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
121
+
122
+ return filter;
123
+ }
124
+
65
125
  /**
66
126
  * Pipeline — orchestrates the RAG flow: Embed → Search → Augment → Generate.
67
127
  *
@@ -191,11 +251,15 @@ export class Pipeline {
191
251
  try {
192
252
  const queryVector = await this.embeddingProvider.embed(question, { taskType: 'query' });
193
253
  const fieldHints = extractQueryFieldHints(question);
194
- // Pass the original text to the vector DB so it can perform hybrid (keyword) search if supported
195
- const rawMatches = await this.vectorDB.query(queryVector, topK, ns, {
196
- __queryText: question,
197
- __fieldHints: fieldHints,
198
- });
254
+ // Build a provider-agnostic filter from hints + original query text. Providers
255
+ // may choose to use `queryText` for hybrid keyword matching and `metadata`
256
+ // for exact/filtered metadata queries.
257
+ const filter = buildQueryFilter(question, fieldHints) as Record<string, unknown>;
258
+
259
+ // Keep the raw hints available as well for providers that want richer parsing
260
+ filter.__fieldHints = fieldHints;
261
+
262
+ const rawMatches = await this.vectorDB.query(queryVector, topK, ns, filter);
199
263
 
200
264
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
201
265
  const context = sources.length