三、AI Agent 设计模式:工具编排

上一篇我们分析了 Agent 生命周期。今天深入第二个主题:工具编排。Claude Code 有 30+ 种工具,如何统一管理?如何设计执行流水线?

工具是什么?

在 Agent 架构中,工具是"能力单元":

typescript 复制代码
type Tool<Input, Output> = {
  // 身份
  name: string
  aliases?: string[]
  description: string

  // Schema
  inputSchema: z.ZodType<Input>
  outputSchema?: z.ZodType<Output>

  // 执行
  call(args, context): Promise<ToolResult<Output>>

  // 权限
  checkPermissions(args, context): Promise<PermissionResult>

  // 属性
  isConcurrencySafe(args): boolean
  isReadOnly(args): boolean
  isDestructive?(args): boolean
  isEnabled(): boolean

  // UI
  renderToolUseMessage(args): React.ReactNode
}

每个工具封装一个能力,有完整的生命周期钩子。

工具分类

按功能分类

类别 工具
文件操作 Read, Edit, Write, Glob, Grep
命令执行 Bash, PowerShell
Agent 管理 Agent, Skill
网络访问 WebFetch, WebSearch
用户交互 AskUserQuestion
任务管理 TaskCreate, TaskUpdate
规划 EnterPlanMode, ExitPlanMode
MCP mcp__{server}__{tool}
其他 NotebookEdit, LSP, Config

按安全属性分类

属性 工具
并发安全 Read, Glob, Grep, WebFetch, WebSearch
只读 Read, Glob, Grep, WebFetch, WebSearch
破坏性 Edit(overwrite), Bash(rm, force push)
需权限 Bash, Edit, Write, Agent

buildTool 工厂函数

统一的工具构建:

typescript 复制代码
const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: () => false,  // 默认不安全
  isReadOnly: () => false,         // 默认写入
  isDestructive: () => false,      // 默认不破坏
  checkPermissions: (input) => ({ behavior: 'allow' }),
  userFacingName: () => this.name,
}

function buildTool<D extends ToolDefinition>(def: D): Tool {
  return {
    ...TOOL_DEFAULTS,
    ...def,
  }
}

安全默认值:工具必须显式声明"安全",不是默认安全。

工具执行流水线

完整的执行流程:

typescript 复制代码
async function executeToolCall(toolUse, context, canUseTool): ToolResult {
  const { name, input, id } = toolUse

  // === 阶段 1: 解析 ===
  const tool = findTool(name, context.options.tools)
  if (!tool) {
    return { type: 'error', error: `Unknown tool: ${name}` }
  }

  // === 阶段 2: 输入验证 ===
  let validatedInput = input
  if (tool.validateInput) {
    const validationResult = await tool.validateInput(input, context)
    if (!validationResult.valid) {
      return { type: 'validation_error', errors: validationResult.errors }
    }
    validatedInput = validationResult.updatedInput ?? input
  }

  // === 阶段 3: PreToolUse Hook ===
  const preHookResult = await executePreToolUseHooks({
    toolName: name,
    toolInput: validatedInput,
    toolUseId: id,
  }, context.abortController.signal)

  if (preHookResult.outcome === 'blocking') {
    return {
      type: 'blocked',
      reason: preHookResult.blockingError,
    }
  }

  // === 阶段 4: 权限检查 ===
  const permissionResult = await checkToolPermission(tool, validatedInput, context)

  if (permissionResult.behavior === 'deny') {
    return {
      type: 'denied',
      reason: permissionResult.message,
    }
  }

  if (permissionResult.behavior === 'ask') {
    // 显示权限对话框
    const userDecision = await showPermissionPrompt({
      toolName: name,
      input: validatedInput,
      message: permissionResult.message,
      suggestions: permissionResult.suggestions,
    })

    if (userDecision.denied) {
      return { type: 'denied', reason: 'User denied' }
    }

    // 用户可能修改输入
    validatedInput = userDecision.updatedInput ?? validatedInput
  }

  // === 阶段 5: 执行 ===
  const startTime = Date.now()
  let result: ToolResult

  try {
    result = await tool.call(validatedInput, context, canUseTool)
  } catch (error) {
    // === 阶段 6a: 错误处理 ===
    await executePostToolUseFailureHooks({
      toolName: name,
      toolInput: validatedInput,
      error,
    })

    return {
      type: 'error',
      error: error.message,
    }
  }

  const durationMs = Date.now() - startTime

  // === 阶段 7: PostToolUse Hook ===
  await executePostToolUseHooks({
    toolName: name,
    toolInput: validatedInput,
    result: result.data,
    durationMs,
  })

  // === 阶段 8: 结果处理 ===
  const finalResult = processToolResult(result, tool, context)

  return {
    type: 'success',
    toolUseId: id,
    content: finalResult,
    durationMs,
  }
}

8 个阶段,每个阶段有明确的职责。

权限检查的多层设计

