# Byted Outbound Call

> AI-powered intelligent phone calling service to automatically handle restaurant reservation, modification, and cancellation tasks without manual dialing. Use when users want to reserve, modify, or cancel restaurant bookings.

- Skill: `bytedance/byted-outbound-call` (Agent Skill, multi-file: 28 files)
- Install (CLI): `npx skillmds@latest add bytedance/byted-outbound-call`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bytedance/byted-outbound-call/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: bytedance (https://skillmd.com/u/bytedance)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/bytedance/byted-outbound-call

---


# 概述
## 目标用户
希望将生活服务场景中「需要打电话」的琐事外包给AI Agent的用户，适用场景目前只包含餐厅门店的预约、改订、取消。
## 注意事项
严禁修改byted-outbound-call下的代码。

---
# 核心用户故事，智能代打电话
1. **核心场景**：作为不想自己打电话的用户，我可以直接用自然语言告诉ArkClaw要联系的门店、具体需求，调用流程：[二、外呼任务执行流程]。
2. **分步场景**：
   - 我可以直接说自然语言需求：「帮我联系A餐厅，电话13500015678，预约3月20日晚上7点的家庭聚餐，4个人」
   - 当预订需要支付押金时，我会收到二次确认提示，确认后再继续执行
   - 我能收到清晰的结果：成功时会返回完整的预订信息，失败时会告知具体原因，以及可选的替代方案

# 核心用户故事，查询外呼任务结果
1. **核心场景**：用户主动的询问外呼任务状态，调用流程：[三、查询外呼任务状态流程]。

---
# 功能说明
## 一、前置准备（使用前需完成）
1. 开通火山引擎账号，完成外呼号线对接与资质备案
2. 在火山外呼平台创建3个专用Agent并配置对应的语音交互逻辑、音色、话术模板，每个场景对应固定剧本ID：
   | 场景 | Agent名称 | 剧本ID |
   |------|----------|--------|
   | 新预约 | 预约Agent | llm_wlne_biffj |
   | 修改已有预约 | 改订Agent | llm_eeac_bigci |
   | 取消已有预约 | 取消Agent | llm_ivqy_bigcj |
3. 每个Agent配置对应的任务并发数，支持多任务并行执行

## 二、外呼任务执行流程

### 阶段1：任务创建与校验
1. **用户输入**：用户用自然语言描述需求，无需固定格式
2. **范围校验**：仅接受餐厅的「预约/改订/取消」类任务，其他类型任务直接拒绝，返回拒绝原因
3. **需求分类**：自动识别用户需求是「预约/改订/取消」中的哪一类，匹配对应剧本ID
4. **信息校验与追问**：校验对应场景的必填信息，任意一项缺失时自动用自然语言追问用户，直到信息补全：
   | 需求类型 | 必填信息 | 对应参数名 |
   |----------|----------|------------|
   | 预约 | 门店名称、门店电话、预约日期(年月日)、预约时间(时分)、人数、用户姓氏、用户联系电话 | `shop_name`/`shop_phone_number`/`date`/`time`/`people_number`/`first_name`/`user_phone_number` |
   | 改订 | 门店名称、门店电话、原本预约的日期(年月日)、原本预约的时间(时分)、原本的人数、新预约日期(年月日)、新预约时间(时分)、新人数、用户姓氏、预订时预留的联系电话 | `shop_name`/`shop_phone_number`/`date`/`time`/`people_number`/`new_date`/`new_time`/`new_people_number`/`first_name`/`user_phone_number` |
   | 取消 | 门店名称、门店电话、预约日期(年月日)、预约时间(时分)、人数、用户姓氏、预订时预留的联系电话 | `shop_name`/`shop_phone_number`/`date`/`time`/`people_number`/`first_name`/`user_phone_number` |
5. **押金二次确认**：如果外呼前检测到该门店预订需要支付押金，自动向用户发送确认提示，用户确认后再继续执行
6. **异常处理**：若本阶段任何步骤出现接口异常（code非200），直接将错误信息整理为自然语言告知用户，终止外呼流程，不进入后续提交步骤。

### 阶段2：必须携带的环境变量列表
当调用本技能的脚本时，下列所有的变量都必填，否则消息通知会报错，必须自动将当前会话的如下信息作为环境变量传递给exec命令。信息获取方式如下：
- OPENCLAW_CHANNEL: 
  - 含义： 当前消息渠道。
  - 获取方式： 从消息元数据(metadata)中的`channel`字段获取。
- OPENCLAW_SESSION_ID: 
  - 含义： 当前会话的chat_id。
  - 获取方式： IF: `channel`为`webchat`，则运行工具(tool)`sessions_list`，从`sessions_list`输出结果中获取当前对话的会话id(sessionId)，并将sessionId赋值给`OPENCLAW_SESSION_ID`。ELSE: 将字符串赋值"no_need"赋值给`OPENCLAW_SESSION_ID`。
- OPENCLAW_USER_ID: 
  - 含义： 发送者用户ID。
  - 获取方式： IF: `channel`为`webchat`，将字符串赋值"no_need"赋值给`OPENCLAW_USER_ID` ELSE: 将消息元数据(metadata)中的`chat_id`字段去除"user:"前缀，赋值给`OPENCLAW_USER_ID`。

### 阶段3：外呼任务提交
按如下步骤一步步执行：
1. 执行脚本：调用`scripts/submit_job.py`提交外呼任务，调用方式（Agent自动调用，不需要用户手动执行）
   推荐方式：通过exec工具内置`env`参数传递（敏感信息不会出现在命令行历史）
   {
   "tool": "exec",
   "command": "python3 scripts/submit_job.py --request-body '{
         "job_name": "【预约/改订/取消】+ 门店名称 + 用户需求",
         "job_description": "用户自然语言原始需求（可选）",
         "script_id": "对应场景的剧本ID：预约=llm_wlne_biffj/改订=llm_eeac_bigci/取消=llm_ivqy_bigcj",
         "params": {
         "user_phone_number": "联系电话（所有场景必填）",
         "date": "预约日期（仅预约场景）",
         "time": "预约时间（仅预约场景）",
         "people_number": "预约人数（仅预约场景）",
         "shop_name": "门店名称（所有场景必填）",
         "shop_phone_number": "门店电话（所有场景必填）",
         "first_name": "用户姓氏（所有场景必填）",
         "new_date": "新预约日期（仅改订场景）",
         "new_time": "新预约时间（仅改订场景）",
         "new_people_number": "新预约人数（仅改订场景）"
      }
   }'
   ",
   "env": {
         "OPENCLAW_SESSION_ID": "{{阶段2中获取到的OPENCLAW_SESSION_ID}}",
         "OPENCLAW_CHANNEL": "{{阶段2中获取到的OPENCLAW_CHANNEL}}",
         "OPENCLAW_USER_ID": "{{阶段2中获取到的OPENCLAW_USER_ID}}"
      }
   }

