@retrivora-ai/rag-engine 0.2.2 → 0.2.4

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.
@@ -18,6 +18,8 @@ interface ChatOptions {
18
18
  interface EmbedOptions {
19
19
  /** Override model for this specific embed call */
20
20
  model?: string;
21
+ /** Specify the task type for models that require prefixes (e.g. nomic-embed-text) */
22
+ taskType?: 'query' | 'document';
21
23
  }
22
24
  interface ILLMProvider {
23
25
  /**
@@ -18,6 +18,8 @@ interface ChatOptions {
18
18
  interface EmbedOptions {
19
19
  /** Override model for this specific embed call */
20
20
  model?: string;
21
+ /** Specify the task type for models that require prefixes (e.g. nomic-embed-text) */
22
+ taskType?: 'query' | 'document';
21
23
  }
22
24
  interface ILLMProvider {
23
25
  /**
@@ -85,6 +85,10 @@ interface EmbeddingConfig {
85
85
  baseUrl?: string;
86
86
  /** Output vector dimension — must match the index dimension */
87
87
  dimensions?: number;
88
+ /** Optional prefix to prepend to queries (for models like nomic-embed-text) */
89
+ queryPrefix?: string;
90
+ /** Optional prefix to prepend to documents during ingestion */
91
+ documentPrefix?: string;
88
92
  /** Provider-specific options for custom adapters */
89
93
  options?: Record<string, unknown>;
90
94
  }
@@ -85,6 +85,10 @@ interface EmbeddingConfig {
85
85
  baseUrl?: string;
86
86
  /** Output vector dimension — must match the index dimension */
87
87
  dimensions?: number;
88
+ /** Optional prefix to prepend to queries (for models like nomic-embed-text) */
89
+ queryPrefix?: string;
90
+ /** Optional prefix to prepend to documents during ingestion */
91
+ documentPrefix?: string;
88
92
  /** Provider-specific options for custom adapters */
89
93
  options?: Record<string, unknown>;
90
94
  }
@@ -130,6 +130,8 @@ function getRagConfig(env = process.env) {
130
130
  apiKey: embeddingApiKeyByProvider[embeddingProvider],
131
131
  baseUrl: readString(env, "EMBEDDING_BASE_URL"),
132
132
  dimensions: embeddingDimensions,
133
+ queryPrefix: readString(env, "EMBEDDING_QUERY_PREFIX"),
134
+ documentPrefix: readString(env, "EMBEDDING_DOCUMENT_PREFIX"),
133
135
  options: {
134
136
  profile: readString(env, "EMBEDDING_UNIVERSAL_PROFILE")
135
137
  }
@@ -903,13 +905,25 @@ ${context}`;
903
905
  return data.message.content;
904
906
  }
905
907
  async embed(text, options) {
906
- var _a, _b, _c, _d, _e, _f, _g;
908
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
907
909
  const model = (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "nomic-embed-text";
908
910
  const baseURL = (_f = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.baseUrl) != null ? _e : this.llmConfig.baseUrl) != null ? _f : "http://localhost:11434";
909
911
  const client = baseURL !== ((_g = this.llmConfig.baseUrl) != null ? _g : "http://localhost:11434") ? axios.create({ baseURL, timeout: 6e4 }) : this.http;
912
+ let prompt = text;
913
+ const queryPrefix = (_h = this.embeddingConfig) == null ? void 0 : _h.queryPrefix;
914
+ const docPrefix = (_i = this.embeddingConfig) == null ? void 0 : _i.documentPrefix;
915
+ if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
916
+ if (!prompt.startsWith(queryPrefix)) {
917
+ prompt = `${queryPrefix}${text}`;
918
+ }
919
+ } else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
920
+ if (!prompt.startsWith(docPrefix)) {
921
+ prompt = `${docPrefix}${text}`;
922
+ }
923
+ }
910
924
  const { data } = await client.post("/api/embeddings", {
911
925
  model,
912
- prompt: text
926
+ prompt
913
927
  });
914
928
  return data.embedding;
915
929
  }
@@ -1582,7 +1596,7 @@ var Pipeline = class {
1582
1596
  };
1583
1597
  const vectors = await BatchProcessor.mapWithRetry(
1584
1598
  chunks.map((c) => c.content),
1585
- (text) => this.embeddingProvider.embed(text),
1599
+ (text) => this.embeddingProvider.embed(text, { taskType: "document" }),
1586
1600
  embedBatchOptions
1587
1601
  );
1588
1602
  if (vectors.length !== chunks.length) {
@@ -1625,8 +1639,8 @@ var Pipeline = class {
1625
1639
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
1626
1640
  const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
1627
1641
  try {
1628
- const queryVector = await this.embeddingProvider.embed(question);
1629
- const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
1642
+ const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
1643
+ const rawMatches = await this.vectorDB.query(queryVector, topK, ns, { __queryText: question });
1630
1644
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
1631
1645
  const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
1632
1646
  ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
@@ -1,3 +1,3 @@
1
- import '../RagConfig-D_rSf8ep.mjs';
2
- export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-BJ8CUArE.mjs';
1
+ import '../RagConfig-Ttch1N4d.mjs';
2
+ export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-sbCtrIRT.mjs';
3
3
  import 'next/server';
@@ -1,3 +1,3 @@
1
- import '../RagConfig-D_rSf8ep.js';
2
- export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-DtNprGGj.js';
1
+ import '../RagConfig-Ttch1N4d.js';
2
+ export { c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from '../index-rK0KAr2S.js';
3
3
  import 'next/server';
@@ -1284,6 +1284,8 @@ function getRagConfig(env = process.env) {
1284
1284
  apiKey: embeddingApiKeyByProvider[embeddingProvider],
1285
1285
  baseUrl: readString(env, "EMBEDDING_BASE_URL"),
1286
1286
  dimensions: embeddingDimensions,
1287
+ queryPrefix: readString(env, "EMBEDDING_QUERY_PREFIX"),
1288
+ documentPrefix: readString(env, "EMBEDDING_DOCUMENT_PREFIX"),
1287
1289
  options: {
1288
1290
  profile: readString(env, "EMBEDDING_UNIVERSAL_PROFILE")
1289
1291
  }
@@ -2057,13 +2059,25 @@ ${context}`;
2057
2059
  return data.message.content;
2058
2060
  }
2059
2061
  async embed(text, options) {
2060
- var _a, _b, _c, _d, _e, _f, _g;
2062
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2061
2063
  const model = (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "nomic-embed-text";
2062
2064
  const baseURL = (_f = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.baseUrl) != null ? _e : this.llmConfig.baseUrl) != null ? _f : "http://localhost:11434";
2063
2065
  const client = baseURL !== ((_g = this.llmConfig.baseUrl) != null ? _g : "http://localhost:11434") ? import_axios.default.create({ baseURL, timeout: 6e4 }) : this.http;
2066
+ let prompt = text;
2067
+ const queryPrefix = (_h = this.embeddingConfig) == null ? void 0 : _h.queryPrefix;
2068
+ const docPrefix = (_i = this.embeddingConfig) == null ? void 0 : _i.documentPrefix;
2069
+ if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
2070
+ if (!prompt.startsWith(queryPrefix)) {
2071
+ prompt = `${queryPrefix}${text}`;
2072
+ }
2073
+ } else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
2074
+ if (!prompt.startsWith(docPrefix)) {
2075
+ prompt = `${docPrefix}${text}`;
2076
+ }
2077
+ }
2064
2078
  const { data } = await client.post("/api/embeddings", {
2065
2079
  model,
2066
- prompt: text
2080
+ prompt
2067
2081
  });
