Graph 呼叫机器人
Graph 呼叫机器人
像与同事交流一样,在 Microsoft Teams 中按名称呼叫或聊天使用 ElevenLabs 智能体。
概述
此方案让智能体成为一个可呼叫的 Teams 身份。用户可以按名称搜索并与其进行 1 对 1 通话,智能体会实时接听——无需电话号码、PSTN 或 Communications Credits。这是唯一可按名称呼叫的方案,也是部署起来最复杂的方案。
它使用 Microsoft Graph 实时媒体机器人(Cloud Communications 呼叫平台)。媒体 SDK(Microsoft.Skype.Bots.Media)仅支持 Windows Server 上的 .NET——在 Teams 通话中处理原始音频没有 Linux 或非 .NET 方案。
这是 Teams 中唯一可按名称呼叫的方案。如需更轻量的设置,请优先选择 widget 选项卡;如果你明确需要电话号码,请使用 ACS。
工作原理
机器人通过应用托管媒体接听,每秒接收 50 个音频帧(20 ms PCM 16 kHz),通过 WebSocket 将其桥接至 ElevenLabs 智能体,并将智能体音频流式传回通话中。
要求
- 一个 Azure Bot 注册和应用(Entra 应用注册)。
- 已获管理员同意的 Graph 应用程序权限:
Calls.AccessMedia.All(原始媒体)以及Calls.Initiate.All。 - 一个 Windows Server VM(≥ 2 个物理核心,例如
Standard_D4s_v3),具有公共 IP 和开放的媒体端口。 - 为媒体/信令端点配置公共 FQDN 上的 CA 签名 TLS 证书(媒体平台拒绝自签名证书)。
- 一个 ElevenLabs 智能体,两端均设为 PCM 16000 Hz:在 Voice 选项卡设置 TTS 输出格式,在 Advanced 选项卡设置用户输入音频格式。
D2s_v3(2 vCPU = 1 个物理核心)会因 MediaPlatform needs a system with at least 2 cores 而失败。请使用至少有 2 个物理核心的规格(例如 D4s_v3)。
权限和角色
第 1 步 — 注册机器人和 Graph 权限
创建应用注册并将 Azure Bot 绑定到该注册,然后授予并同意通话权限(同意需要全局管理员 / 特权角色管理员):
授予两个 Graph 应用程序角色并进行管理员同意(需要全局管理员 / 特权角色管理员),然后确认已分配成功:
如果 admin-consent 返回 Consent validation failed,请改为直接在服务主体上授予应用角色:
在门户的 Entra 管理中心中,依次进入应用注册 → 应用 → API 权限进行验证:两个权限都应显示为已授予,并带有绿色勾选标记。

