跳至主要内容

座席电话集成

使您的语音 AI 座席能够拨打和接听电话。

概述

您可以使用会话发起协议 (SIP) 将 LiveKit 座席与电话系统集成。您可以选择支持来电、外呼或两者。LiveKit 还提供包括 DTMF、SIP REFER 等功能。有关受支持功能的完整列表,请参阅 SIP 功能

电话集成不需要对您现有的座席代码进行重大更改,因为电话呼叫只是使用特殊的参与者类型桥接到 LiveKit 房间。

入门

  1. 请按照 语音 AI 快速入门,获取一个简单的座席并使其运行。
  2. 为您的项目设置 SIP 中继,或通过 LiveKit 电话号码 购买电话号码以进行来电。
  3. 返回本指南以启用来电和外呼。

座席调度

LiveKit 建议对电话集成使用显式座席调度,以确保不会发生意外的自动调度,因为来电和外呼的复杂性。

要启用显式调度,请为您的座席命名。这将禁用自动调度。

server = AgentServer()
@server.rtc_session(agent_name="my-telephony-agent")
async def my_agent(ctx: JobContext):
# ... your existing agent code ...
if __name__ == "__main__":
agents.cli.run_app(server)
完整示例

请参阅有关 座席调度 的文档,以获取更完整的示例。

入站呼叫

使用 LiveKit 电话号码 是开始使用入站呼叫的最快方法。 如果您使用的是第三方 SIP 提供商,请按照 SIP 中继设置 指南配置您的 SIP 中继并创建一个 入站中继

分派规则

创建一个分派规则,将入站呼叫路由到您的座席。 以下规则将所有入站呼叫路由到一个新房间,并将您的座席分派到该房间

{
"dispatch_rule":
{
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"roomConfig": {
"agents": [{
"agentName": "my-telephony-agent"
}]
}
}
}

使用以下命令创建此规则

lk sip dispatch create dispatch-rule.json

接听电话

拨打 generate_reply 方法的您的 AgentSession 以在接听后问候来电者。 此代码紧随 session.start 之后

await session.generate_reply(
instructions="Greet the user and offer your assistance."
)

呼叫您的座席

在您使用以下命令启动座席后,拨打您之前设置的号码以听到您的座席接听电话。

uv run agent.py dev

外呼电话

在设置您的 出站中继 后,您可以先分派座席,然后创建 SIP 参与者来拨打出站呼叫。

以下指南介绍了如何修改 语音 AI 快速入门 以进行出站呼叫。 或者,请参阅 GitHub 上的以下完整示例

出站呼叫者示例

一个出站呼叫座席的完整示例。

拨打号码

添加以下代码,以便您的座席读取电话号码并在连接后通过创建 SIP 参与者来拨打出站呼叫。

您还应该删除初始问候语,或者将其置于 if 语句之后,以确保座席在拨打出站呼叫时先等待用户说话。

SIP 中继 ID

您必须填写此示例的 sip_trunk_id 才能使其工作。 您可以使用 lk sip outbound list 从 LiveKit CLI 获取它。

# add these imports at the top of your file
from livekit import api
import json
# ... any existing code / imports ...
def entrypoint(ctx: agents.JobContext):
# If a phone number was provided, then place an outbound call
# By having a condition like this, you can use the same agent for inbound/outbound telephony as well as web/mobile/etc.
dial_info = json.loads(ctx.job.metadata)
phone_number = dial_info["phone_number"]
# The participant's identity can be anything you want, but this example uses the phone number itself
sip_participant_identity = phone_number
if phone_number is not None:
# The outbound call will be placed after this method is executed
try:
await ctx.api.sip.create_sip_participant(api.CreateSIPParticipantRequest(
# This ensures the participant joins the correct room
room_name=ctx.room.name,
# This is the outbound trunk ID to use (i.e. which phone number the call will come from)
# You can get this from LiveKit CLI with `lk sip outbound list`
sip_trunk_id='ST_xxxx',
# The outbound phone number to dial and identity to use
sip_call_to=phone_number,
participant_identity=sip_participant_identity,
# This will wait until the call is answered before returning
wait_until_answered=True,
))
print("call picked up successfully")
except api.TwirpError as e:
print(f"error creating SIP participant: {e.message}, "
f"SIP status: {e.metadata.get('sip_status_code')} "
f"{e.metadata.get('sip_status')}")
ctx.shutdown()
# .. create and start your AgentSession as normal ...
# Add this guard to ensure the agent only speaks first in an inbound scenario.
# When placing an outbound call, its more customary for the recipient to speak first
# The agent will automatically respond after the user's turn has ended.
if phone_number is None:
await session.generate_reply(
instructions="Greet the user and offer your assistance."
)

