n8n-nodes-query-retriever-rerank
Version:
Advanced n8n community node for intelligent document retrieval with multi-step reasoning, reranking, and comprehensive debugging
349 lines (348 loc) • 17.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryRetrieverRerank = void 0;
const tools_1 = require("@langchain/core/tools");
const sharedFields_1 = require("../../utils/sharedFields");
const helpers_1 = require("../../utils/helpers");
const logWrapper_1 = require("../../utils/logWrapper");
// Import the new modular strategy system
const strategies_1 = require("./strategies");
class QueryRetrieverRerank {
constructor() {
this.description = {
displayName: 'Query Retriever with Rerank',
name: 'queryRetrieverRerank',
group: ['transform'],
version: 1,
description: 'Query retrieval tool with multi-query strategies, intelligent reranking, and comprehensive debugging',
defaults: {
name: 'Query Retriever with Rerank',
},
codex: {
categories: ['AI'],
subcategories: {
AI: ['Tools'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.toolvectorstore/',
},
],
},
},
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [
{
displayName: 'Vector',
maxConnections: 1,
type: "ai_vectorStore" /* NodeConnectionType.AiVectorStore */,
},
{
displayName: 'LLM',
maxConnections: 1,
type: "ai_languageModel" /* NodeConnectionType.AiLanguageModel */,
},
{
displayName: 'Embed',
maxConnections: 1,
type: "ai_embedding" /* NodeConnectionType.AiEmbedding */,
required: true,
},
{
displayName: 'Debug',
maxConnections: 1,
type: "ai_memory" /* NodeConnectionType.AiMemory */,
required: false,
},
],
outputs: ["ai_tool" /* NodeConnectionType.AiTool */],
outputNames: ['Tool'],
properties: [
(0, sharedFields_1.getConnectionHintNoticeField)(["ai_agent" /* NodeConnectionType.AiAgent */]),
{
displayName: 'Tool Options',
name: 'toolOptions',
placeholder: 'Add Option',
description: 'Configure the AI tool name and description',
type: 'collection',
default: {},
options: [
{
displayName: 'Tool Name',
name: 'toolName',
type: 'string',
default: '',
placeholder: 'Leave empty to use node name automatically',
description: 'Custom name for the AI tool. If empty, will use the node name from the interface (safer for API compatibility).',
},
{
displayName: 'Tool Description',
name: 'description',
type: 'string',
default: 'A tool for answering questions by searching through a vector store of documents',
placeholder: 'Describe your data here, e.g. user information, knowledge base, etc.',
description: 'Describe the data in vector store. This will be used to fill the tool description.',
typeOptions: {
rows: 3,
},
},
],
},
{
displayName: 'Retrieval Options',
name: 'retrievalOptions',
placeholder: 'Add Option',
description: 'Configure document retrieval and reranking behavior',
type: 'collection',
default: {},
options: [
{
displayName: 'Documents to Retrieve',
name: 'documentsToRetrieve',
type: 'number',
default: 10,
description: 'Number of documents to initially retrieve from the vector store before reranking.',
typeOptions: {
minValue: 1,
maxValue: 100,
},
},
{
displayName: 'Documents to Return',
name: 'documentsToReturn',
type: 'number',
default: 4,
description: 'Number of top-ranked documents to use for answer generation and return in results.',
typeOptions: {
minValue: 1,
maxValue: 50,
},
},
{
displayName: 'Return Ranked Documents',
name: 'returnRankedDocuments',
type: 'boolean',
default: false,
description: 'Whether to return the reranked documents along with the answer for debugging or citations.',
},
],
},
{
displayName: 'Query Strategy Options',
name: 'queryStrategy',
placeholder: 'Add Option',
description: 'Configure how to process and respond with retrieved documents',
type: 'collection',
default: {
strategyType: 'simple_query'
},
options: [
{
displayName: 'Strategy Type',
name: 'strategyType',
type: 'options',
default: 'simple_query',
description: 'How to process the retrieved documents to generate a response',
options: strategies_1.StrategyRegistry.getStrategyOptions(),
},
{
displayName: 'Number of Query Variations',
name: 'queryVariations',
type: 'number',
default: 3,
description: 'Number of alternative queries to generate for multi-query retrieval',
typeOptions: {
minValue: 2,
maxValue: 8,
},
displayOptions: {
show: {
strategyType: ['multi_query'],
},
},
},
{
displayName: 'Include Original Query',
name: 'includeOriginalQuery',
type: 'boolean',
default: true,
description: 'Whether to include the original query in addition to the generated variations',
displayOptions: {
show: {
strategyType: ['multi_query'],
},
},
},
{
displayName: 'Max Reasoning Steps',
name: 'maxSteps',
type: 'number',
default: 3,
description: 'Maximum number of reasoning steps for multi-step query decomposition',
typeOptions: {
minValue: 1,
maxValue: 8,
},
displayOptions: {
show: {
strategyType: ['multi_step_query'],
},
},
},
{
displayName: 'Enable Early Stopping',
name: 'enableEarlyStop',
type: 'boolean',
default: true,
description: 'Stop reasoning early when sufficient information is gathered to answer the original question',
displayOptions: {
show: {
strategyType: ['multi_step_query'],
},
},
},
{
displayName: 'Query Prompt Template',
name: 'promptTemplate',
type: 'string',
default: '',
placeholder: 'Use the following pieces of context to answer the question: {context}\\n\\nQuestion: {question}\\nAnswer:',
description: 'For Simple Query/Multi-Query: Answer generation template using {context} and {question}. For Multi-Query: Custom instructions for query generation using {count}.',
typeOptions: {
rows: 4,
},
displayOptions: {
show: {
strategyType: ['simple_query', 'multi_query'],
},
},
},
],
},
{
displayName: 'Advanced Options',
name: 'options',
placeholder: 'Add Option',
description: 'Advanced configuration options for the vector store tool',
type: 'collection',
default: {},
options: [
{
displayName: 'Debugging',
name: 'debugging',
type: 'boolean',
default: false,
description: 'Store detailed execution metrics in the connected memory node. Requires a memory node connection.',
},
{
displayName: 'LLM Debug Analysis',
name: 'llmDebugAnalysis',
type: 'boolean',
default: false,
description: 'Generate AI-powered performance analysis using the connected language model. WARNING: This will significantly slow down query response times as it requires an additional LLM call to analyze debug data.',
displayOptions: {
show: {
debugging: [true],
},
},
},
],
},
],
};
}
async supplyData(itemIndex) {
var _a;
const node = this.getNode();
const toolOptions = this.getNodeParameter('toolOptions', itemIndex, {});
// Use custom name if provided, otherwise fall back to node name
const rawName = ((_a = toolOptions.toolName) === null || _a === void 0 ? void 0 : _a.trim()) || node.name;
// Always apply nodeNameToToolName sanitization to ensure API compatibility
const name = (0, helpers_1.nodeNameToToolName)({ name: rawName });
const description = toolOptions.description || 'A tool for answering questions by searching through a vector store of documents';
// Get parameters
const retrievalOptions = this.getNodeParameter('retrievalOptions', itemIndex, {});
const queryStrategy = this.getNodeParameter('queryStrategy', itemIndex, { strategyType: 'simple_query' });
const options = this.getNodeParameter('options', itemIndex, {});
// Get the vector store and language model from input connections
const vectorStore = (await this.getInputConnectionData("ai_vectorStore" /* NodeConnectionType.AiVectorStore */, itemIndex));
const model = (await this.getInputConnectionData("ai_languageModel" /* NodeConnectionType.AiLanguageModel */, 0));
const rerankingEmbeddings = (await this.getInputConnectionData("ai_embedding" /* NodeConnectionType.AiEmbedding */, 0));
const memory = (await this.getInputConnectionData("ai_memory" /* NodeConnectionType.AiMemory */, 0));
if (!vectorStore) {
throw new Error('Vector Store input is required');
}
if (!model) {
throw new Error('Language Model input is required');
}
// Reranking embeddings are now required since we always rerank
if (!rerankingEmbeddings) {
throw new Error('Reranking Embeddings input is required for document reranking');
}
// Create strategy configuration
const strategyConfig = {
documentsToRetrieve: retrievalOptions.documentsToRetrieve || 10,
documentsToReturn: retrievalOptions.documentsToReturn || 4,
returnRankedDocuments: retrievalOptions.returnRankedDocuments || false,
promptTemplate: queryStrategy.promptTemplate,
queryVariations: queryStrategy.queryVariations,
includeOriginalQuery: queryStrategy.includeOriginalQuery,
maxSteps: queryStrategy.maxSteps,
enableEarlyStop: queryStrategy.enableEarlyStop,
};
// Create strategy context
const strategyContext = {
vectorStore,
model,
embeddings: rerankingEmbeddings,
memory,
config: strategyConfig,
debugging: options.debugging || false,
llmDebugAnalysis: options.llmDebugAnalysis || false,
};
// Create the enhanced description
const strategyType = queryStrategy.strategyType;
const strategy = strategies_1.StrategyRegistry.getStrategy(strategyType);
const enhancedDescription = `${description}. ${strategy.getDescription()}${retrievalOptions.returnRankedDocuments ? ' with ranked document citations' : ''}. Supports AI-controlled retrieval parameters.`;
// Create the DynamicTool
const tool = new tools_1.DynamicTool({
name,
description: enhancedDescription,
func: async (input) => {
try {
// Execute the strategy
const result = await strategy.execute(input, strategyContext);
if (result.error) {
throw new Error(result.error);
}
// Handle different return formats
if (result.sourceDocuments && !result.answer) {
// Documents only (none strategy)
return JSON.stringify({
sourceDocuments: result.sourceDocuments
}, null, 2);
}
if (result.sourceDocuments && result.answer) {
// Answer with source documents
return JSON.stringify({
answer: result.answer,
sourceDocuments: result.sourceDocuments
}, null, 2);
}
// Simple answer return
return result.answer || 'No result generated';
}
catch (error) {
throw error;
}
}
});
// Return the tool wrapped with logging for visual feedback
const wrappedTool = (0, logWrapper_1.logWrapper)(tool, this);
return {
response: wrappedTool,
};
}
}
exports.QueryRetrieverRerank = QueryRetrieverRerank;