LiveKit Voice AI Agent 电话机器人:SIP、打断、转人工与通话日志

LiveKit Voice AI 电话机器人教程,覆盖 AgentSession、电话号码、SIP trunk、入站分派、打断、DTMF、转人工、通话日志、测试与合规边界。

LiveKit Agents 可以把语音识别、LLM、语音合成和工具调用放进一个实时会话,再通过 SIP 接入普通电话网络。 Google Trends 的 voice ai agents 在美国区上升 350%,但电话机器人上线失败通常不是模型不够聪明,而是 SIP 路由、打断、转人工和日志链路没有一起验证。 本文用入站客服电话为例,先跑通房间 Agent,再接电话号码,最后处理生产电话必须具备的失败路径。

一通电话会经过哪些组件

呼叫者拨打电话号码。 电话运营商或 LiveKit Phone Numbers 把呼叫送入 SIP trunk。 Inbound Trunk 决定哪些号码和来源可以进入。 Dispatch Rule 把 SIP participant 放进 LiveKit room。 Agent worker 接受 job,并创建 AgentSession。 STT 把音频转成文本,LLM 决定回答或调用工具,TTS 把答案送回电话。 转人工时,系统创建新的 SIP participant 或把呼叫交给外部号码。 每一层都有独立日志和失败状态。

先在浏览器房间验证 Agent

电话网络会增加运营商、号码和 SIP 配置变量。 先用 LiveKit Playground 或本地音频房间验证语音 Agent。 它应能完成:

  • 听到用户语音。
  • 在合理延迟内回答。
  • 用户插话时停止当前 TTS。
  • 调用一个无副作用工具。
  • 会话结束时释放资源。

这一步失败时,不要先排查电话号码。

创建项目与本地环境

准备 LiveKit Cloud 项目,或按官方文档自托管 LiveKit Server。 本地 Python 环境:

1
2
3
python3 -m venv .venv-livekit
source .venv-livekit/bin/activate
python -m pip install --upgrade pip

Windows PowerShell:

1
2
3
python -m venv .venv-livekit
.\.venv-livekit\Scripts\Activate.ps1
python -m pip install --upgrade pip

按当前 quickstart 安装 Agents 与所需插件,不从旧博客固定过期版本。

环境变量只放服务端

1
2
3
LIVEKIT_URL=wss://example.livekit.cloud
LIVEKIT_API_KEY=replace-me
LIVEKIT_API_SECRET=replace-me

STT、LLM 和 TTS 供应商还会有各自 key。 把 .env.local 加入忽略列表:

1
git check-ignore .env.local

API secret 不能进入浏览器或移动端。 客户端只获取服务端签发的短期 room token。

AgentSession 是语音流水线的协调器

AgentSession 负责用户输入、模型、工具、输出和事件。 具体插件名称与参数随版本变化,下面展示结构而不是承诺固定供应商:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from livekit.agents import Agent, AgentSession

class SupportAgent(Agent):
    def __init__(self):
        super().__init__(
            instructions=(
                "You are a concise phone support agent. "
                "Confirm identity before reading private account data. "
                "Offer a human handoff when the caller asks."
            )
        )

async def entrypoint(ctx):
    session = AgentSession(
        stt=build_stt(),
        llm=build_llm(),
        tts=build_tts(),
    )
    await session.start(
        room=ctx.room,
        agent=SupportAgent(),
    )

build_stt() 等函数需要替换为官方当前支持的插件实例。

电话提示词要适应无屏幕场景

用户看不到链接、表格和代码。 回答要短,每次只问一个问题。 数字、日期和邮箱需要复述确认。 不要一次朗读十个菜单项。 转人工、重复、返回上一步和结束通话要有明确说法。 涉及账户操作前,先执行身份验证,而不是根据来电号码自动信任。

为工具划分读写权限

查询营业时间是只读工具。 查询订单可能读取个人数据。 取消订单、退款和改地址属于写操作。 写工具要求二次确认,并把最终参数复述给用户。

