@retrivora-ai/rag-engine 0.2.9 → 0.3.1
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.
- package/dist/{RagConfig--ibz0b3W.d.mts → RagConfig-D3Inaf9N.d.mts} +1 -1
- package/dist/{RagConfig--ibz0b3W.d.ts → RagConfig-D3Inaf9N.d.ts} +1 -1
- package/dist/{chunk-5U2DHPIX.mjs → chunk-GT72OIOD.mjs} +49 -6
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +49 -6
- package/dist/handlers/index.mjs +1 -1
- package/dist/{index-Dr1HN0se.d.ts → index-BhNJQ2SS.d.ts} +1 -1
- package/dist/{index-w8qIEFvi.d.mts → index-Ymwm-_OR.d.mts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/server.d.mts +5 -7
- package/dist/server.d.ts +5 -7
- package/dist/server.js +88 -119
- package/dist/server.mjs +40 -114
- package/package.json +1 -1
- package/src/config/RagConfig.ts +1 -1
- package/src/core/ConfigValidator.ts +16 -0
- package/src/core/Pipeline.ts +42 -5
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +62 -168
package/dist/server.js
CHANGED
|
@@ -1528,6 +1528,20 @@ var ConfigValidator = class {
|
|
|
1528
1528
|
severity: "error"
|
|
1529
1529
|
});
|
|
1530
1530
|
}
|
|
1531
|
+
if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
|
|
1532
|
+
errors.push({
|
|
1533
|
+
field: "vectorDb.options.tables",
|
|
1534
|
+
message: "PostgreSQL tables must be a string or a string array",
|
|
1535
|
+
severity: "error"
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
if (opts.searchFields && typeof opts.searchFields !== "string" && !Array.isArray(opts.searchFields)) {
|
|
1539
|
+
errors.push({
|
|
1540
|
+
field: "vectorDb.options.searchFields",
|
|
1541
|
+
message: "PostgreSQL searchFields must be a string or a string array",
|
|
1542
|
+
severity: "error"
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1531
1545
|
return errors;
|
|
1532
1546
|
}
|
|
1533
1547
|
/**
|
|
@@ -2773,7 +2787,7 @@ function isLikelyPromptPhrase(value) {
|
|
|
2773
2787
|
return /^(what|which|who|where|when|why|how)\b/i.test(value.trim());
|
|
2774
2788
|
}
|
|
2775
2789
|
function extractQueryFieldHints(question) {
|
|
2776
|
-
var _a, _b, _c;
|
|
2790
|
+
var _a, _b, _c, _d;
|
|
2777
2791
|
if (!question.trim()) return [];
|
|
2778
2792
|
const hints = /* @__PURE__ */ new Map();
|
|
2779
2793
|
const addHint = (value, field) => {
|
|
@@ -2795,9 +2809,38 @@ function extractQueryFieldHints(question) {
|
|
|
2795
2809
|
/\b(?:who|what)\s+(?:is|are|was|were)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
2796
2810
|
/\b(?:about|for|regarding)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi
|
|
2797
2811
|
];
|
|
2812
|
+
const personCompanyPatterns = [
|
|
2813
|
+
/\bcompany(?:\s+name)?\s+(?:of|for)\s+["']?([^"'\n?.!,]{2,120})["']?(?=[?.!,]|$)/gi,
|
|
2814
|
+
/\b(?:which|what)\s+company\s+does\s+["']?([^"'\n?.!,]{2,120})["']?\s+work(?:\s+for|\s+at)?(?=[?.!,]|$)/gi,
|
|
2815
|
+
/\bwhere\s+does\s+["']?([^"'\n?.!,]{2,120})["']?\s+work(?:\s+for|\s+at)?(?=[?.!,]|$)/gi
|
|
2816
|
+
];
|
|
2817
|
+
for (const pattern of personCompanyPatterns) {
|
|
2818
|
+
for (const match of question.matchAll(pattern)) {
|
|
2819
|
+
const name = match[1];
|
|
2820
|
+
if (name) addHint(name, "name");
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
const universalPatterns = [
|
|
2824
|
+
{ regex: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi, field: "email", group: 1 },
|
|
2825
|
+
{ regex: /(\+?\d[\d\-\.\s\(\)]{6,}\d)/g, field: "phone", group: 1 },
|
|
2826
|
+
{ 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 },
|
|
2827
|
+
{ regex: /(\$\s?\d{1,3}(?:,\d{3})*(?:\.\d+)?)/g, field: "amount", group: 1 },
|
|
2828
|
+
{ regex: /\b(ID|id|identifier)[: ]\s*([A-Za-z0-9\-]{3,})\b/gi, field: "id", group: 2 },
|
|
2829
|
+
// Generic quoted phrase / proper-noun sequences as keywords (already partially handled above)
|
|
2830
|
+
{ regex: /"([^"]{2,120})"/g, group: 1 },
|
|
2831
|
+
{ regex: /'([^']{2,120})'/g, group: 1 }
|
|
2832
|
+
];
|
|
2833
|
+
for (const p of universalPatterns) {
|
|
2834
|
+
for (const match of question.matchAll(p.regex)) {
|
|
2835
|
+
const val = p.group ? (_a = match[p.group]) != null ? _a : match[0] : match[0];
|
|
2836
|
+
if (!val) continue;
|
|
2837
|
+
if (p.field) addHint(val, p.field);
|
|
2838
|
+
else addHint(val);
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2798
2841
|
for (const pattern of naturalQuestionPatterns) {
|
|
2799
2842
|
for (const match of question.matchAll(pattern)) {
|
|
2800
|
-
const value = (
|
|
2843
|
+
const value = (_b = match[2]) != null ? _b : match[1];
|
|
2801
2844
|
if (value) addHint(value);
|
|
2802
2845
|
}
|
|
2803
2846
|
}
|
|
@@ -2810,8 +2853,8 @@ function extractQueryFieldHints(question) {
|
|
|
2810
2853
|
];
|
|
2811
2854
|
for (const pattern of fieldValuePatterns) {
|
|
2812
2855
|
for (const match of question.matchAll(pattern)) {
|
|
2813
|
-
const field = normalizeHintValue((
|
|
2814
|
-
const value = (
|
|
2856
|
+
const field = normalizeHintValue((_c = match[1]) != null ? _c : "");
|
|
2857
|
+
const value = (_d = match[2]) != null ? _d : "";
|
|
2815
2858
|
if (field && !isLikelyPromptPhrase(field)) {
|
|
2816
2859
|
addHint(value, field);
|
|
2817
2860
|
} else {
|
|
@@ -2819,7 +2862,7 @@ function extractQueryFieldHints(question) {
|
|
|
2819
2862
|
}
|
|
2820
2863
|
}
|
|
2821
2864
|
}
|
|
2822
|
-
for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){
|
|
2865
|
+
for (const match of question.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g)) {
|
|
2823
2866
|
addHint(match[0]);
|
|
2824
2867
|
}
|
|
2825
2868
|
return [...hints.values()];
|
|
@@ -2930,7 +2973,7 @@ var Pipeline = class {
|
|
|
2930
2973
|
const queryVector = await this.embeddingProvider.embed(question, { taskType: "query" });
|
|
2931
2974
|
const fieldHints = extractQueryFieldHints(question);
|
|
2932
2975
|
const filter = buildQueryFilter(question, fieldHints);
|
|
2933
|
-
filter.
|
|
2976
|
+
filter.__entityHints = fieldHints;
|
|
2934
2977
|
const rawMatches = await this.vectorDB.query(queryVector, topK, ns, filter);
|
|
2935
2978
|
const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
|
|
2936
2979
|
const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
|
|
@@ -3784,26 +3827,10 @@ init_PostgreSQLProvider();
|
|
|
3784
3827
|
// src/providers/vectordb/MultiTablePostgresProvider.ts
|
|
3785
3828
|
var import_pg2 = require("pg");
|
|
3786
3829
|
init_BaseVectorProvider();
|
|
3787
|
-
function normalizeFieldName(value) {
|
|
3788
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
3789
|
-
}
|
|
3790
|
-
function sanitizeFilterHints(rawFilter) {
|
|
3791
|
-
if (!Array.isArray(rawFilter == null ? void 0 : rawFilter.__fieldHints)) return [];
|
|
3792
|
-
return rawFilter.__fieldHints.filter((hint) => typeof hint === "object" && hint !== null).map((hint) => __spreadValues({
|
|
3793
|
-
value: typeof hint.value === "string" ? hint.value.trim().toLowerCase() : ""
|
|
3794
|
-
}, typeof hint.field === "string" && hint.field.trim() ? { field: normalizeFieldName(hint.field) } : {})).filter((hint) => Boolean(hint.value));
|
|
3795
|
-
}
|
|
3796
|
-
function stripInternalFilterKeys3(filter) {
|
|
3797
|
-
if (!filter) return {};
|
|
3798
|
-
return Object.fromEntries(
|
|
3799
|
-
Object.entries(filter).filter(([key]) => !key.startsWith("__"))
|
|
3800
|
-
);
|
|
3801
|
-
}
|
|
3802
3830
|
var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
3803
3831
|
constructor(config) {
|
|
3804
|
-
var _a, _b, _c;
|
|
3832
|
+
var _a, _b, _c, _d, _e;
|
|
3805
3833
|
super(config);
|
|
3806
|
-
this.tableSearchConfig = /* @__PURE__ */ new Map();
|
|
3807
3834
|
const opts = config.options || {};
|
|
3808
3835
|
if (!opts.connectionString) {
|
|
3809
3836
|
throw new Error("[MultiTablePostgresProvider] options.connectionString is required");
|
|
@@ -3817,6 +3844,8 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
3817
3844
|
"[MultiTablePostgresProvider] No tables configured. Set VECTOR_DB_TABLES as a comma-separated list of table names or pass options.tables."
|
|
3818
3845
|
);
|
|
3819
3846
|
}
|
|
3847
|
+
const rawSearchFields = (_e = (_d = opts.searchFields) != null ? _d : process.env.VECTOR_DB_SEARCH_FIELDS) != null ? _e : ["name", "product_name", "productname", "title"];
|
|
3848
|
+
this.searchFields = typeof rawSearchFields === "string" ? rawSearchFields.split(",").map((f) => f.trim()).filter(Boolean) : rawSearchFields;
|
|
3820
3849
|
}
|
|
3821
3850
|
async initialize() {
|
|
3822
3851
|
this.pool = new import_pg2.Pool({ connectionString: this.connectionString });
|
|
@@ -3842,7 +3871,6 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
3842
3871
|
if (this.tables.length === 0) {
|
|
3843
3872
|
console.warn('[MultiTablePostgresProvider] No tables with "embedding" columns found in the database.');
|
|
3844
3873
|
} else {
|
|
3845
|
-
this.tableSearchConfig = await this.loadTableSearchConfig(client, this.tables);
|
|
3846
3874
|
console.log(
|
|
3847
3875
|
`[MultiTablePostgresProvider] Connected. Searching across ${this.tables.length} table(s): ${this.tables.join(", ")}`
|
|
3848
3876
|
);
|
|
@@ -3872,59 +3900,44 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
3872
3900
|
* Query all configured tables and merge results, sorted by cosine similarity score.
|
|
3873
3901
|
*/
|
|
3874
3902
|
async query(vector, topK, _namespace, _filter) {
|
|
3875
|
-
var _a, _b;
|
|
3903
|
+
var _a, _b, _c;
|
|
3876
3904
|
if (!this.pool) {
|
|
3877
3905
|
throw new Error("[MultiTablePostgresProvider] Provider not initialized. Call initialize() first.");
|
|
3878
3906
|
}
|
|
3879
3907
|
const vectorLiteral = `[${vector.join(",")}]`;
|
|
3880
3908
|
const allResults = [];
|
|
3881
3909
|
console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
|
|
3882
|
-
const queryText = _filter == null ? void 0 : _filter.
|
|
3883
|
-
const
|
|
3884
|
-
|
|
3910
|
+
const queryText = _filter == null ? void 0 : _filter.queryText;
|
|
3911
|
+
const entityHints = Array.isArray(_filter == null ? void 0 : _filter.__entityHints) ? _filter.__entityHints.filter(
|
|
3912
|
+
(hint) => typeof hint === "object" && hint !== null && typeof hint.value === "string"
|
|
3913
|
+
).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
|
|
3914
|
+
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
3915
|
+
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
|
|
3885
3916
|
const queryPromises = this.tables.map(async (table) => {
|
|
3886
|
-
var _a2, _b2, _c;
|
|
3887
3917
|
try {
|
|
3888
3918
|
let sqlQuery = "";
|
|
3889
3919
|
let params = [];
|
|
3890
|
-
const tableConfig = this.tableSearchConfig.get(table);
|
|
3891
|
-
const availableFields = (_a2 = tableConfig == null ? void 0 : tableConfig.availableFields) != null ? _a2 : [];
|
|
3892
|
-
const searchableFields = (_b2 = tableConfig == null ? void 0 : tableConfig.searchableFields) != null ? _b2 : [];
|
|
3893
|
-
const normalizedFieldMap = (_c = tableConfig == null ? void 0 : tableConfig.normalizedFieldMap) != null ? _c : /* @__PURE__ */ new Map();
|
|
3894
|
-
const whereConditions = [];
|
|
3895
3920
|
if (queryText) {
|
|
3896
|
-
const
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
params.push(hint.value);
|
|
3906
|
-
const paramIndex = params.length;
|
|
3907
|
-
return `CASE WHEN LOWER(COALESCE(to_jsonb(t)->>'${column}', '')) = $${paramIndex}::text THEN 1 ELSE 0 END`;
|
|
3908
|
-
}).filter((expr) => Boolean(expr));
|
|
3909
|
-
const exactFieldScoreExpr = fieldSpecificChecks.length > 0 ? `GREATEST(${fieldSpecificChecks.join(", ")})` : "0";
|
|
3921
|
+
const hasEntityHints = entityHints.length > 0;
|
|
3922
|
+
const exactNameScoreExpr = hasEntityHints ? `+ (
|
|
3923
|
+
SELECT COALESCE(MAX(
|
|
3924
|
+
CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
|
|
3925
|
+
THEN 1.0 ELSE 0.0 END
|
|
3926
|
+
), 0)
|
|
3927
|
+
FROM jsonb_each_text(to_jsonb(t)) AS kv(key, val)
|
|
3928
|
+
WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
|
|
3929
|
+
) * 3.0` : "";
|
|
3910
3930
|
sqlQuery = `
|
|
3911
3931
|
SELECT *,
|
|
3912
3932
|
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
3913
|
-
COALESCE(ts_rank(to_tsvector('english', t
|
|
3914
|
-
${
|
|
3915
|
-
${exactFieldScoreExpr} AS exact_field_score,
|
|
3916
|
-
(
|
|
3917
|
-
(1 - (embedding <=> $1::vector)) +
|
|
3918
|
-
(COALESCE(ts_rank(to_tsvector('english', t.*::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) +
|
|
3919
|
-
(${exactValueScoreExpr} * 3.0) +
|
|
3920
|
-
(${exactFieldScoreExpr} * 5.0)
|
|
3921
|
-
) AS hybrid_score
|
|
3933
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) AS keyword_score,
|
|
3934
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
3922
3935
|
FROM "${table}" t
|
|
3923
3936
|
ORDER BY hybrid_score DESC
|
|
3924
3937
|
LIMIT 50
|
|
3925
3938
|
`;
|
|
3939
|
+
params = [vectorLiteral, queryText];
|
|
3926
3940
|
} else {
|
|
3927
|
-
params = [vectorLiteral];
|
|
3928
3941
|
sqlQuery = `
|
|
3929
3942
|
SELECT *,
|
|
3930
3943
|
(1 - (embedding <=> $1::vector)) AS hybrid_score
|
|
@@ -3932,16 +3945,7 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
3932
3945
|
ORDER BY hybrid_score DESC
|
|
3933
3946
|
LIMIT 50
|
|
3934
3947
|
`;
|
|
3935
|
-
|
|
3936
|
-
for (const [key, value] of Object.entries(explicitFilter)) {
|
|
3937
|
-
const column = availableFields.includes(key) ? key : normalizedFieldMap.get(normalizeFieldName(key));
|
|
3938
|
-
if (!column) continue;
|
|
3939
|
-
params.push(String(value));
|
|
3940
|
-
whereConditions.push(`COALESCE(to_jsonb(t)->>'${column}', '') = $${params.length}::text`);
|
|
3941
|
-
}
|
|
3942
|
-
if (whereConditions.length > 0) {
|
|
3943
|
-
sqlQuery = sqlQuery.replace(`FROM "${table}" t`, `FROM "${table}" t
|
|
3944
|
-
WHERE ${whereConditions.join(" AND ")}`);
|
|
3948
|
+
params = [vectorLiteral];
|
|
3945
3949
|
}
|
|
3946
3950
|
const result = await this.pool.query(sqlQuery, params);
|
|
3947
3951
|
if (result.rowCount && result.rowCount > 0) {
|
|
@@ -3951,12 +3955,11 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
3951
3955
|
}
|
|
3952
3956
|
const tableResults = [];
|
|
3953
3957
|
for (const row of result.rows) {
|
|
3954
|
-
const
|
|
3958
|
+
const _a2 = row, { hybrid_score, id } = _a2, rest = __objRest(_a2, ["hybrid_score", "id"]);
|
|
3955
3959
|
delete rest.embedding;
|
|
3956
3960
|
delete rest.vector_score;
|
|
3957
3961
|
delete rest.keyword_score;
|
|
3958
|
-
delete rest.
|
|
3959
|
-
delete rest.exact_field_score;
|
|
3962
|
+
delete rest.exact_name_score;
|
|
3960
3963
|
const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
|
|
3961
3964
|
` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
|
|
3962
3965
|
tableResults.push({
|
|
@@ -3979,12 +3982,23 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
3979
3982
|
for (const tableResults of resultsArray) {
|
|
3980
3983
|
allResults.push(...tableResults);
|
|
3981
3984
|
}
|
|
3982
|
-
const
|
|
3985
|
+
const resultsByTable = {};
|
|
3986
|
+
for (const res of allResults) {
|
|
3987
|
+
const table = (_a = res.metadata) == null ? void 0 : _a.source_table;
|
|
3988
|
+
if (!resultsByTable[table]) resultsByTable[table] = [];
|
|
3989
|
+
resultsByTable[table].push(res);
|
|
3990
|
+
}
|
|
3991
|
+
const balancedResults = [];
|
|
3992
|
+
const tables = Object.keys(resultsByTable);
|
|
3993
|
+
for (const table of tables) {
|
|
3994
|
+
balancedResults.push(...resultsByTable[table].slice(0, 3));
|
|
3995
|
+
}
|
|
3996
|
+
const finalSorted = balancedResults.sort((a, b) => b.score - a.score);
|
|
3983
3997
|
if (finalSorted.length > 0) {
|
|
3984
3998
|
console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
|
|
3985
|
-
console.log(`[MultiTablePostgresProvider] Final top match from "${(
|
|
3999
|
+
console.log(`[MultiTablePostgresProvider] Final top match from "${(_c = (_b = finalSorted[0].metadata) == null ? void 0 : _b.source_table) != null ? _c : "unknown"}" with score ${finalSorted[0].score.toFixed(4)}`);
|
|
3986
4000
|
}
|
|
3987
|
-
return finalSorted.slice(0, topK);
|
|
4001
|
+
return finalSorted.slice(0, Math.max(topK, 15));
|
|
3988
4002
|
}
|
|
3989
4003
|
async delete(_id, _namespace) {
|
|
3990
4004
|
console.warn("[MultiTablePostgresProvider] delete() is a no-op for multi-table mode.");
|
|
@@ -4005,51 +4019,6 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
4005
4019
|
await this.pool.end();
|
|
4006
4020
|
}
|
|
4007
4021
|
}
|
|
4008
|
-
async loadTableSearchConfig(client, tables) {
|
|
4009
|
-
var _a, _b;
|
|
4010
|
-
if (tables.length === 0) return /* @__PURE__ */ new Map();
|
|
4011
|
-
const configuredSearchFields = this.parseConfiguredSearchFields();
|
|
4012
|
-
const result = await client.query(`
|
|
4013
|
-
SELECT table_name, column_name, data_type
|
|
4014
|
-
FROM information_schema.columns
|
|
4015
|
-
WHERE table_schema = 'public'
|
|
4016
|
-
AND table_name = ANY($1::text[])
|
|
4017
|
-
ORDER BY ordinal_position
|
|
4018
|
-
`, [tables]);
|
|
4019
|
-
const rowsByTable = /* @__PURE__ */ new Map();
|
|
4020
|
-
for (const row of result.rows) {
|
|
4021
|
-
const rows = (_a = rowsByTable.get(row.table_name)) != null ? _a : [];
|
|
4022
|
-
rows.push({ column_name: row.column_name, data_type: row.data_type });
|
|
4023
|
-
rowsByTable.set(row.table_name, rows);
|
|
4024
|
-
}
|
|
4025
|
-
const configByTable = /* @__PURE__ */ new Map();
|
|
4026
|
-
for (const table of tables) {
|
|
4027
|
-
const columns = (_b = rowsByTable.get(table)) != null ? _b : [];
|
|
4028
|
-
const availableFields = columns.filter(({ column_name }) => column_name !== "embedding").map(({ column_name }) => column_name);
|
|
4029
|
-
const inferredFields = columns.filter(
|
|
4030
|
-
({ column_name, data_type }) => column_name !== "embedding" && !["ARRAY", "json", "jsonb", "bytea", "tsvector", "USER-DEFINED"].includes(data_type)
|
|
4031
|
-
).map(({ column_name }) => column_name);
|
|
4032
|
-
const searchableFields = configuredSearchFields.length > 0 ? configuredSearchFields.filter((field) => columns.some((column) => column.column_name === field)) : inferredFields;
|
|
4033
|
-
const normalizedFieldMap = /* @__PURE__ */ new Map();
|
|
4034
|
-
for (const field of availableFields) {
|
|
4035
|
-
normalizedFieldMap.set(normalizeFieldName(field), field);
|
|
4036
|
-
}
|
|
4037
|
-
configByTable.set(table, {
|
|
4038
|
-
availableFields,
|
|
4039
|
-
searchableFields,
|
|
4040
|
-
normalizedFieldMap
|
|
4041
|
-
});
|
|
4042
|
-
}
|
|
4043
|
-
return configByTable;
|
|
4044
|
-
}
|
|
4045
|
-
parseConfiguredSearchFields() {
|
|
4046
|
-
var _a;
|
|
4047
|
-
const raw = (_a = this.config.options) == null ? void 0 : _a.searchFields;
|
|
4048
|
-
if (Array.isArray(raw)) {
|
|
4049
|
-
return raw.filter((field) => typeof field === "string" && field.trim().length > 0);
|
|
4050
|
-
}
|
|
4051
|
-
return [];
|
|
4052
|
-
}
|
|
4053
4022
|
};
|
|
4054
4023
|
|
|
4055
4024
|
// src/server.ts
|
package/dist/server.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
createIngestHandler,
|
|
35
35
|
createUploadHandler,
|
|
36
36
|
getRagConfig
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-GT72OIOD.mjs";
|
|
38
38
|
import "./chunk-EDLTMSNY.mjs";
|
|
39
39
|
import {
|
|
40
40
|
PineconeProvider
|
|
@@ -335,26 +335,10 @@ function createFromPreset(presetName) {
|
|
|
335
335
|
|
|
336
336
|
// src/providers/vectordb/MultiTablePostgresProvider.ts
|
|
337
337
|
import { Pool } from "pg";
|
|
338
|
-
function normalizeFieldName(value) {
|
|
339
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
340
|
-
}
|
|
341
|
-
function sanitizeFilterHints(rawFilter) {
|
|
342
|
-
if (!Array.isArray(rawFilter == null ? void 0 : rawFilter.__fieldHints)) return [];
|
|
343
|
-
return rawFilter.__fieldHints.filter((hint) => typeof hint === "object" && hint !== null).map((hint) => __spreadValues({
|
|
344
|
-
value: typeof hint.value === "string" ? hint.value.trim().toLowerCase() : ""
|
|
345
|
-
}, typeof hint.field === "string" && hint.field.trim() ? { field: normalizeFieldName(hint.field) } : {})).filter((hint) => Boolean(hint.value));
|
|
346
|
-
}
|
|
347
|
-
function stripInternalFilterKeys(filter) {
|
|
348
|
-
if (!filter) return {};
|
|
349
|
-
return Object.fromEntries(
|
|
350
|
-
Object.entries(filter).filter(([key]) => !key.startsWith("__"))
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
338
|
var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
354
339
|
constructor(config) {
|
|
355
|
-
var _a, _b, _c;
|
|
340
|
+
var _a, _b, _c, _d, _e;
|
|
356
341
|
super(config);
|
|
357
|
-
this.tableSearchConfig = /* @__PURE__ */ new Map();
|
|
358
342
|
const opts = config.options || {};
|
|
359
343
|
if (!opts.connectionString) {
|
|
360
344
|
throw new Error("[MultiTablePostgresProvider] options.connectionString is required");
|
|
@@ -368,6 +352,8 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
368
352
|
"[MultiTablePostgresProvider] No tables configured. Set VECTOR_DB_TABLES as a comma-separated list of table names or pass options.tables."
|
|
369
353
|
);
|
|
370
354
|
}
|
|
355
|
+
const rawSearchFields = (_e = (_d = opts.searchFields) != null ? _d : process.env.VECTOR_DB_SEARCH_FIELDS) != null ? _e : ["name", "product_name", "productname", "title"];
|
|
356
|
+
this.searchFields = typeof rawSearchFields === "string" ? rawSearchFields.split(",").map((f) => f.trim()).filter(Boolean) : rawSearchFields;
|
|
371
357
|
}
|
|
372
358
|
async initialize() {
|
|
373
359
|
this.pool = new Pool({ connectionString: this.connectionString });
|
|
@@ -393,7 +379,6 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
393
379
|
if (this.tables.length === 0) {
|
|
394
380
|
console.warn('[MultiTablePostgresProvider] No tables with "embedding" columns found in the database.');
|
|
395
381
|
} else {
|
|
396
|
-
this.tableSearchConfig = await this.loadTableSearchConfig(client, this.tables);
|
|
397
382
|
console.log(
|
|
398
383
|
`[MultiTablePostgresProvider] Connected. Searching across ${this.tables.length} table(s): ${this.tables.join(", ")}`
|
|
399
384
|
);
|
|
@@ -423,59 +408,44 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
423
408
|
* Query all configured tables and merge results, sorted by cosine similarity score.
|
|
424
409
|
*/
|
|
425
410
|
async query(vector, topK, _namespace, _filter) {
|
|
426
|
-
var _a, _b;
|
|
411
|
+
var _a, _b, _c;
|
|
427
412
|
if (!this.pool) {
|
|
428
413
|
throw new Error("[MultiTablePostgresProvider] Provider not initialized. Call initialize() first.");
|
|
429
414
|
}
|
|
430
415
|
const vectorLiteral = `[${vector.join(",")}]`;
|
|
431
416
|
const allResults = [];
|
|
432
417
|
console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
|
|
433
|
-
const queryText = _filter == null ? void 0 : _filter.
|
|
434
|
-
const
|
|
435
|
-
|
|
418
|
+
const queryText = _filter == null ? void 0 : _filter.queryText;
|
|
419
|
+
const entityHints = Array.isArray(_filter == null ? void 0 : _filter.__entityHints) ? _filter.__entityHints.filter(
|
|
420
|
+
(hint) => typeof hint === "object" && hint !== null && typeof hint.value === "string"
|
|
421
|
+
).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
|
|
422
|
+
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
423
|
+
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
|
|
436
424
|
const queryPromises = this.tables.map(async (table) => {
|
|
437
|
-
var _a2, _b2, _c;
|
|
438
425
|
try {
|
|
439
426
|
let sqlQuery = "";
|
|
440
427
|
let params = [];
|
|
441
|
-
const tableConfig = this.tableSearchConfig.get(table);
|
|
442
|
-
const availableFields = (_a2 = tableConfig == null ? void 0 : tableConfig.availableFields) != null ? _a2 : [];
|
|
443
|
-
const searchableFields = (_b2 = tableConfig == null ? void 0 : tableConfig.searchableFields) != null ? _b2 : [];
|
|
444
|
-
const normalizedFieldMap = (_c = tableConfig == null ? void 0 : tableConfig.normalizedFieldMap) != null ? _c : /* @__PURE__ */ new Map();
|
|
445
|
-
const whereConditions = [];
|
|
446
428
|
if (queryText) {
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
params.push(hint.value);
|
|
457
|
-
const paramIndex = params.length;
|
|
458
|
-
return `CASE WHEN LOWER(COALESCE(to_jsonb(t)->>'${column}', '')) = $${paramIndex}::text THEN 1 ELSE 0 END`;
|
|
459
|
-
}).filter((expr) => Boolean(expr));
|
|
460
|
-
const exactFieldScoreExpr = fieldSpecificChecks.length > 0 ? `GREATEST(${fieldSpecificChecks.join(", ")})` : "0";
|
|
429
|
+
const hasEntityHints = entityHints.length > 0;
|
|
430
|
+
const exactNameScoreExpr = hasEntityHints ? `+ (
|
|
431
|
+
SELECT COALESCE(MAX(
|
|
432
|
+
CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
|
|
433
|
+
THEN 1.0 ELSE 0.0 END
|
|
434
|
+
), 0)
|
|
435
|
+
FROM jsonb_each_text(to_jsonb(t)) AS kv(key, val)
|
|
436
|
+
WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
|
|
437
|
+
) * 3.0` : "";
|
|
461
438
|
sqlQuery = `
|
|
462
439
|
SELECT *,
|
|
463
440
|
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
464
|
-
COALESCE(ts_rank(to_tsvector('english', t
|
|
465
|
-
${
|
|
466
|
-
${exactFieldScoreExpr} AS exact_field_score,
|
|
467
|
-
(
|
|
468
|
-
(1 - (embedding <=> $1::vector)) +
|
|
469
|
-
(COALESCE(ts_rank(to_tsvector('english', t.*::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) +
|
|
470
|
-
(${exactValueScoreExpr} * 3.0) +
|
|
471
|
-
(${exactFieldScoreExpr} * 5.0)
|
|
472
|
-
) AS hybrid_score
|
|
441
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) AS keyword_score,
|
|
442
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), NULLIF(REPLACE(plainto_tsquery('english', $2)::text, '&', '|'), '')::tsquery), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
473
443
|
FROM "${table}" t
|
|
474
444
|
ORDER BY hybrid_score DESC
|
|
475
445
|
LIMIT 50
|
|
476
446
|
`;
|
|
447
|
+
params = [vectorLiteral, queryText];
|
|
477
448
|
} else {
|
|
478
|
-
params = [vectorLiteral];
|
|
479
449
|
sqlQuery = `
|
|
480
450
|
SELECT *,
|
|
481
451
|
(1 - (embedding <=> $1::vector)) AS hybrid_score
|
|
@@ -483,16 +453,7 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
483
453
|
ORDER BY hybrid_score DESC
|
|
484
454
|
LIMIT 50
|
|
485
455
|
`;
|
|
486
|
-
|
|
487
|
-
for (const [key, value] of Object.entries(explicitFilter)) {
|
|
488
|
-
const column = availableFields.includes(key) ? key : normalizedFieldMap.get(normalizeFieldName(key));
|
|
489
|
-
if (!column) continue;
|
|
490
|
-
params.push(String(value));
|
|
491
|
-
whereConditions.push(`COALESCE(to_jsonb(t)->>'${column}', '') = $${params.length}::text`);
|
|
492
|
-
}
|
|
493
|
-
if (whereConditions.length > 0) {
|
|
494
|
-
sqlQuery = sqlQuery.replace(`FROM "${table}" t`, `FROM "${table}" t
|
|
495
|
-
WHERE ${whereConditions.join(" AND ")}`);
|
|
456
|
+
params = [vectorLiteral];
|
|
496
457
|
}
|
|
497
458
|
const result = await this.pool.query(sqlQuery, params);
|
|
498
459
|
if (result.rowCount && result.rowCount > 0) {
|
|
@@ -502,12 +463,11 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
502
463
|
}
|
|
503
464
|
const tableResults = [];
|
|
504
465
|
for (const row of result.rows) {
|
|
505
|
-
const
|
|
466
|
+
const _a2 = row, { hybrid_score, id } = _a2, rest = __objRest(_a2, ["hybrid_score", "id"]);
|
|
506
467
|
delete rest.embedding;
|
|
507
468
|
delete rest.vector_score;
|
|
508
469
|
delete rest.keyword_score;
|
|
509
|
-
delete rest.
|
|
510
|
-
delete rest.exact_field_score;
|
|
470
|
+
delete rest.exact_name_score;
|
|
511
471
|
const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
|
|
512
472
|
` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
|
|
513
473
|
tableResults.push({
|
|
@@ -530,12 +490,23 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
530
490
|
for (const tableResults of resultsArray) {
|
|
531
491
|
allResults.push(...tableResults);
|
|
532
492
|
}
|
|
533
|
-
const
|
|
493
|
+
const resultsByTable = {};
|
|
494
|
+
for (const res of allResults) {
|
|
495
|
+
const table = (_a = res.metadata) == null ? void 0 : _a.source_table;
|
|
496
|
+
if (!resultsByTable[table]) resultsByTable[table] = [];
|
|
497
|
+
resultsByTable[table].push(res);
|
|
498
|
+
}
|
|
499
|
+
const balancedResults = [];
|
|
500
|
+
const tables = Object.keys(resultsByTable);
|
|
501
|
+
for (const table of tables) {
|
|
502
|
+
balancedResults.push(...resultsByTable[table].slice(0, 3));
|
|
503
|
+
}
|
|
504
|
+
const finalSorted = balancedResults.sort((a, b) => b.score - a.score);
|
|
534
505
|
if (finalSorted.length > 0) {
|
|
535
506
|
console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
|
|
536
|
-
console.log(`[MultiTablePostgresProvider] Final top match from "${(
|
|
507
|
+
console.log(`[MultiTablePostgresProvider] Final top match from "${(_c = (_b = finalSorted[0].metadata) == null ? void 0 : _b.source_table) != null ? _c : "unknown"}" with score ${finalSorted[0].score.toFixed(4)}`);
|
|
537
508
|
}
|
|
538
|
-
return finalSorted.slice(0, topK);
|
|
509
|
+
return finalSorted.slice(0, Math.max(topK, 15));
|
|
539
510
|
}
|
|
540
511
|
async delete(_id, _namespace) {
|
|
541
512
|
console.warn("[MultiTablePostgresProvider] delete() is a no-op for multi-table mode.");
|
|
@@ -556,51 +527,6 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
556
527
|
await this.pool.end();
|
|
557
528
|
}
|
|
558
529
|
}
|
|
559
|
-
async loadTableSearchConfig(client, tables) {
|
|
560
|
-
var _a, _b;
|
|
561
|
-
if (tables.length === 0) return /* @__PURE__ */ new Map();
|
|
562
|
-
const configuredSearchFields = this.parseConfiguredSearchFields();
|
|
563
|
-
const result = await client.query(`
|
|
564
|
-
SELECT table_name, column_name, data_type
|
|
565
|
-
FROM information_schema.columns
|
|
566
|
-
WHERE table_schema = 'public'
|
|
567
|
-
AND table_name = ANY($1::text[])
|
|
568
|
-
ORDER BY ordinal_position
|
|
569
|
-
`, [tables]);
|
|
570
|
-
const rowsByTable = /* @__PURE__ */ new Map();
|
|
571
|
-
for (const row of result.rows) {
|
|
572
|
-
const rows = (_a = rowsByTable.get(row.table_name)) != null ? _a : [];
|
|
573
|
-
rows.push({ column_name: row.column_name, data_type: row.data_type });
|
|
574
|
-
rowsByTable.set(row.table_name, rows);
|
|
575
|
-
}
|
|
576
|
-
const configByTable = /* @__PURE__ */ new Map();
|
|
577
|
-
for (const table of tables) {
|
|
578
|
-
const columns = (_b = rowsByTable.get(table)) != null ? _b : [];
|
|
579
|
-
const availableFields = columns.filter(({ column_name }) => column_name !== "embedding").map(({ column_name }) => column_name);
|
|
580
|
-
const inferredFields = columns.filter(
|
|
581
|
-
({ column_name, data_type }) => column_name !== "embedding" && !["ARRAY", "json", "jsonb", "bytea", "tsvector", "USER-DEFINED"].includes(data_type)
|
|
582
|
-
).map(({ column_name }) => column_name);
|
|
583
|
-
const searchableFields = configuredSearchFields.length > 0 ? configuredSearchFields.filter((field) => columns.some((column) => column.column_name === field)) : inferredFields;
|
|
584
|
-
const normalizedFieldMap = /* @__PURE__ */ new Map();
|
|
585
|
-
for (const field of availableFields) {
|
|
586
|
-
normalizedFieldMap.set(normalizeFieldName(field), field);
|
|
587
|
-
}
|
|
588
|
-
configByTable.set(table, {
|
|
589
|
-
availableFields,
|
|
590
|
-
searchableFields,
|
|
591
|
-
normalizedFieldMap
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
return configByTable;
|
|
595
|
-
}
|
|
596
|
-
parseConfiguredSearchFields() {
|
|
597
|
-
var _a;
|
|
598
|
-
const raw = (_a = this.config.options) == null ? void 0 : _a.searchFields;
|
|
599
|
-
if (Array.isArray(raw)) {
|
|
600
|
-
return raw.filter((field) => typeof field === "string" && field.trim().length > 0);
|
|
601
|
-
}
|
|
602
|
-
return [];
|
|
603
|
-
}
|
|
604
530
|
};
|
|
605
531
|
export {
|
|
606
532
|
AnthropicProvider,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
package/src/config/RagConfig.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface VectorDBConfig {
|
|
|
31
31
|
*
|
|
32
32
|
* For multi-table PostgreSQL search, the following options are also supported:
|
|
33
33
|
* - tables?: string[] | string
|
|
34
|
-
* - searchFields?: string[] // optional override for which columns receive exact-match boosts
|
|
34
|
+
* - searchFields?: string[] | string // optional override for which columns receive exact-match boosts
|
|
35
35
|
*/
|
|
36
36
|
options: Record<string, unknown>;
|
|
37
37
|
}
|
|
@@ -151,6 +151,22 @@ export class ConfigValidator {
|
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
if (opts.tables && typeof opts.tables !== 'string' && !Array.isArray(opts.tables)) {
|
|
155
|
+
errors.push({
|
|
156
|
+
field: 'vectorDb.options.tables',
|
|
157
|
+
message: 'PostgreSQL tables must be a string or a string array',
|
|
158
|
+
severity: 'error',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (opts.searchFields && typeof opts.searchFields !== 'string' && !Array.isArray(opts.searchFields)) {
|
|
163
|
+
errors.push({
|
|
164
|
+
field: 'vectorDb.options.searchFields',
|
|
165
|
+
message: 'PostgreSQL searchFields must be a string or a string array',
|
|
166
|
+
severity: 'error',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
154
170
|
return errors;
|
|
155
171
|
}
|
|
156
172
|
|