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

无头工具的工作原理

从高层次看,无头工具将工具模式与仅在浏览器端运行的实现分离开来。
  1. 在智能体上注册一个工具,该工具立即调用 interrupt() 以将执行推迟到前端。
  2. 在前端的定义中映射相同的工具名称和参数字段。
  3. 使用 .implement(...) 在前端实现匹配的工具,并将它们传递给 useStream({ tools: [...] })。
  4. 当智能体调用一个匹配的工具时,客户端处理该操作并使用工具结果恢复被中断的运行。

在智能体上注册工具

Playground 定义了一小组遵循相同模式的客户端工具:智能体暴露一个工具模式,前端负责实际执行。 在服务器上定义常规工具,这些工具立即调用 interrupt(),然后在前端的 tools.ts 文件中映射相同的工具名称和参数字段。
agent.py
每个工具都使用一个结构化的有效载荷进行中断,前端可以处理该载荷,然后返回在运行恢复时提供的值。在客户端映射相同的工具名称和模式,以便前端可以附加实现。
tools.ts

实现浏览器行为

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

将实现连接到 useStream

将已实现的工具传递给 useStream。当智能体发出匹配的工具调用时,该钩子会运行客户端实现并为您恢复运行。 定义一个匹配您智能体状态模式的 TypeScript 接口,并将其作为类型参数传递给 useStream,以便类型安全地访问状态值:
types.ts

内联渲染工具活动

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

用例

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

最佳实践

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