概述
您可以使用会话发起协议 (SIP) 将 LiveKit 座席与电话系统集成。您可以选择支持来电、外呼或两者。LiveKit 还提供包括 DTMF、SIP REFER 等功能。有关受支持功能的完整列表,请参阅 SIP 功能。
电话集成不需要对您现有的座席代码进行重大更改,因为电话呼叫只是使用特殊的参与者类型桥接到 LiveKit 房间。
入门
- 请按照 语音 AI 快速入门,获取一个简单的座席并使其运行。
- 为您的项目设置 SIP 中继,或通过 LiveKit 电话号码 购买电话号码以进行来电。
- 返回本指南以启用来电和外呼。
语音 AI 快速入门
按照语音 AI 快速入门,启动您的座席。
LiveKit 电话号码
购买 LiveKit 电话号码以进行来电。
SIP 中继设置
如果您正在使用 SIP 提供商或进行外呼,请配置您的提供商将呼叫路由到 LiveKit。
座席调度
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_trunk_id 才能使其工作。 您可以使用 lk sip outbound list 从 LiveKit CLI 获取它。
# add these imports at the top of your filefrom livekit import apiimport 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 itselfsip_participant_identity = phone_numberif phone_number is not None:# The outbound call will be placed after this method is executedtry:await ctx.api.sip.create_sip_participant(api.CreateSIPParticipantRequest(# This ensures the participant joins the correct roomroom_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 usesip_call_to=phone_number,participant_identity=sip_participant_identity,# This will wait until the call is answered before returningwait_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,房间名称必须与您用于 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 decoratoragent_name="my-telephony-agent",# The room name to use. This should be unique for each callroom=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 fileclass Assistant(Agent):## ... existing init code ...@function_toolasync 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 messageawait hangup_call()
挂断
要结束所有参与者的通话,请使用 delete_room API。如果只有代理会话结束,用户将继续听到静音,直到他们挂断电话。下面的示例显示了一个基本的 hangup_call 函数,您可以将其作为起点使用。
# Add these imports at the top of your filefrom livekit import api, rtcfrom livekit.agents import get_job_context# Add this function definition anywhereasync def hangup_call():ctx = get_job_context()if ctx is None:# Not running in a job contextreturnawait 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_toolasync def end_call(self, ctx: RunContext):"""Called when the user wants to end the call"""await ctx.wait_for_playout() # let the agent finish speakingawait hangup_call()
转移通话到另一个号码
如果代理需要将通话转移到另一个号码或 SIP 目的地,可以使用 TranserSIPParticipant API。
这是一个 冷转移,代理将通话转交给另一方,而无需留在在线上。转移完成后,当前会话结束。
要使用 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 transferringawait 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` formattransfer_to=f"tel:{transfer_to}",))except Exception as e:print(f"error transferring call: {e}")# give the LLM that contextreturn "could not transfer call"
要使用 transfer_sip_participant,您必须在 SIP 干线提供商上启用 SIP REFER。 对于 Twilio,您还必须启用 Enable PSTN Transfer。要了解更多信息,请参阅 冷转移。
示例
以下示例对于更多地了解电话集成非常有帮助。
更多资源
以下指南提供了有关构建电话语音代理的更多信息。