2068
2082
  return data.embedding;
2069
2083
  }
@@ -2692,7 +2706,7 @@ var Pipeline = class {
2692
2706
  };
2693
2707
  const vectors = await BatchProcessor.mapWithRetry(
2694
2708
  chunks.map((c) => c.content),
2695
- (text) => this.embeddingProvider.embed(text),
2709
+ (text) => this.embeddingProvider.embed(text, { taskType: "document" }),
2696
2710
  embedBatchOptions
2697
2711
  );
2698
2712
  if (vectors.length !== chunks.length) {
@@ -2735,8 +2749,8 @@ var Pipeline = class {
2735
2749
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
2736
2750
  const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
2737
2751
  try {
2738
- const queryVector = await this.embeddingProvider.embed(question);
2739
- const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
2752
+ const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
2753
+ const rawMatches = await this.vectorDB.query(queryVector, topK, ns, { __queryText: question });
2740
2754
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
2741
2755
  const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
2742
2756
  ${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-2VR5ZMXV.mjs";
6
+ } from "../chunk-7K4KXB6G.mjs";
7
7
  import "../chunk-EDLTMSNY.mjs";
8
8
  import "../chunk-FWCSY2DS.mjs";
9
9
  export {
@@ -1,4 +1,4 @@
1
- import { c as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, a as RagConfig, C as ChatResponse } from './RagConfig-D_rSf8ep.js';
1
+ import { c as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, a as RagConfig, C as ChatResponse } from './RagConfig-Ttch1N4d.js';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { c as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, a as RagConfig, C as ChatResponse } from './RagConfig-D_rSf8ep.mjs';
1
+ import { c as VectorDBConfig, L as LLMConfig, E as EmbeddingConfig, a as RagConfig, C as ChatResponse } from './RagConfig-Ttch1N4d.mjs';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  /**
package/dist/index.d.mts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React$1, { ReactNode } from 'react';
3
- import { C as ChatMessage } from './DocumentChunker-BEyzadsv.mjs';
4
- export { a as ChatOptions, b as Chunk, c as ChunkOptions, E as EmbedOptions, I as ILLMProvider } from './DocumentChunker-BEyzadsv.mjs';
5
- import { V as VectorMatch, U as UIConfig } from './RagConfig-D_rSf8ep.mjs';
6
- export { C as ChatResponse, E as EmbeddingConfig, I as IngestDocument, L as LLMConfig, R as RAGConfig, a as RagConfig, b as UpsertDocument, c as VectorDBConfig } from './RagConfig-D_rSf8ep.mjs';
3
+ import { C as ChatMessage } from './DocumentChunker-BICIjSuG.mjs';
4
+ export { a as ChatOptions, b as Chunk, c as ChunkOptions, E as EmbedOptions, I as ILLMProvider } from './DocumentChunker-BICIjSuG.mjs';
5
+ import { V as VectorMatch, U as UIConfig } from './RagConfig-Ttch1N4d.mjs';
6
+ export { C as ChatResponse, E as EmbeddingConfig, I as IngestDocument, L as LLMConfig, R as RAGConfig, a as RagConfig, b as UpsertDocument, c as VectorDBConfig } from './RagConfig-Ttch1N4d.mjs';
7
7
 
8
8
  interface ChatWidgetProps {
9
9
  /** Position of the floating button. Defaults to bottom-right. */
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React$1, { ReactNode } from 'react';
3
- import { C as ChatMessage } from './DocumentChunker-BEyzadsv.js';
4
- export { a as ChatOptions, b as Chunk, c as ChunkOptions, E as EmbedOptions, I as ILLMProvider } from './DocumentChunker-BEyzadsv.js';
5
- import { V as VectorMatch, U as UIConfig } from './RagConfig-D_rSf8ep.js';
6
- export { C as ChatResponse, E as EmbeddingConfig, I as IngestDocument, L as LLMConfig, R as RAGConfig, a as RagConfig, b as UpsertDocument, c as VectorDBConfig } from './RagConfig-D_rSf8ep.js';
3
+ import { C as ChatMessage } from './DocumentChunker-BICIjSuG.js';
4
+ export { a as ChatOptions, b as Chunk, c as ChunkOptions, E as EmbedOptions, I as ILLMProvider } from './DocumentChunker-BICIjSuG.js';
5
+ import { V as VectorMatch, U as UIConfig } from './RagConfig-Ttch1N4d.js';
6
+ export { C as ChatResponse, E as EmbeddingConfig, I as IngestDocument, L as LLMConfig, R as RAGConfig, a as RagConfig, b as UpsertDocument, c as VectorDBConfig } from './RagConfig-Ttch1N4d.js';
7
7
 
8
8
  interface ChatWidgetProps {
9
9
  /** Position of the floating button. Defaults to bottom-right. */
package/dist/server.d.mts CHANGED
@@ -1,9 +1,9 @@
1
- import { a as RagConfig, C as ChatResponse, I as IngestDocument, c as VectorDBConfig, b as UpsertDocument, V as VectorMatch, L as LLMConfig, E as EmbeddingConfig, d as VectorDBProvider, e as LLMProvider, f as EmbeddingProvider } from './RagConfig-D_rSf8ep.mjs';
2
- export { R as RAGConfig, U as UIConfig } from './RagConfig-D_rSf8ep.mjs';
3
- import { C as ChatMessage, I as ILLMProvider, a as ChatOptions, E as EmbedOptions } from './DocumentChunker-BEyzadsv.mjs';
4
- export { b as Chunk, c as ChunkOptions, D as DocumentChunker } from './DocumentChunker-BEyzadsv.mjs';
5
- import { H as HealthCheckResult } from './index-BJ8CUArE.mjs';
6
- export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-BJ8CUArE.mjs';
1
+ import { a as RagConfig, C as ChatResponse, I as IngestDocument, c as VectorDBConfig, b as UpsertDocument, V as VectorMatch, L as LLMConfig, E as EmbeddingConfig, d as VectorDBProvider, e as LLMProvider, f as EmbeddingProvider } from './RagConfig-Ttch1N4d.mjs';
2
+ export { R as RAGConfig, U as UIConfig } from './RagConfig-Ttch1N4d.mjs';
3
+ import { C as ChatMessage, I as ILLMProvider, a as ChatOptions, E as EmbedOptions } from './DocumentChunker-BICIjSuG.mjs';
4
+ export { b as Chunk, c as ChunkOptions, D as DocumentChunker } from './DocumentChunker-BICIjSuG.mjs';
5
+ import { H as HealthCheckResult } from './index-sbCtrIRT.mjs';
6
+ export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-sbCtrIRT.mjs';
7
7
  import 'next/server';
8
8
 
9
9
  /**
package/dist/server.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { a as RagConfig, C as ChatResponse, I as IngestDocument, c as VectorDBConfig, b as UpsertDocument, V as VectorMatch, L as LLMConfig, E as EmbeddingConfig, d as VectorDBProvider, e as LLMProvider, f as EmbeddingProvider } from './RagConfig-D_rSf8ep.js';
2
- export { R as RAGConfig, U as UIConfig } from './RagConfig-D_rSf8ep.js';
3
- import { C as ChatMessage, I as ILLMProvider, a as ChatOptions, E as EmbedOptions } from './DocumentChunker-BEyzadsv.js';
4
- export { b as Chunk, c as ChunkOptions, D as DocumentChunker } from './DocumentChunker-BEyzadsv.js';
5
- import { H as HealthCheckResult } from './index-DtNprGGj.js';
6
- export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-DtNprGGj.js';
1
+ import { a as RagConfig, C as ChatResponse, I as IngestDocument, c as VectorDBConfig, b as UpsertDocument, V as VectorMatch, L as LLMConfig, E as EmbeddingConfig, d as VectorDBProvider, e as LLMProvider, f as EmbeddingProvider } from './RagConfig-Ttch1N4d.js';
2
+ export { R as RAGConfig, U as UIConfig } from './RagConfig-Ttch1N4d.js';
3
+ import { C as ChatMessage, I as ILLMProvider, a as ChatOptions, E as EmbedOptions } from './DocumentChunker-BICIjSuG.js';
4
+ export { b as Chunk, c as ChunkOptions, D as DocumentChunker } from './DocumentChunker-BICIjSuG.js';
5
+ import { H as HealthCheckResult } from './index-rK0KAr2S.js';
6
+ export { P as ProviderHealthCheck, c as createChatHandler, a as createHealthHandler, b as createIngestHandler, d as createUploadHandler } from './index-rK0KAr2S.js';
7
7
  import 'next/server';
8
8
 
9
9
  /**
package/dist/server.js CHANGED
@@ -1328,6 +1328,8 @@ function getRagConfig(env = process.env) {
1328
1328
  apiKey: embeddingApiKeyByProvider[embeddingProvider],
1329
1329
  baseUrl: readString(env, "EMBEDDING_BASE_URL"),
1330
1330
  dimensions: embeddingDimensions,
1331
+ queryPrefix: readString(env, "EMBEDDING_QUERY_PREFIX"),
1332
+ documentPrefix: readString(env, "EMBEDDING_DOCUMENT_PREFIX"),
1331
1333
  options: {
1332
1334
  profile: readString(env, "EMBEDDING_UNIVERSAL_PROFILE")
1333
1335
  }
@@ -2101,13 +2103,25 @@ ${context}`;
2101
2103
  return data.message.content;
2102
2104
  }
2103
2105
  async embed(text, options) {
2104
- var _a, _b, _c, _d, _e, _f, _g;
2106
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2105
2107
  const model = (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "nomic-embed-text";
2106
2108
  const baseURL = (_f = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.baseUrl) != null ? _e : this.llmConfig.baseUrl) != null ? _f : "http://localhost:11434";
2107
2109
  const client = baseURL !== ((_g = this.llmConfig.baseUrl) != null ? _g : "http://localhost:11434") ? import_axios.default.create({ baseURL, timeout: 6e4 }) : this.http;
2110
+ let prompt = text;
2111
+ const queryPrefix = (_h = this.embeddingConfig) == null ? void 0 : _h.queryPrefix;
2112
+ const docPrefix = (_i = this.embeddingConfig) == null ? void 0 : _i.documentPrefix;
2113
+ if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
2114
+ if (!prompt.startsWith(queryPrefix)) {
2115
+ prompt = `${queryPrefix}${text}`;
2116
+ }
2117
+ } else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
2118
+ if (!prompt.startsWith(docPrefix)) {
2119
+ prompt = `${docPrefix}${text}`;
2120
+ }
2121
+ }
2108
2122
  const { data } = await client.post("/api/embeddings", {
2109
2123
  model,
2110
- prompt: text
2124
+ prompt
2111
2125
  });
2112
2126
  return data.embedding;
2113
2127
  }
@@ -2781,7 +2795,7 @@ var Pipeline = class {
2781
2795
  };
2782
2796
  const vectors = await BatchProcessor.mapWithRetry(
2783
2797
  chunks.map((c) => c.content),
2784
- (text) => this.embeddingProvider.embed(text),
2798
+ (text) => this.embeddingProvider.embed(text, { taskType: "document" }),
2785
2799
  embedBatchOptions
2786
2800
  );
2787
2801
  if (vectors.length !== chunks.length) {
@@ -2824,8 +2838,8 @@ var Pipeline = class {
2824
2838
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
2825
2839
  const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
2826
2840
  try {
2827
- const queryVector = await this.embeddingProvider.embed(question);
2828
- const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
2841
+ const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
2842
+ const rawMatches = await this.vectorDB.query(queryVector, topK, ns, { __queryText: question });
2829
2843
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
2830
2844
  const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
2831
2845
  ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
@@ -3756,29 +3770,46 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
3756
3770
  const vectorLiteral = `[${vector.join(",")}]`;
3757
3771
  const allResults = [];
3758
3772
  console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
3773
+ const queryText = _filter == null ? void 0 : _filter.__queryText;
3759
3774
  for (const table of this.tables) {
3760
3775
  try {
3761
- const result = await this.pool.query(
3762
- `SELECT *,
3763
- (1 - (embedding <=> $1::vector)) AS vector_score
3764
- FROM "${table}"
3765
- ORDER BY vector_score DESC
3766
- LIMIT 50`,
3767
- [vectorLiteral]
3768
- );
3776
+ let sqlQuery = "";
3777
+ let params = [];
3778
+ if (queryText) {
3779
+ sqlQuery = `
3780
+ SELECT *,
3781
+ (1 - (embedding <=> $1::vector)) AS vector_score,
3782
+ ts_rank(to_tsvector('english', t.*::text), plainto_tsquery('english', $2)) AS keyword_score,
3783
+ ((1 - (embedding <=> $1::vector)) + (ts_rank(to_tsvector('english', t.*::text), plainto_tsquery('english', $2)) * 2.0)) AS hybrid_score
3784
+ FROM "${table}" t
3785
+ ORDER BY hybrid_score DESC
3786
+ LIMIT 50
3787
+ `;
3788
+ params = [vectorLiteral, queryText];
3789
+ } else {
3790
+ sqlQuery = `
3791
+ SELECT *,
3792
+ (1 - (embedding <=> $1::vector)) AS hybrid_score
3793
+ FROM "${table}" t
3794
+ ORDER BY hybrid_score DESC
3795
+ LIMIT 50
3796
+ `;
3797
+ params = [vectorLiteral];
3798
+ }
3799
+ const result = await this.pool.query(sqlQuery, params);
3769
3800
  if (result.rowCount && result.rowCount > 0) {
3770
- console.log(` \u2705 Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].vector_score.toFixed(4)}`);
3801
+ console.log(` \u2705 Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].hybrid_score.toFixed(4)}`);
3771
3802
  } else {
3772
3803
  console.log(` \u26AA Table "${table}": No relevant matches found.`);
3773
3804
  }
3774
3805
  for (const row of result.rows) {
3775
- const _a = row, { vector_score, id } = _a, rest = __objRest(_a, ["vector_score", "id"]);
3806
+ const _a = row, { hybrid_score, vector_score, keyword_score, id } = _a, rest = __objRest(_a, ["hybrid_score", "vector_score", "keyword_score", "id"]);
3776
3807
  delete rest.embedding;
3777
3808
  const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
3778
3809
  ` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
3779
3810
  allResults.push({
3780
3811
  id: `${table}-${id}`,
3781
- score: parseFloat(String(vector_score)),
3812
+ score: parseFloat(String(hybrid_score)),
3782
3813
  content,
3783
3814
  metadata: __spreadProps(__spreadValues({}, rest), { source_table: table })
3784
3815
  });
package/dist/server.mjs CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  createIngestHandler,
35
35
  createUploadHandler,
36
36
  getRagConfig
37
- } from "./chunk-2VR5ZMXV.mjs";
37
+ } from "./chunk-7K4KXB6G.mjs";
38
38
  import "./chunk-EDLTMSNY.mjs";
39
39
  import {
40
40
  PineconeProvider
@@ -413,29 +413,46 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
413
413
  const vectorLiteral = `[${vector.join(",")}]`;
414
414
  const allResults = [];
415
415
  console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
416
+ const queryText = _filter == null ? void 0 : _filter.__queryText;
416
417
  for (const table of this.tables) {
417
418
  try {
418
- const result = await this.pool.query(
419
- `SELECT *,
420
- (1 - (embedding <=> $1::vector)) AS vector_score
421
- FROM "${table}"
422
- ORDER BY vector_score DESC
423
- LIMIT 50`,
424
- [vectorLiteral]
425
- );
419
+ let sqlQuery = "";
420
+ let params = [];
421
+ if (queryText) {
422
+ sqlQuery = `
423
+ SELECT *,
424
+ (1 - (embedding <=> $1::vector)) AS vector_score,
425
+ ts_rank(to_tsvector('english', t.*::text), plainto_tsquery('english', $2)) AS keyword_score,
426
+ ((1 - (embedding <=> $1::vector)) + (ts_rank(to_tsvector('english', t.*::text), plainto_tsquery('english', $2)) * 2.0)) AS hybrid_score
427
+ FROM "${table}" t
428
+ ORDER BY hybrid_score DESC
429
+ LIMIT 50
430
+ `;
431
+ params = [vectorLiteral, queryText];
432
+ } else {
433
+ sqlQuery = `
434
+ SELECT *,
435
+ (1 - (embedding <=> $1::vector)) AS hybrid_score
436
+ FROM "${table}" t
437
+ ORDER BY hybrid_score DESC
438
+ LIMIT 50
439
+ `;
440
+ params = [vectorLiteral];
441
+ }
442
+ const result = await this.pool.query(sqlQuery, params);
426
443
  if (result.rowCount && result.rowCount > 0) {
427
- console.log(` \u2705 Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].vector_score.toFixed(4)}`);
444
+ console.log(` \u2705 Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].hybrid_score.toFixed(4)}`);
428
445
  } else {
429
446
  console.log(` \u26AA Table "${table}": No relevant matches found.`);
430
447
  }
431
448
  for (const row of result.rows) {
432
- const _a = row, { vector_score, id } = _a, rest = __objRest(_a, ["vector_score", "id"]);
449
+ const _a = row, { hybrid_score, vector_score, keyword_score, id } = _a, rest = __objRest(_a, ["hybrid_score", "vector_score", "keyword_score", "id"]);
433
450
  delete rest.embedding;
434
451
  const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
435
452
  ` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
436
453
  allResults.push({
437
454
  id: `${table}-${id}`,
438
- score: parseFloat(String(vector_score)),
455
+ score: parseFloat(String(hybrid_score)),
439
456
  content,
440
457
  metadata: __spreadProps(__spreadValues({}, rest), { source_table: table })
441
458
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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",
@@ -81,6 +81,10 @@ export interface EmbeddingConfig {
81
81
  baseUrl?: string;
82
82
  /** Output vector dimension — must match the index dimension */
83
83
  dimensions?: number;
84
+ /** Optional prefix to prepend to queries (for models like nomic-embed-text) */
85
+ queryPrefix?: string;
86
+ /** Optional prefix to prepend to documents during ingestion */
87
+ documentPrefix?: string;
84
88
  /** Provider-specific options for custom adapters */
85
89
  options?: Record<string, unknown>;
86
90
  }
@@ -150,6 +150,8 @@ export function getRagConfig(env: Record<string, string | undefined> = process.e
150
150
  apiKey: embeddingApiKeyByProvider[embeddingProvider],
151
151
  baseUrl: readString(env, 'EMBEDDING_BASE_URL'),
152
152
  dimensions: embeddingDimensions,
153
+ queryPrefix: readString(env, 'EMBEDDING_QUERY_PREFIX'),
154
+ documentPrefix: readString(env, 'EMBEDDING_DOCUMENT_PREFIX'),
153
155
  options: {
154
156
  profile: readString(env, 'EMBEDDING_UNIVERSAL_PROFILE'),
155
157
  },
@@ -82,7 +82,7 @@ export class Pipeline {
82
82
 
83
83
  const vectors = await BatchProcessor.mapWithRetry(
84
84
  chunks.map(c => c.content),
85
- (text) => this.embeddingProvider.embed(text),
85
+ (text) => this.embeddingProvider.embed(text, { taskType: 'document' }),
86
86
  embedBatchOptions
87
87
  );
88
88
 
@@ -134,8 +134,9 @@ export class Pipeline {
134
134
  const scoreThreshold = this.config.rag?.scoreThreshold ?? 0.0;
135
135
 
136
136
  try {
137
- const queryVector = await this.embeddingProvider.embed(question);
138
- const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
137
+ const queryVector = await this.embeddingProvider.embed(question, { taskType: 'query' });
138
+ // Pass the original text to the vector DB so it can perform hybrid (keyword) search if supported
139
+ const rawMatches = await this.vectorDB.query(queryVector, topK, ns, { __queryText: question });
139
140
 
140
141
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
141
142
  const context = sources.length
@@ -21,6 +21,8 @@ export interface ChatOptions {
21
21
  export interface EmbedOptions {
22
22
  /** Override model for this specific embed call */
23
23
  model?: string;
24
+ /** Specify the task type for models that require prefixes (e.g. nomic-embed-text) */
25
+ taskType?: 'query' | 'document';
24
26
  }
25
27
 
26
28
  export interface ILLMProvider {
@@ -73,9 +73,25 @@ export class OllamaProvider implements ILLMProvider {
73
73
  ? axios.create({ baseURL, timeout: 60_000 })
74
74
  : this.http;
75
75
 
76
+ let prompt = text;
77
+
78
+ // Dynamically apply prefixes from configuration if they exist
79
+ const queryPrefix = this.embeddingConfig?.queryPrefix;
80
+ const docPrefix = this.embeddingConfig?.documentPrefix;
81
+
82
+ if (options?.taskType === 'query' && queryPrefix) {
83
+ if (!prompt.startsWith(queryPrefix)) {
84
+ prompt = `${queryPrefix}${text}`;
85
+ }
86
+ } else if (options?.taskType === 'document' && docPrefix) {
87
+ if (!prompt.startsWith(docPrefix)) {
88
+ prompt = `${docPrefix}${text}`;
89
+ }
90
+ }
91
+
76
92
  const { data } = await client.post<OllamaEmbedResponse>('/api/embeddings', {
77
93
  model,
78
- prompt: text,
94
+ prompt,
79
95
  });
80
96
 
81
97
  return data.embedding;
@@ -46,7 +46,7 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
46
46
 
47
47
  async initialize(): Promise<void> {
48
48
  this.pool = new Pool({ connectionString: this.connectionString });
49
-
49
+
50
50
  // Dynamically discover all tables that have an 'embedding' column
51
51
  const client: PoolClient = await this.pool.connect();
52
52
  try {
@@ -56,9 +56,9 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
56
56
  WHERE column_name = 'embedding'
57
57
  AND table_schema = 'public'
58
58
  `);
59
-
59
+
60
60
  const discoveredTables = res.rows.map(r => r.table_name);
61
-
61
+
62
62
  if (this.tables.length === 0) {
63
63
  // No static tables configured, use everything discovered
64
64
  this.tables = discoveredTables;
@@ -66,7 +66,7 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
66
66
  // Filter configured tables to ensure they actually exist with embeddings
67
67
  const staticTables = [...this.tables];
68
68
  this.tables = staticTables.filter(t => discoveredTables.includes(t));
69
-
69
+
70
70
  if (this.tables.length < staticTables.length) {
71
71
  const missing = staticTables.filter(t => !discoveredTables.includes(t));
72
72
  console.warn(`[MultiTablePostgresProvider] Some configured tables were skipped (missing 'embedding' column): ${missing.join(', ')}`);
@@ -123,31 +123,50 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
123
123
 
124
124
  console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
125
125
 
126
+ const queryText = _filter?.__queryText as string | undefined;
127
+
126
128
  for (const table of this.tables) {
127
129
  try {
128
- // We use a hybrid approach:
129
- // 1. Vector similarity (Semantic)
130
- // 2. Simple keyword matching (Exact) to boost relevance for specific names
131
- const result = await this.pool.query(
132
- `SELECT *,
133
- (1 - (embedding <=> $1::vector)) AS vector_score
134
- FROM "${table}"
135
- ORDER BY vector_score DESC
136
- LIMIT 50`,
137
- [vectorLiteral]
138
- );
130
+ let sqlQuery = '';
131
+ let params: any[] = [];
132
+
133
+ if (queryText) {
134
+ // Actual Hybrid Search: Semantic Vector Score + Full-Text Keyword Rank
135
+ sqlQuery = `
136
+ SELECT *,
137
+ (1 - (embedding <=> $1::vector)) AS vector_score,
138
+ ts_rank(to_tsvector('english', t.*::text), plainto_tsquery('english', $2)) AS keyword_score,
139
+ ((1 - (embedding <=> $1::vector)) + (ts_rank(to_tsvector('english', t.*::text), plainto_tsquery('english', $2)) * 2.0)) AS hybrid_score
140
+ FROM "${table}" t
141
+ ORDER BY hybrid_score DESC
142
+ LIMIT 50
143
+ `;
144
+ params = [vectorLiteral, queryText];
145
+ } else {
146
+ // Fallback to Vector-only Search
147
+ sqlQuery = `
148
+ SELECT *,
149
+ (1 - (embedding <=> $1::vector)) AS hybrid_score
150
+ FROM "${table}" t
151
+ ORDER BY hybrid_score DESC
152
+ LIMIT 50
153
+ `;
154
+ params = [vectorLiteral];
155
+ }
156
+
157
+ const result = await this.pool.query(sqlQuery, params);
139
158
 
140
159
  if (result.rowCount && result.rowCount > 0) {
141
- console.log(` ✅ Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].vector_score.toFixed(4)}`);
160
+ console.log(` ✅ Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].hybrid_score.toFixed(4)}`);
142
161
  } else {
143
162
  console.log(` ⚪ Table "${table}": No relevant matches found.`);
144
163
  }
145
164
 
146
165
  for (const row of result.rows) {
147
- const { vector_score, id, ...rest } = row as Record<string, unknown>;
166
+ const { hybrid_score, vector_score, keyword_score, id, ...rest } = row as Record<string, unknown>;
148
167
  delete rest.embedding;
149
168
 
150
- const content = `[TYPE: ${table.replace(/s$/, '').toUpperCase()}]\n` +
169
+ const content = `[TYPE: ${table.replace(/s$/, '').toUpperCase()}]\n` +
151
170
  Object.entries(rest)
152
171
  .filter(([k, v]) => v !== null && typeof v !== 'object' && k !== 'id')
153
172
  .map(([k, v]) => `${k}: ${v}`)
@@ -155,7 +174,7 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
155
174
 
156
175
  allResults.push({
157
176
  id: `${table}-${id}`,
158
- score: parseFloat(String(vector_score)),
177
+ score: parseFloat(String(hybrid_score)),
159
178
  content,
160
179
  metadata: { ...rest, source_table: table },
161
180
  });
@@ -179,7 +198,7 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
179
198
  // 2. Take top 3 from EACH table guaranteed, then fill the rest with overall best
180
199
  const balancedResults: VectorMatch[] = [];
181
200
  const tables = Object.keys(resultsByTable);
182
-
201
+
183
202
  // First, ensure every table's best match is included
184
203
  for (const table of tables) {
185
204
  balancedResults.push(...resultsByTable[table].slice(0, 3));