无头工具使您的智能体能够调用那些必须在用户应用程序中而非在服务器上实际执行的工具。智能体看到的仍然是一个普通的工具模式,但其具体实现位于前端,因此它可以访问浏览器 API,如 IndexedDB、地理位置、剪贴板、画布或文件选择器。 当数据需要保存在设备本地时,这种模式特别有用。本页上的 Playground 示例使用了一套基于 IndexedDB 的小型浏览器记忆工具包,以及一个完全在客户端运行的地理位置工具。

无头工具的工作原理

从高层次看,无头工具将工具模式与仅在浏览器端运行的实现分离开来。
  1. 在智能体上注册一个仅包含模式(schema-only)的工具定义。
  2. 使用 .implement(...) 在前端实现匹配的工具。
  3. 将这些实现传递给 useStream({ tools: [...] })。
  4. 当智能体发出匹配的工具调用时,客户端运行它并使用工具结果恢复被中断的运行。
将工具的定义和实现放在不同的模块中。在您的智能体和前端之间共享定义,以确保工具名称和模式保持一致,然后将仅与浏览器相关的代码放在一个仅限客户端的 impl 模块中。

在智能体上注册工具

Playground 定义了一小组遵循相同模式的客户端工具:智能体暴露一个工具模式,前端负责实际执行。 在共享的 tools.ts 文件中定义一次工具,并从智能体和前端使用该文件。

实现浏览器行为

将仅限客户端的行为放在一个单独的模块中,并使用 .implement(...) 将其附加。真实的 Playground 包含了一个更完整的 IndexedDB 存储,具有搜索、列表、过期和删除操作。以下示例在更高层次上展示了相同的结构:
impl.ts

将实现连接到 useStream

将已实现的工具传递给 useStream。当智能体发出匹配的工具调用时,该钩子会运行客户端实现并为您恢复运行。 智能体状态可以从智能体定义中推断出来:

内联渲染工具活动

Playground 将每个记忆或地理位置操作渲染为独立的卡片,并在输入框附近保留一个小型记忆统计面板。关键步骤是将 stream.toolCalls 中的每个条目映射回触发它的 AI 消息:
这与 工具调用 中更丰富的 UI 模式配合得很好,其中每个工具结果都可以渲染为专门的卡片,而不是原始的 JSON。

用例

当工作依赖于仅存在于客户端的 API 或数据时,使用无头工具:
  • 在 IndexedDB 或 localStorage 中的本地记忆
  • 设备 API,如地理位置、剪贴板、摄像头或文件选择器
  • 画布、音频或其他仅限浏览器的渲染原语
  • 应保留在用户设备上的隐私敏感数据
  • 需要直接访问内存中前端状态的 UI 操作

最佳实践

  • 保持工具小巧且类型化。优先使用多个窄范围的工具,而不是一个通用的“运行任意浏览器代码”工具。
  • 返回 JSON 可序列化的结果。不要尝试返回 DOM 节点、文件句柄或其他不可序列化的浏览器对象。
  • 共享定义,分离实现。智能体和客户端应就工具名称和模式达成一致,但只有客户端应加载浏览器 API。
  • 在 UI 中呈现工具状态。使用 stream.toolCalls 和 onTool 来显示待处理、成功和错误状态。
  • 在需要时添加审查。对于敏感的客户端操作,将此模式与 人在环中 结合使用。