typescript 复制代码
async function checkToolPermission(tool, input, context): PermissionResult {
  // === 层 1: 工具自身检查 ===
  const toolPermission = await tool.checkPermissions(input, context)
  if (toolPermission.behavior !== 'passthrough') {
    return toolPermission
  }

  // === 层 2: 规则匹配 ===
  const ruleResult = checkPermissionRules(tool.name, input, context)
  if (ruleResult.behavior !== 'passthrough') {
    return ruleResult
  }

  // === 层 3: 模式检查 ===
  const modeResult = checkPermissionMode(context.toolPermissionContext.mode)
  if (modeResult.behavior !== 'passthrough') {
    return modeResult
  }

  // === 层 4: 工作目录检查 ===
  const cwdResult = checkWorkingDirectory(tool.name, input, context)
  if (cwdResult.behavior !== 'passthrough') {
    return cwdResult
  }

  // === 层 5: 安全模式检查 ===
  const safetyResult = checkSafetyPatterns(tool.name, input)
  if (safetyResult.behavior !== 'passthrough') {
    return safetyResult
  }

  // === 层 6: 默认询问 ===
  return {
    behavior: 'ask',
    message: `Permission required for ${tool.name}`,
  }
}

层层把关,每层可以决定结果,只有全部 passthrough 才最终询问用户。

工具注册与发现

typescript 复制代码
type Tools = Map<string, Tool>

async function buildTools(context: ToolUseContext): Tools {
  const tools = new Map()

  // 1. 核心工具(始终加载)
  const coreTools = [
    FileReadTool,
    FileEditTool,
    FileWriteTool,
    GlobTool,
    GrepTool,
    BashTool,
    AgentTool,
    SkillTool,
    WebFetchTool,
    WebSearchTool,
    AskUserQuestionTool,
    TaskCreateTool,
    TaskUpdateTool,
    EnterPlanModeTool,
    ExitPlanModeTool,
  ]

  for (const tool of coreTools) {
    if (tool.isEnabled()) {
      tools.set(tool.name, tool)
      for (const alias of tool.aliases ?? []) {
        tools.set(alias, tool)
      }
    }
  }

  // 2. MCP 工具(动态加载)
  for (const mcpClient of context.options.mcpClients) {
    if (mcpClient.type === 'connected') {
      for (const mcpTool of mcpClient.tools) {
        const toolName = `mcp__${mcpClient.name}__${mcpTool.name}`
        const tool = createMcpTool(mcpTool, mcpClient)
        tools.set(toolName, tool)
      }
    }
  }

  // 3. 动态 Skill 工具
  const skills = await loadSkills(context)
  for (const skill of skills) {
    if (skill.isMcp) {
      const skillTool = createSkillTool(skill)
      tools.set(skillTool.name, skillTool)
    }
  }

  return tools
}

三层来源:核心工具、MCP 工具、动态 Skill。

MCP 工具动态创建

typescript 复制代码
function createMcpTool(mcpTool, mcpClient): Tool {
  return buildTool({
    name: `mcp__${mcpClient.name}__${mcpTool.name}`,
    isMcp: true,
    mcpInfo: {
      serverName: mcpClient.name,
      toolName: mcpTool.name,
    },

    // Schema 从 MCP 定义转换
    inputSchema: convertMcpSchemaToZod(mcpTool.inputSchema),

    // 执行调用 MCP server
    async call(args, context) {
      const result = await mcpClient.client.callTool({
        name: mcpTool.name,
        arguments: args,
      })

      return { data: result.content }
    },

    // 权限继承 MCP server 权限
    async checkPermissions(args, context) {
      return checkMcpPermission(mcpClient, mcpTool, args, context)
    },

    // MCP 工具默认不安全
    isConcurrencySafe: () => false,
    isReadOnly: () => mcpTool.annotations?.readOnly ?? false,
  })
}

MCP 工具从协议定义动态生成,每个 server 的工具独立命名。

工具调用追踪

typescript 复制代码
type ToolCallTracking = {
  toolUseId: string
  toolName: string
  startTime: number
  status: 'pending' | 'running' | 'completed' | 'failed'
  input?: unknown
  result?: unknown
  error?: Error
}

const toolCallRegistry = new Map<string, ToolCallTracking>()

function registerToolCall(toolUseId, toolName, input) {
  toolCallRegistry.set(toolUseId, {
    toolUseId,
    toolName,
    startTime: Date.now(),
    status: 'running',
    input,
  })
}

function completeToolCall(toolUseId, result) {
  const tracking = toolCallRegistry.get(toolUseId)
  if (tracking) {
    tracking.status = 'completed'
    tracking.result = result
    tracking.durationMs = Date.now() - tracking.startTime
  }
}

追踪每个工具调用,用于 UI 显示和调试。

进度报告

typescript 复制代码
type ToolProgress = {
  type: 'start' | 'progress' | 'complete'
  toolUseId: string
  data?: {
    type: 'bash_progress'
    output: string
  } | {
    type: 'file_progress'
    bytesRead: number
  }
}

// 工具调用时传入 onProgress
const result = await tool.call(input, context, canUseTool, onProgress)

