autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
120 lines (119 loc) • 5.01 kB
JavaScript
/**
* ToolRequirementAnalyzer — 工具需求分析器
*
* 分析结构化意图,确定满足需求的最佳路径:
* 1. Reuse — 注册表中已有完全匹配的工具
* 2. Compose — 可通过组合已有工具满足
* 3. Generate — 必须由 LLM 生成新工具代码
*/
import Logger from '#infra/logging/Logger.js';
/* ────────────── Action → Tool Keyword mapping ────────────── */
const ACTION_TOOL_HINTS = {
read: ['read', 'get', 'fetch', 'load', 'file'],
search: ['search', 'find', 'query', 'lookup'],
write: ['write', 'save', 'create', 'update', 'set'],
delete: ['delete', 'remove', 'clear'],
transform: ['transform', 'convert', 'parse', 'format'],
validate: ['validate', 'check', 'guard', 'lint'],
analyse: ['analyze', 'analyse', 'inspect', 'stats'],
list: ['list', 'browse', 'enumerate'],
execute: ['execute', 'run', 'invoke', 'call'],
};
/* ────────────────────── Class ────────────────────── */
export class ToolRequirementAnalyzer {
#registry;
#logger = Logger.getInstance();
constructor(registry) {
this.#registry = registry;
}
/**
* 分析需求并推荐 Forge 模式
*/
analyze(requirement) {
// 1. 尝试精确匹配
const exactMatch = this.#tryExactMatch(requirement);
if (exactMatch) {
return exactMatch;
}
// 2. 尝试组合匹配
const composeMatch = this.#tryComposeMatch(requirement);
if (composeMatch) {
return composeMatch;
}
// 3. Fallback: 需要生成
return {
mode: 'generate',
confidence: 0.5,
reasoning: `No existing tool matches "${requirement.action} ${requirement.target}". Code generation required.`,
};
}
/* ── Internal ── */
#tryExactMatch(req) {
// 直接检查 action_target 形式的工具名
const directName = `${req.action}_${req.target}`.toLowerCase();
if (this.#registry.has(directName)) {
return {
mode: 'reuse',
confidence: 1.0,
reasoning: `Exact tool match: "${directName}"`,
matchedTool: directName,
};
}
// 模糊匹配:遍历已注册工具,看名称是否同时包含 action 和 target 关键词
const allTools = this.#registry.getToolNames();
const actionLower = req.action.toLowerCase();
const targetLower = req.target.toLowerCase();
for (const tool of allTools) {
const toolLower = tool.toLowerCase();
if (toolLower.includes(actionLower) && toolLower.includes(targetLower)) {
return {
mode: 'reuse',
confidence: 0.85,
reasoning: `Fuzzy match: tool "${tool}" contains both "${req.action}" and "${req.target}"`,
matchedTool: tool,
};
}
}
// 通过 action hint 词尝试
const hints = ACTION_TOOL_HINTS[actionLower] ?? [actionLower];
for (const tool of allTools) {
const toolLower = tool.toLowerCase();
const matchesHint = hints.some((h) => toolLower.includes(h));
const matchesTarget = toolLower.includes(targetLower);
if (matchesHint && matchesTarget) {
return {
mode: 'reuse',
confidence: 0.7,
reasoning: `Hint match: tool "${tool}" matches action hint and target "${req.target}"`,
matchedTool: tool,
};
}
}
return null;
}
#tryComposeMatch(req) {
const allTools = this.#registry.getToolNames();
const actionLower = req.action.toLowerCase();
const targetLower = req.target.toLowerCase();
const hints = ACTION_TOOL_HINTS[actionLower] ?? [actionLower];
// 寻找和 action 相关的工具
const actionRelated = allTools.filter((t) => {
const tl = t.toLowerCase();
return hints.some((h) => tl.includes(h));
});
// 寻找和 target 相关的工具
const targetRelated = allTools.filter((t) => t.toLowerCase().includes(targetLower));
// 取并集
const candidates = [...new Set([...actionRelated, ...targetRelated])];
if (candidates.length >= 2) {
this.#logger.debug(`ToolRequirementAnalyzer: compose candidates for "${req.intent}": ${candidates.join(', ')}`);
return {
mode: 'compose',
confidence: 0.65,
reasoning: `Found ${candidates.length} composable tools for "${req.action} ${req.target}"`,
composableTools: candidates.slice(0, 5),
};
}
return null;
}
}