启动座席并按照下一节中的说明呼叫您的座席。

使用您的座席拨打电话

使用 LiveKit CLI 或 Python API 指示您的座席拨打出站电话。

在此示例中,作业的元数据包括要拨打的电话号码。 您可以根据您的用例扩展此信息以包含更多信息。

座席名称必须与您分配给座席的名称匹配。 如果您在 座席分派 部分中之前设置了它,那么就是 my-telephony-agent

房间名称必须与 Node.js 匹配

对于 Node.js,房间名称必须与您用于 CreateSIPParticipant API 调用名称匹配。 如果您使用 拨打号码 部分中的示例代码,那么就是 new-room。 否则,座席会拨打号码,但不会加入正确的房间。

以下命令创建一个新房间,并将您的座席分派到该房间,并附带要拨打的电话号码。

lk dispatch create \
--new-room \
--agent-name my-telephony-agent \
--metadata '{"phone_number": "+15105550123"}' # insert your own phone number here
await lkapi.agent_dispatch.create_dispatch(
api.CreateAgentDispatchRequest(
# Use the agent name you set in the rtc_session decorator
agent_name="my-telephony-agent",
# The room name to use. This should be unique for each call
room=f"outbound-{''.join(str(random.randint(0, 9)) for _ in range(10))}",
# Here we use JSON to pass the phone number, and could add more information if needed.
metadata='{"phone_number": "+15105550123"}'
)
)

语音邮件检测

您的座席可能仍然会遇到自动系统,例如应答机或语音邮件。 您可以赋予您的 LLM 通过工具调用检测可能语音邮件系统的能力,然后执行特殊操作,例如留言和 挂断

import asyncio # add this import at the top of your file
class Assistant(Agent):
## ... existing init code ...
@function_tool
async def detected_answering_machine(self):
"""Call this tool if you have detected a voicemail system, AFTER hearing the voicemail greeting"""
await self.session.generate_reply(
instructions="Leave a voicemail message letting the user know you'll call back later."
)
await asyncio.sleep(0.5) # Add a natural gap to the end of the voicemail message
await hangup_call()

挂断

要结束所有参与者的通话,请使用 delete_room API。如果只有代理会话结束,用户将继续听到静音,直到他们挂断电话。下面的示例显示了一个基本的 hangup_call 函数,您可以将其作为起点使用。

# Add these imports at the top of your file
from livekit import api, rtc
from livekit.agents import get_job_context
# Add this function definition anywhere
async def hangup_call():
ctx = get_job_context()
if ctx is None:
# Not running in a job context
return
await ctx.api.room.delete_room(
api.DeleteRoomRequest(
room=ctx.room.name,
)
)
class MyAgent(Agent):
...
# to hang up the call as part of a function call
@function_tool
async def end_call(self, ctx: RunContext):
"""Called when the user wants to end the call"""
await ctx.wait_for_playout() # let the agent finish speaking
await hangup_call()

转移通话到另一个号码

如果代理需要将通话转移到另一个号码或 SIP 目的地,可以使用 TranserSIPParticipant API。

这是一个 冷转移,代理将通话转交给另一方,而无需留在在线上。转移完成后,当前会话结束。

Node.js 所需的包

要使用 Node.js 示例,您必须安装 @livekit/rtc-node

pnpm add @livekit/rtc-node
class Assistant(Agent):
## ... existing init code ...
@function_tool()
async def transfer_call(self, ctx: RunContext):
"""Transfer the call to a human agent, called after confirming with the user"""
transfer_to = "+15105550123"
participant_identity = "+15105550123"
# let the message play fully before transferring
await ctx.session.generate_reply(
instructions="Inform the user that you're transferring them to a different agent."
)
job_ctx = get_job_context()
try:
await job_ctx.api.sip.transfer_sip_participant(
api.TransferSIPParticipantRequest(
room_name=job_ctx.room.name,
participant_identity=participant_identity,
# to use a sip destination, use `sip:user@host` format
transfer_to=f"tel:{transfer_to}",
)
)
except Exception as e:
print(f"error transferring call: {e}")
# give the LLM that context
return "could not transfer call"
SIP REFER

要使用 transfer_sip_participant,您必须在 SIP 干线提供商上启用 SIP REFER。 对于 Twilio,您还必须启用 Enable PSTN Transfer。要了解更多信息,请参阅 冷转移

示例

以下示例对于更多地了解电话集成非常有帮助。

更多资源

以下指南提供了有关构建电话语音代理的更多信息。