function onProgress(progress: ToolProgress) {
  // 更新 UI
  updateToolProgress(progress)

  // 广播给 SDK
  broadcastToolProgress(progress)
}

长时间工具(如 Bash)可以实时报告进度。

工具结果处理

大结果截断

typescript 复制代码
const MAX_RESULT_SIZE_CHARS = 100_000

function processToolResult(result, tool, context): string {
  const content = formatResultContent(result)

  if (content.length > tool.maxResultSizeChars) {
    // 写入临时文件
    const filePath = writeToolResultToFile(content)

    // 返回摘要
    return `Result saved to: ${filePath}\nPreview: ${content.slice(0, 1000)}...`
  }

  return content
}

大结果不直接返回给 API,写入文件,返回引用。

结果格式化

typescript 复制代码
function formatResultContent(result: unknown): string {
  if (typeof result === 'string') {
    return result
  }

  if (result.type === 'text') {
    return result.content
  }

  if (result.type === 'image') {
    return `[Image: ${result.filePath}]`
  }

  if (result.type === 'error') {
    return `Error: ${result.message}`
  }

  return JSON.stringify(result, null, 2)
}

统一格式化,确保 API 理解。

工具并发控制

typescript 复制代码
type ConcurrencyState = {
  runningTools: Set<string>
  safeToolsRunning: Set<string>
  unsafeToolsRunning: Set<string>
}

function canExecuteTool(tool, input, state): boolean {
  const isSafe = tool.isConcurrencySafe(input)

  if (isSafe) {
    // 安全工具可以并发
    return true
  }

  // 不安全工具需要等待其他不安全工具完成
  return state.unsafeToolsRunning.size === 0
}

async function executeWithConcurrencyControl(tool, input, state) {
  // 等待并发条件满足
  while (!canExecuteTool(tool, input, state)) {
    await waitForUnsafeToolCompletion(state)
  }

  // 注册执行
  state.runningTools.add(toolUseId)
  if (!tool.isConcurrencySafe(input)) {
    state.unsafeToolsRunning.add(toolUseId)
  }

  // 执行
  const result = await tool.call(input, context)

  // 清理
  state.runningTools.delete(toolUseId)
  if (!tool.isConcurrencySafe(input)) {
    state.unsafeToolsRunning.delete(toolUseId)
  }

  return result
}

安全工具(Read, Glob)可以并发,不安全工具(Edit, Bash)串行执行。

上下文修改器

某些工具执行后需要修改上下文:

typescript 复制代码
type ToolResultWithContextModifier = {
  data: unknown
  contextModifier?: (ctx: ToolUseContext) => ToolUseContext
}

// SkillTool 的例子
async call({ skill, args }, context) {
  const processedCommand = await processSlashCommand(skill, args, context)

  return {
    data: { success: true },
    newMessages: processedCommand.messages,
    contextModifier(ctx) {
      return {
        ...ctx,
        options: {
          ...ctx.options,
          allowedTools: processedCommand.allowedTools,
          model: processedCommand.model,
        },
      }
    },
  }
}

// 执行后应用修改
if (result.contextModifier) {
  context = result.contextModifier(context)
}

SkillTool 可以修改 allowedTools------这允许 skill 限制可用工具。

总结

工具编排展示了工厂模式和流水线设计:

设计点 实现
工厂模式 buildTool() 统一构建,安全默认值
分类系统 功能分类 + 安全属性分类
执行流水线 8 阶段:解析→验证→Hook→权限→执行→错误→Hook→结果
权限多层 6 层检查,层层把关
动态加载 MCP 工具、Skill 工具动态创建
进度报告 onProgress 回调,实时 UI
并发控制 安全并发,不安全串行
上下文修改 contextModifier 模式

下一篇,我们将深入上下文管理------Token 预算如何分配,文件状态如何追踪。

相关推荐
Elastic 中国社区官方博客1 小时前
Elasticsearch:为 AI Agent builder 创建 skill plugin
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
肖有米XTKF86461 小时前
肖有米团队开发:青蓝送水系统模式制度商业解析
人工智能·团队开发·csdn开发云
Data_Journal1 小时前
2026年十大数据集网站
大数据·开发语言·数据库·人工智能·python
helloworddm1 小时前
Vulkan GPU图像处理之直方图均衡化:Kompute框架实战与性能分析
图像处理·人工智能
xingyuzhisuan1 小时前
适合微调Llama 3 70B模型的最低GPU配置推荐
运维·人工智能·算法·llama·gpu算力
珠海西格电力1 小时前
如何实现零碳园区管理系统“云-边-端”架构的协同
大数据·数据库·人工智能·架构·能源
初心未改HD1 小时前
机器学习之逻辑回归详解
人工智能·机器学习·逻辑回归
简简单单做算法1 小时前
基于GAN生成对抗网络模型的图像生成与虚拟场景构建系统matlab仿真
人工智能·神经网络·生成对抗网络·matlab·gan·虚拟场景构建
橙色阳光五月天1 小时前
使用 hyperframes 结合其他技术是否可以做出XX动物园游览动态图
人工智能·ai·ai作画·自动化·视频