使用 Azure 和 OpenAI GPT 的 AI 驱动的呼叫中心解决方案。
在 API 调用中从 AI 代理发送电话呼叫。或者,直接从配置的电话号码呼叫机器人!
保险、IT 支持、客户服务等。机器人可以在几秒钟内(真的)进行自定义以满足您的需求。
# Ask the bot to call a phone number data='{ "bot_company": "Contoso", "bot_name": "Amélie", "phone_number": "+11234567890", "task": "Help the customer with their digital workplace. Assistant is working for the IT support department. The objective is to help the customer with their issue and gather information in the claim.", "agent_phone_number": "+33612345678", "claim": [ { "name": "hardware_info", "type": "text" }, { "name": "first_seen", "type": "datetime" }, { "name": "building_location", "type": "text" } ] }'curl
--header 'Content-Type: application/json'
--request POST
--url https://xxx/call
--data $data
注意
这个项目是一个概念验证。它不打算用于生产。这演示了如何结合使用 Azure 通信服务、Azure 认知服务和 Azure OpenAI 来构建自动化呼叫中心解决方案。
- 在公共网站上访问索赔
- 访问客户对话历史记录
- 允许用户更改对话的语言
- Assistant 可以向用户发送 SMS 以获���更多信息
- ���以从电话号码调用 Bot
- 机器人使用多种语音音调(例如,happy、sad、neutral)来保持对话的吸引力
- 机器人可以理解公司产品(= 词典)(例如,特定保险产品的名称)
- 自行创建一个任务的待办事项列表以完成声明
- 可自定义的提示
- 需要时脱离人工代理
- 从 LLM 中过滤掉不适当的内容,例如亵渎性语言或同意的公司名称
- 使用 GPT-4o 和 GPT 4o-mini 深入了解客户要求
- 遵循声明的特定数据架构
- 可以访问文档数据库(小样本培训 / RAG)
- 帮助用户查找完成索赔所需的信息
- 越狱检测
- 通过 usign a Redis 缓存降低 AI 搜索成本
- 使用 Application Insights 进行监控和跟踪
- 使用功能标志执行用户测试
- 在对话期间接收 SMS 以获取明确措辞
- 记录审计和质量保证的电话
- 响应从 LLM 流式传输给用户,以避免长时间停顿
- 通话后发送短信报告
- 脱离会话后收回对话
- 需要时回拨用户
- 模拟 IVR 工作流程
YouTube 上提供了法语演示。不要犹豫,以 x1.5 的速度观看演示,以快速了解该项目。
演示中显示的主要交互:
- 用户呼叫中心
- 机器人回答并开始对话
- 机器人将 conversation、claim 和 todo list 存储在数据库中
调用期间存储的数据提取:
{
"claim": {
"incident_datetime": "2024-10-08T02:00:00",
"incident_description": "La trottinette électrique fait des bruits bizarres et émet de la fumée blanche.",
"incident_location": "46 rue du Charles de Gaulle",
"injuries": "Douleur au genou suite à une chute.",
"involved_parties": "Lesne",
"policy_number": "B02131325XPGOLMP"
},
"messages": [
{
"created_at": "2024-10-08T11:23:41.824758Z",
"action": "call",
"content": "",
"persona": "human",
"style": "none",
"tool_calls": []
},
{
"created_at": "2024-10-08T11:23:55.421654Z",
"action": "talk",
"content": "Bonjour, je m'appelle Amélie, de Contoso Assurance ! Comment puis-je vous aider aujourd'hui ?",
"persona": "assistant",
"style": "cheerful",
"tool_calls": []
},
{
"created_at": "2024-10-08T11:24:19.972737Z",
"action": "talk",
"content": "Oui bien sûr. Bonjour, je vous appelle parce que j'ai un problème avec ma trottinette électrique. Elle marche plus depuis ce matin, elle fait des bruits bizarres et il y a une fumée blanche qui sort de la trottinette.",
"persona": "human",
"style": "none",
"tool_calls": []
}
],
"next": {
"action": "case_closed",
"justification": "The customer provided all necessary information for the claim, and they expressed satisfaction with the assistance received. No further action is required at this time."
},
"synthesis": {
"long": "You reported an issue with your electric scooter, which started making strange noises and emitting white smoke. This incident occurred at 2:00 AM while you were riding it, leading to a fall and resulting in knee pain. The location of the incident was noted, and your policy details were confirmed. I have documented all the necessary information to file your claim. Please take care of your knee, and feel free to reach out if you need further assistance.",
"satisfaction": "high",
"short": "the breakdown of your scooter",
"improvement_suggestions": "Ensure that the assistant provides clear next steps and offers to schedule follow-up calls proactively to enhance customer support."
},
...
}报告位于 (如 )。它显示对话历史记录、索赔数据和提醒。https://[your_domain]/report/[phone_number]http://localhost:8080/report/%2B133658471534
<details class="details-reset details-overlay details-overlay-dark" style="display: contents">
<summary role="button" aria-label="“打开”对话框" class="btn my-2 mr-2 p-0 d-inline-flex" aria-haspopup="dialog" _mstaria-label="154752" _msthash="335">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" class="octicon m-2">
<path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 1.06L2.56 7h10.88l-2.22-2.22a.75.75 0 011.06-1.06l3.5 3.5a.75.75 0 010 1.06l-3.5 3.5a.75.75 0 11-1.06-1.06l2.22-2.22H2.56l2.22 2.22a.75.75 0 11-1.06 1.06l-3.5-3.5a.75.75 0 010-1.06l3.5-3.5z"></path>
</svg>
</summary>
<details-dialog class="Box Box--overlay render-full-screen d-flex flex-column anim-fade-in fast" aria-label="mermaid rendered container" role="dialog" aria-modal="true" _msthidden="1" _mstaria-label="611598" _msthash="336">
<div _msthidden="1">
<button aria-label="Close dialog" data-close-dialog="" type="button" data-view-component="true" class="Link--muted btn-link position-absolute render-full-screen-close" _msthidden="A" _mstaria-label="177619" _msthash="337">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:text-bottom" class="octicon octicon-x">
<path fill-rule="evenodd" d="M5.72 5.72a.75.75 0 011.06 0L12 10.94l5.22-5.22a.75.75 0 111.06 1.06L13.06 12l5.22 5.22a.75.75 0 11-1.06 1.06L12 13.06l-5.22 5.22a.75.75 0 01-1.06-1.06L10.94 12 5.72 6.78a.75.75 0 010-1.06z"></path>
</svg>
</button>
<div class="Box-body border-0" role="presentation"></div>
</div>
</details-dialog>
</details>
---
title: System diagram (C4 model)
---
graph
user(["User"])
agent(["Agent"])
<details class="details-reset details-overlay details-overlay-dark" style="display: contents">
<summary role="button" aria-label="“打开”对话框" class="btn my-2 mr-2 p-0 d-inline-flex" aria-haspopup="dialog" _mstaria-label="154752" _msthash="344">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" class="octicon m-2">
<path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 1.06L2.56 7h10.88l-2.22-2.22a.75.75 0 011.06-1.06l3.5 3.5a.75.75 0 010 1.06l-3.5 3.5a.75.75 0 11-1.06-1.06l2.22-2.22H2.56l2.22 2.22a.75.75 0 11-1.06 1.06l-3.5-3.5a.75.75 0 010-1.06l3.5-3.5z"></path>
</svg>
</summary>
<details-dialog class="Box Box--overlay render-full-screen d-flex flex-column anim-fade-in fast" aria-label="mermaid rendered container" role="dialog" aria-modal="true" _msthidden="1" _mstaria-label="611598" _msthash="345">
<div _msthidden="1">
<button aria-label="Close dialog" data-close-dialog="" type="button" data-view-component="true" class="Link--muted btn-link position-absolute render-full-screen-close" _msthidden="A" _mstaria-label="177619" _msthash="346">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:text-bottom" class="octicon octicon-x">
<path fill-rule="evenodd" d="M5.72 5.72a.75.75 0 011.06 0L12 10.94l5.22-5.22a.75.75 0 111.06 1.06L13.06 12l5.22 5.22a.75.75 0 11-1.06 1.06L12 13.06l-5.22 5.22a.75.75 0 01-1.06-1.06L10.94 12 5.72 6.78a.75.75 0 010-1.06z"></path>
</svg>
</button>
<div class="Box-body border-0" role="presentation"></div>
</div>
</details-dialog>
</details>
---
title: Claim AI component diagram (C4 model)
---
graph LR
agent(["Agent"])
user(["User"])
<details class="details-reset details-overlay details-overlay-dark" style="display: contents">
<summary role="button" aria-label="“打开”对话框" class="btn my-2 mr-2 p-0 d-inline-flex" aria-haspopup="dialog" _mstaria-label="154752" _msthash="353">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" class="octicon m-2">
<path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 1.06L2.56 7h10.88l-2.22-2.22a.75.75 0 011.06-1.06l3.5 3.5a.75.75 0 010 1.06l-3.5 3.5a.75.75 0 11-1.06-1.06l2.22-2.22H2.56l2.22 2.22a.75.75 0 11-1.06 1.06l-3.5-3.5a.75.75 0 010-1.06l3.5-3.5z"></path>
</svg>
</summary>
<details-dialog class="Box Box--overlay render-full-screen d-flex flex-column anim-fade-in fast" aria-label="mermaid rendered container" role="dialog" aria-modal="true" _msthidden="1" _mstaria-label="611598" _msthash="354">
<div _msthidden="1">
<button aria-label="Close dialog" data-close-dialog="" type="button" data-view-component="true" class="Link--muted btn-link position-absolute render-full-screen-close" _msthidden="A" _mstaria-label="177619" _msthash="355">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" style="display:inline-block;vertical-align:text-bottom" class="octicon octicon-x">
<path fill-rule="evenodd" d="M5.72 5.72a.75.75 0 011.06 0L12 10.94l5.22-5.22a.75.75 0 111.06 1.06L13.06 12l5.22 5.22a.75.75 0 11-1.06 1.06L12 13.06l-5.22 5.22a.75.75 0 01-1.06-1.06L10.94 12 5.72 6.78a.75.75 0 010-1.06z"></path>
</svg>
</button>
<div class="Box-body border-0" role="presentation"></div>
</div>
</details-dialog>
</details>
sequenceDiagram
autonumber
首选使用 GitHub Codespaces 快速入门。环境将使用所有必需的工具自动设置。
在 macOS 中,使用 Homebrew 时,只需键入 .make brew
对于其他系统,请确保已安装以下内容:
- 与 Bash 兼容的 shell,例如 或
bashzsh - YQ
- 品牌、(Ubuntu)、(CentOS)、(macOS)
apt install makeyum install makebrew install make - Azure CLI
- Azure Functions Core 工具
- Twilio CLI(可选)
然后,需要 Azure 资源:
1. 创建新的资源组
- 喜欢使用小写字母,并且不使用短划线以外的特殊字符(例如
ccai-customer-a)
2. 创建通信服务资源
- 与资源组同名
- 启用系统托管标识
3. 购���电话号码
- 从通信服务资源
- 允许入站和出站通信
- 启用语音(必需)和短信(可选)功能
现在,先决条件已配置(本地 + Azure),可以完成部署。
GitHub Actions 上提供了预构建的容器映像,它将用于在 Azure 上部署解决方案:
- 来自分支的最新版本:
ghcr.io/clemlesne/call-center-ai:main - 特定标签:(推荐)
ghcr.io/clemlesne/call-center-ai:0.1.0
本地配置文件名为 。安装脚本(包括 Makefile 和 Bicep)将使用它来配置 Azure 资源。config.yaml
使用以下内容填充文件(必须根据您的需要进行自定义):
# config.yaml conversation: initiate: # Phone number the bot will transfer the call to if customer asks for a human agent agent_phone_number: "+33612345678" bot_company: Contoso bot_name: Amélie lang: {}communication_services: # Phone number purshased from Communication Services phone_number: "+33612345678"
sms: {}
prompts: llm: {} tts: {}
az login
make deploy name=my-rg-name
- 等待部署完成
- 名为
trainings - 名为
default
make logs name=my-rg-name
提示
若要使用服务主体向 Azure 进行身份验证,还可以在文件中添加以下内容:.env
AZURE_CLIENT_ID=xxx AZURE_CLIENT_SECRET=xxx AZURE_TENANT_ID=xxx
提示
如果应用程序已部署在 Azure 上,则可以运行以将配置从 Azure Function App 复制到本地计算机。make name=my-rg-name sync-local-config
本地配置文件名为 :config.yaml
# config.yaml resources: public_url: https://xxx.blob.core.windows.net/publicconversation: initiate: agent_phone_number: "+33612345678" bot_company: Contoso bot_name: Robert
communication_services: access_key: xxx call_queue_name: call-33612345678 endpoint: https://xxx.france.communication.azure.com phone_number: "+33612345678" post_queue_name: post-33612345678 recording_container_url: https://xxx.blob.core.windows.net/recordings resource_id: xxx sms_queue_name: sms-33612345678
cognitive_service: # Must be of type "AI services multi-service account" endpoint: https://xxx.cognitiveservices.azure.com
llm: fast: mode: azure_openai azure_openai: context: 16385 deployment: gpt-4o-mini-2024-07-18 endpoint: https://xxx.openai.azure.com model: gpt-4o-mini streaming: true slow: mode: azure_openai azure_openai: context: 128000 deployment: gpt-4o-2024-08-06 endpoint: https://xxx.openai.azure.com model: gpt-4o streaming: true
ai_search: access_key: xxx endpoint: https://xxx.search.windows.net index: trainings
ai_translation: access_key: xxx endpoint: https://xxx.cognitiveservices.azure.com
make deploy-bicep deploy-post name=my-rg-name
- 这将在没有 API 服务器的情况下部署 Azure 资源,从而允许您在本地测试机器人
- 等待部署完成
复制到 ,然后填写必填字段:local.example.settings.jsonlocal.settings.json
APPLICATIONINSIGHTS_CONNECTION_STRING作为 Application Insights 资源的连接字符串AzureWebJobsStorage作为 Azure 存储帐户的连接字符串
重要
Tunnel 需要在单独的终端中运行,因为它需要一直运行
# Log in once devtunnel login# Start the tunnel make tunnel
注意
要覆盖特定的配置值,您可以使用环境变量���例如,要覆盖该值,您可以使用变量:llm.fast.endpointLLM__FAST__ENDPOINT
LLM__FAST__ENDPOINT=https://xxx.openai.azure.com
注意
此外,还可以使用脚本来测试应用程序,而无需打电话(= 没有通信服务)。使用以下命令运行脚本:local.py
python3 -m tests.local
make dev
- 文件更改时会自动重新加载代码,无需重新启动服务器
- API 服务器位于
http://localhost:8080
默认情况下,通话录音处于关闭状态。要启用它:
- 在 Azure 存储帐户中创建新容器(即 ),如果您在 Azure 上部署了解决方案,则它已经完成
recordings - 将应用程序配置中的功能标志更新为
recording_enabledtrue
训练数据存储在 AI Search 上,供机器人按需检索。
所需的索引架构:
| 字段名称 | Type |
检索 | 搜索 | 尺寸 | 矢量化器 |
|---|---|---|---|---|---|
| 答 | Edm.String |
是的 | 是的 | ||
| 上下文 | Edm.String |
是的 | 是的 | ||
| created_at | Edm.String |
是的 | 不 | ||
| document_synthesis | Edm.String |
是的 | 是的 | ||
| file_path | Edm.String |
是的 | 不 | ||
| 身份证 | Edm.String |
是的 | 不 | ||
| 问题 | Edm.String |
是的 | 是的 | ||
| 向量 | Collection(Edm.Single) |
不 | 是的 | 1536 | OpenAI ADA |
用于填充索引的软件包含在 Synthetic RAG Index 存储库中。
该机器人可以在多种语言中使用。它可以理解用户选择的语言。
请参阅 Text-to-Speech 服务支持的语言列表。
# config.yaml conversation: initiate: lang: default_short_code: fr-FR availables: - pronunciations_en: ["French", "FR", "France"] short_code: fr-FR voice: fr-FR-DeniseNeural - pronunciations_en: ["Chinese", "ZH", "China"] short_code: zh-CN voice: zh-CN-XiaoqiuNeural
如果生成并部署了 Azure 语音自定义神经语音 (CNV),请在语言配置上添加字段:custom_voice_endpoint_id
# config.yaml conversation: initiate: lang: default_short_code: fr-FR availables: - pronunciations_en: ["French", "FR", "France"] short_code: fr-FR voice: xxx custom_voice_endpoint_id: xxx
为每个类别的内容安全定义了级别。分数越高,审核越严格,从 0 到 7。审核适用于所有机器人数据,包括网页和对话。在 Azure OpenAI 内容筛选器中配置它们。
完全支持数据架构的自定义。您可以根据需要添加或删除字段,具体取决于要求。
默认情况下,的 架构由以下部分组成:
caller_email(email)caller_name(text)caller_phone(phone_number)
验证值以确保数据格式提交到您的架构。它们可以是:
datetimeemailphone_number(E164格式)text
最后,可以提供可选说明。描述必须简短且有意义,它将传递给 LLM。
入站呼叫的默认架构在配置中定义:
# config.yaml conversation: default_initiate: claim: - name: additional_notes type: text # description: xxx - name: device_info type: text # description: xxx - name: incident_datetime type: datetime # description: xxx
通过在 API 调用中添加字段,可以为每个调用自定义声明架构。claimPOST /call
目标是机器人在通话期间将执行的操作的描述。它用于为 LLM 提供上下文。它应该简短、有意义,并且用英文书写。
此解决方案是特权,而不是覆盖 LLM 提示符。
入站呼叫的默认任务在配置中定义:
# config.yaml conversation: initiate: task: | Help the customer with their insurance claim. Assistant requires data from the customer to fill the claim. The latest claim data will be given. Assistant role is not over until all the relevant data is gathered.
通过在 API 调用中添加字段,可以为每个调用自定义 Task。taskPOST /call
对话选项表示为功能。它们可以通过应用程序配置进行配置,而无需重新部署或重新启动应用程序。更新功能后,需要延迟 60 秒才能使更改生效。
|姓名 |描述 |类型 |默认 |
|-|-|-|
| |机器人应答的硬超��(以秒为单位)。| |180 元 |
| |机器人应答的软超时(以秒为单位)。| |30 元 |
| |回调的超时时间(以小时为单位)。| |72 元 |
| |电话静音的超时时间(以秒为单位)。| |1 |
| |是否开启通话录音。| |假 |
| |是否使用较慢的 LLM 进行聊天。| |真 |
| |语音识别的最大重试次数。| |2 |answer_hard_timeout_secintanswer_soft_timeout_secintcallback_timeout_hourintphone_silence_timeout_secintrecording_enabledboolslow_llm_for_chatboolvoice_recognition_retry_maxint
要使用与 OpenAI 补全 API 兼容的模型,您需要创建一个账户并获取以下信息:
- API 密钥
- 上下文窗口大小
- 端点 URL
- 型号名称
- 流式处理功能
然后,在文件中添加以下内容:config.yaml
# config.yaml llm: fast: mode: openai openai: context: 128000 endpoint: https://api.openai.com model: gpt-4o-mini streaming: true slow: mode: openai openai: context: 128000 endpoint: https://api.openai.com model: gpt-4o streaming: true
要使用 Twilio 进行短信,您需要创建一个帐户并获取以下信息:
- 帐户 SID
- 身份验证令牌
- 电话号码
然后,在文件中添加以下内容:config.yaml
# config.yaml sms: mode: twilio twilio: account_sid: xxx auth_token: xxx phone_number: "+33612345678"
请注意,提示示例包含占位符。这些占位符将替换为具有相应数据的机器人。例如,在内部替换为 bot 名称。{xxx}{bot_name}
请务必用英文编写所有 TTS 提示。此语言用作对话翻译的枢轴语言。
# config.yaml prompts: tts: hello_tpl: | Hello, I'm {bot_name}, from {bot_company}! I'm an IT support specialist. Here's how I work: when I'm working, you'll hear a little music; then, at the beep, it's your turn to speak. You can speak to me naturally, I'll understand. Examples: - "I've got a problem with my computer, it won't turn on". - "The external screen is flashing, I don't know why". What's your problem? llm: default_system_tpl: | Assistant is called {bot_name} and is in a call center for the company {bot_company} as an expert with 20 years of experience in IT service. # Context Today is {date}. Customer is calling from {phone_number}. Call center number is {bot_phone_number}. chat_system_tpl: | # Objective Provide internal IT support to employees. Assistant requires data from the employee to provide IT support. The assistant's role is not over until the issue is resolved or the request is fulfilled. # Rules - Answers in {default_lang}, even if the customer speaks another language - Cannot talk about any topic other than IT support - Is polite, helpful, and professional - Rephrase the employee's questions as statements and answer them - Use additional context to enhance the conversation with useful details - When the employee says a word and then spells out letters, this means that the word is written in the way the employee spelled it (e.g. "I work in Paris PARIS", "My name is John JOHN", "My email is Clemence CLEMENCE at gmail GMAIL dot com COM") - You work for {bot_company}, not someone else # Required employee data to be gathered by the assistant - Department - Description of the IT issue or request - Employee name - Location # General process to follow 1. Gather information to know the employee's identity (e.g. name, department) 2. Gather details about the IT issue or request to understand the situation (e.g. description, location) 3. Provide initial troubleshooting steps or solutions 4. Gather additional information if needed (e.g. error messages, screenshots) 5. Be proactive and create reminders for follow-up or further assistance # Support status {claim} # Reminders {reminders}
延迟主要来自两件事:
- 事实上,Azure 通信服务在转发音频的方式上是连续的(从技术上讲,它只处理文本,而不是音频,并且一旦转换了整个音频,就会等待指定的空白时间)
- LLM,更具体地说是 API 调用和推断的第一个句子之间的延迟,可能会很长(因为句子一旦可用,就会一个接一个地发送),如果它产生幻觉并返回空答案,则甚至更长(这种情况经常发生,并且 applicatoipn 会重试调用)
从现在开始,您唯一能做的就是 LLM 部分。这可以通过 Azure 上的 PTU 或使用不太智能的模型来实现,例如(在最新版本上默认选择)。使用 Azure OpenAI 上的 PTU,在某些情况下可以将延迟除以 2。gpt-4o-mini
该应用程序本机连接到 Azure Application Insights,因此您可以监控响应时间并查看时间花费在哪里。这是识别瓶颈的良好开端。
如果您有任何优化响应延迟的想法,请随时提出问题或提出 PR。
在开发时,没有 LLM 框架可用于处理所有这些功能:具有多工具的流功能、可用性问题的备份模型、触发工具中的回调机制。因此,直接使用 OpenAI SDK,并实施了一些算法来处理可靠性。
