Agent 工具注册与发现机制:从硬编码到插件式动态加载的架构演进
📅 2026/7/11 2:24:09
👁️ 阅读次数
📝 编程学习
Agent 工具注册与发现机制:从硬编码到插件式动态加载的架构演进
一、硬编码工具的维护灾难:每新增一个工具都要改核心代码
Agent 的核心能力是"调用工具"——搜索信息、查询数据库、发送邮件、执行脚本。最初的实现通常是硬编码的 switch-case:
def call_tool(tool_name, params): if tool_name == "search": return search_web(params["query"]) elif tool_name == "calculator": return calculate(params["expression"]) elif tool_name == "weather": return get_weather(params["city"]) # 每次新增工具都要加一个 elif,改核心代码当工具只有 3 个时,这段代码没问题。当工具增加到 15 个时,switch-case 变成了一面难以维护的代码墙。更糟的是——新增工具需要修改核心 Agent 逻辑、重新部署、重启服务。这在开源项目中意味着:外部贡献者无法独立开发新工具,每个工具 PR 都要等核心维护者合并。
工具注册与发现机制是解药。它的核心思想是:Agent 不关心工具的具体实现,只关心工具的"签名"——名称、描述、参数 Schema。工具提供方按规范注册工具,Agent 运行时动态发现并调用。
graph TB subgraph Discovery[工具发现] D1[本地插件目录] D2[远程注册中心] D3[环境变量配置] end D1 --> R[工具注册表 Registry] D2 --> R D3 --> R R --> S[Schema 索引<br/>名称 + 描述 + 参数] subgraph Agent[Agent 运行时] A1[用户查询] --> A2[LLM 分析意图] A2 --> A3[从 Registry 匹配工具] A3 --> A4{工具存在?} A4 -->|是| A5[调用工具执行] A4 -->|否| A6[降级: 告知用户不支持] end subgraph Tools[已注册工具] T1[SearchTool] T2[CalculatorTool] T3[WeatherTool] T4[自定义插件 Tool] end S --> A3 A5 --> T1 A5 --> T2 A5 --> T3 A5 --> T4 style A6 fill:#ffd43b,color:#000 style A5 fill:#51cf66,color:#fff二、工具注册的核心抽象:Tool 接口与 Register 机制
工具注册系统的核心是一个统一的Tool接口和Registry容器。接口定义了每个工具必须具备的能力:
interface Tool { name: string; // 唯一标识 description: string; // 给 LLM 看的描述 parameters: JSONSchema; // 参数定义 execute(params: any): Promise<any>; // 执行逻辑 }Registry负责管理所有已注册的工具,提供注册、注销、查询三个能力。关键设计:工具注册发生在 Agent 启动时,而非运行时。这意味着工具的加载是一次性的,不存在运行时动态替换的安全风险。
三、完整的工具注册系统实现
// tool-registry.ts import { z } from 'zod'; // 工具接口定义 export interface Tool<TInput = any, TOutput = any> { name: string; description: string; schema: z.ZodSchema<TInput>; execute(input: TInput): Promise<TOutput>; } // 工具注册表 export class ToolRegistry { private tools: Map<string, Tool> = new Map(); private loadedPlugins: Set<string> = new Set(); // 注册单个工具 register(tool: Tool): void { if (this.tools.has(tool.name)) { throw new Error(`Tool "${tool.name}" is already registered`); } this.tools.set(tool.name, tool); } // 注销工具 unregister(name: string): void { this.tools.delete(name); } // 获取工具 get(name: string): Tool | undefined { return this.tools.get(name); } // 生成给 LLM 的 Function Calling Schema generateLLMSchema(): Array<{ type: 'function'; function: { name: string; description: string; parameters: any }; }> { const schemas: Array<any> = []; for (const tool of this.tools.values()) { schemas.push({ type: 'function', function: { name: tool.name, description: tool.description, parameters: this.zodToJSONSchema(tool.schema), }, }); } return schemas; } // 执行工具调用 async execute(name: string, input: any): Promise<any> { const tool = this.tools.get(name); if (!tool) { throw new Error(`Tool "${name}" not found. Available: ${this.listNames()}`); } // 参数验证 const parsed = tool.schema.safeParse(input); if (!parsed.success) { throw new Error(`Invalid parameters for "${name}": ${parsed.error.message}`); } return tool.execute(parsed.data); } listNames(): string[] { return Array.from(this.tools.keys()); } size(): number { return this.tools.size; } // Zod Schema 转 JSON Schema(用于 Function Calling) private zodToJSONSchema(schema: z.ZodSchema): any { // 使用 zod-to-json-schema 库转换 // 简化实现 return zodToJsonSchema(schema); } // 从插件目录加载工具(动态发现) async loadPlugins(pluginDir: string): Promise<number> { const fs = require('fs'); const path = require('path'); if (!fs.existsSync(pluginDir)) return 0; const files = fs.readdirSync(pluginDir); let loaded = 0; for (const file of files) { if (!file.endsWith('.js') && !file.endsWith('.ts')) continue; const pluginPath = path.join(pluginDir, file); if (this.loadedPlugins.has(pluginPath)) continue; try { const plugin = require(pluginPath); if (plugin.default && typeof plugin.default === 'object') { this.register(plugin.default); this.loadedPlugins.add(pluginPath); loaded++; } } catch (err) { console.error(`Failed to load plugin ${file}:`, err); } } return loaded; } } // ========== 工具实现示例 ========== // 搜索工具 export const SearchTool: Tool = { name: 'web_search', description: '搜索互联网信息。当需要查找实时信息时使用。', schema: z.object({ query: z.string().describe('搜索关键词'), max_results: z.number().optional().default(5).describe('最大结果数'), }), async execute(input) { // 实际调用搜索引擎 API const response = await fetch(`https://api.search.example.com?q=${encodeURIComponent(input.query)}&n=${input.max_results}`); return response.json(); }, }; // 计算器工具 export const CalculatorTool: Tool = { name: 'calculator', description: '执行数学计算。支持 +、-、*、/ 和括号。', schema: z.object({ expression: z.string().describe('数学表达式,如 "2 + 3 * 4"'), }), async execute(input) { try { // 安全计算(使用 Function 在一些场景不安全,实际应使用 math.js) const result = Function(`"use strict"; return (${input.expression})`)(); return { expression: input.expression, result }; } catch (err) { return { error: `计算失败: ${err.message}` }; } }, }; // ========== Agent 中使用 ========== import OpenAI from 'openai'; class Agent { private registry: ToolRegistry; private openai: OpenAI; constructor(registry: ToolRegistry) { this.registry = registry; this.openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); } async chat(userMessage: string) { const messages: any[] = [ { role: 'system', content: '你是一个 AI 助手,可以使用工具帮助用户。' }, { role: 'user', content: userMessage }, ]; const response = await this.openai.chat.completions.create({ model: 'gpt-4o', messages, tools: this.registry.generateLLMSchema(), }); const choice = response.choices[0]; // 处理工具调用 if (choice.finish_reason === 'tool_calls' && choice.message.tool_calls) { for (const toolCall of choice.message.tool_calls) { const args = JSON.parse(toolCall.function.arguments); const result = await this.registry.execute(toolCall.function.name, args); messages.push(choice.message); messages.push({ role: 'tool', tool_call_id: toolCall.id, content: JSON.stringify(result), }); } // 继续对话 const finalResponse = await this.openai.chat.completions.create({ model: 'gpt-4o', messages, }); return finalResponse.choices[0].message.content; } return choice.message.content; } }四、动态加载的安全考量
插件隔离:动态加载的插件代码运行在 Agent 进程中,没有沙箱隔离。恶意插件可以访问文件系统、环境变量、网络。安全策略:
- 插件应有签名验证(通过 checksum 或 GPG 签名)
- 生产环境推荐使用 WASM 沙箱或子进程隔离
- 加载前做静态分析(AST 扫描是否访问敏感 API)
不适用场景:
- 工具数量固定且不需要外部贡献(< 5 个工具)
- 金融等严格安全审查的场景(不允许动态加载未知代码)
- Serverless 环境(启动时间不包含插件扫描)
五、总结
工具注册与发现机制将 Agent 的工具管理从"硬编码"升级为"插件化"。核心只有两个组件:Tool接口(定义工具契约)和Registry(管理工具生命周期)。
落地路径:先定义Tool接口,将现有的 3-5 个工具迁移到接口模式;然后实现Registry和插件加载;最后在 Agent 的 Function Calling 中集成。
少即是多。不要在 3 个工具的时候就上插件系统——等到工具数量超过 8 个、或者有外部贡献者需要独立开发工具时再引入。
编程学习
技术分享
实战经验