@retrivora-ai/rag-engine 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,11 +34,6 @@ export class QueryProcessor {
34
34
  return /^(what|which|who|where|when|why|how|the|is|are|was|were|in|on|at|by|for|with|about|a|an|to|of)\b/i.test(value.trim());
35
35
  }
36
36
 
37
- private static readonly COMMON_METADATA_FIELDS = [
38
- 'category', 'type', 'brand', 'status', 'priority', 'id', 'name',
39
- 'email', 'phone', 'price', 'rating', 'color', 'size', 'material',
40
- 'sku', 'role', 'department', 'location', 'tag', 'label'
41
- ];
42
37
 
43
38
  /**
44
39
  * Scans a natural language question for potential metadata hints and keywords.
@@ -49,7 +44,6 @@ export class QueryProcessor {
49
44
  if (!question.trim()) return [];
50
45
 
51
46
  const hints = new Map<string, QueryFieldHint>();
52
- const fieldsToSearch = [...new Set([...this.COMMON_METADATA_FIELDS, ...validFields])];
53
47
 
54
48
  const addHint = (value: string, field?: string) => {
55
49
  const normalizedValue = this.normalizeHintValue(value);
@@ -98,21 +92,22 @@ export class QueryProcessor {
98
92
  }
99
93
 
100
94
  // 4. Dynamic Schema-driven patterns
101
- // We look for common metadata fields (and any user-defined ones) in natural language.
102
- if (fieldsToSearch.length > 0) {
103
- for (const field of fieldsToSearch) {
104
- // Pattern 1: "[field] is [value]", "[field]: [value]", "[field] of [value]"
105
- const forwardPattern = new RegExp(`\\b${field}\\s*(?:is|are|:|of)?\\s+["']?([^"\\n?.!,]{2,60})["']?(?=[?.!,]|$)`, 'gi');
106
- for (const match of question.matchAll(forwardPattern)) {
107
- const value = match[1];
108
- if (value && !this.isLikelyPromptPhrase(value)) {
109
- addHint(value, field);
110
- }
111
- }
95
+ // We look for any word-like key followed by a colon or equals.
96
+ const genericPattern = /\b([a-z][a-z0-9_]{1,30})\s*[:=]\s*["']?([^"'\n?.!,]{1,60})["']?/gi;
97
+ for (const match of question.matchAll(genericPattern)) {
98
+ const field = match[1];
99
+ const value = match[2];
100
+ if (field && !this.isLikelyPromptPhrase(field)) {
101
+ addHint(value, field);
102
+ }
103
+ }
112
104
 
113
- // Pattern 2: "[value] [field]" (e.g. "blue color", "electronics category")
114
- const reversePattern = new RegExp(`\\b([^"\\n?.!,\\s]{2,60})\\s+${field}\\b`, 'gi');
115
- for (const match of question.matchAll(reversePattern)) {
105
+ // 5. Explicitly requested fields (if any)
106
+ if (validFields.length > 0) {
107
+ for (const field of validFields) {
108
+ // More aggressive pattern for explicitly allowed fields: "[field] [value]"
109
+ const pattern = new RegExp(`\\b${field}\\s*(?:is|are|:|of)?\\s+["']?([^"\\n?.!,]{1,60})["']?(?=[?.!,]|$)`, 'gi');
110
+ for (const match of question.matchAll(pattern)) {
116
111
  const value = match[1];
117
112
  if (value && !this.isLikelyPromptPhrase(value)) {
118
113
  addHint(value, field);
@@ -139,6 +139,24 @@ export class MongoDBProvider extends BaseVectorProvider {
139
139
 
140
140
  async query(vector: number[], topK: number, namespace?: string, filter?: Record<string, unknown>): Promise<VectorMatch[]> {
141
141
  const publicFilter = this.sanitizeFilter(filter);
142
+
143
+ // Split filters: only 'namespace' is safe for the $vectorSearch 'filter' argument
144
+ // unless explicitly indexed. Moving others to a subsequent $match stage.
145
+ const vectorSearchFilter: Record<string, unknown> = {};
146
+ const matchFilter: Record<string, unknown> = {};
147
+
148
+ if (namespace) {
149
+ vectorSearchFilter.namespace = namespace;
150
+ }
151
+
152
+ for (const [key, value] of Object.entries(publicFilter)) {
153
+ if (key === 'namespace') {
154
+ vectorSearchFilter.namespace = value;
155
+ } else {
156
+ matchFilter[key] = value;
157
+ }
158
+ }
159
+
142
160
  const pipeline: Record<string, unknown>[] = [
143
161
  {
144
162
  $vectorSearch: {
@@ -146,12 +164,20 @@ export class MongoDBProvider extends BaseVectorProvider {
146
164
  path: this.embeddingKey,
147
165
  queryVector: vector,
148
166
  numCandidates: (this.config.options.numCandidates as number) || Math.max(topK * 20, 200),
149
- limit: topK,
150
- ...(Object.keys(publicFilter).length > 0 || namespace
151
- ? { filter: { ...publicFilter, ...(namespace ? { namespace } : {}) } }
152
- : {}),
167
+ limit: topK * 2, // Increase limit slightly to account for post-filtering
168
+ ...(Object.keys(vectorSearchFilter).length > 0 ? { filter: vectorSearchFilter } : {}),
153
169
  },
154
170
  },
171
+ ];
172
+
173
+ // Add post-filtering stage if there are additional filters
174
+ if (Object.keys(matchFilter).length > 0) {
175
+ pipeline.push({ $match: matchFilter });
176
+ }
177
+
178
+ // Final limit and projection
179
+ pipeline.push(
180
+ { $limit: topK },
155
181
  {
156
182
  $project: {
157
183
  _id: 1,
@@ -160,8 +186,8 @@ export class MongoDBProvider extends BaseVectorProvider {
160
186
  namespace: 1,
161
187
  score: { $meta: 'vectorSearchScore' },
162
188
  },
163
- },
164
- ];
189
+ }
190
+ );
165
191
 
166
192
  const results = await this.collection!.aggregate(pipeline).toArray();
167
193