第十一章 · 扩展机制
前十章,我们一步步搭出了一个功能完备的 Agent:它能思考、能用工具、有记忆、有权限控制、能分工协作、能错误恢复、还能流式输出。
但有个问题越来越突出:每加一个功能,核心代码就要改一次。今天加搜索、明天加代码格式化、后天加 Git 操作……Agent 越来越臃肿,维护越来越痛苦。
这一章,我们给 Agent 装上扩展机制——钩子系统 + 插件架构 + MCP 协议,三套机制让 Agent 像乐高一样可插拔、可组合、无限扩展。
📌 本文你将掌握
✅ 理解扩展机制的三层架构(核心引擎→钩子→插件)
✅ 设计钩子系统:事件、优先级、管道模式、错误隔离
✅ 实现插件架构:清单声明 + 生命周期管理
✅ 理解 MCP 协议:Agent 与外部服务的标准化通信
✅ 对比 Claude Code 的扩展系统设计
✅ 掌握扩展机制的五大设计原则
01 为什么需要扩展机制
想象一个场景:你的 Agent 今天需要搜索网页,明天要格式化代码,后天要操作 Git 仓库。如果每次新增功能都改核心代码,很快就会变成一个维护噩梦。
扩展机制遵循两个核心原则:
① 关注点分离:核心逻辑与扩展功能解耦,各自独立演化
② 开闭原则:对扩展开放,对修改关闭——不改核心代码就能加功能
扩展机制分三层,从下到上依次叠加:
┌─────────────────────────────────────────┐│ 应用层 (Application) ││ ┌───────────────────────────────────┐ ││ │ 插件系统 (Plugin System) │ ││ │ ┌──────┐ ┌──────┐ ┌──────┐ │ ││ │ │插件A │ │插件B │ │插件C │ │ ││ │ └──────┘ └──────┘ └──────┘ │ ││ └───────────────────────────────────┘ ││ ▼ ││ ┌───────────────────────────────────┐ ││ │ 钩子系统 (Hook System) │ ││ │ ┌─────┐ ┌─────┐ ┌───────────┐ │ ││ │ │前置 │ │后置 │ │错误/完成 │ │ ││ │ └─────┘ └─────┘ └───────────┘ │ ││ └───────────────────────────────────┘ ││ ▼ ││ ┌───────────────────────────────────┐ ││ │ 核心引擎 (Core Engine) │ ││ │ Tool Call │ LLM Call │ Agent Loop │ ││ └───────────────────────────────────┘ │└─────────────────────────────────────────┘
💡 一句话理解:核心引擎是"发动机",钩子系统是"传感网络",插件系统是"配件商城"。三层各管各的,互不干扰。
02 钩子系统:在关键节点"埋伏兵"
钩子系统是扩展机制的基石。它在 Agent 生命周期中定义一系列事件,外部代码注册处理函数到这些事件上,触发时依次执行。
Agent 执行流程中的钩子触发点:
┌──────────┐ ┌──────────────┐ ┌──────────┐│ 接收任务 │ │ 调用工具 │ │ 调用 LLM │└────┬─────┘ └──────┬───────┘ └────┬─────┘│ │ │▼ ▼ ▼┌──────────┐ ┌────────────┐ ┌──────────────┐│beforeAll │ │beforeTool │ │beforeLlmCall │└──────────┘ └──────┬─────┘ └──────┬───────┘│ │▼ ▼┌────────────┐ ┌──────────────┐│ 工具执行 │ │ LLM 响应 │└──────┬─────┘ └──────┬───────┘│ │▼ ▼┌────────────┐ ┌──────────────┐│afterTool │ │afterLlmCall │└──────┬─────┘ └──────┬───────┘│ │└───────┬───────┘│┌────────┴────────┐▼ ▼┌──────────┐ ┌────────────┐│ onError │ │ onComplete │└──────────┘ └────────────┘
钩子系统有三大核心特性:
① 优先级排序:每个钩子注册时指定优先级(数值越小越先执行),关键校验设高优先级,日志记录设低优先级
② 管道模式:钩子可以修改上下文对象,后续钩子接收的是修改后的数据(类似 HTTP 中间件的"洋葱模型")
③ 错误隔离:单个钩子失败不影响其他钩子,系统捕获异常后继续执行
管道模式的执行流程:
输入上下文│▼┌─────────┐ ┌─────────┐ ┌─────────┐│ 钩子 A │ ──► │ 钩子 B │ ──► │ 钩子 C │ ──► 输出│(优先级 5)│ │(优先级10)│ │(优先级15)│└─────────┘ └─────────┘ └─────────┘
钩子系统核心 API:
// 注册钩子hookSystem.register(event, handler, priority?);// 执行钩子链const context = await hookSystem.execute(event, initialData);// 移除钩子hookSystem.unregister(event, handler);hookSystem.unregisterByName(name);
完整使用示例——三个钩子协同工作:
// 创建钩子系统实例consthookSystem = newHookSystem();// 注册日志钩子(低优先级,后执行)hookSystem.register("beforeToolCall", loggingHook, 20);hookSystem.register("afterToolCall", loggingHook, 20);// 注册验证钩子(高优先级,先执行)hookSystem.register("beforeToolCall", validationHook, 5);// 执行时,先验证后记录日志constresult = await hookSystem.execute("beforeToolCall", {data: { toolName: "search", args: { query: "AI Agent" } },});
💡 执行顺序:validationHook(优先级 5)先跑校验 → loggingHook(优先级 20)后记日志。如果校验失败,可以拦截后续执行。
03 插件架构:把能力打包成"即插即用"
如果说钩子是"点",插件就是"面"——一个插件可以包含多个钩子 + 多个工具 + 系统提示,打包成一个可复用的整体。
每个插件通过一个清单文件声明自己的能力:
interfacePluginManifest {name: string; // 插件名称(唯一标识)version: string; // 语义化版本号description: string; // 功能描述hooks?: Array<{ // 钩子注册列表event: string;handler: Function;}>;tools?: ToolDefinition[]; // 提供的工具systemPrompt?: string; // 追加到系统提示的文本}
插件从注册到卸载,经历完整的生命周期:
注册插件│▼┌───────────┐│ load(加载) │── 读取清单,验证依赖└─────┬─────┘│▼┌────────────┐│init(初始化)│── 分配资源,建立连接└─────┬──────┘│▼┌──────────────┐│activate(激活)│── 注册钩子/工具,开始服务└──────┬───────┘│┌────┴────┐││▼▼运行中 停用/卸载│▼┌──────────────┐│ deactivate │── 释放资源,取消注册└──────────────┘
两个实战插件示例:
CodeFormatter 插件:
const codeFormatterPlugin: PluginManifest = {name: "code-formatter",version: "1.0.0",description: "代码格式化能力",tools: [{name: "format_code",description: "格式化代码",inputSchema: { ... },execute: async (args) => { ... },}],hooks: [{event: "beforeToolCall",handler: (ctx) => { /* 验证输入 */ },}],systemPrompt: "你拥有代码格式化能力...",};
GitHelper 插件:
const gitHelperPlugin: PluginManifest = {name: "git-helper",version: "2.1.0",description: "Git 版本控制辅助",tools: [{ name: "git_status", ... },{ name: "git_commit", ... },],hooks: [{event: "afterToolCall",handler: (ctx) => { /* 记录操作日志 */ },}],systemPrompt: "你具备 Git 版本控制辅助能力...",};
💡 关键点:注册这两个插件后,Agent 自动获得代码格式化和 Git 操作能力,核心代码不需要任何修改。这就是"对扩展开放,对修改关闭"。
04 MCP 协议:Agent 与外部世界的"普通话"
MCP(Model Context Protocol)是 Anthropic 提出的开放协议,定义了 AI 模型与外部工具/数据源之间的标准化交互方式。
如果说我们的插件系统是 Agent 内部的扩展机制,那么 MCP 就是 Agent 之间以及 Agent 与外部服务之间的扩展机制:
┌───────────────┐ MCP 协议 ┌───────────────┐│ │ ◄══════════════════► │ ││ MCP 客户端 │ │ MCP 服务器 ││ (AI Agent) │ │ (工具/数据源) ││ │ │ ││ 发现能力 │ ── 列出工具/资源 ──► │ ││ 调用工具 │ ── 执行工具调用 ───► │ ││ 获取资源 │ ── 读取资源/数据 ──► │ ││ 订阅变更 │ ◄── 状态变更通知 ──── │ │└───────────────┘ └───────────────┘
MCP 的核心概念与我们的插件系统高度相似:
MCP 不是要替代我们的插件系统,而是互补关系:
插件系统:Agent 进程内的扩展 → 适合与 Agent 生命周期绑定的能力(如代码格式化)
MCP:Agent 进程间的扩展 → 适合独立运行的外部服务(如数据库、文件系统、云服务)
💡 最佳实践:在实际框架中,MCP 服务器适配器本身就可以实现为一个插件。这样插件系统成为 MCP 的"网关",Agent 同时拥有进程内的高效集成和进程间的灵活扩展。
05 对比 Claude Code:Anthropic 怎么做扩展
Claude Code 是 Anthropic 官方的命令行 AI Agent,它的扩展机制设计非常值得借鉴:
┌─────────────────────────────────────────┐│ClaudeCodeCLI│├─────────────────────────────────────────┤│││ 插件 /SlashCommands││┌─────────┐┌─────────┐│││/review │ │ /fix │...││└─────────┘└─────────┘││││ 工具 (Tools) ││┌────┐┌────┐┌────┐┌────┐│││Bash││Read││Edit││grep│...││└────┘└────┘└────┘└────┘││││MCP 服务器 ││┌────────────────────────┐│││ 外部 MCPServer(filesys)│...││└────────────────────────┘││││ 钩子 (Hooks) ││┌────────┐┌────────┐│││前置钩子 ││后置钩子 │...││└────────┘└────────┘│├─────────────────────────────────────────┤│ settings.json /CLAUDE.md │└─────────────────────────────────────────┘
从 Claude Code 学到的四条设计启示:
① 配置驱动 > 代码驱动:通过 manifest 文件声明扩展,而不是直接改代码
② 分层扩展:不同需求用不同机制(钩子 vs 插件 vs MCP),不一刀切
③ 进程隔离:不稳定的第三方工具用独立进程运行(MCP)
④ 渐进式复杂度:简单场景用钩子,复杂场景用插件,跨进程用 MCP
06 十一章合一:Agent 完整能力图谱
从第一章到第十一章,我们一步步搭出了一个完整的 Agent 系统:
从"出生"到"长骨骼",十一章的内容构成了一个完整的 Agent 架构体系。扩展机制是最后一块拼图——它让 Agent 不再是一个固定的程序,而是一个可无限生长的平台。
07 总结:五条扩展设计原则
原则一:核心引擎只做最基础的事,能力通过扩展注入
原则二:钩子是"点",插件是"面",MCP 是"跨进程"
原则三:每个扩展点都要有错误隔离,一个插件崩不能带垮全局
原则四:配置驱动优于代码驱动,声明式注册优于硬编码
原则五:渐进式复杂度——简单用钩子,复杂用插件,跨进程用 MCP
扩展机制的核心思想是"关注点分离"——核心引擎专注基础运行,具体能力通过钩子和插件动态组合。这种架构让 Agent 从小巧的核心逐步成长,适应越来越复杂的应用场景。
下一章预告
第十二章我们将深入探讨 Agent 的性能优化与生产化部署——如何让 Agent 跑得更快、更稳、更省 Token,从"能跑"到"能上线"。
课后练习
练习 1:实现一个 RateLimitHook 钩子,在 beforeToolCall 中限制调用频率(1秒内不超过3次)
练习 2:为 PluginSystem 添加 getTool(name) 和 hasPlugin(name) 方法
练习 3:构建一个翻译插件,提供 translate 工具 + beforeToolCall 验证 + afterToolCall 统计
练习 4(选做):设计 MCPPluginAdapter,将 MCP 服务器包装为插件
如果这篇文章对你有帮助
点个「在看」分享给更多人关注公众号,下一章讲性能优化与生产化部署
AI Agent 从零到一系列 · 第十一章
夜雨聆风