1
2
3
4
async def cancel_order(order_id: str, confirmed: bool) -> str:
    if not confirmed:
        return "confirmation_required"
    return await order_service.cancel(order_id)

不要让 LLM 根据自然语言自行伪造 confirmed=True。 确认状态应由会话逻辑管理。

打断能力决定电话体验

用户开始说话时,Agent 应停止或淡出当前 TTS。 过度敏感会把咳嗽、背景电视和回声当成打断。 过度迟钝则让用户无法纠正错误。 调试时记录:

  • 用户语音开始时间。
  • VAD 检测时间。
  • TTS 停止时间。
  • STT 最终结果时间。
  • 新回答首音频时间。

只有端到端时间线才能说明延迟来自哪一段。

Turn Detection 与电话噪声

电话音频频宽、压缩和噪声与网页麦克风不同。 短暂停顿不一定表示用户说完。 地址、订单号和姓名中间可能自然停顿。 使用 LiveKit 当前提供的 turn detection 和 VAD 配置,从真实电话录音做测试。 不要只拿安静办公室的浏览器麦克风调参数。

购买或接入电话号码

可以使用 LiveKit Phone Numbers,或配置支持的第三方 SIP provider。 选择号码时确认国家、地区、短信能力、紧急呼叫限制和身份要求。 号码购买成功不等于入站路由已经完成。 还需要 trunk 和 dispatch rule 把呼叫送进 room。 运营商控制台与 LiveKit 控制台各保存一个可核对的 ID。

Inbound Trunk 限定允许的号码

Inbound Trunk 定义接收哪些电话号码和 SIP 来源。 生产与测试使用不同号码或 trunk。 不要把任意 SIP 来源全部接受后再依赖 Agent 拒绝。 若运营商支持 IP 或凭据认证,按官方建议启用。 修改 trunk 后拨打一次测试电话,确认命中的是新配置而非旧规则缓存。

Dispatch Rule 决定 room 命名与 Agent

为每通电话生成唯一 room,避免两个陌生来电进入同一会话。 room 名称可以包含内部 call ID,但不要包含完整电话号码。 metadata 中只放路由所需字段。 Agent worker 根据 job metadata 选择客服、销售或预约 Agent。 没有匹配 Agent 时,要有可听见的失败提示或转接路径,不能保持静音。

入站电话的最小验收

用一部不在公司 Wi-Fi 的真实手机拨号。 记录:

1
2
3
4
5
carrier_call_id
sip_participant_id
room_name
agent_job_id
session_id

确认来电后 3–5 秒内至少有欢迎语或等待提示。 如果电话接通但没有声音,分别检查 SIP participant 的 track、Agent 是否加入以及 TTS 是否发布。

403404 不要只看 Agent 日志

SIP 层 403 可能来自认证、来源限制、号码未验证或运营商策略。 404 可能是号码路由或 trunk 不匹配。 LiveKit room 已创建但没有 Agent,重点看 dispatch 与 worker availability。 Agent 已启动但没有 caller participant,重点看 SIP 接入。 保存运营商和 LiveKit 两边的 call ID。

DTMF 作为语音识别的备用输入

电话键盘适合确认菜单、数字选项和敏感操作。 例如按 1 转人工、按 2 重复、按 9 结束。 不要让 DTMF 与语音命令触发两次同一操作。 为每个输入分配事件 ID,并用状态机去重。 银行卡、密码等敏感数据不应由普通日志记录完整按键序列。

转人工不是让 Agent 说一句“已转接”

真正的 handoff 至少包括目标队列、上下文摘要和电话路由。 LiveKit Agent handoff 适合在不同 AI Agent 间切换角色。 转真人客服则通常需要新的 SIP outbound participant 或外部呼叫中心集成。 排队期间播放提示,并允许用户取消。 转接失败要返回原 Agent 或提供回拨,而不是直接挂断。

传给人工的上下文应最小化

可以包含:

  • 已验证的客户 ID。
  • 用户本次目标。
  • 已执行的只读查询。
  • 尚未完成的操作。
  • 用户是否同意录音。

