@retrivora-ai/rag-engine 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MongoDBProvider
3
- } from "./chunk-73I6VWU3.mjs";
3
+ } from "./chunk-5AJ4XHLW.mjs";
4
4
  import "./chunk-IMP6FUCY.mjs";
5
5
  import "./chunk-X4TOT24V.mjs";
6
6
  export {
@@ -244,6 +244,10 @@ interface RAGConfig {
244
244
  useGraphRetrieval?: boolean;
245
245
  /** Whether to perform reranking on retrieved results */
246
246
  useReranking?: boolean;
247
+ /** List of metadata fields that are valid for filtering.
248
+ * Used to dynamically extract hints from natural language queries.
249
+ */
250
+ filterableFields?: string[];
247
251
  }
248
252
  interface RagConfig {
249
253
  /**
@@ -244,6 +244,10 @@ interface RAGConfig {
244
244
  useGraphRetrieval?: boolean;
245
245
  /** Whether to perform reranking on retrieved results */
246
246
  useReranking?: boolean;
247
+ /** List of metadata fields that are valid for filtering.
248
+ * Used to dynamically extract hints from natural language queries.
249
+ */
250
+ filterableFields?: string[];
247
251
  }
248
252
  interface RagConfig {
249
253
  /**
@@ -1098,7 +1098,7 @@ var ProviderRegistry = class {
1098
1098
  return PostgreSQLProvider;
1099
1099
  }
1100
1100
  case "mongodb": {
1101
- const { MongoDBProvider } = await import("./MongoDBProvider-BO2Y5DRR.mjs");
1101
+ const { MongoDBProvider } = await import("./MongoDBProvider-YNKC7EJ6.mjs");
1102
1102
  return MongoDBProvider;
1103
1103
  }
1104
1104
  case "milvus": {
@@ -1904,12 +1904,14 @@ var QueryProcessor = class {
1904
1904
  * Checks if a string is likely a question word or common prompt phrase.
1905
1905
  */
1906
1906
  static isLikelyPromptPhrase(value) {
1907
- return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
1907
+ 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());
1908
1908
  }
1909
1909
  /**
1910
1910
  * Scans a natural language question for potential metadata hints and keywords.
1911
+ * @param question The user's query
1912
+ * @param validFields Optional list of known filterable fields to look for
1911
1913
  */
1912
- static extractQueryFieldHints(question) {
1914
+ static extractQueryFieldHints(question, validFields = []) {
1913
1915
  var _a, _b, _c, _d;
1914
1916
  if (!question.trim()) return [];
1915
1917
  const hints = /* @__PURE__ */ new Map();
@@ -1943,11 +1945,29 @@ var QueryProcessor = class {
1943
1945
  if (name) addHint(name, "name");
1944
1946
  }
1945
1947
  }
1948
+ const genericPattern = /\b([a-z][a-z0-9_]{1,30})\s*[:=]\s*["']?([^"'\n?.!,]{1,60})["']?/gi;
1949
+ for (const match of question.matchAll(genericPattern)) {
1950
+ const field = match[1];
1951
+ const value = match[2];
1952
+ if (field && !this.isLikelyPromptPhrase(field)) {
1953
+ addHint(value, field);
1954
+ }
1955
+ }
1956
+ if (validFields.length > 0) {
1957
+ for (const field of validFields) {
1958
+ const pattern = new RegExp(`\\b${field}\\s*(?:is|are|:|of)?\\s+["']?([^"\\n?.!,]{1,60})["']?(?=[?.!,]|$)`, "gi");
1959
+ for (const match of question.matchAll(pattern)) {
1960
+ const value = match[1];
1961
+ if (value && !this.isLikelyPromptPhrase(value)) {
1962
+ addHint(value, field);
1963
+ }
1964
+ }
1965
+ }
1966
+ }
1946
1967
  const universalPatterns = [
1947
1968
  { regex: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi, field: "email", group: 1 },
1948
1969
  { regex: /(\+?\d[\d\-\.\s\(\)]{6,}\d)/g, field: "phone", group: 1 },
1949
1970
  { regex: /(\b\d{4}-\d{2}-\d{2}\b|\b\d{1,2}\/\d{1,2}\/\d{2,4}\b)/g, field: "date", group: 1 },
1950
- { regex: /(\$\s?\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g, field: "amount", group: 1 },
1951
1971
  { regex: /\b(ID|id|identifier)[: ]\s*([A-Za-z0-9\-]{3,})\b/gi, field: "id", group: 2 },
1952
1972
  { regex: /"([^"]{2,120})"/g, group: 1 },
1953
1973
  { regex: /'([^']{2,120})'/g, group: 1 }
@@ -2241,7 +2261,7 @@ var Pipeline = class {
2241
2261
  */
2242
2262
  askStream(_0) {
2243
2263
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
2244
- var _a, _b, _c, _d, _e, _f;
2264
+ var _a, _b, _c, _d, _e, _f, _g;
2245
2265
  yield new __await(this.initialize());
2246
2266
  const ns = namespace != null ? namespace : this.config.projectId;
2247
2267
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
@@ -2251,7 +2271,7 @@ var Pipeline = class {
2251
2271
  if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
2252
2272
  searchQuery = yield new __await(this.rewriteQuery(question, history));
2253
2273
  }
2254
- const hints = QueryProcessor.extractQueryFieldHints(question);
2274
+ const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
2255
2275
  const filter = QueryProcessor.buildQueryFilter(question, hints);
2256
2276
  const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
2257
2277
  namespace: ns,
@@ -2259,7 +2279,7 @@ var Pipeline = class {
2259
2279
  filter
2260
2280
  }));
2261
2281
  let sources = rawSources.filter((m) => m.score >= scoreThreshold);
2262
- if ((_f = this.config.rag) == null ? void 0 : _f.useReranking) {
2282
+ if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
2263
2283
  sources = yield new __await(this.reranker.rerank(sources, question, topK));
2264
2284
  } else {
2265
2285
  sources = sources.slice(0, topK);
@@ -118,6 +118,18 @@ var MongoDBProvider = class extends BaseVectorProvider {
118
118
  }
119
119
  async query(vector, topK, namespace, filter) {
120
120
  const publicFilter = this.sanitizeFilter(filter);
121
+ const vectorSearchFilter = {};
122
+ const matchFilter = {};
123
+ if (namespace) {
124
+ vectorSearchFilter.namespace = namespace;
125
+ }
126
+ for (const [key, value] of Object.entries(publicFilter)) {
127
+ if (key === "namespace") {
128
+ vectorSearchFilter.namespace = value;
129
+ } else {
130
+ matchFilter[key] = value;
131
+ }
132
+ }
121
133
  const pipeline = [
122
134
  {
123
135
  $vectorSearch: __spreadValues({
@@ -125,9 +137,15 @@ var MongoDBProvider = class extends BaseVectorProvider {
125
137
  path: this.embeddingKey,
126
138
  queryVector: vector,
127
139
  numCandidates: this.config.options.numCandidates || Math.max(topK * 20, 200),
128
- limit: topK
129
- }, Object.keys(publicFilter).length > 0 || namespace ? { filter: __spreadValues(__spreadValues({}, publicFilter), namespace ? { namespace } : {}) } : {})
130
- },
140
+ limit: topK * 2
141
+ }, Object.keys(vectorSearchFilter).length > 0 ? { filter: vectorSearchFilter } : {})
142
+ }
143
+ ];
144
+ if (Object.keys(matchFilter).length > 0) {
145
+ pipeline.push({ $match: matchFilter });
146
+ }
147
+ pipeline.push(
148
+ { $limit: topK },
131
149
  {
132
150
  $project: {
133
151
  _id: 1,
@@ -137,7 +155,7 @@ var MongoDBProvider = class extends BaseVectorProvider {
137
155
  score: { $meta: "vectorSearchScore" }
138
156
  }
139
157
  }
140
- ];
158
+ );
141
159
  const results = await this.collection.aggregate(pipeline).toArray();
142
160
  return results.map((res) => ({
143
161
  id: res._id,
@@ -1,3 +1,3 @@
1
- import '../RagConfig-BEmz4hVP.mjs';
2
- export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-DFSy0CG6.mjs';
1
+ import '../RagConfig-BjC6zSTV.mjs';
2
+ export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-CU_fQq__.mjs';
3
3
  import 'next/server';
@@ -1,3 +1,3 @@
1
- import '../RagConfig-BEmz4hVP.js';
2
- export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-CSfaCeux.js';
1
+ import '../RagConfig-BjC6zSTV.js';
2
+ export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-C3bLmWcR.js';
3
3
  import 'next/server';
@@ -650,6 +650,18 @@ var init_MongoDBProvider = __esm({
650
650
  }
651
651
  async query(vector, topK, namespace, filter) {
652
652
  const publicFilter = this.sanitizeFilter(filter);
653
+ const vectorSearchFilter = {};
654
+ const matchFilter = {};
655
+ if (namespace) {
656
+ vectorSearchFilter.namespace = namespace;
657
+ }
658
+ for (const [key, value] of Object.entries(publicFilter)) {
659
+ if (key === "namespace") {
660
+ vectorSearchFilter.namespace = value;
661
+ } else {
662
+ matchFilter[key] = value;
663
+ }
664
+ }
653
665
  const pipeline = [
654
666
  {
655
667
  $vectorSearch: __spreadValues({
@@ -657,9 +669,15 @@ var init_MongoDBProvider = __esm({
657
669
  path: this.embeddingKey,
658
670
  queryVector: vector,
659
671
  numCandidates: this.config.options.numCandidates || Math.max(topK * 20, 200),
660
- limit: topK
661
- }, Object.keys(publicFilter).length > 0 || namespace ? { filter: __spreadValues(__spreadValues({}, publicFilter), namespace ? { namespace } : {}) } : {})
662
- },
672
+ limit: topK * 2
673
+ }, Object.keys(vectorSearchFilter).length > 0 ? { filter: vectorSearchFilter } : {})
674
+ }
675
+ ];
676
+ if (Object.keys(matchFilter).length > 0) {
677
+ pipeline.push({ $match: matchFilter });
678
+ }
679
+ pipeline.push(
680
+ { $limit: topK },
663
681
  {
664
682
  $project: {
665
683
  _id: 1,
@@ -669,7 +687,7 @@ var init_MongoDBProvider = __esm({
669
687
  score: { $meta: "vectorSearchScore" }
670
688
  }
671
689
  }
672
- ];
690
+ );
673
691
  const results = await this.collection.aggregate(pipeline).toArray();
674
692
  return results.map((res) => ({
675
693
  id: res._id,
@@ -3362,12 +3380,14 @@ var QueryProcessor = class {
3362
3380
  * Checks if a string is likely a question word or common prompt phrase.
3363
3381
  */
3364
3382
  static isLikelyPromptPhrase(value) {
3365
- return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
3383
+ 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());
3366
3384
  }
3367
3385
  /**
3368
3386
  * Scans a natural language question for potential metadata hints and keywords.
3387
+ * @param question The user's query
3388
+ * @param validFields Optional list of known filterable fields to look for
3369
3389
  */
3370
- static extractQueryFieldHints(question) {
3390
+ static extractQueryFieldHints(question, validFields = []) {
3371
3391
  var _a, _b, _c, _d;
3372
3392
  if (!question.trim()) return [];
3373
3393
  const hints = /* @__PURE__ */ new Map();
@@ -3401,11 +3421,29 @@ var QueryProcessor = class {
3401
3421
  if (name) addHint(name, "name");
3402
3422
  }
3403
3423
  }
3424
+ const genericPattern = /\b([a-z][a-z0-9_]{1,30})\s*[:=]\s*["']?([^"'\n?.!,]{1,60})["']?/gi;
3425
+ for (const match of question.matchAll(genericPattern)) {
3426
+ const field = match[1];
3427
+ const value = match[2];
3428
+ if (field && !this.isLikelyPromptPhrase(field)) {
3429
+ addHint(value, field);
3430
+ }
3431
+ }
3432
+ if (validFields.length > 0) {
3433
+ for (const field of validFields) {
3434
+ const pattern = new RegExp(`\\b${field}\\s*(?:is|are|:|of)?\\s+["']?([^"\\n?.!,]{1,60})["']?(?=[?.!,]|$)`, "gi");
3435
+ for (const match of question.matchAll(pattern)) {
3436
+ const value = match[1];
3437
+ if (value && !this.isLikelyPromptPhrase(value)) {
3438
+ addHint(value, field);
3439
+ }
3440
+ }
3441
+ }
3442
+ }
3404
3443
  const universalPatterns = [
3405
3444
  { regex: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi, field: "email", group: 1 },
3406
3445
  { regex: /(\+?\d[\d\-\.\s\(\)]{6,}\d)/g, field: "phone", group: 1 },
3407
3446
  { regex: /(\b\d{4}-\d{2}-\d{2}\b|\b\d{1,2}\/\d{1,2}\/\d{2,4}\b)/g, field: "date", group: 1 },
3408
- { regex: /(\$\s?\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g, field: "amount", group: 1 },
3409
3447
  { regex: /\b(ID|id|identifier)[: ]\s*([A-Za-z0-9\-]{3,})\b/gi, field: "id", group: 2 },
3410
3448
  { regex: /"([^"]{2,120})"/g, group: 1 },
3411
3449
  { regex: /'([^']{2,120})'/g, group: 1 }
@@ -3699,7 +3737,7 @@ var Pipeline = class {
3699
3737
  */
3700
3738
  askStream(_0) {
3701
3739
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
3702
- var _a, _b, _c, _d, _e, _f;
3740
+ var _a, _b, _c, _d, _e, _f, _g;
3703
3741
  yield new __await(this.initialize());
3704
3742
  const ns = namespace != null ? namespace : this.config.projectId;
3705
3743
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
@@ -3709,7 +3747,7 @@ var Pipeline = class {
3709
3747
  if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
3710
3748
  searchQuery = yield new __await(this.rewriteQuery(question, history));
3711
3749
  }
3712
- const hints = QueryProcessor.extractQueryFieldHints(question);
3750
+ const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
3713
3751
  const filter = QueryProcessor.buildQueryFilter(question, hints);
3714
3752
  const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
3715
3753
  namespace: ns,
@@ -3717,7 +3755,7 @@ var Pipeline = class {
3717
3755
  filter
3718
3756
  }));
3719
3757
  let sources = rawSources.filter((m) => m.score >= scoreThreshold);
3720
- if ((_f = this.config.rag) == null ? void 0 : _f.useReranking) {
3758
+ if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
3721
3759
  sources = yield new __await(this.reranker.rerank(sources, question, topK));
3722
3760
  } else {
3723
3761
  sources = sources.slice(0, topK);
@@ -8,7 +8,7 @@ import {
8
8
  sseFrame,
9
9
  sseMetaFrame,
10
10
  sseTextFrame
11
- } from "../chunk-MWL4AGQI.mjs";
11
+ } from "../chunk-3GKA3PJF.mjs";
12
12
  import "../chunk-YLTMFW4M.mjs";
13
13
  import "../chunk-X4TOT24V.mjs";
14
14
  export {
@@ -1,4 +1,4 @@
1
- import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-BEmz4hVP.js';
1
+ import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-BjC6zSTV.js';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  interface ValidationError {
@@ -1,4 +1,4 @@
1
- import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-BEmz4hVP.mjs';
1
+ import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-BjC6zSTV.mjs';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  interface ValidationError {
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React$1, { ReactNode } from 'react';
2
- import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-BEmz4hVP.mjs';
3
- export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-BEmz4hVP.mjs';
2
+ import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-BjC6zSTV.mjs';
3
+ export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-BjC6zSTV.mjs';
4
4
  export { C as Chunk, a as ChunkOptions } from './DocumentChunker-Dh9TvmGG.mjs';
5
5
 
6
6
  interface ChatWidgetProps {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React$1, { ReactNode } from 'react';
2
- import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-BEmz4hVP.js';
3
- export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-BEmz4hVP.js';
2
+ import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-BjC6zSTV.js';
3
+ export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-BjC6zSTV.js';
4
4
  export { C as Chunk, a as ChunkOptions } from './DocumentChunker-Dh9TvmGG.js';
5
5
 
6
6
  interface ChatWidgetProps {
package/dist/server.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-BEmz4hVP.mjs';
1
+ import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-BjC6zSTV.mjs';
2
2
  export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-Dh9TvmGG.mjs';
3
- import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-DFSy0CG6.mjs';
4
- export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-DFSy0CG6.mjs';
3
+ import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-CU_fQq__.mjs';
4
+ export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-CU_fQq__.mjs';
5
5
  import 'next/server';
6
6
 
7
7
  /**
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-BEmz4hVP.js';
1
+ import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-BjC6zSTV.js';
2
2
  export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-Dh9TvmGG.js';
3
- import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-CSfaCeux.js';
4
- export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-CSfaCeux.js';
3
+ import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-C3bLmWcR.js';
4
+ export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-C3bLmWcR.js';
5
5
  import 'next/server';
6
6
 
7
7
  /**
package/dist/server.js CHANGED
@@ -662,6 +662,18 @@ var init_MongoDBProvider = __esm({
662
662
  }
663
663
  async query(vector, topK, namespace, filter) {
664
664
  const publicFilter = this.sanitizeFilter(filter);
665
+ const vectorSearchFilter = {};
666
+ const matchFilter = {};
667
+ if (namespace) {
668
+ vectorSearchFilter.namespace = namespace;
669
+ }
670
+ for (const [key, value] of Object.entries(publicFilter)) {
671
+ if (key === "namespace") {
672
+ vectorSearchFilter.namespace = value;
673
+ } else {
674
+ matchFilter[key] = value;
675
+ }
676
+ }
665
677
  const pipeline = [
666
678
  {
667
679
  $vectorSearch: __spreadValues({
@@ -669,9 +681,15 @@ var init_MongoDBProvider = __esm({
669
681
  path: this.embeddingKey,
670
682
  queryVector: vector,
671
683
  numCandidates: this.config.options.numCandidates || Math.max(topK * 20, 200),
672
- limit: topK
673
- }, Object.keys(publicFilter).length > 0 || namespace ? { filter: __spreadValues(__spreadValues({}, publicFilter), namespace ? { namespace } : {}) } : {})
674
- },
684
+ limit: topK * 2
685
+ }, Object.keys(vectorSearchFilter).length > 0 ? { filter: vectorSearchFilter } : {})
686
+ }
687
+ ];
688
+ if (Object.keys(matchFilter).length > 0) {
689
+ pipeline.push({ $match: matchFilter });
690
+ }
691
+ pipeline.push(
692
+ { $limit: topK },
675
693
  {
676
694
  $project: {
677
695
  _id: 1,
@@ -681,7 +699,7 @@ var init_MongoDBProvider = __esm({
681
699
  score: { $meta: "vectorSearchScore" }
682
700
  }
683
701
  }
684
- ];
702
+ );
685
703
  const results = await this.collection.aggregate(pipeline).toArray();
686
704
  return results.map((res) => ({
687
705
  id: res._id,
@@ -3455,12 +3473,14 @@ var QueryProcessor = class {
3455
3473
  * Checks if a string is likely a question word or common prompt phrase.
3456
3474
  */
3457
3475
  static isLikelyPromptPhrase(value) {
3458
- return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
3476
+ 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());
3459
3477
  }
3460
3478
  /**
3461
3479
  * Scans a natural language question for potential metadata hints and keywords.
3480
+ * @param question The user's query
3481
+ * @param validFields Optional list of known filterable fields to look for
3462
3482
  */
3463
- static extractQueryFieldHints(question) {
3483
+ static extractQueryFieldHints(question, validFields = []) {
3464
3484
  var _a, _b, _c, _d;
3465
3485
  if (!question.trim()) return [];
3466
3486
  const hints = /* @__PURE__ */ new Map();
@@ -3494,11 +3514,29 @@ var QueryProcessor = class {
3494
3514
  if (name) addHint(name, "name");
3495
3515
  }
3496
3516
  }
3517
+ const genericPattern = /\b([a-z][a-z0-9_]{1,30})\s*[:=]\s*["']?([^"'\n?.!,]{1,60})["']?/gi;
3518
+ for (const match of question.matchAll(genericPattern)) {
3519
+ const field = match[1];
3520
+ const value = match[2];
3521
+ if (field && !this.isLikelyPromptPhrase(field)) {
3522
+ addHint(value, field);
3523
+ }
3524
+ }
3525
+ if (validFields.length > 0) {
3526
+ for (const field of validFields) {
3527
+ const pattern = new RegExp(`\\b${field}\\s*(?:is|are|:|of)?\\s+["']?([^"\\n?.!,]{1,60})["']?(?=[?.!,]|$)`, "gi");
3528
+ for (const match of question.matchAll(pattern)) {
3529
+ const value = match[1];
3530
+ if (value && !this.isLikelyPromptPhrase(value)) {
3531
+ addHint(value, field);
3532
+ }
3533
+ }
3534
+ }
3535
+ }
3497
3536
  const universalPatterns = [
3498
3537
  { regex: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi, field: "email", group: 1 },
3499
3538
  { regex: /(\+?\d[\d\-\.\s\(\)]{6,}\d)/g, field: "phone", group: 1 },
3500
3539
  { regex: /(\b\d{4}-\d{2}-\d{2}\b|\b\d{1,2}\/\d{1,2}\/\d{2,4}\b)/g, field: "date", group: 1 },
3501
- { regex: /(\$\s?\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g, field: "amount", group: 1 },
3502
3540
  { regex: /\b(ID|id|identifier)[: ]\s*([A-Za-z0-9\-]{3,})\b/gi, field: "id", group: 2 },
3503
3541
  { regex: /"([^"]{2,120})"/g, group: 1 },
3504
3542
  { regex: /'([^']{2,120})'/g, group: 1 }
@@ -3792,7 +3830,7 @@ var Pipeline = class {
3792
3830
  */
3793
3831
  askStream(_0) {
3794
3832
  return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
3795
- var _a, _b, _c, _d, _e, _f;
3833
+ var _a, _b, _c, _d, _e, _f, _g;
3796
3834
  yield new __await(this.initialize());
3797
3835
  const ns = namespace != null ? namespace : this.config.projectId;
3798
3836
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
@@ -3802,7 +3840,7 @@ var Pipeline = class {
3802
3840
  if ((_e = this.config.rag) == null ? void 0 : _e.useQueryTransformation) {
3803
3841
  searchQuery = yield new __await(this.rewriteQuery(question, history));
3804
3842
  }
3805
- const hints = QueryProcessor.extractQueryFieldHints(question);
3843
+ const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
3806
3844
  const filter = QueryProcessor.buildQueryFilter(question, hints);
3807
3845
  const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
3808
3846
  namespace: ns,
@@ -3810,7 +3848,7 @@ var Pipeline = class {
3810
3848
  filter
3811
3849
  }));
3812
3850
  let sources = rawSources.filter((m) => m.score >= scoreThreshold);
3813
- if ((_f = this.config.rag) == null ? void 0 : _f.useReranking) {
3851
+ if ((_g = this.config.rag) == null ? void 0 : _g.useReranking) {
3814
3852
  sources = yield new __await(this.reranker.rerank(sources, question, topK));
3815
3853
  } else {
3816
3854
  sources = sources.slice(0, topK);
package/dist/server.mjs CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  sseFrame,
41
41
  sseMetaFrame,
42
42
  sseTextFrame
43
- } from "./chunk-MWL4AGQI.mjs";
43
+ } from "./chunk-3GKA3PJF.mjs";
44
44
  import "./chunk-YLTMFW4M.mjs";
45
45
  import {
46
46
  PineconeProvider
@@ -50,7 +50,7 @@ import {
50
50
  } from "./chunk-FLOSGE6A.mjs";
51
51
  import {
52
52
  MongoDBProvider
53
- } from "./chunk-73I6VWU3.mjs";
53
+ } from "./chunk-5AJ4XHLW.mjs";
54
54
  import {
55
55
  MilvusProvider
56
56
  } from "./chunk-U55XRW3U.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
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",
@@ -174,6 +174,10 @@ export interface RAGConfig {
174
174
  useGraphRetrieval?: boolean;
175
175
  /** Whether to perform reranking on retrieved results */
176
176
  useReranking?: boolean;
177
+ /** List of metadata fields that are valid for filtering.
178
+ * Used to dynamically extract hints from natural language queries.
179
+ */
180
+ filterableFields?: string[];
177
181
  }
178
182
 
179
183
  // ---------------------------------------------------------------------------
@@ -315,7 +315,7 @@ export class Pipeline {
315
315
  }
316
316
 
317
317
  // 2. Parallel Retrieval
318
- const hints = QueryProcessor.extractQueryFieldHints(question);
318
+ const hints = QueryProcessor.extractQueryFieldHints(question, this.config.rag?.filterableFields);
319
319
  const filter = QueryProcessor.buildQueryFilter(question, hints);
320
320
 
321
321
  const { sources: rawSources, graphData } = await this.retrieve(searchQuery, {
@@ -31,13 +31,16 @@ export class QueryProcessor {
31
31
  * Checks if a string is likely a question word or common prompt phrase.
32
32
  */
33
33
  static isLikelyPromptPhrase(value: string): boolean {
34
- return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
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
+
37
38
  /**
38
39
  * Scans a natural language question for potential metadata hints and keywords.
40
+ * @param question The user's query
41
+ * @param validFields Optional list of known filterable fields to look for
39
42
  */
40
- static extractQueryFieldHints(question: string): QueryFieldHint[] {
43
+ static extractQueryFieldHints(question: string, validFields: string[] = []): QueryFieldHint[] {
41
44
  if (!question.trim()) return [];
42
45
 
43
46
  const hints = new Map<string, QueryFieldHint>();
@@ -88,12 +91,36 @@ export class QueryProcessor {
88
91
  }
89
92
  }
90
93
 
91
- // 4. Universal patterns (email, phone, etc.)
94
+ // 4. Dynamic Schema-driven patterns
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
+ }
104
+
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)) {
111
+ const value = match[1];
112
+ if (value && !this.isLikelyPromptPhrase(value)) {
113
+ addHint(value, field);
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ // 5. Universal patterns (email, phone, etc.)
92
120
  const universalPatterns: Array<{ regex: RegExp; field?: string; group?: number }> = [
93
121
  { regex: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi, field: 'email', group: 1 },
94
122
  { regex: /(\+?\d[\d\-\.\s\(\)]{6,}\d)/g, field: 'phone', group: 1 },
95
123
  { regex: /(\b\d{4}-\d{2}-\d{2}\b|\b\d{1,2}\/\d{1,2}\/\d{2,4}\b)/g, field: 'date', group: 1 },
96
- { regex: /(\$\s?\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g, field: 'amount', group: 1 },
97
124
  { regex: /\b(ID|id|identifier)[: ]\s*([A-Za-z0-9\-]{3,})\b/gi, field: 'id', group: 2 },
98
125
  { regex: /"([^"]{2,120})"/g, group: 1 },
99
126
  { regex: /'([^']{2,120})'/g, group: 1 },
@@ -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