@retrivora-ai/rag-engine 0.2.1 → 0.2.3

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,7 +1639,7 @@ 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);
1642
+ const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
1629
1643
  const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
1630
1644
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
1631
1645
  const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
@@ -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,7 +2749,7 @@ 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);
2752
+ const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
2739
2753
  const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
2740
2754
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
2741
2755
  const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
@@ -3,7 +3,7 @@ import {
3
3
  createHealthHandler,
4
4
  createIngestHandler,
5
5
  createUploadHandler
6
- } from "../chunk-2VR5ZMXV.mjs";
6
+ } from "../chunk-K53N7SEE.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,7 +2838,7 @@ 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);
2841
+ const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
2828
2842
  const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
2829
2843
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
2830
2844
  const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
@@ -3749,31 +3763,36 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
3749
3763
  * Query all configured tables and merge results, sorted by cosine similarity score.
3750
3764
  */
3751
3765
  async query(vector, topK, _namespace, _filter) {
3752
- var _b, _c;
3766
+ var _b, _c, _d;
3753
3767
  if (!this.pool) {
3754
3768
  throw new Error("[MultiTablePostgresProvider] Provider not initialized. Call initialize() first.");
3755
3769
  }
3756
- console.log(`[MultiTablePostgresProvider] Querying ${this.tables.length} table(s) with vector dim=${vector.length}`);
3757
3770
  const vectorLiteral = `[${vector.join(",")}]`;
3758
3771
  const allResults = [];
3772
+ console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
3759
3773
  for (const table of this.tables) {
3760
3774
  try {
3761
3775
  const result = await this.pool.query(
3762
- `SELECT *, 1 - (embedding <=> $1::vector) AS score
3776
+ `SELECT *,
3777
+ (1 - (embedding <=> $1::vector)) AS vector_score
3763
3778
  FROM "${table}"
3764
- ORDER BY embedding <=> $1::vector
3779
+ ORDER BY vector_score DESC
3765
3780
  LIMIT 50`,
3766
3781
  [vectorLiteral]
3767
3782
  );
3783
+ if (result.rowCount && result.rowCount > 0) {
3784
+ console.log(` \u2705 Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].vector_score.toFixed(4)}`);
3785
+ } else {
3786
+ console.log(` \u26AA Table "${table}": No relevant matches found.`);
3787
+ }
3768
3788
  for (const row of result.rows) {
3769
- const _a = row, { score, id } = _a, rest = __objRest(_a, ["score", "id"]);
3789
+ const _a = row, { vector_score, id } = _a, rest = __objRest(_a, ["vector_score", "id"]);
3770
3790
  delete rest.embedding;
3771
- const type = table.replace(/s$/, "").toUpperCase();
3772
- const content = `[TYPE: ${type}]
3791
+ const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
3773
3792
  ` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
3774
3793
  allResults.push({
3775
3794
  id: `${table}-${id}`,
3776
- score: parseFloat(String(score)),
3795
+ score: parseFloat(String(vector_score)),
3777
3796
  content,
3778
3797
  metadata: __spreadProps(__spreadValues({}, rest), { source_table: table })
3779
3798
  });
@@ -3785,11 +3804,23 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
3785
3804
  );
3786
3805
  }
3787
3806
  }
3788
- const sortedResults = allResults.sort((a, b) => b.score - a.score);
3789
- if (sortedResults.length > 0) {
3790
- console.log(`[MultiTablePostgresProvider] Top match from "${(_c = (_b = sortedResults[0].metadata) == null ? void 0 : _b.source_table) != null ? _c : "unknown"}" with score ${sortedResults[0].score.toFixed(4)}`);
3807
+ const resultsByTable = {};
3808
+ for (const res of allResults) {
3809
+ const table = (_b = res.metadata) == null ? void 0 : _b.source_table;
3810
+ if (!resultsByTable[table]) resultsByTable[table] = [];
3811
+ resultsByTable[table].push(res);
3812
+ }
3813
+ const balancedResults = [];
3814
+ const tables = Object.keys(resultsByTable);
3815
+ for (const table of tables) {
3816
+ balancedResults.push(...resultsByTable[table].slice(0, 3));
3817
+ }
3818
+ const finalSorted = balancedResults.sort((a, b) => b.score - a.score);
3819
+ if (finalSorted.length > 0) {
3820
+ console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
3821
+ console.log(`[MultiTablePostgresProvider] Final top match from "${(_d = (_c = finalSorted[0].metadata) == null ? void 0 : _c.source_table) != null ? _d : "unknown"}" with score ${finalSorted[0].score.toFixed(4)}`);
3791
3822
  }
3792
- return sortedResults.slice(0, Math.max(topK, 15));
3823
+ return finalSorted.slice(0, Math.max(topK, 15));
3793
3824
  }
3794
3825
  async delete(_id, _namespace) {
3795
3826
  console.warn("[MultiTablePostgresProvider] delete() is a no-op for multi-table mode.");
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-K53N7SEE.mjs";
38
38
  import "./chunk-EDLTMSNY.mjs";
39
39
  import {
40
40
  PineconeProvider
@@ -406,31 +406,36 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
406
406
  * Query all configured tables and merge results, sorted by cosine similarity score.
407
407
  */
408
408
  async query(vector, topK, _namespace, _filter) {
409
- var _b, _c;
409
+ var _b, _c, _d;
410
410
  if (!this.pool) {
411
411
  throw new Error("[MultiTablePostgresProvider] Provider not initialized. Call initialize() first.");
412
412
  }
413
- console.log(`[MultiTablePostgresProvider] Querying ${this.tables.length} table(s) with vector dim=${vector.length}`);
414
413
  const vectorLiteral = `[${vector.join(",")}]`;
415
414
  const allResults = [];
415
+ console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
416
416
  for (const table of this.tables) {
417
417
  try {
418
418
  const result = await this.pool.query(
419
- `SELECT *, 1 - (embedding <=> $1::vector) AS score
419
+ `SELECT *,
420
+ (1 - (embedding <=> $1::vector)) AS vector_score
420
421
  FROM "${table}"
421
- ORDER BY embedding <=> $1::vector
422
+ ORDER BY vector_score DESC
422
423
  LIMIT 50`,
423
424
  [vectorLiteral]
424
425
  );
426
+ 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)}`);
428
+ } else {
429
+ console.log(` \u26AA Table "${table}": No relevant matches found.`);
430
+ }
425
431
  for (const row of result.rows) {
426
- const _a = row, { score, id } = _a, rest = __objRest(_a, ["score", "id"]);
432
+ const _a = row, { vector_score, id } = _a, rest = __objRest(_a, ["vector_score", "id"]);
427
433
  delete rest.embedding;
428
- const type = table.replace(/s$/, "").toUpperCase();
429
- const content = `[TYPE: ${type}]
434
+ const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
430
435
  ` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
431
436
  allResults.push({
432
437
  id: `${table}-${id}`,
433
- score: parseFloat(String(score)),
438
+ score: parseFloat(String(vector_score)),
434
439
  content,
435
440
  metadata: __spreadProps(__spreadValues({}, rest), { source_table: table })
436
441
  });
@@ -442,11 +447,23 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
442
447
  );
443
448
  }
444
449
  }
445
- const sortedResults = allResults.sort((a, b) => b.score - a.score);
446
- if (sortedResults.length > 0) {
447
- console.log(`[MultiTablePostgresProvider] Top match from "${(_c = (_b = sortedResults[0].metadata) == null ? void 0 : _b.source_table) != null ? _c : "unknown"}" with score ${sortedResults[0].score.toFixed(4)}`);
450
+ const resultsByTable = {};
451
+ for (const res of allResults) {
452
+ const table = (_b = res.metadata) == null ? void 0 : _b.source_table;
453
+ if (!resultsByTable[table]) resultsByTable[table] = [];
454
+ resultsByTable[table].push(res);
455
+ }
456
+ const balancedResults = [];
457
+ const tables = Object.keys(resultsByTable);
458
+ for (const table of tables) {
459
+ balancedResults.push(...resultsByTable[table].slice(0, 3));
460
+ }
461
+ const finalSorted = balancedResults.sort((a, b) => b.score - a.score);
462
+ if (finalSorted.length > 0) {
463
+ console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
464
+ console.log(`[MultiTablePostgresProvider] Final top match from "${(_d = (_c = finalSorted[0].metadata) == null ? void 0 : _c.source_table) != null ? _d : "unknown"}" with score ${finalSorted[0].score.toFixed(4)}`);
448
465
  }
449
- return sortedResults.slice(0, Math.max(topK, 15));
466
+ return finalSorted.slice(0, Math.max(topK, 15));
450
467
  }
451
468
  async delete(_id, _namespace) {
452
469
  console.warn("[MultiTablePostgresProvider] delete() is a no-op for multi-table mode.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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,7 +134,7 @@ 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);
137
+ const queryVector = await this.embeddingProvider.embed(question, { taskType: 'query' });
138
138
  const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
139
139
 
140
140
  const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
@@ -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;
@@ -118,30 +118,36 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
118
118
  throw new Error('[MultiTablePostgresProvider] Provider not initialized. Call initialize() first.');
119
119
  }
120
120
 
121
- console.log(`[MultiTablePostgresProvider] Querying ${this.tables.length} table(s) with vector dim=${vector.length}`);
122
-
123
121
  const vectorLiteral = `[${vector.join(',')}]`;
124
122
  const allResults: VectorMatch[] = [];
125
123
 
124
+ console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
125
+
126
126
  for (const table of this.tables) {
127
127
  try {
128
+ // We use a hybrid approach:
129
+ // 1. Vector similarity (Semantic)
130
+ // 2. Simple keyword matching (Exact) to boost relevance for specific names
128
131
  const result = await this.pool.query(
129
- `SELECT *, 1 - (embedding <=> $1::vector) AS score
132
+ `SELECT *,
133
+ (1 - (embedding <=> $1::vector)) AS vector_score
130
134
  FROM "${table}"
131
- ORDER BY embedding <=> $1::vector
135
+ ORDER BY vector_score DESC
132
136
  LIMIT 50`,