2. 查看上一步脚本的输出, 按如下要求进行处理：
   1. IF 如果脚本输出内容里包含ERROR，将ERROR信息整理为自然语言告知用户，终止流程。
   2. ELSE IF 如果脚本输出内容里没有ERROR，以比较友好的方式，把任务提交的参数和返回的`JobId`，整理为自然语言，在此环节告知用户任务已提交，在完成通知后终止流程。
   参考返回样式：
     - **任务提交成功返回示例**：
         > ✅ 已为你发起外呼任务「海底捞火锅(中关村店)」，程时间较长请耐心等待，任务完成后会通知你！
         > 📋 任务ID：{JobId}
         > 📅 时间：3月20日 19:00
         > 👥 人数：4位
         > 📝 预订人：李先生 

## 三、查询外呼任务状态流程
1. 执行脚本：调用`scripts/query_job.py`查询外呼任务状态和详情，调用方式（Agent自动调用，不需要用户手动执行）
   {
   "tool": "exec",
   "command": "python3 scripts/query_job.py --job_id '{JobId}'"
   }
2. 查看上一步脚本的输出，脚本已自动整理好结果，按如下要求进行处理：
   1. IF 如果脚本输出内容里包含ErrorCode，将错误信息整理为自然语言告知用户，终止流程。
   2. ELSE IF 脚本输出已包含整理好的JobDetail信息：
      - 脚本输出已包含：JobId、StatusDescription（状态描述）
      - 若任务已完成（StatusDescription包含"已完成预订的呼叫"），还会包含：CallResultDescription（接通情况）、Summary（信息汇总）
      - 直接将整理好的信息以友好的方式告知用户
   参考返回样式：
   - **查询等待中的任务返回示例**：
   > 📋 任务ID：{JobId}
   > ⏳ 当前状态：呼叫任务已提交，正在排队等待系统发起呼叫。
   - **查询处理中的任务返回示例**：
   > 📋 任务ID：{JobId}
   > 🔄 当前状态：呼叫任务正在处理中，如商家未接听，系统会自动重呼，请耐心等待。
   - **查询呼叫结束正在分析的任务返回示例**：
   > 📋 任务ID：{JobId}
   > 📊 当前状态：所有预定的呼叫（包括重呼）均已执行完毕，正在分析通话结果。
   - **查询已完成且接通的任务返回示例**：
   > 📋 任务ID：{JobId}
   > ✅ 任务状态：已完成预订的呼叫，并且完成通话结果分析。
   > 📞 接通情况：正常接通
   > 预约详情：
   > > 📅 用餐时间：2026年3月28日 21:00
   > > 👥 人数：1位
   > > 📝 预订人：高先生
   > > 📞 预留电话：尾号 8838
   - **查询已完成但未接通的任务返回示例**：
   > 📋 任务ID：{JobId}
   > ✅ 任务状态：已完成预订的呼叫，并且完成通话结果分析。
   > 📞 接通情况：无人接听
   - **任务不存在返回示例**：
   > ❌ 查询失败：呼叫任务不存在，请确认任务ID是否正确

