跳转到内容

事件驱动工具执行

事件驱动工具执行

学习目标

读完本章后,你将能够:

  • 理解事件驱动工具执行的核心概念:工具定义与执行实例的解耦
  • 掌握延迟加载(deferred loading)策略的设计与适用场景
  • 实现动态工具发现协议(如 tool_search
  • 处理工具冲突与提供商原生工具的优先级

前置知识


核心概念

1. 事件驱动 vs 实例驱动

传统的工具调用系统通常将工具定义(名称、描述、参数 Schema)与执行实例绑定在一起——工具对象既告诉模型”我能做什么”,也负责”实际执行”。

事件驱动模式将两者解耦

  • 工具定义(Tool Definition):轻量级可序列化对象,仅包含名称、描述和 JSON Schema,用于注入 prompt
  • 工具执行实例(Tool Instance):重量级运行时对象,携带数据库连接、API 客户端、用户上下文等
  • 事件处理器(Event Handler):接收 tool_call 事件,根据名称查找执行实例并调用

2. 延迟加载(Deferred Loading)

延迟加载是指工具的定义在 prompt 中可见,但执行实例不立即创建,而是在模型实际调用时才实例化。

适用场景

  • 工具数量过多(如 MCP 服务器有 50+ 工具),全量实例化消耗大量资源
  • 工具依赖外部认证(如 OAuth),仅在用户实际需要时才走认证流程
  • 工具实例化成本高(如需要启动子进程、连接远程服务)

设计权衡

优势代价
减少启动开销,仅实例化被调用的工具首次调用有额外延迟
支持动态工具发现需要额外的工具搜索轮次
避免不必要的认证流程模型可能调用不存在的工具

3. 动态工具发现协议

当工具数量超过 prompt 上下文预算时,系统可以采用两阶段发现策略:

  1. 阶段 1:注入一个 tool_search 工具,模型通过自然语言查询可用工具
  2. 阶段 2tool_search 返回匹配的工具名称和描述,模型据此决定是否调用具体工具

发现结果可以缓存,避免每个 turn 都重新搜索:

4. 工具冲突解决

不同模型提供商可能有原生工具(如 Anthropic 的 web_search、Google 的 googleSearch),与平台级同名工具产生冲突。

解决策略:

原则:当平台工具与提供商原生工具功能重叠时,优先使用提供商原生工具(性能更好、集成更深),除非平台工具提供了额外能力(如多提供商聚合)。

5. 工具输出引用机制

对于代码执行类工具,工具输出可能包含对其他工具结果的引用占位符(如 {{tool<idx>turn<turn>}})。在执行前需要将这些占位符替换为实际的输出内容。

设计要点

  • 仅在启用代码执行环境时激活此功能
  • 占位符替换发生在工具描述注入阶段,而非执行阶段
  • SDK 默认的截断限制(~400 KB per output, 5 MB total)防止超出 shell ARG_MAX

陷阱与对策

陷阱 1:子 Agent 继承父 Agent 的工具搜索状态

问题:如果子 Agent 直接复用父 Agent 的 toolRegistry 引用,父 Agent 对 defer_loading 的修改会泄漏到子 Agent 的上下文中。

对策:子 Agent 必须使用深度克隆的 toolRegistry(克隆 Map + 每个 LCTool 对象浅拷贝),并且跳过 defer_loading 覆盖逻辑。

陷阱 2:工具搜索格式化变更导致解析失败

问题tool_search 的输出格式可能从文本变为 JSON,解析器需要向后兼容。

对策:实现双格式解析器,优先尝试 JSON 格式,失败后回退到正则表达式解析文本格式。

陷阱 3:提供商工具与平台工具的组合冲突

问题:某些提供商(如 Google)同时启用原生工具和平台工具时会导致模型混淆。

对策:实施严格的冲突检测,仅对明确支持组合的模型(如 gemini-3)允许同时启用。


设计权衡

何时使用事件驱动工具执行

适合

  • 工具数量多(10+),且不是每个 turn 都会全部调用
  • 需要动态加载工具(如 MCP 服务器的热插拔)
  • 工具实例化成本高(需要外部连接、认证)

不适合

  • 工具数量少(< 5),且每次 turn 都会调用
  • 对工具调用延迟极度敏感的场景
  • 工具无状态且实例化成本极低

延迟加载 vs 预加载对比

维度延迟加载预加载
启动延迟低(仅序列化定义)高(创建所有实例)
首次调用延迟高(需实例化)低(已有实例)
内存占用按需增长固定高
认证处理按需触发预认证
实现复杂度