UNPKG

n8n

Version:

n8n Workflow Automation Tool

211 lines 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createKnowledgeRetrievalTools = createKnowledgeRetrievalTools; const tool_1 = require("@n8n/agents/tool"); const agent_knowledge_retrieval_1 = require("../../agent-knowledge-retrieval"); const MODEL_OUTPUT_GLOB_FILE_LIMIT = 20; const MODEL_OUTPUT_SEARCH_MATCH_LIMIT = 8; const MODEL_OUTPUT_SEARCH_TEXT_CHARS = 240; const MODEL_OUTPUT_READ_TEXT_BUDGET = 6_000; const MODEL_OUTPUT_READ_RANGE_MIN_CHARS = 800; function formatKnowledgeToolError(error) { if (error instanceof Error) { return { error: error.message, errorType: error.name, }; } return { error: String(error), errorType: typeof error, }; } async function runKnowledgeTool(operation) { try { return await operation(); } catch (error) { return formatKnowledgeToolError(error); } } function isRecord(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); } function truncateForModel(text, maxChars) { if (text.length <= maxChars) { return { text, truncated: false }; } return { text: text.slice(0, maxChars), truncated: true }; } function isKnowledgeToolErrorOutput(output) { return (isRecord(output) && typeof output.error === 'string' && typeof output.errorType === 'string'); } function isGlobKnowledgeFilesResult(output) { return isRecord(output) && Array.isArray(output.files) && typeof output.hasMore === 'boolean'; } function isSearchKnowledgeResult(output) { if (!isRecord(output) || typeof output.outputMode !== 'string' || typeof output.hasMore !== 'boolean') { return false; } if (output.outputMode === 'content') return Array.isArray(output.matches); if (output.outputMode === 'files_with_matches') return Array.isArray(output.files); if (output.outputMode === 'count') return Array.isArray(output.counts); return false; } function isReadKnowledgeResult(output) { return (isRecord(output) && typeof output.file === 'string' && typeof output.fileId === 'string' && typeof output.displayName === 'string' && Array.isArray(output.ranges)); } function toGlobKnowledgeModelOutput(output) { if (isKnowledgeToolErrorOutput(output) || !isGlobKnowledgeFilesResult(output)) return output; const files = output.files.map((file) => ({ file: file.file, fileId: file.fileId, displayName: file.displayName, mimeType: file.mimeType, fileSizeBytes: file.fileSizeBytes, })); return { files, returnedFiles: files.length, offset: output.offset, hasMore: output.hasMore, }; } function toSearchKnowledgeModelOutput(output) { if (isKnowledgeToolErrorOutput(output) || !isSearchKnowledgeResult(output)) return output; if (output.outputMode === 'files_with_matches') { const files = output.files.slice(0, MODEL_OUTPUT_GLOB_FILE_LIMIT).map((file) => ({ file: file.file, fileId: file.fileId, displayName: file.displayName, })); return { outputMode: output.outputMode, files, returnedFiles: output.files.length, shownFiles: files.length, limit: output.limit, hasMore: output.hasMore || output.files.length > MODEL_OUTPUT_GLOB_FILE_LIMIT, truncated: output.truncated || output.files.length > MODEL_OUTPUT_GLOB_FILE_LIMIT, }; } if (output.outputMode === 'count') { const counts = output.counts.slice(0, MODEL_OUTPUT_GLOB_FILE_LIMIT); return { outputMode: output.outputMode, counts, returnedCounts: output.counts.length, shownCounts: counts.length, limit: output.limit, hasMore: output.hasMore || output.counts.length > MODEL_OUTPUT_GLOB_FILE_LIMIT, truncated: output.truncated || output.counts.length > MODEL_OUTPUT_GLOB_FILE_LIMIT, }; } let anyLocalTruncation = output.matches.length > MODEL_OUTPUT_SEARCH_MATCH_LIMIT; const matches = output.matches.slice(0, MODEL_OUTPUT_SEARCH_MATCH_LIMIT).map((match) => { const truncated = truncateForModel(match.text, MODEL_OUTPUT_SEARCH_TEXT_CHARS); if (truncated.truncated) anyLocalTruncation = true; const modelMatch = { file: match.file, fileId: match.fileId, displayName: match.displayName, lineNumber: match.lineNumber, text: truncated.text, textTruncated: match.textTruncated || truncated.truncated, }; if (!match.context?.length) return modelMatch; const context = match.context.map((line) => { const truncatedContext = truncateForModel(line.text, MODEL_OUTPUT_SEARCH_TEXT_CHARS); if (truncatedContext.truncated) anyLocalTruncation = true; return { lineNumber: line.lineNumber, text: truncatedContext.text, matched: line.matched, }; }); return { ...modelMatch, context, }; }); return { outputMode: output.outputMode, matches, returnedMatches: output.matches.length, shownMatches: matches.length, limit: output.limit, hasMore: output.hasMore || output.matches.length > MODEL_OUTPUT_SEARCH_MATCH_LIMIT, truncated: output.truncated || anyLocalTruncation, }; } function toReadKnowledgeModelOutput(output) { if (isKnowledgeToolErrorOutput(output) || !isReadKnowledgeResult(output)) return output; const perRangeBudget = Math.max(MODEL_OUTPUT_READ_RANGE_MIN_CHARS, Math.floor(MODEL_OUTPUT_READ_TEXT_BUDGET / Math.max(output.ranges.length, 1))); let remainingBudget = MODEL_OUTPUT_READ_TEXT_BUDGET; let anyLocalTruncation = false; const ranges = []; for (const range of output.ranges) { if (remainingBudget <= 0) { anyLocalTruncation = true; break; } const maxChars = Math.min(perRangeBudget, remainingBudget); const truncated = truncateForModel(range.text, maxChars); if (truncated.truncated) anyLocalTruncation = true; remainingBudget -= truncated.text.length; ranges.push({ startLine: range.startLine, endLine: range.endLine, citation: range.citation, text: truncated.text, textTruncated: truncated.truncated, }); } if (ranges.length < output.ranges.length) { anyLocalTruncation = true; } return { file: output.file, fileId: output.fileId, displayName: output.displayName, ranges, returnedRanges: output.ranges.length, shownRanges: ranges.length, truncated: output.truncated || anyLocalTruncation, }; } function createKnowledgeRetrievalTools({ projectId, agentId, sandboxService, }) { const globTool = new tool_1.Tool('find_file') .description('Find uploaded knowledge files by filename pattern, such as `*knowledge*`, `*agent*tool*`, or `*sandbox*`. Use `*` to list every uploaded file, an extension pattern like `*.pdf` to filter by type, or a name fragment when the user gives title or filename clues. Use first for knowledge lookup to discover plausible candidate files; call until enough candidates are found, then copy returned file values into search_text path or read_file. Results are paged: page through the full list with `limit` and `offset` while `hasMore` is true. Returns file, fileId, mimeType, and fileSizeBytes metadata; does not read file contents.') .input(agent_knowledge_retrieval_1.globKnowledgeFilesInputSchema) .handler(async (input) => await runKnowledgeTool(async () => await sandboxService.globKnowledgeFiles(projectId, agentId, input))) .toModelOutput(toGlobKnowledgeModelOutput); const searchTool = new tool_1.Tool('search_text') .description('Search uploaded knowledge file contents with a ripgrep regex `pattern`. Omit `path` to search across every uploaded knowledge file. Pass `path` — one exact file value or an array of exact file values returned by find_file, search_text, or read_file — to scope the search to specific files. Does not accept empty path, `*`, fileId, absolute paths, guessed paths, arrays of non-strings, offsets, or semantic questions. Searching more than 20 files with an explicit `path` requires batching arrays across multiple calls (max 20 paths per call). `output_mode` defaults to content for snippets; use files_with_matches for matching files only or count for per-file match counts. Use `head_limit` to bound results and small `-C` values for nearby context. Answer from snippets when enough; use read_file only for quotes, citations, exact wording, source text, or larger missing context.') .input(agent_knowledge_retrieval_1.searchKnowledgeInputSchema) .handler(async (input) => await runKnowledgeTool(async () => await sandboxService.searchKnowledge(projectId, agentId, input))) .toModelOutput(toSearchKnowledgeModelOutput); const readTool = new tool_1.Tool('read_file') .description(`Read one uploaded knowledge file using an exact \`file\` or \`fileId\` returned by a knowledge tool. Use only for exact quotes, citations, source text, or extra surrounding context not covered by search_text snippets or small -C context. Prefer bounded line ranges; each call supports up to ${agent_knowledge_retrieval_1.MAX_READ_RANGES} ranges and output is truncated when too large. Read all needed ranges from the same file in one call when practical, and do not use read_file for broad manual scanning. If using fileId without an exact matching file value, omit file.`) .input(agent_knowledge_retrieval_1.readKnowledgeInputSchema) .handler(async (input) => await runKnowledgeTool(async () => await sandboxService.readKnowledge(projectId, agentId, input))) .toModelOutput(toReadKnowledgeModelOutput); return [globTool, searchTool, readTool]; } //# sourceMappingURL=search-knowledge.tool.js.map