|
| 1 | +# Blade 工具系统架构文档 |
| 2 | + |
| 3 | +> **版本**: v2.0 |
| 4 | +> **更新时间**: 2025-10-10 |
| 5 | +> **状态**: ✅ 已完成并优化 |
| 6 | +
|
| 7 | +## 一、架构概览 |
| 8 | + |
| 9 | +Blade 工具系统基于**统一、简洁、类型安全**的设计原则,提供强大而灵活的工具扩展能力。 |
| 10 | + |
| 11 | +### 核心特性 |
| 12 | + |
| 13 | +- ✅ **统一接口**: 所有工具(内置/MCP/自定义)使用相同的 `Tool` 接口 |
| 14 | +- ✅ **类型安全**: 端到端 TypeScript 类型推断,基于 Zod Schema |
| 15 | +- ✅ **简洁 API**: 通过 `createTool` 函数创建工具,无需复杂继承 |
| 16 | +- ✅ **MCP 集成**: 自动转换 MCP 工具为标准 Tool |
| 17 | +- ✅ **模块化**: 清晰的目录结构,职责分明 |
| 18 | + |
| 19 | +## 二、目录结构 |
| 20 | + |
| 21 | +``` |
| 22 | +src/tools/ |
| 23 | +├── core/ # 核心工具系统 |
| 24 | +│ ├── createTool.ts # 统一的工具创建 API ⭐ |
| 25 | +│ ├── ToolInvocation.ts # 工具调用抽象 |
| 26 | +│ └── index.ts # 核心导出 |
| 27 | +│ |
| 28 | +├── types/ # 类型定义(统一位置) ⭐ |
| 29 | +│ ├── ToolTypes.ts # 工具类型(Tool、ToolConfig、ToolDescription) |
| 30 | +│ ├── ExecutionTypes.ts # 执行上下文类型 |
| 31 | +│ ├── SecurityTypes.ts # 安全相关类型(ValidationError等) |
| 32 | +│ └── index.ts # 类型统一导出 |
| 33 | +│ |
| 34 | +├── validation/ # 验证系统 |
| 35 | +│ ├── zod-schemas.ts # Zod Schema 工具函数 |
| 36 | +│ ├── zod-to-json.ts # Zod → JSON Schema 转换 |
| 37 | +│ ├── error-formatter.ts # 错误格式化 |
| 38 | +│ └── index.ts # 验证导出 |
| 39 | +│ |
| 40 | +├── registry/ # 工具注册系统 |
| 41 | +│ ├── ToolRegistry.ts # 主注册表 |
| 42 | +│ ├── ToolResolver.ts # 工具解析器 |
| 43 | +│ └── index.ts # 注册系统导出 |
| 44 | +│ |
| 45 | +├── builtin/ # 内置工具(13个) |
| 46 | +│ ├── file/ # 文件操作(4个) |
| 47 | +│ │ ├── read.ts # 读取文件 |
| 48 | +│ │ ├── write.ts # 写入文件 |
| 49 | +│ │ ├── edit.ts # 编辑文件 |
| 50 | +│ │ └── multi-edit.ts # 批量编辑 |
| 51 | +│ ├── search/ # 搜索工具(3个) |
| 52 | +│ │ ├── glob.ts # 文件模式匹配 |
| 53 | +│ │ ├── grep.ts # 内容搜索 |
| 54 | +│ │ └── find.ts # 文件查找 |
| 55 | +│ ├── shell/ # Shell 命令(3个) |
| 56 | +│ │ ├── bash.ts # Bash 命令 |
| 57 | +│ │ ├── shell.ts # 通用 Shell |
| 58 | +│ │ └── script.ts # 脚本执行 |
| 59 | +│ ├── web/ # 网络工具(2个) |
| 60 | +│ │ ├── web-fetch.ts # 网页抓取 |
| 61 | +│ │ └── api-call.ts # API 调用 |
| 62 | +│ ├── task/ # 任务管理(1个) |
| 63 | +│ │ └── task.ts # 任务代理 |
| 64 | +│ └── index.ts # 内置工具导出 |
| 65 | +│ |
| 66 | +└── index.ts # 工具系统统一导出 |
| 67 | +``` |
| 68 | + |
| 69 | +### 架构优化成果 |
| 70 | + |
| 71 | +**类型系统优化** ✅ |
| 72 | +- 所有类型定义统一在 `types/` 文件夹 |
| 73 | +- **迁移到标准 `@types/json-schema`**: 使用社区标准类型定义 |
| 74 | +- 删除重复定义:`core/types.ts`(110行)、`McpTypes.ts`(70行) |
| 75 | +- 删除自定义 `JSONSchema7` 接口(22行),使用标准定义 |
| 76 | +- 解决了 `ValidationError`/`ValidationResult` 重复定义问题 |
| 77 | +- 修复了 `output`/`displayMessage` 字段名称问题 |
| 78 | + |
| 79 | +**代码精简** ✅ |
| 80 | +- 删除未使用代码:`ToolDiscovery.ts`(201行) |
| 81 | +- 删除冗余类型定义:`convertZodToJsonSchema` 函数(61行) |
| 82 | +- 净删除:442行重复/未使用代码 |
| 83 | +- 新增:74行辅助函数 + 30行类型守卫 |
| 84 | +- **总净减少:338行代码** |
| 85 | + |
| 86 | +**依赖优化** ✅ |
| 87 | +- 新增:`@types/json-schema@7.0.15` (devDependency, ~3KB) |
| 88 | +- **零运行时影响**: 类型定义编译后完全消失 |
| 89 | +- 与生态对齐:兼容 Ajv、json-schema-to-typescript 等库 |
| 90 | + |
| 91 | +## 三、核心接口 |
| 92 | + |
| 93 | +### Tool 接口 |
| 94 | + |
| 95 | +```typescript |
| 96 | +export interface Tool<TParams = unknown> { |
| 97 | + // 基本信息 |
| 98 | + readonly name: string; |
| 99 | + readonly displayName: string; |
| 100 | + readonly kind: ToolKind; |
| 101 | + readonly description: ToolDescription; |
| 102 | + readonly version: string; |
| 103 | + readonly category?: string; |
| 104 | + readonly tags: string[]; |
| 105 | + |
| 106 | + // 安全控制 |
| 107 | + readonly requiresConfirmation: boolean; |
| 108 | + |
| 109 | + // 方法 |
| 110 | + getFunctionDeclaration(): FunctionDeclaration; |
| 111 | + getMetadata(): Record<string, unknown>; |
| 112 | + build(params: TParams): ToolInvocation<TParams>; |
| 113 | + execute(params: TParams, signal?: AbortSignal): Promise<ToolResult>; |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +### ToolConfig 接口 |
| 118 | + |
| 119 | +```typescript |
| 120 | +export interface ToolConfig<TSchema = unknown, TParams = unknown> { |
| 121 | + name: string; |
| 122 | + displayName: string; |
| 123 | + kind: ToolKind; |
| 124 | + schema: TSchema; // Zod Schema |
| 125 | + description: ToolDescription; |
| 126 | + requiresConfirmation?: boolean | ConfirmationCallback<TParams>; |
| 127 | + execute: (params: TParams, context: ExecutionContext) => Promise<ToolResult>; |
| 128 | + version?: string; |
| 129 | + category?: string; |
| 130 | + tags?: string[]; |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +### ToolResult 接口 |
| 135 | + |
| 136 | +```typescript |
| 137 | +export interface ToolResult { |
| 138 | + success: boolean; |
| 139 | + llmContent: string | object; // 传递给 LLM 的内容 |
| 140 | + displayContent: string; // 显示给用户的内容 |
| 141 | + error?: ToolError; |
| 142 | + metadata?: Record<string, unknown>; |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +## 四、使用示例 |
| 147 | + |
| 148 | +### 创建简单工具 |
| 149 | + |
| 150 | +```typescript |
| 151 | +import { createTool } from '@/tools/core'; |
| 152 | +import { ToolKind } from '@/tools/types'; |
| 153 | +import { z } from 'zod'; |
| 154 | + |
| 155 | +export const helloTool = createTool({ |
| 156 | + name: 'hello', |
| 157 | + displayName: 'Hello World', |
| 158 | + kind: ToolKind.Other, |
| 159 | + |
| 160 | + schema: z.object({ |
| 161 | + name: z.string().describe('要打招呼的名字'), |
| 162 | + }), |
| 163 | + |
| 164 | + description: { |
| 165 | + short: '打招呼的示例工具', |
| 166 | + important: ['这是一个演示工具'] |
| 167 | + }, |
| 168 | + |
| 169 | + async execute(params, context) { |
| 170 | + return { |
| 171 | + success: true, |
| 172 | + llmContent: `Hello, ${params.name}!`, |
| 173 | + displayContent: `✅ Hello, ${params.name}!`, |
| 174 | + }; |
| 175 | + } |
| 176 | +}); |
| 177 | +``` |
| 178 | + |
| 179 | +### MCP 工具集成 |
| 180 | + |
| 181 | +```typescript |
| 182 | +import { createMcpTool } from '@/mcp/createMcpTool'; |
| 183 | + |
| 184 | +// MCP 工具自动转换为标准 Tool |
| 185 | +export function createMcpTool( |
| 186 | + mcpClient: McpClient, |
| 187 | + serverName: string, |
| 188 | + toolDef: McpToolDefinition |
| 189 | +) { |
| 190 | + // 1. JSON Schema → Zod Schema 自动转换 |
| 191 | + const zodSchema = convertJsonSchemaToZod(toolDef.inputSchema); |
| 192 | + |
| 193 | + // 2. 使用 createTool 创建标准工具 |
| 194 | + return createTool({ |
| 195 | + name: `mcp__${serverName}__${toolDef.name}`, |
| 196 | + displayName: `${serverName}: ${toolDef.name}`, |
| 197 | + kind: ToolKind.External, |
| 198 | + schema: zodSchema, |
| 199 | + description: { |
| 200 | + short: toolDef.description || `MCP 工具: ${toolDef.name}`, |
| 201 | + important: [`来自 MCP 服务器: ${serverName}`] |
| 202 | + }, |
| 203 | + requiresConfirmation: true, |
| 204 | + |
| 205 | + async execute(params, context) { |
| 206 | + const result = await mcpClient.callTool(toolDef.name, params); |
| 207 | + return convertMcpResult(result); |
| 208 | + } |
| 209 | + }); |
| 210 | +} |
| 211 | +``` |
| 212 | + |
| 213 | +## 五、工具注册 |
| 214 | + |
| 215 | +```typescript |
| 216 | +import { ToolRegistry } from '@/tools/registry'; |
| 217 | +import { getBuiltinTools } from '@/tools/builtin'; |
| 218 | + |
| 219 | +// 创建注册表 |
| 220 | +const registry = new ToolRegistry(); |
| 221 | + |
| 222 | +// 注册内置工具 |
| 223 | +const builtinTools = await getBuiltinTools(); |
| 224 | +builtinTools.forEach(tool => registry.register(tool)); |
| 225 | + |
| 226 | +// 注册 MCP 工具 |
| 227 | +const mcpTools = await mcpRegistry.getAvailableTools(); |
| 228 | +mcpTools.forEach(tool => registry.registerMcpTool(tool)); |
| 229 | + |
| 230 | +// 获取所有工具 |
| 231 | +const allTools = registry.getAll(); // Tool[] |
| 232 | + |
| 233 | +// 获取 LLM 函数声明 |
| 234 | +const declarations = registry.getFunctionDeclarations(); |
| 235 | +``` |
| 236 | + |
| 237 | +## 六、类型系统 |
| 238 | + |
| 239 | +### 类型文件说明 |
| 240 | + |
| 241 | +| 文件 | 说明 | 主要类型 | |
| 242 | +|------|------|----------| |
| 243 | +| `ToolTypes.ts` | 核心工具类型 | `Tool`, `ToolConfig`, `ToolDescription`, `ToolKind`, `ToolResult`, `FunctionDeclaration` | |
| 244 | +| `ExecutionTypes.ts` | 执行上下文类型 | `ExecutionContext` | |
| 245 | +| `SecurityTypes.ts` | 安全相关类型 | `ValidationError`, `ValidationResult`, `PermissionResult` | |
| 246 | +| **`@types/json-schema`** | **JSON Schema 标准类型** | **`JSONSchema7`**, **`JSONSchema7Definition`** | |
| 247 | + |
| 248 | +### 类型导入示例 |
| 249 | + |
| 250 | +```typescript |
| 251 | +// 从统一入口导入工具类型 |
| 252 | +import type { |
| 253 | + Tool, |
| 254 | + ToolConfig, |
| 255 | + ToolResult, |
| 256 | + ExecutionContext, |
| 257 | +} from '@/tools/types'; |
| 258 | + |
| 259 | +// JSON Schema 类型需要直接从标准包导入 |
| 260 | +import type { JSONSchema7 } from 'json-schema'; |
| 261 | +``` |
| 262 | + |
| 263 | +### JSONSchema7 类型使用 |
| 264 | + |
| 265 | +**为什么使用标准类型?** |
| 266 | +- ✅ 社区标准,广泛兼容 |
| 267 | +- ✅ 完整的 JSON Schema Draft-07 支持 |
| 268 | +- ✅ 官方维护,自动跟进规范更新 |
| 269 | +- ✅ 零运行时开销(编译后消失) |
| 270 | + |
| 271 | +**两种类型:** |
| 272 | +```typescript |
| 273 | +import type { JSONSchema7, JSONSchema7Definition } from 'json-schema'; |
| 274 | + |
| 275 | +// JSONSchema7 - 完整的 schema 对象 |
| 276 | +const schema: JSONSchema7 = { |
| 277 | + type: 'object', |
| 278 | + properties: { |
| 279 | + name: { type: 'string' } |
| 280 | + } |
| 281 | +}; |
| 282 | + |
| 283 | +// JSONSchema7Definition - 可以是 boolean (JSON Schema 规范允许) |
| 284 | +const def1: JSONSchema7Definition = schema; // 对象 |
| 285 | +const def2: JSONSchema7Definition = true; // true = 接受任何值 |
| 286 | +const def3: JSONSchema7Definition = false; // false = 拒绝任何值 |
| 287 | +``` |
| 288 | + |
| 289 | +**类型守卫示例:** |
| 290 | +```typescript |
| 291 | +// 处理 properties 可能是 boolean 的情况 |
| 292 | +if (schema.properties) { |
| 293 | + for (const [key, value] of Object.entries(schema.properties)) { |
| 294 | + if (typeof value === 'object' && value !== null) { |
| 295 | + // 安全地使用 value as JSONSchema7 |
| 296 | + processSchema(value as JSONSchema7); |
| 297 | + } |
| 298 | + } |
| 299 | +} |
| 300 | +``` |
| 301 | + |
| 302 | +## 七、最佳实践 |
| 303 | + |
| 304 | +### 1. 工具命名 |
| 305 | + |
| 306 | +- 使用小写字母和下划线:`read_file`、`multi_edit` |
| 307 | +- MCP 工具使用前缀:`mcp__server__tool` |
| 308 | + |
| 309 | +### 2. 描述格式 |
| 310 | + |
| 311 | +```typescript |
| 312 | +description: { |
| 313 | + short: '简短描述(1行)', |
| 314 | + long: '详细说明(可选)', |
| 315 | + usageNotes: [ |
| 316 | + '使用说明1', |
| 317 | + '使用说明2' |
| 318 | + ], |
| 319 | + important: [ |
| 320 | + '重要提示1', |
| 321 | + '重要提示2' |
| 322 | + ] |
| 323 | +} |
| 324 | +``` |
| 325 | + |
| 326 | +### 3. 返回值格式 |
| 327 | + |
| 328 | +```typescript |
| 329 | +return { |
| 330 | + success: true, |
| 331 | + llmContent: data, // 给 LLM 的结构化数据 |
| 332 | + displayContent: message, // 给用户的友好消息 |
| 333 | + metadata: { // 元数据(可选) |
| 334 | + timestamp: Date.now(), |
| 335 | + // ... |
| 336 | + } |
| 337 | +}; |
| 338 | +``` |
| 339 | + |
| 340 | +### 4. 辅助函数 |
| 341 | + |
| 342 | +每个工具应包含 `formatDisplayMessage` 辅助函数: |
| 343 | + |
| 344 | +```typescript |
| 345 | +function formatDisplayMessage( |
| 346 | + path: string, |
| 347 | + metadata: Record<string, unknown> |
| 348 | +): string { |
| 349 | + // 构建用户友好的消息 |
| 350 | + return `✅ 操作成功: ${path}`; |
| 351 | +} |
| 352 | +``` |
| 353 | + |
| 354 | +## 八、下一步计划 |
| 355 | + |
| 356 | +### 短期任务 |
| 357 | +- [ ] 完善工具单元测试 |
| 358 | +- [ ] 添加工具性能监控 |
| 359 | +- [ ] 实现工具执行管道 |
| 360 | + |
| 361 | +### 中期任务 |
| 362 | +- [ ] 支持更多 MCP 服务器 |
| 363 | +- [ ] 实现工具权限系统 |
| 364 | +- [ ] 添加工具缓存机制 |
| 365 | + |
| 366 | +### 长期目标 |
| 367 | +- [ ] 构建工具市场 |
| 368 | +- [ ] 提供工具开发 SDK |
| 369 | +- [ ] 建立工具生态系统 |
| 370 | + |
| 371 | +## 九、参考资料 |
| 372 | + |
| 373 | +- [工具使用指南](./tools/TOOL_USAGE_GUIDE.md) |
| 374 | +- [MCP 协议文档](./protocols/mcp-support.md) |
| 375 | +- [源码目录](../src/tools/) |
0 commit comments