OpenMontage 中基于 ElevenLabs + Twilio 的语音 Agent 外呼实践:agents 技能 outbound-calls 参考文档详解
【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage
本文围绕 OpenMontage 仓库中 outbound-calls.md 参考文档展开,系统讲解如何通过 ElevenLabs 语音 Agent 平台结合 Twilio 集成发起外呼电话:从前置条件、最小调用示例,到请求参数、响应结构、逐通电话的配置覆盖(Agent/TTS/电话配置/动态变量)与批量外呼完整示例。读完本文,你可以在 OpenMontage 的 agent 技能体系下,独立编写并调试一条「AI 主动拨打电话」的完整链路,并理解每个覆盖项在平台配置中的对应关系。
文档定位:agents 技能的外呼参考页
outbound-calls.md是仓库 agents 技能(ElevenLabs Agents Platform)的参考文档之一,与 installation.md、agent-configuration.md、client-tools.md 并列存放于references/目录。技能主文件SKILL.md的 frontmatter 声明了该技能的运行前提:需要联网访问和ELEVENLABS_API_KEY环境变量(见 SKILL.md 元数据)。
该文档的主题很明确:让已在 ElevenLabs 平台配置的语音 Agent,通过 Twilio 电话线路主动拨出电话,并支持在每次外呼时临时覆盖 Agent 的问候语、语言、语音音色以及注入动态变量。仓库中.claude/skills/目录下存在同名镜像文档 outbound-calls.md,内容保持一致,方便不同 AI 编程助手(Claude Code、Codex 等)读取同一套技能知识。
前置条件
发起外呼前需要满足三个条件(引自原文档 Prerequisites 一节):
- 一个已配置好的 ElevenLabs Agent——可通过 CLI 创建并推送:
npm install -g @elevenlabs/cli elevenlabs auth login elevenlabs agents init elevenlabs agents add "My Assistant" --template complete elevenlabs agents push - 一个绑定到该 Agent 的 Twilio 电话号码——需要从 ElevenLabs 控制台获取
agent_phone_number_id; - ElevenLabs API Key——在 OpenMontage 中,该密钥对应根目录 .env.example 中的
ELEVENLABS_API_KEY变量(位于 "Voice" 分组,用于 TTS 旁白、音乐生成等语音能力),SDK 与 cURL 请求均以此密钥认证。
SDK 安装方式(引自 installation.md):
- Python:
pip install elevenlabs; - JavaScript/TypeScript:
npm install @elevenlabs/elevenlabs-js。文档特别强调旧版elevenlabsnpm 包(v1.x)已弃用,不应再使用; - cURL:将密钥放入环境变量后,通过
xi-api-key请求头传递。
最小调用:Python、JavaScript 与 cURL
outbound-calls.md的 Basic Usage 一节将三语言的最小示例指向主技能文件的 Outbound Calls 章节(原文档中的局部相对链接../SKILL.md#outbound-calls,此处转换为仓库根路径 SKILL.md),其代码如下:
Python
response = client.conversational_ai.twilio.outbound_call( agent_id="your-agent-id", agent_phone_number_id="your-phone-number-id", to_number="+1234567890", call_recording_enabled=True ) print(f"Call initiated: {response.conversation_id}")JavaScript
const response = await client.conversationalAi.twilio.outboundCall({ agentId: "your-agent-id", agentPhoneNumberId: "your-phone-number-id", toNumber: "+1234567890", callRecordingEnabled: true, });cURL(直接调用 REST API)
curl -X POST "https://api.elevenlabs.io/v1/convai/twilio/outbound-call" \ -H "xi-api-key: $ELEVENLABS_API_KEY" -H "Content-Type: application/json" \ -d '{"agent_id": "your-agent-id", "agent_phone_number_id": "your-phone-number-id", "to_number": "+1234567890", "call_recording_enabled": true}'从三组示例可以推断,SDK 的conversational_ai.twilio.outbound_call与conversationalAi.twilio.outboundCall均是对POST /v1/convai/twilio/outbound-call这一端点的封装:Python 用 snake_case 参数名,JavaScript 用 camelCase,请求体字段则与下表中的参数名一致。
请求参数
外呼接口接受以下参数(原文档 Request Parameters 表完整保留):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
agent_id | string | 是 | ElevenLabs Agent 的 ID |
agent_phone_number_id | string | 是 | 绑定到该 Agent 的 Twilio 电话号码 ID |
to_number | string | 是 | 被叫号码,必须为 E.164 格式(如+1234567890) |
conversation_initiation_client_data | object | 否 | 覆盖本次通话的会话设置(详见下文) |
call_recording_enabled | boolean | 否 | 是否允许 Twilio 录制通话 |
telephony_call_config | object | 否 | 电话呼叫设置,如振铃超时时间 |
两个必填参数构成了外呼的「主叫身份」:agent_id决定谁(哪个 Agent 人设)来打电话,agent_phone_number_id决定用哪条 Twilio 线路呼出;to_number采用 E.164 国际号码格式(国家码 + 号码,不含空格与连字符)。
响应结构
调用成功后返回如下 JSON(原文档 Response 一节):
{ "success": true, "message": "Call initiated successfully", "conversation_id": "conv_abc123", "callSid": "CA1234567890abcdef" }| 字段 | 类型 | 说明 |
|---|---|---|
success | boolean | 呼叫是否成功发起 |
message | string | 状态信息 |
conversation_id | string | ElevenLabs 侧的会话 ID,用于后续追踪会话 |
callSid | string | Twilio 侧的 Call SID,可用于在 Twilio 侧查询呼叫状态 |
注意响应里同时保留了两个体系的标识:conversation_id归属于 ElevenLabs Conversational AI 会话体系,callSid归属于 Twilio 呼叫体系。这意味着外呼链路上存在两个可追踪的句柄——前者用于追踪 Agent 对话内容与工具调用,后者用于查询传统电话侧的状态(如未接、忙线)。
自定义单通电话:conversation_initiation_client_data
原文档 Customizing the Call 一节的要点是:通过conversation_initiation_client_data可以为单次外呼临时覆盖 Agent 的默认设置,而不会修改 Agent 本身。下面完整保留原文档的 Python 与 JavaScript 示例:
Python
response = client.conversational_ai.twilio.outbound_call( agent_id="your-agent-id", agent_phone_number_id="your-phone-number-id", to_number="+1234567890", call_recording_enabled=True, conversation_initiation_client_data={ "conversation_config_override": { "agent": { "first_message": "Hello! This is a reminder about your appointment tomorrow.", "language": "en" }, "tts": { "voice_id": "JBFqnCBsd6RMkjVDRZzb" } }, "dynamic_variables": { "customer_name": "John", "appointment_time": "2:00 PM" } } )JavaScript
const response = await client.conversationalAi.twilio.outboundCall({ agentId: "your-agent-id", agentPhoneNumberId: "your-phone-number-id", toNumber: "+1234567890", callRecordingEnabled: true, conversationInitiationClientData: { conversationConfigOverride: { agent: { firstMessage: "Hello! This is a reminder about your appointment tomorrow.", language: "en", }, tts: { voiceId: "JBFqnCBsd6RMkjVDRZzb", }, }, dynamicVariables: { customer_name: "John", appointment_time: "2:00 PM", }, }, });conversation_initiation_client_data的结构分两部分:conversation_config_override覆盖conversation_config中的对应小节(示例中覆盖了agent与tts),dynamic_variables则是本次通话注入的键值对。对照 agent-configuration.md 中conversation_config的完整结构(agent、tts、asr、turn、conversation、vad等小节),可以推断覆盖对象与 Agent 配置使用的是同一套字段命名——即你在 Agent 控制台配置过的字段,都可以在单通电话级别做临时替换。
配置覆盖项详解
Agent 设置
| 选项 | 类型 | 说明 |
|---|---|---|
first_message | string | 本次通话的自定义开场白 |
language | string | 语言代码(如en、es、fr) |
prompt | object | 覆盖 Agent 的 prompt 与 LLM 设置 |
其中prompt对应 Agent 配置中的 LLM 设置(system prompt、模型、温度等),结构详见 agent-configuration.md 的 prompt 章节;覆盖后仅对当前通话生效。
TTS 设置
| 选项 | 类型 | 说明 |
|---|---|---|
voice_id | string | 本次通话使用的语音 ID |
stability | number | 语音稳定性(0.0–1.0) |
similarity_boost | number | 音色相似度增强(0.0–1.0) |
speed | number | 语速倍率 |
这几个字段与 Agent 级 TTS 配置同构。参考 agent-configuration.md 中给出的默认值作为调试基准:stability默认0.5(越低越富表现力)、similarity_boost默认0.8(越高越贴近原音色)、speed默认1.0(有效范围约 0.7–1.2)。示例中的JBFqnCBsd6RMkjVDRZzb是 ElevenLabs 预置音色 "George" 的 ID,与技能主文件 SKILL.md 列出的常用音色 一致。
电话呼叫配置
| 选项 | 类型 | 说明 |
|---|---|---|
ringing_timeout_secs | integer | 对被叫振铃多久后放弃,默认60秒 |
该配置位于telephony_call_config参数(请求参数表中的object类型项)之下,属于 Twilio 线路层行为:被叫未接听时,达到该秒数后呼叫被放弃。批量外呼场景下可按业务调整(例如营销提醒类可缩短以节省线路资源,重要通知类可保持默认)。
动态变量
通过dynamic_variables向 Agent 的 prompt 注入自定义数据,在 prompt 中用{{variable_name}}语法引用。例如把customer_name与appointment_time传入后,Agent 的开场白模板即可渲染出「John,您明天下午 2:00 有一个预约」。
原文档还提到一个sanitize选项:在动态变量赋值时,若开启该选项,赋值内容会在送入 LLM 和会话转录(transcript)之前从工具响应中剔除,但变量赋值本身照常完成。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
sanitize | boolean | false | 为true时,赋值内容在发送到 LLM/转录前从工具响应中移除,但仍参与变量赋值 |
这一设计的用途很具体:当工具响应里携带敏感值(例如完整的账号或账单明细),你可以只让变量拿到该值用于后续逻辑,而不让原始响应文本进入 LLM 上下文与通话记录,从而降低敏感信息在转录中的暴露面。
完整示例:批量个性化外呼
原文档 Complete Example 一节给出了一段可直接运行的 Python 代码:遍历客户列表,为每位客户发起一通携带个性化开场白与动态变量的外呼,并做基本的错误处理。完整保留如下:
from elevenlabs import ElevenLabs client = ElevenLabs() # Make personalized outbound calls customers = [ {"name": "Alice", "phone": "+1234567890", "balance": "$150.00"}, {"name": "Bob", "phone": "+0987654321", "balance": "$75.50"}, ] for customer in customers: try: response = client.conversational_ai.twilio.outbound_call( agent_id="payment-reminder-agent", agent_phone_number_id="your-phone-number-id", to_number=customer["phone"], call_recording_enabled=True, conversation_initiation_client_data={ "conversation_config_override": { "agent": { "first_message": f"Hello {customer['name']}, this is a friendly reminder about your account." } }, "dynamic_variables": { "customer_name": customer["name"], "balance": customer["balance"] } } ) print(f"Called {customer['name']}: {response.conversation_id}") except Exception as e: print(f"Failed to call {customer['name']}: {e}")这段代码覆盖了外呼的三个关键实践点:
- 一人一话术:用 f-string 把
customer['name']写进first_message,同时用dynamic_variables把balance等字段交给 Agent 的 prompt 模板,两者互补——开场白立即个性化,后续对话中 Agent 也能引用动态变量; - 失败隔离:逐通
try/except,单个号码失败不影响批量任务继续; - 可追踪:成功时打印
conversation_id,后续可用它回查该通电话的会话数据;配合响应中的callSid,还可以在 Twilio 侧核对线路状态。
错误处理方面,技能主文件 SKILL.md 归纳了平台常见错误码:401(密钥无效)、404(资源不存在,如agent_id或agent_phone_number_id写错)、422(配置非法)、429(限流)。批量外呼时建议把 429 单独识别并做退避重试。
在 OpenMontage 仓库中如何找到这份文档
- 技能入口:.agents/skills/agents/SKILL.md 的 "Outbound Calls" 章节给出三语言最小示例,并在末尾以「configuration overrides and dynamic variables」为由指向本文档;
- 参考文档目录:
references/下按主题拆分——安装(installation)、Agent 配置全量字段(agent-configuration)、客户端工具(client-tools)、外呼(outbound-calls)、网页组件嵌入(widget-embedding),便于按需检索; - 多助手镜像:
.claude/skills/agents/references/下存有同套文档副本,例如 outbound-calls.md,内容一致; - 密钥配置:仓库根目录 .env.example 提供
ELEVENLABS_API_KEY占位项,实际使用时在本地.env中填入真实密钥即可,SDK 会优先读取该环境变量。
需要再次强调的前提:外呼能力依赖 ElevenLabs 平台的 Twilio 集成与真实电话线路,agent_phone_number_id必须在 ElevenLabs 控制台为 Agent 绑定后取得;本文涉及的接口行为、参数与默认值均以当前仓库文档为准。
【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考