---
# 使用示例
## 示例1：完整信息直接执行
> **用户输入**：帮我预约明天晚上7点的海底捞火锅中关村店，他们电话是13800138000，3个人，我姓王，联系电话13512345678
> **技能思考**：用户query已经包含了门店名称、预约时间、预约人数、用户姓氏、联系电话等所有必要信息，无需追问，调用submit-job并通知客户
> **技能响应**：好的，马上帮你联系海底捞火锅中关村店预约，完成后第一时间告诉你结果~

## 示例2：缺失信息自动追问
> **用户输入**：帮我取消明天的海底捞预订
> **技能思考**：用户query中包含了门店名称、时间，需要提供其他信息。
> **技能响应**：请问海底捞的门店电话是多少？预订时预留的姓氏和联系电话是什么呢？
> **用户补全**：电话是13500015678，我姓李，预留电话是13512345678
> **技能思考**：用户query中已经包含所有必填信息，无需追问，直接调用cancel-job并通知客户。
> **技能响应**：好的，马上帮你取消~

## 示例3：超出范围拒绝
> **用户输入**：帮我给领导打个电话请假
>
> **技能响应**：抱歉哦，我目前只支持生活服务类门店的预约、改订、取消类外呼任务，其他类型的通话需求暂时无法帮你处理~

## 示例4：查询外呼任务状态
> **用户输入**：帮我查一下任务ID为 job_123456 的外呼任务状态
> **技能思考**：用户需要查询指定JobId的任务状态，调用query_job脚本
> **技能响应**：好的，马上帮你查询任务状态~
> **技能思考**：执行查询脚本，获取已整理好的状态结果
> **技能响应**：
> > 📋 任务ID：job_123456
> > ✅ 任务状态：已完成预订的呼叫，并且完成通话结果分析。
> > 📞 接通情况：正常接通
> > 预约详情：
> > > 📅 用餐时间：2026年3月28日 21:00
> > > 👥 人数：1位
> > > 📝 预订人：高先生
> > > 📞 预留电话：尾号 8838

---
# 错误处理
| 错误场景 | 处理逻辑                        | 返回示例                                      |
|----------|-----------------------------|-------------------------------------------|
| 商户电话无效/空号 | 直接返回失败，提示用户核对号码             | ❌ 拨打失败，你提供的门店电话135xxxxxxx是空号，请核对后重新提交     |
| 外呼接口调用失败 | 提取接口返回的msg信息整理后返回           | ❌ 外呼服务暂时不可用：接口返回「余额不足」，请充值后再试             |
| 任务提交异常 | 直接返回接口错误信息                  | ❌ 任务提交失败：接口返回「剧本ID不存在」，请检查配置是否正确          |
| 环境变量校验失败 | 检查哪些变量没有传递，重试并传递进去          | 重试脚本                                      |

---
# 注意事项
1. 请确保提供的门店电话真实有效，否则会导致外呼失败
2. 涉及押金支付的场景会先经过用户二次确认，不会自动扣款
