开发无服务器 WhatsApp 聊天机器人
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
本文是#ServerlessSeptember活动的一部分。在这个涵盖所有 Serverless 相关内容的合集中,您可以找到其他有用的文章、详细的教程和视频。整个九月,社区成员和云技术倡导者每天都会发布新文章——没错,每天都有!
请访问https://docs.microsoft.com/azure/azure-functions/了解更多关于 Microsoft Azure 如何启用您的无服务器函数的信息。
聊天机器人是一种人工智能软件,能够以自然语言与用户互动。它们可以通过提取用户消息中的关键词来识别用户意图,并针对用户的请求提供相应的回复。如今,聊天机器人非常重要,因为它们可以高效地处理重复性、耗时的任务,使公司能够专注于其他业务并优化资源(尤其是人力资源)。
微软提供了两项关键技术:LUIS和Bot Framework,让您轻松开发和创建聊天机器人。即使您不是人工智能专家,也能开发和部署对话式聊天机器人,与用户互动并处理他们的需求。结合Azure Bot Service,您就能拥有一个连接到云端的聊天机器人,只需极少的配置步骤,即可将其集成到多个渠道,例如Skype、Microsoft Teams、Facebook Messenger,甚至可以通过WebChat通道集成到您的网站。
然而,Azure Bot 服务目前不支持某些渠道。例如,WhatsApp就是一个显著的例子,它是最受欢迎的即时通讯应用,截至 2019 年 7 月,月活跃用户达 16 亿。我们的客户非常希望能够通过这款即时通讯应用与我们的应用进行沟通。
那么,我们该如何解决这个问题呢?Azure Functions 来帮忙!我们可以通过创建一个无服务器代码来简化操作,该代码通过Webhook连接到 WhatsApp 号码。这意味着每次用户向该号码发送消息时,都会触发一个 Azure 函数。我们还可以通过将其连接到 LUIS 自然语言处理模型,为回复注入智能。简而言之,我们将使用三种技术:
- LUIS(语言理解智能服务)
- Azure Functions
- Twilio API(用于访问 WhatsApp)
我们开始吧!
第一部分:路易斯
LUIS 代表语言理解智能服务 (Language Understanding Intelligent Service)。它是微软人工智能平台的一部分,能够帮助我们识别用户消息中的意图和关键要素。为了创建一个智能语言处理模型,我们需要先用示例(语句)对其进行训练。
步骤 1.创建一个新的LUIS 应用程序。
步骤 2.将geographyV2预构建实体添加到项目中。实体代表要识别的文本部分(例如,城市)。
步骤 3.创建一个新的意图:GetCityWeather。意图代表用户提出的要求,例如预订酒店房间、查找产品或请求特定城市的天气状况。
步骤 4.为该意图添加至少5 个示例语句。请注意,城市会被自动识别为 geographyV2 实体。通过提供用户可能针对特定意图说出的示例语句,模型会变得更加智能。
步骤 5.点击“训练”按钮创建 LUIS 模型。完成后,使用新请求进行测试,查看其是否正常工作(应能检测到意图和实体):
步骤 6.发布模型。选择“生产”插槽,然后转到“Azure 资源”。从“示例查询”框中复制 URL,因为我们稍后将在第 3 部分中将其用于 Azure Functions 的查询和请求。
第二部分:OpenWeatherMap
OpenWeatherMap是一项可以用来获取特定城市天气信息的服务。
第三部分:Azure Functions
Azure Functions 是一种无服务器计算服务,它允许您按需或响应事件运行脚本或代码段,而无需显式地预配或管理基础架构。
步骤 1.从 Azure 门户创建一个新的函数应用。它的名称是唯一的,所以serverlesschatbot这个名称对您无效,请使用其他名称 :-)
步骤 2.资源创建完成后,添加一个名为receive-message的新 HTTP 触发器。然后单击“查看文件”并添加一个function.proj文件。
步骤 3.此文件用于将 NuGet 包包含到我们的项目中。我们添加的是 Twilio 扩展,以便我们的代码稍后可以与 WhatsApp 号码进行交互。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Twilio" Version="3.0.0" />
</ItemGroup>
</Project>
步骤 4.接下来,我们来看 run.csx 的代码,它是 Azure 函数的主要部分。在本例中,我们将同时使用第 1 部分(步骤 6)中的 URL 和第 2 部分(步骤 4)中的 API 密钥,因此请将它们替换到代码中。
在代码的第一部分,我们包含了几个用于反序列化 LUIS 和 OpenWeatherMap 服务后返回的 JSON 响应的类。然后,Run方法中的代码解析了向 WhatsApp 号码发送消息后生成的有效负载。提取文本部分后,我们使用evaluateMessage方法将文本发送给 LUIS 已发布的模型,该模型处理消息并提取城市信息。如果成功,则向 OpenWeatherMap 发出请求以获取特定城市的天气信息。最后,将此信息作为响应发送给用户。
#r "System.Runtime"
#r "Newtonsoft.Json"
using System.Net;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Twilio.TwiML;
// LUIS classes
public class LuisModel
{
public string query { get; set; }
public TopScoringIntent topScoringIntent { get; set; }
public List<Intent> intents { get; set; }
public List<Entity> entities { get; set; }
}
public class TopScoringIntent
{
public string intent { get; set; }
public double score { get; set; }
}
public class Intent
{
public string intent { get; set; }
public double score { get; set; }
}
public class Entity
{
public string entity { get; set; }
public string type { get; set; }
public int startIndex { get; set; }
public int endIndex { get; set; }
}
// OpenWeatherMap classes
public class WeatherModel
{
public Coord coord { get; set; }
public List<Weather> weather { get; set; }
public string @base { get; set; }
public Main main { get; set; }
public int visibility { get; set; }
public Wind wind { get; set; }
public Clouds clouds { get; set; }
public int dt { get; set; }
public Sys sys { get; set; }
public int id { get; set; }
public string name { get; set; }
public int cod { get; set; }
}
public class Weather
{
public int id { get; set; }
public string main { get; set; }
public string description { get; set; }
public string icon { get; set; }
}
public class Coord
{
public double lon { get; set; }
public double lat { get; set; }
}
public class Main
{
public double temp { get; set; }
public double pressure { get; set; }
public double humidity { get; set; }
public double temp_min { get; set; }
public double temp_max { get; set; }
}
public class Wind
{
public double speed { get; set; }
}
public class Clouds
{
public double all { get; set; }
}
public class Sys
{
public int type { get; set; }
public int id { get; set; }
public double message { get; set; }
public string country { get; set; }
public long sunrise { get; set; }
public long sunset { get; set; }
}
// Main code
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
var data = await req.Content.ReadAsStringAsync();
var formValues = data.Split('&')
.Select(value => value.Split('='))
.ToDictionary(pair => Uri.UnescapeDataString(pair[0]).Replace("+", " "),
pair => Uri.UnescapeDataString(pair[1]).Replace("+", " "));
var text = formValues["Body"].ToString();
var message = await evaluateMessage(text);
var response = new MessagingResponse().Message(message);
var twiml = response.ToString();
twiml = twiml.Replace("utf-16", "utf-8");
return new HttpResponseMessage
{
Content = new StringContent(twiml, Encoding.UTF8, "application/xml")
};
}
private static readonly HttpClient httpClient = new HttpClient();
private static async Task<string> evaluateMessage(string text)
{
try
{
var luisURL = "Your-LUIS-URL-From-Step6-Part1";
var luisResult = await httpClient.GetStringAsync($"{luisURL}{text});
var luisModel = JsonConvert.DeserializeObject<LuisModel>(luisResult);
if (luisModel.topScoringIntent.intent == "GetCityWeather")
{
var entity = luisModel.entities.FirstOrDefault();
if (entity != null)
{
if (entity.type == "builtin.geographyV2.city")
{
var city = entity.entity;
var apiKey = "Your-OpenWeatherMapKey-From-Step4-Part2";
var weatherURL = $"http://api.openweathermap.org/data/2.5/weather?appid={apiKey}&q={city}";
var weatherResult = await httpClient.GetStringAsync(weatherURL);
var weatherModel = JsonConvert.DeserializeObject<WeatherModel>(weatherResult);
weatherModel.main.temp -= 273.15;
var weather = $"{weatherModel.weather.First().main} ({weatherModel.main.temp.ToString("N2")} °C)";
return $"Weather of {city} is: {weather}";
}
}
}
else
return "Sorry, I could not understand you!";
}
catch(Exception ex)
{
}
return "Sorry, there was an error!";
}
最后一部分:Twilio
要与 WhatsApp 号码通信,我们可以使用 Twilio API。
第一步:创建免费的 Twilio 帐户 
步骤 2.访问可编程短信控制面板,然后选择 WhatsApp Beta,再点击“开始使用”。
步骤 3.激活 Twilio Sandbox for WhatsApp。
步骤 4.通过您的设备向指定号码发送特定的 WhatsApp 消息来设置测试沙箱
步骤 5.加入对话后,再次单击“沙盒”以访问配置。将“收到消息时”下方的 URL 替换为上一步步骤 5 中获取的 Azure 函数 URL。
成功了!耶!
由于涉及多种技术,设置所有功能确实需要一些时间。但是,您现在可以想象一下各种可能性。如果您告诉用户他们可以通过 WhatsApp 与您的应用互动,或者他们可以将问题发送到一个特定的号码,由该号码处理所有问题,他们会作何感想?您可以使用其他技术(例如QnA Maker)来替代 LUIS 处理用户的问题。甚至可以发送图片,并让认知服务对其进行分析!
天空才是极限!:-)
当然,这一切都得益于Azure Functions,实现了无服务器体验。
感谢您的阅读,希望这篇文章对您有所帮助(欢迎在评论区留言 :-D)。如果您想了解更多关于 Azure、Xamarin、人工智能等方面的知识,欢迎访问我的博客和YouTube 频道,我通常会在那里分享我的知识和经验。
祝您编程愉快!
路易斯
PS:我还要感谢 Azure Advocates(https://twitter.com/azureadvocates)发起的#ServerlessSeptember活动!每天都能从社区和专家那里学到新东西,真是太棒了。
本出版物参考资料:
Twilio






