OpenMontage 语音智能体工具开发指南:ElevenLabs Agent 的 Webhook、Client 与系统内置工具
在 OpenMontage 的智能体技能体系中,.agents/skills/agents/ 技能负责指导开发者使用 ElevenLabs Conversational AI 平台构建实时语音智能体(语音助手、客服机器人、交互式语音角色等),而本文聚焦其中的 Client Tools 参考文档:讲清楚如何用三类工具——服务端 Webhook 工具、浏览器端 Client 工具、平台内置 System 工具——扩展语音 Agent 的能力边界。读完本文,你可以独立设计一个能查数据库、调 API、操作前端 UI 并能优雅挂断/转接的语音智能体,并掌握工具描述编写、错误处理与超时配置的实战要点。
工具类型总览
ElevenLabs 的智能体工具体系分为三种类型,执行位置与典型用途各不相同:
| 类型 | 执行位置 | 典型用途 |
|---|---|---|
| Webhook | 服务端(通过 HTTP) | 数据库查询、API 调用、需要保密凭证的安全操作 |
| Client | 浏览器端 JavaScript | UI 更新、localStorage 读写、页面导航 |
| System | ElevenLabs 内置 | 结束通话、转接人工、标准动作 |
选型原则:涉及密钥、数据库或需要审计的操作放在 Webhook 端;纯前端交互(弹卡片、跳转页面)放在 Client 端;通话生命周期控制(挂断、转接)交给内置工具,无需自己实现。
工具定义的位置
三类工具都定义在智能体的 conversation_config.agent.prompt 中,但放置位置不同:Webhook 与 Client 工具放入 tools 数组,System 工具放入 built_in_tools 对象:
conversation_config={
"agent": {
"prompt": {
"prompt": "You are helpful.",
"llm": "gemini-2.0-flash",
"tools": [...], # Webhook 和 client 工具
"built_in_tools": {...} # System 工具(end_call、transfer 等)
}
}
}
这一点很关键:如果把内置工具误放进 tools 数组,平台不会按预期行为处理;反之,自定义工具不能出现在 built_in_tools 中。完整的 conversation_config 结构(包括 tts、asr、turn、vad 等兄弟字段)可在 Agent Configuration 参考 中查阅。
Webhook 工具:让 Agent 调用你自己的服务端逻辑
Webhook 工具在 Agent 需要外部数据或动作时触发,由 ElevenLabs 服务端向你的 HTTP 端点发起请求。
基本 Webhook 定义
下面通过 Python SDK 创建一个带天气查询 Webhook 的 Agent,展示 api_schema 的完整字段(URL、方法、请求头、请求体 JSON Schema):
agent = client.conversational_ai.agents.create(
name="Weather Assistant",
conversation_config={
"agent": {
"prompt": {
"prompt": "You are a helpful assistant that can check the weather.",
"llm": "gemini-2.0-flash",
"tools": [{
"type": "webhook",
"name": "get_weather",
"description": "Get current weather for a city. Use when user asks about weather.",
"api_schema": {
"url": "https://api.example.com/weather",
"method": "POST",
"request_headers": {
"Authorization": "Bearer {{API_KEY}}"
},
"request_body_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g., 'San Francisco'"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units"
}
},
"required": ["city"]
}
}
}]
}
},
"tts": {"voice_id": "JBFqnCBsd6RMkjVDRZzb"}
}
)
注意 description 中写明了触发时机("Use when user asks about weather"),这是引导 LLM 正确选择工具的核心手段,详见后文最佳实践。
Webhook 请求格式
当 Agent 调用 Webhook 工具时,ElevenLabs 会向你配置的 URL 发送如下 JSON:
{
"tool_call_id": "call_abc123",
"tool_name": "get_weather",
"parameters": {
"city": "San Francisco",
"units": "fahrenheit"
},
"conversation_id": "conv_xyz789"
}
tool_call_id 用于在你的服务端日志中追踪单次调用,conversation_id 可用来把工具结果与会话上下文关联。
Webhook 响应格式
你的服务端只需返回 result 字段,支持两种形态:
- 直接返回自然语言字符串,Agent 可基于其组织回复:
{
"result": "The weather in San Francisco is 68°F and sunny."
}
- 返回结构化数据,交由 Agent 自行理解:
{
"result": {
"temperature": 68,
"condition": "sunny",
"humidity": 45
}
}
带认证的 Webhook
通过 request_headers 传递 Bearer Token 或自定义业务头,并用 response_timeout_secs 控制超时:
# 位于 conversation_config.agent.prompt.tools 内
{
"type": "webhook",
"name": "lookup_order",
"description": "Look up order status by order ID",
"response_timeout_secs": 10,
"api_schema": {
"url": "https://api.mystore.com/orders/lookup",
"method": "POST",
"request_headers": {
"Authorization": "Bearer {{ORDER_API_KEY}}",
"X-Store-ID": "store_123"
},
"request_body_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Order ID (e.g., ORD-12345)"
}
},
"required": ["order_id"]
}
}
}
用工作区环境变量管理多环境配置
为了让同一份服务端工具配置在 staging 与 production 间复用,可以使用工作区(workspace)环境变量:
{{system_env__label}}语法可用于工具 URL;- 机密环境变量(secret env var)可填充
request_headers; - 认证连接(auth-connection)环境变量可填充
api_schema.auth_connection。
{
"api_schema": {
"url": "https://{{system_env__api_host}}.example.com/orders",
"method": "GET",
"request_headers": {
"X-Api-Key": { "env_var_label": "orders_api_key" }
},
"auth_connection": { "env_var_label": "orders_oauth" }
}
}
工作区认证连接支持 OAuth2 client credentials、OAuth2 JWT、Private Key JWT、Basic Auth、Bearer Auth 以及自定义 Header 认证。此外,系统动态变量也可用在工具参数和 Header 中:{{system__conversation_history}} 会把完整对话上下文以惰性求值的 JSON 历史对象(含 user、agent、tool 条目)传给 Webhook 或子 Agent,适合需要全量会话上下文的场景。同样的环境变量解析机制也适用于 MCP 服务器连接配置。
Webhook 工具可选参数
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
response_timeout_secs |
int | 20 |
超时秒数,取值范围 5–120 |
disable_interruptions |
bool | false |
工具执行期间禁止用户打断 |
execution_mode |
string | "immediate" |
immediate、post_tool_speech 或 async |
tool_call_sound |
string | - | 执行期间播放的声音:typing、elevator1–elevator4 |
force_pre_tool_speech |
bool | false |
强制 Agent 在执行工具前先说话(如"稍等,我查一下") |
tool_error_handling_mode |
string | "auto" |
auto、summarized、passthrough 或 hide |
注意:api_schema.method 的默认值是 GET。凡是带请求体的 Webhook 工具,必须显式设置 "method": "POST",否则请求体会被丢弃。
服务端实现:Node.js
app.post("/webhook/get_weather", async (req, res) => {
const { parameters, conversation_id } = req.body;
const { city, units = "fahrenheit" } = parameters;
// 从你的数据源获取天气
const weather = await weatherService.get(city, units);
res.json({
result: `It's ${weather.temp}°${units === "celsius" ? "C" : "F"} and ${weather.condition} in ${city}.`,
});
});
服务端实现:Python
@app.post("/webhook/get_weather")
async def get_weather(request: Request):
data = await request.json()
city = data["parameters"]["city"]
units = data["parameters"].get("units", "fahrenheit")
# 从你的数据源获取天气
weather = weather_service.get(city, units)
return {
"result": f"It's {weather['temp']}°{'C' if units == 'celsius' else 'F'} and {weather['condition']} in {city}."
}
Client 工具:在用户浏览器中执行 JavaScript
Client 工具在用户的浏览器里执行 JavaScript,适合 UI 更新、页面导航和访问浏览器 API(localStorage、摄像头、位置等)。
注册 Client 工具
Client 工具的实际函数体在启动会话时通过 clientTools 传入,而不是写在 Agent 配置里:
import { Conversation } from "@elevenlabs/client";
const conversation = await Conversation.startSession({
agentId: "your-agent-id",
clientTools: {
show_product: async ({ productId }) => {
// 更新 UI 展示商品
const modal = document.getElementById("product-modal");
modal.innerHTML = await fetchProductCard(productId);
modal.showModal();
return { success: true, message: "Showing product" };
},
navigate_to: async ({ page }) => {
// 页面跳转
window.location.href = `/${page}`;
return { success: true };
},
save_preference: async ({ key, value }) => {
// 存入 localStorage
localStorage.setItem(key, value);
return { saved: true };
},
},
});
在 Agent 配置中声明 Client 工具
Agent 需要通过 conversation_config.agent.prompt.tools 知晓有哪些 Client 工具及其参数结构(工具名与 clientTools 中的键一一对应):
agent = client.conversational_ai.agents.create(
name="Shopping Assistant",
conversation_config={
"agent": {
"prompt": {
"prompt": """You are a shopping assistant.
When users want to see a product, use show_product.
When users want to go somewhere, use navigate_to.""",
"llm": "gemini-2.0-flash",
"tools": [
{
"type": "client",
"name": "show_product",
"description": "Display a product card to the user",
"parameters": {
"type": "object",
"properties": {
"productId": {
"type": "string",
"description": "Product ID to display"
}
},
"required": ["productId"]
}
},
{
"type": "client",
"name": "navigate_to",
"description": "Navigate user to a different page",
"parameters": {
"type": "object",
"properties": {
"page": {
"type": "string",
"enum": ["cart", "checkout", "account", "home"],
"description": "Page to navigate to"
}
},
"required": ["page"]
}
}
]
}
},
"tts": {"voice_id": "JBFqnCBsd6RMkjVDRZzb"}
}
)
这里体现了 Client 工具的两段式设计:服务端只声明"契约"(名称、描述、参数 Schema),浏览器侧才提供具体实现。对 page 参数使用 enum 约束取值,能显著降低 LLM 产生非法值导致的跳转失败。
Client 工具可选参数
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
expects_response |
bool | false |
工具是否向 Agent 返回数据 |
Client 工具的返回值
当 expects_response 为真时,浏览器函数的返回值会作为工具结果回传给 Agent,供其组织后续对话:
clientTools: {
check_cart: async () => {
const cart = JSON.parse(localStorage.getItem("cart") || "[]");
return {
itemCount: cart.length,
total: cart.reduce((sum, item) => sum + item.price, 0),
items: cart.map((item) => item.name),
};
};
}
Agent 收到该数据后可以回应:"You have 3 items in your cart totaling $45.99."。
系统工具:built_in_tools
系统工具由 ElevenLabs 平台内置提供,配置在 conversation_config.agent.prompt.built_in_tools 中(不要放进 tools 数组)。基础用法:
"built_in_tools": {
"end_call": {},
"transfer_to_number": {...},
"transfer_to_agent": {...},
"language_detection": {},
"skip_turn": {},
"voicemail_detection": {...},
"play_keypad_touch_tone": {}
}
当前 API Schema 中,built_in_tools 还额外暴露了 agent_prompt_change、memory_entry_create、memory_entry_delete、memory_entry_search 和 memory_entry_update,可用于动态调整 Agent 指令并管理长期记忆条目。
end_call:结束当前会话
"built_in_tools": {
"end_call": {}
}
启用后,Agent 可以先说"Goodbye!"再程序化地结束通话,保证对话有礼貌的收尾。
transfer_to_number:转接电话号码
转接电话需要配套的电信(telephony)集成,通过 transfers 数组声明目标号码与触发条件:
"built_in_tools": {
"transfer_to_number": {
"transfers": [{
"transfer_destination": {"type": "phone", "phone_number": "+1234567890"},
"condition": "User asks to speak with a human agent"
}]
}
}
transfer_to_agent:转接另一个 ElevenLabs Agent
用于多 Agent 协作场景(如销售咨询转销售专员),condition 字段描述 LLM 判定转接的语义条件:
"built_in_tools": {
"transfer_to_agent": {
"transfers": [{
"agent_id": "other-agent-id",
"condition": "User asks about sales"
}]
}
}
最佳实践
1. 工具描述要具体可执行
LLM 依赖 description 决定何时调用工具,写得含糊会导致漏调或误调:
# 好 - 具体且可执行
"description": "Look up order status. Use when customer asks about their order, delivery, or shipping."
# 差 - 含糊
"description": "Order tool"
2. 参数描述帮助 LLM 提取正确值
在参数描述中给出格式示例(如订单号格式、用途说明),可提升抽取准确率:
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Order ID in format ORD-XXXXX (e.g., ORD-12345)"
},
"email": {
"type": "string",
"description": "Customer email address for verification"
}
}
}
3. 错误处理:tool_error_handling_mode
通过该字段控制工具错误如何传递给 Agent:
| 模式 | 行为 |
|---|---|
auto |
由 ElevenLabs 自动决定错误处理方式 |
summarized |
错误先被摘要,再发送给 Agent |
passthrough |
完整错误细节直接传给 Agent |
hide |
错误对 Agent 隐藏 |
同时,服务端应返回有帮助的错误信息而非空响应,让 Agent 能向用户解释问题:
// 服务端 Webhook 示例
app.post("/webhook/lookup_order", async (req, res) => {
const { order_id } = req.body.parameters;
const order = await db.orders.find(order_id);
if (!order) {
return res.json({
result: {
error: true,
message: `Order ${order_id} not found. Please verify the order ID.`,
},
});
}
res.json({ result: order });
});
4. 超时设置要匹配真实耗时
用 response_timeout_secs 为 Webhook 设置合理超时(5–120 秒,默认 20 秒):
{
"type": "webhook",
"name": "slow_operation",
"description": "Run a slow operation",
"response_timeout_secs": 30,
"api_schema": {
"url": "https://api.example.com/slow-operation",
"method": "POST"
}
}
慢操作调大超时、并配合 tool_call_sound 播放等待音效、force_pre_tool_speech 让 Agent 先口头告知用户"正在查询",可以显著改善体验。
完整示例:电商客服 Agent
下面把三类工具组合在一起,创建一个电商支持助手(服务端查订单 + 浏览器展示商品 + 内置挂断与转人工):
agent = client.conversational_ai.agents.create(
name="E-commerce Assistant",
conversation_config={
"agent": {
"first_message": "Hi! How can I help you today?",
"language": "en",
"prompt": {
"prompt": """You are an e-commerce support assistant.
Available actions:
- lookup_order: Check order status
- show_product: Display products to customer
- end_call: End conversation politely
- transfer_to_number: Transfer to human support
Always verify order ID before lookup. Offer transfer for complex issues.""",
"llm": "gemini-2.0-flash",
"tools": [
# Webhook:服务端订单查询
{
"type": "webhook",
"name": "lookup_order",
"description": "Look up order status by order ID or email",
"api_schema": {
"url": "https://api.mystore.com/orders/lookup",
"method": "POST",
"request_headers": {"Authorization": "Bearer {{API_KEY}}"},
"request_body_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"email": {"type": "string"}
}
}
}
},
# Client:浏览器端商品展示
{
"type": "client",
"name": "show_product",
"description": "Display product details to the customer",
"parameters": {
"type": "object",
"properties": {
"product_id": {"type": "string"}
},
"required": ["product_id"]
}
}
],
"built_in_tools": {
"end_call": {},
"transfer_to_number": {
"transfers": [{
"transfer_destination": {"type": "phone", "phone_number": "+1234567890"},
"condition": "User asks for human support"
}]
}
}
}
},
"tts": {"voice_id": "JBFqnCBsd6RMkjVDRZzb", "model_id": "eleven_flash_v2_5"}
}
)
几个值得注意的细节:
- 系统提示词中显式列出可用动作并要求"查订单前先核对订单号",这是对工具调用的软性护栏;
- Webhook 工具用了
{{API_KEY}}占位符,实际密钥由工作区环境变量解析,避免把真实凭证硬编码进 Agent 配置; tts.model_id指定了eleven_flash_v2_5低延迟语音模型,适合实时对话场景。
在 OpenMontage 技能体系中的位置与延伸阅读
本文所述内容来自 OpenMontage 仓库中随 Agent 技能分发的参考文档 .agents/skills/agents/references/client-tools.md。该技能的主入口 .agents/skills/agents/SKILL.md 给出了创建 Agent 的完整快速上手(CLI elevenlabs agents init/add/push 工作流、Python/JavaScript SDK、cURL 三种方式、LLM 提供商与模型目录、常见语音 ID),并在 "Tools" 一节以压缩示例引向本文档作为完整工具参考。围绕同一主题,仓库中还有以下配套文档可以继续深入:
- 安装指南:CLI 与 SDK 的安装和鉴权设置;
- Agent 配置参考:
conversation_config全字段说明(tts、asr、turn、vad等)与 Agent CRUD 示例; - Widget 嵌入参考:
<elevenlabs-convai>网页嵌入组件的定制属性; - 外呼参考:基于 Twilio 的主动外呼、动态变量与配置覆盖。
需要说明的适用前提:本文所有配置项、字段默认值与行为描述均以仓库内这份参考文档记载的 ElevenLabs Conversational AI API 为准;实际接入时请以你账号对应的 API 版本与官方文档核对字段可用性,并准备好 ELEVENLABS_API_KEY 环境变量(技能元数据中声明了该要求)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00