@retrivora-ai/rag-engine 1.8.2 → 1.8.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.
- package/dist/handlers/index.js +142 -16
- package/dist/handlers/index.mjs +5848 -17
- package/dist/index.d.mts +9 -15
- package/dist/index.d.ts +9 -15
- package/dist/index.js +1355 -919
- package/dist/index.mjs +1437 -896
- package/dist/server.d.mts +6 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +171 -17
- package/dist/server.mjs +5923 -68
- package/package.json +6 -6
- package/src/app/globals.css +35 -11
- package/src/components/ChatWidget.tsx +0 -1
- package/src/components/MarkdownComponents.tsx +3 -0
- package/src/components/MessageBubble.tsx +3 -2
- package/src/components/ObservabilityPanel.tsx +1 -1
- package/src/components/UIDispatcher.tsx +0 -3
- package/src/config/ConfigBuilder.ts +38 -1
- package/src/core/LangChainAgent.ts +1 -4
- package/src/core/Pipeline.ts +31 -18
- package/src/core/QueryProcessor.ts +65 -0
- package/src/rag/Reranker.ts +99 -6
- package/src/utils/ProductExtractor.ts +3 -3
- package/dist/ChromaDBProvider-MIDOR4FW.mjs +0 -8
- package/dist/MilvusProvider-U7SKC27V.mjs +0 -8
- package/dist/MongoDBProvider-YNKC7EJ6.mjs +0 -8
- package/dist/MultiTablePostgresProvider-ZLGSKTJR.mjs +0 -8
- package/dist/PineconeProvider-QZNRKTN2.mjs +0 -8
- package/dist/QdrantProvider-RLJTNGPY.mjs +0 -8
- package/dist/RedisProvider-SR65SCKV.mjs +0 -8
- package/dist/SimpleGraphProvider-SLOXO4M7.mjs +0 -62
- package/dist/UniversalVectorProvider-IN67OS56.mjs +0 -9
- package/dist/WeaviateProvider-5FWDFITI.mjs +0 -8
- package/dist/chunk-5AJ4XHLW.mjs +0 -201
- package/dist/chunk-5YGUXK7Z.mjs +0 -80
- package/dist/chunk-CFVEZTBJ.mjs +0 -102
- package/dist/chunk-ICKRMZQK.mjs +0 -76
- package/dist/chunk-IMP6FUCY.mjs +0 -30
- package/dist/chunk-LR3VMDVK.mjs +0 -157
- package/dist/chunk-LZVVLSDN.mjs +0 -4077
- package/dist/chunk-M6JSPGAR.mjs +0 -117
- package/dist/chunk-OZFBG4BA.mjs +0 -291
- package/dist/chunk-PSFPZXHX.mjs +0 -245
- package/dist/chunk-U55XRW3U.mjs +0 -96
- package/dist/chunk-VUQJVIJT.mjs +0 -148
- package/dist/chunk-X4TOT24V.mjs +0 -89
- package/dist/chunk-YLTMFW4M.mjs +0 -49
package/dist/handlers/index.js
CHANGED
|
@@ -3380,11 +3380,70 @@ Text to extract from:
|
|
|
3380
3380
|
// src/rag/Reranker.ts
|
|
3381
3381
|
var Reranker = class {
|
|
3382
3382
|
/**
|
|
3383
|
-
* Re-ranks matches based on a secondary relevance score.
|
|
3384
|
-
* In a production environment, this would call a Cross-Encoder model.
|
|
3385
|
-
* Here we implement a placeholder that filters by score and limits count.
|
|
3383
|
+
* Re-ranks matches based on a secondary relevance score using an LLM.
|
|
3386
3384
|
*/
|
|
3387
|
-
async rerank(matches, query, limit = 5) {
|
|
3385
|
+
async rerank(matches, query, limit = 5, llm) {
|
|
3386
|
+
if (!llm || matches.length <= 1) {
|
|
3387
|
+
const keywords = query.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/).filter((w) => w.length > 2);
|
|
3388
|
+
if (keywords.length === 0) {
|
|
3389
|
+
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
3390
|
+
}
|
|
3391
|
+
const scoredMatches = matches.map((match) => {
|
|
3392
|
+
const contentLower = match.content.toLowerCase();
|
|
3393
|
+
let keywordScore = 0;
|
|
3394
|
+
keywords.forEach((keyword) => {
|
|
3395
|
+
if (contentLower.includes(keyword)) {
|
|
3396
|
+
keywordScore += 1;
|
|
3397
|
+
}
|
|
3398
|
+
});
|
|
3399
|
+
const normalizedKeywordScore = keywordScore / keywords.length;
|
|
3400
|
+
const combinedScore = match.score + normalizedKeywordScore * 0.5;
|
|
3401
|
+
return __spreadProps(__spreadValues({}, match), {
|
|
3402
|
+
score: combinedScore
|
|
3403
|
+
});
|
|
3404
|
+
});
|
|
3405
|
+
return scoredMatches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
3406
|
+
}
|
|
3407
|
+
try {
|
|
3408
|
+
const topN = matches.slice(0, 10);
|
|
3409
|
+
const prompt = `You are a relevance ranking expert.
|
|
3410
|
+
Given the following user query and a list of retrieved document chunks, rank the chunks by relevance to the query.
|
|
3411
|
+
Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
|
|
3412
|
+
Use the indices provided in brackets like [0], [1], etc.
|
|
3413
|
+
Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
|
|
3414
|
+
|
|
3415
|
+
Query: "${query}"
|
|
3416
|
+
|
|
3417
|
+
Documents:
|
|
3418
|
+
${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}`;
|
|
3419
|
+
const response = await llm.chat(
|
|
3420
|
+
[{ role: "user", content: prompt }],
|
|
3421
|
+
"",
|
|
3422
|
+
{ temperature: 0, maxTokens: 50 }
|
|
3423
|
+
);
|
|
3424
|
+
const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
|
|
3425
|
+
const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
|
|
3426
|
+
if (rankedIndices.length > 0) {
|
|
3427
|
+
const rankedMatches = [];
|
|
3428
|
+
const usedIndices = /* @__PURE__ */ new Set();
|
|
3429
|
+
for (const index of rankedIndices) {
|
|
3430
|
+
if (index >= 0 && index < topN.length && !usedIndices.has(index)) {
|
|
3431
|
+
rankedMatches.push(topN[index]);
|
|
3432
|
+
usedIndices.add(index);
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
for (let i = 0; i < topN.length; i++) {
|
|
3436
|
+
if (!usedIndices.has(i)) {
|
|
3437
|
+
rankedMatches.push(topN[i]);
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
const rest = matches.slice(10);
|
|
3441
|
+
rankedMatches.push(...rest);
|
|
3442
|
+
return rankedMatches.slice(0, limit);
|
|
3443
|
+
}
|
|
3444
|
+
} catch (error) {
|
|
3445
|
+
console.warn("[Reranker] LLM re-ranking failed, falling back to score-based sorting:", error);
|
|
3446
|
+
}
|
|
3388
3447
|
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
3389
3448
|
}
|
|
3390
3449
|
};
|
|
@@ -3444,7 +3503,7 @@ var LangChainAgent = class {
|
|
|
3444
3503
|
*/
|
|
3445
3504
|
async initialize(chatModel) {
|
|
3446
3505
|
try {
|
|
3447
|
-
const [{ DynamicTool },
|
|
3506
|
+
const [{ DynamicTool }, , { createAgent }] = await Promise.all([
|
|
3448
3507
|
import("@langchain/core/tools"),
|
|
3449
3508
|
import("@langchain/core/messages"),
|
|
3450
3509
|
import("langchain")
|
|
@@ -3494,7 +3553,6 @@ When presenting structured data, statistics, or comparisons, decide if it is bes
|
|
|
3494
3553
|
tools: [searchTool],
|
|
3495
3554
|
systemPrompt: finalSystemPrompt
|
|
3496
3555
|
});
|
|
3497
|
-
void HumanMessage;
|
|
3498
3556
|
} catch (error) {
|
|
3499
3557
|
const isMissing = error instanceof Error && error.message.includes("Cannot find module");
|
|
3500
3558
|
const hint = isMissing ? " Make sure 'langchain' and '@langchain/core' are installed:\n npm install langchain @langchain/core" : "";
|
|
@@ -3972,6 +4030,69 @@ var QueryProcessor = class {
|
|
|
3972
4030
|
if (filter.keywords && filter.keywords.length === 0) delete filter.keywords;
|
|
3973
4031
|
return filter;
|
|
3974
4032
|
}
|
|
4033
|
+
/**
|
|
4034
|
+
* Determines the retrieval strategy based on the query content.
|
|
4035
|
+
*/
|
|
4036
|
+
static async determineRetrievalStrategy(query, llm, customGraphKeywords, customVectorKeywords) {
|
|
4037
|
+
if (llm) {
|
|
4038
|
+
try {
|
|
4039
|
+
const prompt = `You are a routing expert in a RAG system.
|
|
4040
|
+
Given the user query, determine which retrieval mechanism is needed:
|
|
4041
|
+
- 'vector': For semantic search, finding specific documents or content based on similarity.
|
|
4042
|
+
- 'graph': For finding relationships between entities, multi-hop questions, or network structures.
|
|
4043
|
+
- 'both': If the query requires both specific content and relationship/structural analysis.
|
|
4044
|
+
|
|
4045
|
+
Query: "${query}"
|
|
4046
|
+
|
|
4047
|
+
Return ONLY 'vector', 'graph', or 'both'. No explanation.`;
|
|
4048
|
+
const response = await llm.chat(
|
|
4049
|
+
[{ role: "user", content: prompt }],
|
|
4050
|
+
"",
|
|
4051
|
+
{ temperature: 0, maxTokens: 10 }
|
|
4052
|
+
);
|
|
4053
|
+
const cleanResponse = response.trim().toLowerCase();
|
|
4054
|
+
if (cleanResponse.includes("vector")) return "vector";
|
|
4055
|
+
if (cleanResponse.includes("graph")) return "graph";
|
|
4056
|
+
if (cleanResponse.includes("both")) return "both";
|
|
4057
|
+
} catch (error) {
|
|
4058
|
+
console.warn("[QueryProcessor] LLM strategy classification failed, falling back to keywords:", error);
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
const normalized = query.toLowerCase();
|
|
4062
|
+
const graphKeywords = customGraphKeywords || [
|
|
4063
|
+
"relationship",
|
|
4064
|
+
"connect",
|
|
4065
|
+
"link",
|
|
4066
|
+
"network",
|
|
4067
|
+
"friend",
|
|
4068
|
+
"colleague",
|
|
4069
|
+
"manager",
|
|
4070
|
+
"hierarchy",
|
|
4071
|
+
"multi-hop",
|
|
4072
|
+
"shortest path",
|
|
4073
|
+
"between",
|
|
4074
|
+
"related to",
|
|
4075
|
+
"associated with",
|
|
4076
|
+
"belongs to",
|
|
4077
|
+
"part of"
|
|
4078
|
+
];
|
|
4079
|
+
const hasGraphKeyword = graphKeywords.some((kw) => normalized.includes(kw));
|
|
4080
|
+
const vectorKeywords = customVectorKeywords || [
|
|
4081
|
+
"find",
|
|
4082
|
+
"search",
|
|
4083
|
+
"tell me about",
|
|
4084
|
+
"what is",
|
|
4085
|
+
"how to",
|
|
4086
|
+
"documents about"
|
|
4087
|
+
];
|
|
4088
|
+
const hasVectorKeyword = vectorKeywords.some((kw) => normalized.includes(kw));
|
|
4089
|
+
if (hasGraphKeyword && !hasVectorKeyword) {
|
|
4090
|
+
return "graph";
|
|
4091
|
+
} else if (hasGraphKeyword && hasVectorKeyword) {
|
|
4092
|
+
return "both";
|
|
4093
|
+
}
|
|
4094
|
+
return "vector";
|
|
4095
|
+
}
|
|
3975
4096
|
};
|
|
3976
4097
|
|
|
3977
4098
|
// src/utils/synonyms.ts
|
|
@@ -5081,6 +5202,7 @@ var Pipeline = class {
|
|
|
5081
5202
|
let context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
|
|
5082
5203
|
${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
|
|
5083
5204
|
if (graphData && graphData.nodes.length > 0) {
|
|
5205
|
+
console.log(`[Graph Retrieval] Found ${graphData.nodes.length} relevant entities.`);
|
|
5084
5206
|
const graphContext = graphData.nodes.map(
|
|
5085
5207
|
(n) => `Entity: ${n.label} (${n.id})${n.properties ? " - " + JSON.stringify(n.properties) : ""}`
|
|
5086
5208
|
).join("\n");
|
|
@@ -5146,7 +5268,7 @@ ${context}`;
|
|
|
5146
5268
|
};
|
|
5147
5269
|
const hallucinationResult = yield new __await(scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0));
|
|
5148
5270
|
const trainedSchema = yield new __await(trainingPromise);
|
|
5149
|
-
const uiTransformation = yield new __await(this.generateUiTransformation(question, sources,
|
|
5271
|
+
const uiTransformation = yield new __await(this.generateUiTransformation(question, sources, trainedSchema));
|
|
5150
5272
|
const trace = {
|
|
5151
5273
|
requestId,
|
|
5152
5274
|
query: question,
|
|
@@ -5185,7 +5307,7 @@ ${context}`;
|
|
|
5185
5307
|
* Universal retrieval method combining all enabled providers.
|
|
5186
5308
|
* Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
|
|
5187
5309
|
*/
|
|
5188
|
-
async generateUiTransformation(question, sources,
|
|
5310
|
+
async generateUiTransformation(question, sources, trainedSchema) {
|
|
5189
5311
|
if (!sources || sources.length === 0) {
|
|
5190
5312
|
return UITransformer.transform(question, sources, this.config, trainedSchema);
|
|
5191
5313
|
}
|
|
@@ -5202,15 +5324,19 @@ ${context}`;
|
|
|
5202
5324
|
const topK = (_b = options.topK) != null ? _b : 5;
|
|
5203
5325
|
const cacheKey = `${ns}::${query}`;
|
|
5204
5326
|
let queryVector = this.embeddingCache.get(cacheKey);
|
|
5327
|
+
const strategy = await QueryProcessor.determineRetrievalStrategy(query, this.llmProvider);
|
|
5328
|
+
console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
|
|
5205
5329
|
const [retrievedVector, graphData] = await Promise.all([
|
|
5206
|
-
|
|
5207
|
-
|
|
5330
|
+
// Only embed if we need vector search (strategy is 'vector' or 'both')
|
|
5331
|
+
strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
|
|
5332
|
+
// Only query graph if we need graph search (strategy is 'graph' or 'both')
|
|
5333
|
+
(strategy === "graph" || strategy === "both") && this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
|
|
5208
5334
|
]);
|
|
5209
|
-
if (!queryVector) {
|
|
5335
|
+
if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
|
|
5210
5336
|
this.embeddingCache.set(cacheKey, retrievedVector);
|
|
5211
5337
|
queryVector = retrievedVector;
|
|
5212
5338
|
}
|
|
5213
|
-
const sources = await this.vectorDB.query(queryVector, topK, ns, options.filter);
|
|
5339
|
+
const sources = (strategy === "vector" || strategy === "both") && queryVector && queryVector.length > 0 ? await this.vectorDB.query(queryVector, topK, ns, options.filter) : [];
|
|
5214
5340
|
return { sources, graphData };
|
|
5215
5341
|
}
|
|
5216
5342
|
/** Rewrite the user query for better retrieval performance. */
|
|
@@ -5239,13 +5365,13 @@ Optimized Search Query:`;
|
|
|
5239
5365
|
await this.initialize();
|
|
5240
5366
|
const ns = namespace != null ? namespace : this.config.projectId;
|
|
5241
5367
|
try {
|
|
5242
|
-
const { sources } = await this.retrieve(query, { namespace: ns, topK:
|
|
5368
|
+
const { sources } = await this.retrieve(query, { namespace: ns, topK: 5 });
|
|
5243
5369
|
if (sources.length === 0) return [];
|
|
5244
5370
|
const context = sources.map((s) => s.content).join("\n\n---\n\n");
|
|
5245
|
-
const prompt = `Based on the following snippets from a document, what are
|
|
5371
|
+
const prompt = `Based on the following snippets from a document, what are at least 5 short, relevant questions a user might ask?
|
|
5246
5372
|
Focus on questions that can be answered by the context.
|
|
5247
5373
|
Keep each question under 10 words and make them very specific to the content.
|
|
5248
|
-
Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3"].
|
|
5374
|
+
Return ONLY a JSON array of strings like ["Question 1", "Question 2", "Question 3", "Question 4", "Question 5"].
|
|
5249
5375
|
|
|
5250
5376
|
Context:
|
|
5251
5377
|
${context}
|
|
@@ -5262,7 +5388,7 @@ Suggestions:`;
|
|
|
5262
5388
|
if (match) {
|
|
5263
5389
|
const suggestions = JSON.parse(match[0]);
|
|
5264
5390
|
if (Array.isArray(suggestions)) {
|
|
5265
|
-
return suggestions.map((s) => String(s)).slice(0,
|
|
5391
|
+
return suggestions.map((s) => String(s)).slice(0, 5);
|
|
5266
5392
|
}
|
|
5267
5393
|
}
|
|
5268
5394
|
} catch (error) {
|