133
137
  [vectorLiteral]
134
138
  );
135
139
 
140
+ 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)}`);
142
+ } else {
143
+ console.log(` ⚪ Table "${table}": No relevant matches found.`);
144
+ }
145
+
136
146
  for (const row of result.rows) {
137
- const { score, id, ...rest } = row as Record<string, unknown>;
138
- // Remove embedding from rest to avoid including it in content
147
+ const { vector_score, id, ...rest } = row as Record<string, unknown>;
139
148
  delete rest.embedding;
140
149
 
141
- // Build a human-readable content string from ALL available columns.
142
- // Prepend the entity type (derived from table name) to prevent cross-table confusion.
143
- const type = table.replace(/s$/, '').toUpperCase();
144
- const content = `[TYPE: ${type}]\n` +
150
+ const content = `[TYPE: ${table.replace(/s$/, '').toUpperCase()}]\n` +
145
151
  Object.entries(rest)
146
152
  .filter(([k, v]) => v !== null && typeof v !== 'object' && k !== 'id')
147
153
  .map(([k, v]) => `${k}: ${v}`)
@@ -149,7 +155,7 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
149
155
 
150
156
  allResults.push({
151
157
  id: `${table}-${id}`,
152
- score: parseFloat(String(score)),
158
+ score: parseFloat(String(vector_score)),
153
159
  content,
154
160
  metadata: { ...rest, source_table: table },
155
161
  });
@@ -162,14 +168,32 @@ export class MultiTablePostgresProvider extends BaseVectorProvider {
162
168
  }
163
169
  }
164
170
 
165
- // Merge and rank by score descending
166
- const sortedResults = allResults.sort((a, b) => b.score - a.score);
171
+ // 1. Group results by table to ensure we take the top ones from each
172
+ const resultsByTable: Record<string, VectorMatch[]> = {};
173
+ for (const res of allResults) {
174
+ const table = res.metadata?.source_table as string;
175
+ if (!resultsByTable[table]) resultsByTable[table] = [];
176
+ resultsByTable[table].push(res);
177
+ }
178
+
179
+ // 2. Take top 3 from EACH table guaranteed, then fill the rest with overall best
180
+ const balancedResults: VectorMatch[] = [];
181
+ const tables = Object.keys(resultsByTable);
182
+
183
+ // First, ensure every table's best match is included
184
+ for (const table of tables) {
185
+ balancedResults.push(...resultsByTable[table].slice(0, 3));
186
+ }
187
+
188
+ // 3. Sort the balanced list by score
189
+ const finalSorted = balancedResults.sort((a, b) => b.score - a.score);
167
190
 
168
- if (sortedResults.length > 0) {
169
- console.log(`[MultiTablePostgresProvider] Top match from "${sortedResults[0].metadata?.source_table ?? 'unknown'}" with score ${sortedResults[0].score.toFixed(4)}`);
191
+ if (finalSorted.length > 0) {
192
+ console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
193
+ console.log(`[MultiTablePostgresProvider] Final top match from "${finalSorted[0].metadata?.source_table ?? 'unknown'}" with score ${finalSorted[0].score.toFixed(4)}`);
170
194
  }
171
195
 
172
- return sortedResults.slice(0, Math.max(topK, 15));
196
+ return finalSorted.slice(0, Math.max(topK, 15));
173
197
  }
174
198
 
175
199
  async delete(_id: string | number, _namespace?: string): Promise<void> { // eslint-disable-line @typescript-eslint/no-unused-vars