如何在 AI 语音代理中使用 SIP 电话进行呼叫转接
Source: Dev.to
如何在 AI 语音代理中使用 SIP 电话实现呼叫转接
在构建基于 AI 的语音代理(如聊天机器人或虚拟客服)时,呼叫转接是提升用户体验的关键功能之一。通过 SIP(Session Initiation Protocol)电话系统,开发者可以在通话过程中将用户从 AI 代理无缝转接到真人坐席或其他服务。本文将逐步演示如何在 AI 语音代理中实现呼叫转接。
目录
前置条件
| 条件 | 说明 |
|---|---|
| SIP 服务器 | 推荐使用 Asterisk、FreeSWITCH、或商业托管的 SIP 提供商(如 Twilio SIP Trunk、Plivo)。 |
| AI 语音平台 | 本文以 Google Dialogflow CX、Microsoft Bot Framework 或 Rasa 为例。 |
| 编程语言 | 示例代码使用 Node.js(sip.js)和 Python(pjsua2),但思路可迁移到其他语言。 |
| 网络 | 确保服务器对外开放 5060/5061 端口(UDP/TCP),并已配置防火墙规则。 |
| 证书 | 若使用 TLS(推荐),请准备有效的 SSL 证书。 |
概念回顾:SIP 与呼叫转接
- SIP(Session Initiation Protocol):用于在 IP 网络上建立、修改和终止多媒体会话的信令协议。
- INVITE:发起通话的请求。
- ACK:确认收到的响应。
- BYE:结束通话。
- REFER:请求对端将当前会话转接到另一个 SIP URI。
- Replaces:在转接过程中,用新的会话取代旧的会话(常与
REFER配合使用)。
关键点:在 AI 代理内部,通常会先通过
INVITE与用户建立通话,然后在需要转接时发送REFER,并在收到200 OK后结束原始会话(BYE)。
步骤一:配置 SIP 服务器
-
创建 SIP 域/租户
- 在 Asterisk 中编辑
sip.conf,添加一个用于 AI 代理的用户:
[ai_agent] type=friend host=dynamic secret=YourStrongPassword context=from-ai disallow=all allow=ulaw - 在 Asterisk 中编辑
-
设置转接策略
- 在
extensions.conf中定义转接拨号计划(示例为转接到1001坐席):
[from-ai] exten => _X.,1,NoOp(Incoming call from AI agent) same => n,Dial(SIP/1001,30) same => n,Hangup() - 在
-
启用
REFER支持- 确保
allowtransfer=yes(Asterisk)或相应的allow_transfer参数已打开。
- 确保
-
重启 SIP 服务
sudo asterisk -rx "sip reload"
步骤二:在 AI 代理中捕获转接意图
在对话流中,需要检测用户是否请求转接(如 “我想和人工客服聊聊”)。以下以 Dialogflow CX 为例:
-
创建意图:
Transfer_To_Human,训练示例包括 “人工客服”“转接”“和真人说话”。 -
在 Fulfillment 中返回转接信号:
{ "sessionInfo": { "parameters": { "transfer": true, "targetNumber": "sip:1001@yourdomain.com" } } } -
在后端监听该参数,当
transfer为true时触发转接逻辑。
步骤三:使用 SIP REFER 实现转接
下面展示 Node.js(sip.js)的实现思路。核心步骤:
- 获取当前会话对象(
session)。 - 构造
REFER请求,目标为坐席的 SIP URI。 - 发送
REFER并监听响应。 - 收到
200 OK后发送BYE结束原始会话(可选,取决于业务需求)。
// 假设 `session` 为当前的 SIP 会话实例
function transferCall(targetSipUri) {
const referRequest = session.refer(targetSipUri, {
// 可选的 Refer-To Header
extraHeaders: [
`Refer-To: ${targetSipUri}`,
'Replaces: ' + session.id // 若需要使用 Replaces
]
});
referRequest.then(response => {
if (response.statusCode === 200) {
console.log('Transfer accepted, ending original call...');
session.bye(); // 结束原始通话
} else {
console.warn('Transfer failed with status:', response.statusCode);
}
}).catch(err => {
console.error('Error sending REFER:', err);
});
}
注意:
targetSipUri必须是 SIP 服务器已注册的用户(如sip:1001@yourdomain.com)。- 若使用 TLS,请在
session初始化时指定transport: 'TLS'。
完整示例代码
以下示例整合了 Dialogflow CX webhook 与 sip.js,实现从 AI 代理到坐席的自动转接。
// server.js
const express = require('express');
const bodyParser = require('body-parser');
const { UserAgent, Registerer, Inviter, Session } = require('sip.js');
const app = express();
app.use(bodyParser.json());
// SIP UA 配置(请替换为实际凭证)
const ua = new UserAgent({
uri: 'sip:ai_agent@yourdomain.com',
transportOptions: {
server: 'wss://sip-ws.yourdomain.com'
},
authorizationUsername: 'ai_agent',
authorizationPassword: 'YourStrongPassword'
});
let currentSession = null;
// 注册到 SIP 服务器
ua.start().then(() => {
const registerer = new Registerer(ua);
return registerer.register();
}).catch(console.error);
// 接收 Dialogflow CX webhook
app.post('/dialogflow-webhook', async (req, res) => {
const intent = req.body.queryResult.intent.displayName;
const parameters = req.body.queryResult.parameters;
if (intent === 'Transfer_To_Human' && parameters.transfer) {
const target = parameters.targetNumber; // 如 sip:1001@yourdomain.com
if (currentSession) {
transferCall(target);
res.json({ fulfillmentText: '正在为您转接,请稍候…' });
} else {
res.json({ fulfillmentText: '当前没有活跃通话,无法转接。' });
}
} else {
// 其他意图的常规处理
res.json({ fulfillmentText: '抱歉,我没有听懂,请再说一遍。' });
}
});
// 发起呼叫(示例:从外部系统触发)
app.post('/start-call', async (req, res) => {
const callee = req.body.callee; // 如 sip:user@example.com
const inviter = new Inviter(ua, callee);
try {
const outgoingSession = await inviter.invite();
currentSession = outgoingSession;
res.json({ status: 'call_started' });
} catch (e) {
console.error(e);
res.status(500).json({ error: 'call_failed' });
}
});
// 转接函数(与上文相同)
function transferCall(targetSipUri) {
if (!currentSession) {
console.warn('No active session to transfer.');
return;
}
const referRequest = currentSession.refer(targetSipUri, {
extraHeaders: [
`Refer-To: ${targetSipUri}`,
'Replaces: ' + currentSession.id
]
});
referRequest.then(response => {
if (response.statusCode === 200) {
console.log('Transfer successful, ending original call.');
currentSession.bye();
currentSession = null;
} else {
console.warn('Transfer failed:', response.statusCode);
}
}).catch(err => {
console.error('REFER error:', err);
});
}
app.listen(3000, () => console.log('Webhook server listening on :3000'));
代码说明:
- SIP UA 初始化:使用
sip.js创建 WebSocket‑based SIP 用户代理。 - Webhook 入口:
/dialogflow-webhook负责解析 Dialogflow 的请求并判断是否需要转接。 - 转接实现:
transferCall通过REFER将通话转给坐席,并在成功后发送BYE。 - 错误处理:所有网络/协议错误均记录到控制台,便于调试。
常见问题排查
| 场景 | 可能原因 | 解决方案 |
|---|---|---|
| REFER 返回 403 | SIP 服务器未授权当前用户发送 REFER。 | 在 SIP 服务器上为 ai_agent 开启 allowtransfer=yes(Asterisk)或对应的转接权限。 |
| 坐席未收到来电 | Refer-To 中的 URI 拼写错误或未在服务器注册。 | 确认坐席的 SIP URI 正确且已注册(sip:1001@yourdomain.com)。 |
| 通话在转接后立即挂断 | BYE 发送过早,导致坐席未能建立新会话。 | 在收到 200 OK(REFER)后,等待坐席确认(如 180 Ringing)再发送 BYE,或使用 Replaces 机制。 |
| TLS 握手失败 | 证书不受信任或端口未开放。 | 使用受信任的 CA 证书,确保防火墙放行 5061(TLS)或对应的 WebSocket 端口。 |
| Dialogflow 未触发转接 | 意图匹配阈值过低或参数未正确返回。 | 检查意图训练短语,确认 webhook 返回的 JSON 结构与 Dialogflow 期望一致。 |
结论
Overview
当来电者联系企业时,他们期望得到快速且明确的解决方案。AI 语音代理应当能够判断何时提供帮助,何时将通话转接给人工客服。VideoSDK 中的通话转接功能让您的 AI 代理能够在不挂断来电者的情况下,将正在进行的 SIP 通话转移到另一个电话号码,从而确保对话流畅无缝。
呼叫转接工作原理
- AI 代理评估来电者的意图,以确定是否需要转接。
- 当需要转接时,代理会触发一个 function tool。
- function tool 指示系统立即将正在进行的 SIP 通话转接到指定号码,无需来电者重新拨号。
- 从来电者的角度来看,转接是自动的,通话会持续进行,不会出现掉线或尴尬的停顿。
设置呼叫转接代理
下面是一个最小示例,展示如何使用 transfer_call 工具转接通话的代理。
import os
import logging
from videosdk.agents import (
Agent, AgentSession, CascadingPipeline, function_tool,
WorkerJob, ConversationFlow, JobContext, RoomOptions, Options
)
from videosdk.plugins.deepgram import DeepgramSTT
from videosdk.plugins.google import GoogleLLM
from videosdk.plugins.cartesia import CartesiaTTS
from videosdk.plugins.silero import SileroVAD
from videosdk.plugins.turn_detector import TurnDetector, pre_download_model
# Set up basic logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.StreamHandler()]
)
# Pre‑download the turn‑detector model
pre_download_model()
class CallTransferAgent(Agent):
def __init__(self):
super().__init__(
instructions=(
"You are the Call Transfer Agent. "
"Help transfer ongoing calls to a new number using the transfer_call tool."
)
)
async def on_enter(self) -> None:
await self.session.say("Hello! How can I help you today?")
async def on_exit(self) -> None:
await self.session.say("Goodbye, and thank you for calling!")
@function_tool
async def transfer_call(self) -> None:
"""Transfer the call to the provided number."""
token = os.getenv("VIDEOSDK_AUTH_TOKEN")
transfer_to = os.getenv("CALL_TRANSFER_TO")
return await self.session.call_transfer(token, transfer_to)
async def entrypoint(ctx: JobContext):
agent = CallTransferAgent()
conversation_flow = ConversationFlow(agent)
pipeline = CascadingPipeline(
stt=DeepgramSTT(),
llm=GoogleLLM(),
tts=CartesiaTTS(),
vad=SileroVAD(),
turn_detector=TurnDetector()
)
session = AgentSession(
agent=agent,
pipeline=pipeline,
conversation_flow=conversation_flow
)
await session.start(wait_for_participant=True, run_until_shutdown=True)
def make_context() -> JobContext:
room_options = RoomOptions(name="Call Transfer Agent", playground=True)
return JobContext(room_options=room_options)
if __name__ == "__main__":
job = WorkerJob(
entrypoint=entrypoint,
jobctx=make_context,
options=Options(
agent_id="YOUR_AGENT_ID",
register=True,
host="localhost",
port=8081
)
)
job.start()
代码要点
transfer_call函数工具:从环境变量中获取 VideoSDK 授权令牌和目标电话号码,然后调用self.session.call_transfer。- 管道组件:Deepgram 用于语音转文字,Google LLM 用于语言理解,Cartesia 用于文字转语音,Silero VAD 用于语音活动检测,TurnDetector 用于管理说话者轮次。
- 作业配置:使用期望的
agent_id、主机和端口设置WorkerJob。
运行代理
- 定义环境变量
VIDEOSDK_AUTH_TOKEN和CALL_TRANSFER_TO。 - 执行脚本(
python your_script.py)。 - 当调用
transfer_call工具时,代理将等待入站参与者,处理对话并转接通话。
呼叫转接的好处
- 无缝交接:来电者永不遇到掉线或需要重新拨号的情况。
- 提升用户体验:对话自然流畅,降低挫败感。
- 可扩展的支持:AI 代理可以处理常规查询,并自动将复杂问题转接给人工客服。
附加资源
- Quick Start Example – 入站/出站呼叫处理和路由规则的详细指南。
- Call‑Transfer Implementation on GitHub – 包含示例项目的完整仓库。
- VideoSDK Documentation – 所有 SDK 功能的综合参考。