不要把完整模型思维过程、无关历史或所有通话文本塞给客服。 摘要要标记哪些是用户明确陈述,哪些是 Agent 推断。

出站电话需要更严格的授权

出站 SIP 可以用于回访、提醒和调查。 但它也容易触发骚扰电话、时区和同意问题。 号码列表必须来自有合法依据的业务系统。 调用前检查当地时间、退订状态和频率上限。 禁止让 LLM 自行选择任意号码拨出。 每个 outbound call 需要明确的业务任务 ID。

通话录音与转写分开管理

录音、实时转写、摘要和业务字段是不同数据对象。 用户同意录音,不一定等于同意长期保存完整转写。 分别设置访问权限与保留周期。 录音 URL 使用短期签名,不放进普通应用日志。 删除请求要覆盖对象存储、索引、摘要和备份策略。

事件日志不要记录原始密钥

推荐记录:

1
2
3
4
5
6
7
8
{
  "session_id": "sess_example",
  "call_id": "call_example",
  "event": "human_handoff_requested",
  "timestamp": "2026-07-28T12:00:00Z",
  "target_queue": "support_cn",
  "result": "queued"
}

电话号码只保留掩码或内部 ID。 STT 文本默认不进入基础设施 debug 日志。

延迟预算要分段测量

总响应延迟可以拆成:

1
VAD + final STT + LLM first token + TTS first audio + network

分别记录 P50、P95 和失败率。 平均值会掩盖偶发十秒停顿。 LLM 工具调用期间播放短等待提示,但不要每一秒重复。 缓存固定欢迎语可以减少首次 TTS 延迟。

回声与双重语音如何定位

先确认 caller 只订阅一个 Agent 音轨。 检查 Agent 是否重复加入同一 room。 运营商侧录音若正常而手机听到回声,可能是终端声学回声。 room 录音已经双声,则检查 track publication 和 TTS 重复发送。 不要用增加 VAD 阈值掩盖重复音轨。

Worker 断线时保持可恢复体验

Agent worker 崩溃后,SIP 电话可能仍处于接通状态。 设置 job 超时、worker 健康检查和备用路由。 短时间恢复可以让新 worker 读取会话状态。 无法恢复时播放失败说明并提供人工号码。 不要让用户在静音线路无限等待。

压力测试不能用同一个手机号手工拨打

使用官方测试工具或受控 SIP 测试账号产生并发。 逐步增加并发,观察 room、worker、STT、LLM、TTS 和运营商配额。 每个供应商限制可能不同。 压测号码与生产号码隔离。 提前通知运营商,避免被判定为异常呼叫。

费用按完整通话链计算

成本包括电话号码、SIP 分钟、LiveKit、STT、LLM、TTS、录音存储和转人工。 工具调用还可能产生业务 API 成本。 记录每通电话的实际秒数与供应商用量。 失败和等待通话同样计费。 按成功解决的电话计算单位成本,比只看每分钟价格更有意义。

一套故障演练清单

停止 Agent worker,确认有可听见的降级。 让 STT key 失效,确认不会反复向用户答非所问。 让 LLM 超时,确认可以转人工。 让 TTS 失败,确认系统不会保持接通静音。 让人工队列不可用,确认提供回拨或留言。 连续按两次 DTMF,确认写操作不会重复。

上线判定

  • 浏览器房间内的 Agent 已独立通过。
  • SIP trunk 与 dispatch ID 可追踪。
  • 每通电话使用独立 room。
  • 用户插话能停止 TTS。
  • 读写工具权限分离。
  • 转人工完成真实电话路由。
  • 录音、转写和摘要分别管理。
  • 运营商与 LiveKit call ID 可以关联。
  • 断线、超时和配额失败均有语音降级。
  • 出站电话有同意、时区和频率控制。

电话 Voice AI 的质量取决于完整呼叫链,而不是单轮回答效果。先把浏览器房间跑通,再逐层增加 SIP、DTMF、转人工和录音,能让每种失败都有明确责任位置。

LiveKit 文档入口