第 2 步 — 配置 Windows VM、证书和端口
在 VM 上执行以下操作(媒体平台的原生代码需要这些组件,而 Windows Server 默认不包含它们):
同时在 Windows 防火墙中开放相同端口,并记录证书指纹 — 机器人会将 Kestrel(443 + 通知端口)和媒体平台(8445)绑定到该证书。
VM 自身的 *.cloudapp.azure.com FQDN 可用于 Let’s Encrypt 证书,无需单独的域名。
第 3 步 — 构建并运行机器人
从 Microsoft 的 microsoft-graph-comms-samples PublicSamples/EchoBot 开始 — 它以 net6.0 为目标框架,可通过 .NET SDK 构建(无需 Visual Studio Build Tools):
在 appsettings.json 的 AppSettings 部分配置 AadAppId、AadAppSecret、ServiceDnsName/MediaDnsName(VM FQDN)、CertificateThumbprint 和端口(通话 443、通知 9441、媒体 8445)。为下方 ElevenLabs 桥接添加两个设置:ElevenLabsAgentId 和 ElevenLabsOrigin(wss://api.elevenlabs.io,或你的数据驻留主机)。将其作为 Windows 计划任务 / 服务运行,以便在重启后持续运行。
任务计划程序默认的执行时间限制(72 小时) 会悄然终止长期运行的任务 — 开机时启动的机器人会在 3 天后停止,通话将失败并提示 “we couldn’t connect you”。请禁用此限制并添加失败后重启:
原版 EchoBot 在拨打标准端口 443 的通话时会崩溃:HttpHelpers.SetAbsoluteUri
会调用 req.Host.Port.Value,当 Host 标头未明确指定端口时,该值为 null。请将其修复为
req.Host.Port ?? (req.IsHttps ? 443 : 80)。
将回声替换为 ElevenLabs
EchoBot 的音频接口很清晰:SpeechService.AppendAudioBuffer(in) 和 OnSendMediaBufferEventArgs(out) 事件。将其 Azure-Speech 主体替换为 ElevenLabs 智能体 WebSocket 桥接,并保持相同接口:
两端都是 PCM 16 kHz 单声道,因此可直接透传 base64 — 将智能体设为 pcm_16000。当 ElevenLabs 发出 interruption(插话)时,桥接会触发 FlushMedia;将其连接到媒体流,以丢弃所有排队的 AudioMediaBuffer,否则智能体会继续覆盖来电者讲话。完整消息参考请见 WebSocket 文档。通话结束挂断和暖转接将在下方章节说明。
Connect() 中的 URL 会连接到公开智能体。对于私有智能体,请在服务器端请求短期有效的
签名 URL — 使用 API 密钥调用 GET /v1/convai/conversation/get-signed-url?agent_id=... — 然后连接到返回的 URL。使用数据驻留时,将 ElevenLabsOrigin 设为相应的数据驻留主机(wss://api.eu.residency.elevenlabs.io、.in. 或 .sg.)— 签名 URL 请求使用对应的 https:// 主机。
第 4 步 — 使其可在 Teams 中呼叫
-
在 Azure Bot 的 Teams 通道中启用 Calling,并将通话 webhook 设为
https://YOUR_FQDN/api/calling:在门户中,路径为 Azure Bot 资源 → Channels → Microsoft Teams → Calling 选项卡:

Azure Bot → Channels — 已连接的 Microsoft Teams 通道 
Microsoft Teams 通道 → Calling — 已启用通话并设置机器人 webhook -
使用
bots[0].supportsCalling: true和机器人的应用 ID 构建 Teams 应用清单,然后旁加载该应用(Apps → Manage your apps → Upload a custom app),或者不通过 UI 将其发布到整个组织:New-TeamsApp -DistributionMethod organization -Path ./bot-app.zip(MicrosoftTeams PowerShell 模块)。
在 Teams 中按名称搜索该应用并呼叫它 — 机器人会接听,ElevenLabs 智能体将开始说话。

按名称进行 1 对 1 呼叫无需电话号码或资源账户 — 这些仅用于 PSTN 拨入。Calls.AccessMedia.All 可启用原始音频桥接。
文本聊天(同一机器人)
同一个 Azure Bot 也可以在 Teams 中回复文本 — 用户既可以呼叫智能体,也可以与其聊天。通话和消息是机器人上的独立通道:通话 webhook 处理语音,Bot Framework 消息终结点(/api/messages)处理聊天。

将机器人的消息终结点指向提供该服务的任意主机(媒体机器人或其他服务 — 不必是 Windows VM):
使用 Bot Framework SDK 实现该终结点,并通过与语音相同的会话 WebSocket,以文本模式将每条消息转发给智能体 — 发送 user_message 事件,读取 agent_response 事件。首先在智能体覆盖设置中启用首条消息字段 — 下方代码将其覆盖为空,使回复回答用户消息而不是智能体问候语:
按标准方式注册(使用 CloudAdapter,通过 AddTransient<IBot, ChatBot>() 注册机器人,并添加 /api/messages 控制器),然后将聊天范围添加到清单的机器人条目:
此代码片段会为每条消息打开一个新会话,因此每轮对话彼此独立。若需聊天记忆,请为每个 Teams conversation.id 保持一个 WebSocket 连接(跨轮次复用),并清理闲置会话 — 智能体便会记住该聊天中的早期消息。必须在智能体的覆盖设置中启用 first_message 覆盖 — 如果发送不允许的覆盖,服务器会关闭会话。如果无法启用,请省略该覆盖,改为丢弃每个会话的第一条 agent_response(问候语),并返回下一条。
如果始终收不到聊天回复,请在智能体的高级设置中启用 agent_response 客户端事件 — 文本回复通过此事件传送。
通话结束
当 ElevenLabs 结束会话时(其结束通话工具会关闭 WebSocket),请挂断 Teams 通话:
暖转接给人工客服
智能体会触发自定义 transfer_to_human 客户端工具;机器人会将 Teams 用户邀请加入进行中的通话(协商式添加),然后退出:
协商式转接(replacesCallId)要求双方都是同一租户中的 Teams 用户;PSTN 转接目标需要应用实例。若要先向人工客服说明情况,请从智能体传入 reason 参数,并在桥接前播放给人工客服。
故障排除
MediaPlatform needs a system with at least 2 cores
MediaPlatform needs a system with at least 2 cores
VM 只有一个物理核心。请调整为 ≥ 2 个物理核心(例如 D4s_v3),然后重启。
Unable to load DLL 'NativeMedia'
Unable to load DLL 'NativeMedia'
安装 VC++ Redistributable(vcredist140)和 Server-Media-Foundation Windows 功能,然后重启机器人。
来电返回 500 / 通话无法连接
这是 EchoBot 在端口 443 上的空端口 bug — 请修复 HttpHelpers.SetAbsoluteUri(参见第 3 步)。同时确认该证书由 CA 签发,且可通过端口 443 访问。
呼叫机器人时提示“we couldn't connect you”
确认 Teams 通道已启用 Calling 并配置正确的 /api/calling webhook,Graph Calls.AccessMedia.All 权限已获同意,且 NSG 和 Windows 防火墙均已开放端口 443/8445/9441。如果呼叫之前可用但后来停止,请检查机器人进程是否仍在 VM 上运行 — 任务计划程序默认的 72 小时执行限制会在开机数天后将其终止(参见第 3 步中的警告)。