Azure 动物收养代理和失物招领
基于 Azure 的宠物收养代理,
注意:了解如何在 Windows Phone 应用中方便地实现枚举属性的双向数据绑定!(参见系统架构图之后的说明。)
(Windows Azure 比赛参赛作品)
目录
- 第一阶段 - 项目概述
- 第二阶段 - 带猫脸检测的失物招领网站
- 第三阶段 - 愿望清单,Azure 托管的 SQL 表
- 第四阶段 - 虚拟机上的虚拟助手
- 第五阶段 - 关于小鼠和大象 (Of Mice and Men)
第一阶段 - 项目概述
为什么选择 Azure?
Azure 兼具软件、平台和基础设施即服务功能,是创建高性能、可靠、无处不在的客户端/服务器解决方案的终极选择。Azure 平台消除了分布式数据库应用程序开发通常涉及的大量时间和成本,使像我这样的开发者能够将时间集中在为用户创建功能和价值上,而不是浪费在支持代码和基础设施上。
(注意:我已通过 MSDN 拥有 Azure 账户。)
引言
Azure 是协调搜索和通知服务创建宠物领养代理的完美平台,该代理将潜在宠物主人与无数可爱的猫狗联系起来,它们正等待着给予爱。通过提供基于云的、易于配置的 SQL 存储,Azure 非常适合存储用户通过运行在其 Windows Phone 上的 FindAPet 客户端应用程序指定的搜索条件。然后,Azure Mobile Services 用于在符合其存储偏好的宠物在全美各地的动物收容所可用时,向他们发送推送通知。 PetFinder API 提供对几乎所有类型和品种的待领养宠物库存的即时访问,以及直接访问收容它们的动物收容所。它还提供了完成该系统所需的必要动物和收容所搜索功能,该系统完成后将挽救无数无辜的、充满爱心的动物的生命。
该系统的辅助功能将是失物招领服务。丢失宠物的主人可以输入宠物的描述。类似于 Google Alerts,该系统将定期扫描主人家附近 50 英里半径内的收容所。如果找到符合主人描述的宠物,将向主人发送带有该宠物所在收容所链接的推送通知。(感谢 Simon Jackson 的失物招领创意!)
概述
该系统的入口点将是运行在 Windows Phone 平台上的 FindAPet 应用(客户端),使用 MVVM Light 框架。它将提供一个简单的搜索界面,帮助用户找到他们想领养的宠物类型。下面的屏幕截图展示了一个用户正在寻找领养一只三花猫的示例会话。
![]() | ![]() |
然后,系统会向他们展示附近的动物收容所列表。使用手机的地理位置服务,用户的当前位置将作为距离搜索的中心点,如下图截图所示,其中爱达荷州博伊西被识别为用户的当前位置。用户勾选他们愿意前往的收容所。
用户的宠物和收容所偏好存储在Azure 托管的 SQL 表中。当指定收容所的宠物可用时,将向用户发送推送通知,其中包含待领养宠物的信息和照片,以便轻松查看。如果他们喜欢该宠物并考虑领养,可以将该宠物添加到他们的愿望清单,如下面截图所示(请注意,由于用户在搜索中选择了成年年龄,因此也显示了一只成年猫)。
推送通知还将作为提醒,通知用户候选宠物何时接近其终止日期,以确保他们不会错过在找到终生伴侣的同时挽救生命的机会。最后,系统将利用 Nokia Here Maps 服务为用户提供前往宠物所在收容所的路线,以便他们可以接走他们的新家人,并一起回家分享许多快乐的回忆。
在不创建新值转换器或创建辅助属性的情况下,实现枚举属性的双向数据绑定(MVVM Light)!
Code Project 是关于源代码的,这个比赛也是,所以我将包含一个我在开发 FindAPet Windows Phone 客户端时创建的有用类的源代码。我厌倦了创建辅助属性来提供易于数据绑定的类型,以便公开枚举属性进行绑定。为每种枚举类型创建新的值转换器同样乏味。随附的源文件为您提供了一个可以放入您的 MVVM Light C# 项目的类。该代码为枚举类型提供了一个通用值转换器,因此您可以通过“创建新数据绑定”对话框选项完全创建绑定,而无需添加任何支持代码来实现。
使用此代码,您现在为任何ViewModel
枚举属性提供了一个值转换器,该转换器可在枚举值的描述属性字符串和它们所代表的枚举常量之间进行双向转换。这允许您进行双向数据绑定到枚举属性,而无需编写任何“粘合代码”。您需要做的就是将Converter
字段设置为EnumToDescAttrConverter
,并将绑定属性的枚举类型的*完全限定类型名称*放入ConverterParameter
字段(*参见下图示例*)。如果您在确定正确的完全限定Enum
类型名称时遇到困难,只需在ConvertBack()
方法中设置一个断点,在那里会抛出一个异常。然后,在*立即窗口*中调用System.Reflection.Assembly.GetExecutingAssembly().DefinedTypes.ToList()
,以获取该执行上下文中所有当前定义的系统类型的列表。找到正确的完全限定类型名称并将其粘贴到ConverterParameter
字段中。
它的工作原理如下:每当视图访问绑定的枚举属性时,EnumToDescAttrConverter
在调用其Convert()
方法时会获取enum
的描述属性。
// Consumer wants to convert an enum to a description
// attribute string.
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
// Since we don't know what the correct default
// value should be, a NULL value is unacceptable.
if (value == null)
throw new ArgumentNullException(
"(EnumToDescAttrConverter:Convert) The value is unassigned.");
Enum e = (Enum)value;
return e.GetDescription();
}
这样,列表框和其他视图元素就可以使用易于阅读的string
进行工作了。现在,当视图想要使用新值更新枚举属性时会发生什么?例如,用户进行ListBox
选择,从而触发一个属性*设置*调用,因为ListBox
的SelectedItem
属性绑定到枚举属性。这就是作为ConverterParameter
输入的*完全限定类型名称*发挥作用的地方。
EnumToDescAttrConverter.ConvertBack()
方法使用通过Parameter
参数传递的*完全限定类型名称*,使用反射创建正确类型的具体enum
值。然后,它会搜索该enum
类型的描述属性,以查找与value
参数中提供的描述相关联的enum
值,并返回该enum
值。
// Convert an enumeration value in Description attribute form
// back to the appropriate enum value.
public object ConvertBack
(object value, Type targetType, object parameter, CultureInfo culture)
{
// Since we don't know what the correct default value should be,
// a NULL value is unacceptable.
if (value == null)
throw new ArgumentNullException(
"(EnumToDescAttrConverter:ConvertBack) The value is unassigned.");
string strValue = (string)value;
// Parameter parameter must be set since it must contain the concrete Enum class name.
if (parameter == null)
throw new ArgumentNullException(
"(EnumToDescAttrConverter:ConvertBack) The Parameter parameter is unassigned.");
string theEnumClassName = parameter.ToString();
// Create an instance of the concrete enumeration class from the given class name.
Enum e = (Enum)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance
(theEnumClassName);
if (e == null)
throw new ArgumentException(
"(EnumToDescAttrConverter:ConvertBack) Invalid enumeration class name: "
+ theEnumClassName
+ ". Set a break point here and call "
+ "System.Reflection.Assembly.GetExecutingAssembly().DefinedTypes.ToList()"
+ " in the immediate window to find the right type. Put that type into "
+ "the Converter parameter for the data bound element you are working with."
);
System.Type theEnumType = e.GetType();
Enum eRet = null;
// Now search for the enum value that is associated with the given description.
foreach (MemberInfo memInfo in theEnumType.GetMembers())
{
object[] attrs = memInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs != null && attrs.Length > 0)
{
if (((DescriptionAttribute)attrs[0]).Description == strValue)
{
// Ignore the case
eRet = (Enum)Enum.Parse(theEnumType, memInfo.Name, true);
break; // Found it.
}
}
} // foreach (MemberInfo memInfo in typeof(TEnum).GetMembers())
// If the string can not be converted to a valid enum value, throw an
// Exception.
if (eRet == null)
throw new ArgumentException(
String.Format("{0} can not be converted to an enum value: ", strValue));
return eRet;
}
注意:使用ToDescriptionsList<>()
方法可以方便地从Enum
类型获取描述属性,以填充ListBox
或其他类似元素。将其调用放在返回人类友好字符串列表以供 UI 元素绑定到枚举属性的属性中。(例如,列表框的ItemsSource
属性)
public static List<string> ToDescriptionsList<t>()
{
// GetValues() is not available on Windows Phone.
// return Enum.GetValues(typeof(T)).Cast<t>();
List<string> listRet = new List<string>();
foreach (var x in typeof(T).GetFields())
{
Enum e;
if (x.IsLiteral)
{
e = (Enum)x.GetValue(typeof(Enum));
listRet.Add(e.GetDescription());
} // if (x.IsLiteral)
} // foreach()
return listRet;
}
下面是表示PetFinder
API 可以搜索的动物种类的枚举类型。这是一个使用Description
属性将人类友好字符串与enum
值关联的示例。
// Example of an Enumerated type with Description Attributes
// (barnyard, bird, cat, dog, horse, pig, reptile, smallfurry)
// List of Animal types the breed list method accepts.
public enum EnumAnimalType
{
[Description("Barnyard")]
barnyard,
[Description("Birds")]
bird,
[Description("Cats & Kittens")]
cat,
[Description("Dogs & Puppies")]
dog,
[Description("Horses & Ponies")]
horse,
[Description("Pigs")]
pig,
[Description("Reptiles")]
reptile,
[Description("Other Small & Furry")]
smallfurry
}
使用通用转换器类进行双向数据绑定的示例,其中包含我通过即时窗口发现的*完全限定类型名称*。
第二阶段 - 使用 WebMatrix 3 快速构建和部署 Azure 网站
速度!我只用一天时间就构建和部署了整个失物招领网站,它还能检测照片中的猫脸!更妙的是,借助 WebMatrix 3 的 Azure 支持,您 无需使用 TFS、GitHub 或任何其他存储库即可部署您的网站。只需在本地编辑您的网站,准备好后,按“发布”键! 如果您愿意,甚至可以直接编辑 Azure 上的远程网站!
此外,本文介绍的网站在很大程度上得益于出色的 KittyDar 项目,该项目是网站用于确保上传的失物招领照片质量的猫脸检测技术的来源。该网站已运行一周。您可以使用以下链接尝试使用该网站和猫脸检测技术。您无需注册即可使用该网站,只需上传您自己的猫照片。
Azure 的最大好处之一是能够以惊人的速度创建和部署专业网站。借助 WebMatrix 3 与 Azure 的无缝集成,在大多数情况下,您只需在 WebMatrix 3 中按“发布”按钮即可将您的网站发布到 Azure 或更新它! 在我为 Azure 开发者挑战撰写的 Code Project 文章的这一部分,我将展示如何构建一个网站,该网站接受担忧的宠物主人上传的丢失猫的照片,然后检查照片的质量,以确保其适合他人轻松识别其猫。您将在本文的下载中找到 WebMatrix 3 项目的替换文件。使用说明如下。
PUBLIC_KEY
和PRIVATE_KEY
。仔细遵循该教程中有关 ReCAPTCHA 密钥的说明,您在使用网站上的 CAPTCHA 时将不会遇到任何问题。检测猫脸和失物招领网站
如项目概述中所述,Azure 动物领养代理将包含一个失物招领组件,允许丢失宠物的担忧主人上传他们心爱宠物的照片,以帮助他人找到它们。该网站也可供宠物收容所使用,以便他们扫描附近宠物主人的猫狗照片,看看他们是否捡到了属于其中一个主人的走失动物。收容所工作人员然后可以联系宠物主人,告知他们这个好消息。
得益于 KittyDar Cat Detector 项目,猫主人将获得优势。由于 KittyDar 仅在猫脸正对着相机且呈肖像方向时才检测到猫脸,因此它可以用来帮助猫主人提交尽可能有用的照片,以便识别他们的宠物。这可以防止主人上传一张可能具有纪念意义但对于识别猫来说用处不大的照片。此外,KittyDar 可以检测照片中是否存在多个猫脸。在这种情况下,将指示主人使用一张只有他们的猫的照片。
以下是 FindAPet 网站上的一些示例照片,它们展示了 KittyDar 猫脸检测器的优势。在成功检测到猫脸的照片中显示的红色边界矩形显示了检测引擎确定的猫脸位置。
WebMatrix 3 - 快速部署 Azure 网站的强大工具
WebMatrix 3 是 Microsoft 用于快速网站开发的最新版本。此版本最强大的功能是其与 Azure 的紧密集成。这使您能够轻松创建和部署网站,即使是支持数据库的网站,如 FindAPet 失物招领猫网站。使用正确的入门模板,WebMatrix 3 将处理在 Azure 上创建和配置站点的所有繁琐任务。
作为我的失物招领网站的基础,我使用了 WebMatrix 3 照片库模板。随附的下载提供了您在 Azure 帐户上重新创建失物招领猫网站所需的一切。但请仔细阅读下面的部分,我将在其中解释我修改了照片库模板生成的代码的各个位置。您将了解我可能需要修改代码以适应您自己项目需求的重要位置。您还将了解我为使默认模板代码正常工作而需要进行的一些修复。如果您只想创建自己的 KittyDar 失物招领网站,而不关心细节,请跳过“长篇解释”部分,直接跳转到“简短解释”。
以下是我创建该网站的详细步骤。
长篇解释
重现失物招领照片库及猫脸检测器的详细步骤
PUBLIC_KEY
和 PRIVATE_KEY
字符串常量为您的 ReCAPTCHA 密钥。仔细按照该教程中有关密钥的说明操作,您在使用网站上的 CAPTCHA 时将不会遇到任何问题。
步骤 1
按照本教程中的说明,启动并运行 WebMatrix 3 照片库模板。
撰写本文时,一次不成功的 CAPTCHA 尝试会显示错误消息,甚至在进行 CAPTCHA 尝试之前。要解决此问题,请在Register.cshtml中替换显示以下内容的行:
@Html.ValidationSummary("Account creation was not successful.
Please correct the errors and try again.", excludeFieldErrors: true, htmlAttributes: null)
用
@if(IsPost == true )
{
@Html.ValidationSummary( "Account creation was not successful.
Please correct the errors and try again.", excludeFieldErrors: true, htmlAttributes: null)
}
步骤 2
撰写本文时,照片库模板在站点布局文件_SiteLayout.cshtml中引用了旧版本的 JQuery。如果仍然如此,请打开该文件,并将引用版本 1.8.2 的HEAD
元素中的SCRIPT
元素行替换为以下代码片段:
<!-- The line below references an old version of jquery and was commented out -->
<!-- <script src="~/Scripts/jquery-1.8.2.min.js"></script>-->
<!-- The line references the newer version of jquery included in the Photo Gallery template -->
<script src="~/Scripts/jquery-2.0.0.min.js"></script>
步骤 3
将照片库网站发布到您的 Azure 帐户并进行检查。现在您应该准备好进入*真正的乐趣*了。
步骤 4
将 KittyDar JavaScript 文件添加到您的 WebMatrix 3 项目Scripts文件夹中。您可以在本文附带的下载文件中的Scripts文件夹中找到这些文件。
- kittydar-0.1.0.min.js
- kittydar-0.1.6.js
- kittydar-demo.js
- kittydar-detection-worker.js
这些文件提供了检测照片中的猫脸并在检测过程中和检测成功后绘制边界矩形所需的代码。
步骤 5
在WebMatrix
中打开Upload.cshtml网页文件,并替换此标题元素:
<h1> Upload Lost Cat Photo </h1>
用
<h1>Upload Lost Cat Photo</h1>
<p>
The photo or your lost cat that you upload will be placed in the
<a class ="italic" href ="~/View/ @galleryId " title ="@ gallery.Name">
@gallery.Name </a> gallery.
</p>
<p> The photo needs to be a picture of your cat looking at the camera,
preferably straight ahead and not looking up, down, or to the side.</p>
步骤 6
将以下代码片段添加到View.cshtml(位于Photo文件夹中),该网页显示单个照片。它为该页面添加了 KittyDar 支持脚本。
@section Head {
<!-- KittyDar cat face detector script files: https://github.com/harthur/kittydar -->
<script src ="~/Scripts/kittydar-demo.js"></script>
<script src ="~/Scripts/kittydar-0.1.6.js"></script>
<!-- CSS to format and align properly the elements used to show the KittyDar operation
annotations and result. -->
<link rel ="stylesheet" href ="kitydar.css"/>
}
步骤 7
找到大照片的图像元素。将现有的IMG
标签元素替换为下面的代码片段。此代码片段显示照片,以及 KittyDar 检测引擎生成的边界框和注释。当 KittyDar 检测到猫脸时,您将看到绘制在图像画布上的边界框,直到找到脸部并绘制最终边界框,或者操作失败,画布将被清除边界框。这些元素由kittydar-demo.js和kittydar-detection-worker.js脚本管理。内联 HTML 注释解释了每个文档元素的作用。
<!-- <img class="large-photo" alt="@Html.AttributeEncode(photo.FileTitle)"
id="kittydar-viewer" src="@Href("~/Photo/Thumbnail",
photo.Id, new { size="large" })" /> -->
<div id ="viewer-container">
<!-- This is where the uploaded cat photo is displayed.
It is also where any bounding rectangles that display the cat face
detector's progress and final result are displayed -->
<div class="kittydar-viewer" id ="kittydar-viewer"></div>
<div id="viewer">
<canvas id="preview">
</canvas>
<canvas id="annotations">
</canvas>
</div>
<!-- This is where progress messages from the KittyDar detector
are displayed as it searches for a cat face in the uploaded picture. -->
<div class="kittydar-progress" id ="kittydar-progress">(none)</div>
</div>
<div id ="detection-result">
<!-- This is where the result of the detection will be displayed,
whether it succeeded or failed. -->
<div class="kittydar-result" id ="kittydar-result">(none)</div>
</div>
步骤 8
将以下代码片段添加到结束DIV
标签之前。它将在当前显示的 foto 上运行 KittyDar 猫脸检测器。
<script>
// Run the KittyDar cat detector on this photo
detectFromUrl("@Href( "~/Photo/Thumbnail", photo.Id, new { size="large" })");
</script>
步骤 9
将以下代码片段添加到_SiteLayout.cshtml文件,以确保我们的视图页面中的自定义HEAD
元素被渲染。
<!-- Render the head section if defined, like it is in View.cshtml -->
@RenderSection( "Head", required: false);
步骤 10
将kittydar.css添加到Content文件夹。它将确保 KittyDar 视图元素被正确格式化。
步骤 11
使用 WebMatrix 3 的发布按钮将您的网站发布到 Azure。您完成了!
注意:如果您已按照长篇解释步骤修改了您的网站,则无需遵循这些步骤,可以跳过本节。
步骤 1
按照本教程中的说明,启动并运行 WebMatrix 3 照片库模板,但跳过 CAPTCHA 设置步骤,因为包含 CAPTCHA 代码的文件即将被覆盖。
步骤 2
立即备份您的 WebMatrix 3 项目文件。然后,下载KittyDar-Photo-Gallery.zip文件。打开压缩文件夹,选择其中包含的所有文件和目录,然后将它们复制到您使用 WebMatrix 3 照片库模板创建的网站的*根*目录。目标目录应包含Web.config文件,这样您就知道复制操作的目标目录是正确的。
步骤 3
返回照片库教程,并回到标题为“在注册页面启用 CAPTCHA”的部分。按照说明在您的网站上正确设置 CAPTCHA。
步骤 4
使用 WebMatrix 3 的发布按钮将您的网站发布到 Azure。就是这样!现在测试您的网站并进行任何必要的更改。
测试您的网站
- 运行项目。
- 如果您还没有帐户,请注册一个帐户。这将是您的测试帐户。
- 登录到您的测试帐户。
- 创建一个名为“失物招领”的新相册。
- 选择相册并打开它。
- 点击“上传照片”开始实际测试。
- 使用“选择文件”按钮选择一个包含猫脸图片的文件进行上传。如果您没有自己的猫图片,您可以在此链接找到许多公共领域的猫图片进行测试:
特别是,这张照片是一个很好的测试照片:
请确保您拥有上传照片的权利,或者这些照片属于公共领域,因为这些照片将在网上公开!
彩蛋
要查看FindAPet
上的彩蛋,请在主观看页面上查看包含多个*已检测到*猫脸的任何照片。更好的是,只需访问以下链接:
资源
WebMatrix 3
- 主页
- ZDNet 概述(重点介绍了 3.0 版本的主要新功能:Azure 集成)
- 操作方法文章(汇总)
- 照片库教程(含 ReCAPTCHA 配置说明)
- Razor 语法教程
- Razor API(ASP.NET)快速参考
- 向子页面添加 HTML HEAD 部分
- 使用 Razor API 理解布局和 RenderSection
- 直接编辑 Azure 上的远程网站
Azure
- WebMatrix 3 介绍视频(展示 Azure 集成)
- 配置自定义域
KittyDar
- GitHub 上的 KittyDar
- 单个下载 Javascript 文件,用于浏览器内使用
- KittyDar 演示(需要 IE)
- 猫脸检测样本
第三阶段 - 愿望清单,Azure 托管的 SQL 表
为了与第二阶段的主题保持一致,我在 Azure 开发者挑战的这个环节中,旨在展示使用 Azure 进行关键数据库任务的便捷性和速度,在本例中,是创建云端 SQL 表的任务。这是一个快速教程,向您展示如何创建 Azure SQL 数据库中的简单表,以及如何集成和同步 Visual Studio 中的 Azure 和本地数据库项目之间的数据库管理工作。您还将找到一些重要的提示和警告,以确保您的体验顺利。
领养愿望清单
在本比赛的最后阶段,我将介绍用户访问 Azure 动物领养代理的主要入口点,即 Windows Phone 应用程序。通过该应用程序,他们将搜索PetFinder
的待领养宠物库存,该库存包含全美各地的众多参与动物收容所的宠物。当他们浏览附近可领养的宠物时,可以将喜欢的宠物添加到他们的愿望清单。
云端的愿望清单
如本文第一阶段所述,整个系统会在用户添加到愿望清单的宠物即将被安乐死时提醒用户。如果宠物处于危险之中,将向用户发送电子邮件,以便他们可以赶到动物收容所并在为时已晚之前领养该宠物。这要求愿望清单必须位于云端,而不是用户手机上,以便在 Azure 服务器上运行的持久任务可以定期扫描愿望清单数据库,查找处于危险中的宠物。 然后,它将生成一封电子邮件,提醒相应的用户注意这一紧急情况,以便他们能够尽快赶到收容所领养该宠物。
创建愿望清单 SQL 表
登录 Azure Portal 并选择 SQL 表选项。
点击创建 SQL 数据库以创建新数据库。
您将看到“设置”屏幕。输入数据库名称,并根据需要调整其他设置。我只使用了默认设置,并将 SQL 数据库命名为
findapet_wishlist
。
Azure 现在将向您显示活动 SQL 数据库列表。如您所见,我拥有的唯一数据库是我刚刚创建的
findapet_wishlist
数据库。
现在我将向数据库添加愿望清单表。单击
findapet_wishlist
数据库行,并调出 SQL 数据库设计向导。
点击下载 SQL 数据库的入门项目以下载 Azure 为您准备的项目,这将使在 Visual Studio 中远程处理表更加容易。运行 Visual Studio 并打开SQLDataProj.sln文件。然后,在解决方案资源管理器窗格中双击名为SQLDatabaseProj.publish.xml的文件。您将看到下面的屏幕。
注意:如果您没有安装 SQL Server Data Tools (SSDT) 的 Visual Studio 版本,请立即从此网页下载并安装它们。 如果 Visual Studio 的顶层菜单栏中没有显示“SQL”菜单选项,如图所示,则需要从该网页下载。
将数据库注册为数据层应用程序并启用数据库漂移检测。
我选择将数据库注册为数据层应用程序 (DAC),方法是勾选相应的复选框。这使得在服务器之间移动数据库以进行负载平衡和其他有用目的更加容易。DAC 非常适合像愿望清单数据库这样的简单数据库,但对于更复杂的数据库可能会有问题。本文深入讨论了 DAC,以便您可以做出适合您的决定。在本文的这个环节后面,我将讨论 SQL Server Data Tools (SSDT) 来管理数据库。通过勾选阻止发布选项,我可以让 SSDT 检查数据结构是否偏离了 DAC 映像,如果偏离,任何从 Visual Studio 进行发布的尝试都将被阻止,并显示一个指示此情况的警告。这是一个有用的功能,可以防止出现不当更新或数据库损坏,如果服务器上的数据库映像与 Visual Studio 中的 DAC 映像不匹配。有关 SSDT、DAC 和数据库漂移的更多详细信息,请参阅本文。
下面的屏幕显示了我的更改。
IP 地址防火墙设置
有必要告诉 Azure,我们当前的 IP 地址对于管理数据库是有效的。如果您不设置防火墙规则,那么每次尝试进行远程操作(如发布数据库等)时,都会看到类似此错误的错误。
幸运的是,Visual Studio 和 Azure 可以非常轻松地更新防火墙规则以接受您当前的 IP 地址。返回 Azure 管理门户,然后单击为此 IP 地址设置 Windows Azure 防火墙规则。您将看到 Azure 管理门户的以下屏幕(当然会显示您的实际 IP 地址)。
只需单击是,即可更新数据库的防火墙规则以接受您的 IP 地址。
数据库登录设置
返回 Visual Studio 和发布数据库对话框。通过单击“发布数据库”对话框中的编辑按钮,将打开连接属性对话框,您可以提供数据库的登录设置。使用下面的对话框,提供您通过 Azure 管理门户设置的正确用户名和密码。
建议单击测试连接按钮,以确保您的登录设置和 IP 防火墙设置是否正确。验证它们后,您就可以发布数据库了。
单击确定关闭结果对话框,然后再次单击确定关闭连接属性对话框。您将回到发布数据库对话框。单击发布按钮将更改发布到 Azure。完成后,数据工具操作窗格应显示以下进度和结果消息。
现在愿望清单表已在 Azure 中上线,是时候添加愿望清单表、定义其字段以及定义表的索引了。重要!我将做一些非传统的事情,使用 Azure 管理门户来完成这些操作,而不是使用 SSDT。我这样做是为了向您展示一个重要的 SSDT 功能,用于同步远程和本地数据库,所以请跟随。
注意:Chrome 浏览器用户! 在 Chrome 中,我没有看到 Azure 在尝试“设计 SQL 数据库”或“运行 Transact-SQL 查询”选项时弹出的弹出窗口。我也没收到弹出窗口被阻止的警告。我不得不切换到 Internet Explorer 来完成以下步骤。然后我收到了 IE 的弹出窗口警告,所以我选择了“始终允许”选项,然后就可以成功执行设计任务了。
返回 Azure 管理门户,选择“连接到数据库”子标题下的“设计 SQL 数据库”选项,如下图所示。
Azure 会要求您登录数据库,如下图所示。输入您分配给数据库的用户名和密码,然后登录。
您将看到数据库设计屏幕,如下图所示。
点击新建表以创建数据库的主表。将表命名为wishlist。然后,添加 6 列,确保它们与下图所示的定义匹配。确保 ID 列被标记为主键,并且所有字段都被标记为必需。
注意:如果您担心在 Azure 上轻松扩展数据库,那么不要在任何列上标记“标识”属性,因为 SQL Azure Federations 不支持标识列。有关 SQL Azure Federations 和扩展的更多信息,请访问此网页。
您的列定义现在应该看起来像这样。
添加pet_name
和shelter_name
字段的原因是加快我们与愿望清单表结合显示操作的速度。有了这些字段,我们就不必调用PetFinder
API 服务器来获取这些项了。许多PetFinder
API 方法只返回 ID 元素,您必须调用另一个方法来获取相关的名称。这种策略减少了我们对PetFinder
API 的调用次数,这有助于我们将调用次数保持在其 API 的速率限制之内。
现在单击索引和键以定义这些项。添加两个索引,如下所示:
- 点击添加索引。添加一个名为
IX_wishlist_user_email
的索引,它索引user_email
列。 - 再次单击添加索引。添加一个名为
IX_wishlist_shelter_id
的索引,它索引shelter_id
列。
如果您成功了,您的屏幕现在应该看起来像下面的屏幕。
user_email
列被索引,以便我们可以使用用户的电子邮件地址作为键来检索该用户的所有宠物。这使我们可以进行快速查询,以找到构成一个用户愿望清单的记录集。shelter_id
列被索引,以便我们可以跨所有用户的愿望清单抓取属于一个特定收容所的所有动物,从而使扫描即将被安乐死的动物的任务更快、更容易执行。完成后,单击保存图标以保存更改。
同步本地和远程数据库模式
通过直接更改远程数据库模式,我们制造了一个问题。我们从 Visual Studio 使用 SSDT 发布的数据库模式是一个空数据库。由于远程数据库现在拥有一个包含完整列和索引集的新表,Azure 数据模式和我们的本地数据库模式不再匹配。它们不同步了!幸运的是,SSDT 提供了一种简单的方法来同步它们。我故意这样做只是为了向您展示如何使用 SSDT 同步两个数据库模式。
SSDT 可以比较两个数据库模式并报告它们之间的差异。我们现在就来做。单击 Visual Studio IDE 顶部的SQL菜单选项,选择模式比较,然后选择新建模式比较,如下图所示。
这将创建一个新的模式比较,如下所示。
由于Azure 数据库映像是最新的,这是由于我们最近的修改,我们需要将其指定为比较的源。单击 Visual Studio IDE 顶部的SQL菜单选项,选择模式比较,然后选择选择源,如下图所示。
使用选择源模式对话框,如下所示,选择 Azure 数据库作为比较的源。
单击确定后,是时候选择比较的目标了。我们希望将当前的Visual Studio SQL 数据库项目作为目标,因为我们想将我们从远程 Azure 数据库所做的更改传播到我们的本地数据库项目。单击 Visual Studio IDE 顶部的SQL菜单选项,选择模式比较,然后选择选择目标,如下图所示。
使用下面的选择目标模式对话框,选择 Visual Studio SQL 数据库项目作为比较的目标。我们的 SQL 数据库项目名为SQLDatabaseProj
。
单击确定后,是时候进行比较了。单击 Visual Studio IDE 顶部的SQL菜单选项,选择模式比较,然后选择比较,如下图所示。
SSDT 执行比较,并向您显示两个模式之间的差异,如您在下图中所见。在结果窗格的左侧是创建愿望清单表、我们添加的字段以及索引的 SQL 指令,因为所有这些指令都使得我们的本地 SQL 数据库项目看起来像远程 Azure 数据库。右侧是空白的,因为我们的 SQL 数据库项目目前只包含数据库定义,没有其他内容。在结果窗格上方是操作表。它显示了单击更新按钮时将执行的操作。此表中只有一行用于将愿望清单表及其索引添加到目标。操作复选框当前已选中,因为我们确实希望将表及其索引添加到本地数据库项目中。在更复杂的情况下,可能需要多种操作来同步两个数据库模式。操作复选框允许您决定在更新期间要执行或不执行哪些操作。
单击更新按钮。Visual Studio 执行更新,然后提示我们进行另一次比较以刷新模式比较屏幕。我们这样做,比较屏幕变为如下所示。
屏幕显示我们已成功同步两个数据库,它们之间没有差异。
结论
Azure 本身是一个强大、功能齐全的数据库管理器。但是,Azure 和 Visual Studio 的结合提供了本地和远程数据库功能,使得 SQL 数据库管理变得强大、专业且易于使用。我们不仅快速创建了服务于FindAPet
愿望清单功能的核心数据库、表、列和索引,还创建了一个本地 Visual Studio 项目,我们可以使用它来帮助在服务器之间移动数据库以进行扩展和其他重要目的。最后,我们使用了与 Visual Studio 无缝集成的 SSDT 工具包,轻松地将我们的本地 SQL 数据库项目与 Azure 云中远程数据库的结构同步。SSDT 提供了一种强大的方法来以稳健的方式管理本地和远程数据库,该方法可防止数据库漂移的危险,这种现象经常会渗透到即使是最 well-managed 的数据库项目中。
注意:有关 SSDT 进行数据库模式比较的众多选项和功能的深入讨论,请阅读本文。
如果您没有安装 SQL Server Data Tools (SSDT) 的 Visual Studio 版本,请立即从此网页下载并安装它们。
第四阶段 - 虚拟机上的虚拟助手
您的大部分网站需求都可以通过 Azure 的网站、工作角色和其他常见服务来满足。但有时,在 Web 上提供服务的唯一有效解决方案是虚拟机 (VM)。一个很好的例子是当您拥有一个作为服务器的独立程序时,而这样一个程序的绝佳示例是聊天机器人。一个成功的聊天机器人会营造出与另一个人交谈的错觉,而实际上您是在与一台计算机交谈,它使用复杂的模式匹配算法来分析您的输入,然后返回一个合理的响应。在这个比赛阶段,我们将配置一个 Azure 上的 Ubuntu Linux VM。我们将使用该 VM 来运行一个聊天机器人,它向我们的网站访问者解释如何使用我们在第二阶段创建的网站。
为什么选择 Linux?
我选择使用的聊天机器人是 ChatScript,这是由人工智能专家 Bruce Wilcox 编写的经过实战考验且功能强大的开源实现,它曾多次赢得最佳聊天机器人 Loebner Prize,并被几个流行的网站用于其虚拟助手。它是在宽松的开源许可证下分发的,允许您对其进行几乎任何操作,而无需您分发源代码。在软件的服务器文档(ChatScript Server Manual.pdf)中,有一段关于性能和不同操作系统的重要段落。
“ChatScript 最快的服务器操作系统是 Linux。Mac 在操作系统本身方面倾向于在高客户端负载下出现故障。Windows 通常是较慢的服务器。并且 ChatScript 的 Linux 版本支持 forking ChatScript(Windows 下没有此类支持),因此您可以在每个核心上运行一个引擎的 fork,最大化 CPU 处理能力,同时仍服务于单个端口。每个添加的核心速度提升几乎是线性的。”
由于 Linux 的内存占用也比 Windows 小得多,因此您可以在 Azure Extra Small Compute 实例上运行 Linux ChatScript 服务器!考虑到 Azure 的免费使用级别,这意味着您基本上可以免费运行您的聊天机器人!如果您开始遇到非常大的使用量,只需将您的 VM 升级到更强大的计算实例。Azure 的优点之一是它可以像运行 Windows VM 一样轻松地运行 Linux VM。(注意:如果您真的想使用 Windows 运行 ChatScript,也可以。它运行得很好。但本文侧重于提供最佳计算和经济性能的解决方案。)
步骤 1 - 配置 Linux VM
在 Azure 上设置 Linux VM 非常简单。为了避免重复造轮子,本教程将在几分钟内让您成功运行一个Ubuntu 12.0.4 LTS VM。
遵循教程的重要注意事项!
- 当您到达选择 VM 大小的屏幕时,请务必选择Extra Small(共享核心,768 MB 内存),而不是作者选择的Small。
- 您不需要 RDP(远程桌面协议)!
只需遵循文章的前两部分,它将向您展示如何设置 VM 和配置它。然后,当您到达告诉您如何安装 RDP 支持的第三部分时,再回来这里。RDP 是远程控制 Linux 盒子的绝佳工具,但它具有关键的安全隐患,因此必须小心配置,并且它可能导致您带宽消耗大幅增加。您完全不需要 RDP 即可成功管理运行 ChatScript 的 VM。请特别注意该教程的第二部分关于设置 root 密码的有用提示。
步骤 2 - 安装 BitVise
管理 Linux VM 的一个好工具是 BitVise,这是一个开源应用程序,您可以用于 SSH 会话,并具有 Windows 风格的文件浏览器,用于在 Linux VM 和本地 PC 之间传输文件。对于非商业个人使用是免费的,否则您需要购买许可证。下载并安装它。您需要它来管理您的 Linux VM。但是,经验丰富的 Linux 用户已经拥有自己的工具来管理 Linux VM,可以跳过此步骤。
现在运行 Bitvise。您需要做的第一件事是配置连接到 Linux VM 所需的主机和用户登录详细信息,使用 Bitvise 屏幕上的登录选项卡。主机字段应包含您在步骤一的 VM 教程中为 VM 选择的 DNS。用户名和密码字段应包含您在教程中 VM 配置屏幕中为 VM 选择的用户名和密码。初始方法字段应设置为password。完成后,Bitvise 登录对话框应显示与下方类似的值,但当然是您自己的特定值。您是否选择保存与此登录关联的密码,取决于您。
现在单击登录按钮连接到您的 Linux VM。一旦连接,Bitvise 将自动打开一个 Xterminal 窗口,以便您可以通过 SSH 会话输入命令,并且有一个文件浏览器窗口,以便您可以使用 SFTP 安全地管理您的文件。
提示:我发现将 Xterminal 窗口的宽度从默认的 80 个字符更改为 160 个字符很有用。您可以在 Bitvise 主屏幕的终端选项卡中进行此操作。只需查找该选项卡上的屏幕宽度字段。
步骤 3 - 安装 Ubuntu 32 位兼容包
Azure 安装的是 64 位版本的 Ubuntu。ChatScript 带有一个预先构建的 Linux 可执行文件,名为 LinuxChatscript32。它是一个 32 位程序,没有帮助无法在 64 位 VM 上运行。切换到 Bitvise 自动打开的 Xterminal 窗口,然后输入此命令:
sudo apt-get install ia32-libs
根据sudo
命令的要求输入您的超级用户密码。回答确认提示后,安装库大约需要 5 到 10 分钟。完成后,您的 VM 现在已准备好运行 32 位程序,例如 ChatScript。
步骤 4 - 下载 ChatScript
下载 ChatScript,并将压缩存档的内容解压缩到您在本地 PC 上选择的目录中。该目录将是您的本地安装目录。
步骤 5 - 将 ChatScript 传输到 Linux VM
切换到 Bitvise 为您打开的文件浏览器窗口。左窗格(本地文件)包含您的本地 PC 文件,右窗格(远程文件)包含在 VM 中运行的 Ubuntu 实例上的文件。在本地文件窗格中,导航到包含 ChatScript 3.1 zip 文件解压缩内容的目录。然后导航到该目录的父目录,以便您可以看到顶级的 ChatScript 目录。远程文件窗格应已设置为您在步骤一的 VM 教程中配置为管理 VM 的用户的顶级目录。将 ChatScript 目录从本地文件拖到远程文件,以便整个文件结构被复制到 Ubuntu 实例。
完成此操作后,Bitvise 将开始文件传输,并在状态栏显示“正在准备上传列表...”消息几分钟。然后,它将显示传输的进行状态。对于我的 ISP 来说,传输大约需要 22 分钟,所以您可以去吃个三明治,或者在等待完成时再写一篇文章给 Code Project。
文件传输完成后,您需要通过设置其文件权限如下:,告知 Linux 文件名为LinuxChatscript32的程序是可执行的。
- 导航到ChatScript子目录,该目录现在应该在远程文件窗格中可见,方法是双击它。
- 找到名为LinuxChatscript32的文件。
- 右键单击它,然后从弹出菜单中选择属性。
- 选择权限选项卡。
- 确保您已勾选复选框,以便
LinuxChatscript32
的权限与下图中的权限匹配。
现在ChatScript
服务器已准备好运行。
步骤 6 - 使 ChatScript 更加健壮
您希望 ChatScript 服务器始终运行,或者接近始终运行。如果它崩溃,您希望 Linux VM 自动重启它,而无需您的干预。Linux 有一个名为 cron 的后台进程,用于在预定时间启动后台任务,并且默认安装在 Azure Ubuntu Linux VM 中。为了实现此目标,我们在 cron 表中添加一个条目,告诉 Linux VM 每 5 分钟启动一次 ChatScript。如果 ChatScript 使用的端口已被打开,它将简单地退出。因此,如果 ChatScript 崩溃,那么它将在下一个 5 分钟间隔时在 cron 启动它时重新加载。如果它已经在运行,那么 ChatScript 将简单地退出。最终结果是一种自动方法,可以使我们的 ChatScript 几乎始终运行。如果 ChatScript 崩溃,那么最多我们将有 5 分钟或更少的时间没有其服务。
以下是如何进行此操作的 cron 表条目。再次切换到 Xterminal 窗口。在提示符处输入:
crontab -e
然后执行以下操作:
- 接受默认选项Nano作为编辑 cron 表的编辑器。
- 将下面的行复制到剪贴板,然后右键单击左上角的 Xterminal 系统图标,然后选择编辑->粘贴。
0,5,10,15,20,25,30,35,40,45,50,55 * * * * cd /home/[username]/ChatScript; ./LinuxChatscript32 2>/home/[username]/cronserver.log
- 将 [username] 替换为您在 Linux VM 教程过程中为VM 配置对话框指定的用户名。
- 现在键入Ctrl-X保存文件。
- 键入Y确认保存。
- 单击回车键以接受提供的 crontab 文件名。
您将看到一条状态消息,告知您 cron 表已安装。现在,每五分钟,cron 将更改为ChatScript
的顶层目录,并执行LinuxChatscript32
,即 ChatScript 服务器程序。尝试过程中发生的任何错误都将写入给定目录中的cronserver.log文件。您可以通过以下命令轻松查看cron
表的目录,该命令会将cron
表的当前内容转储到屏幕上(注意,破折号后的字符是小写“L”)
crontab -l
要查看cronserver.log文件的内容,请在 Xterminal 窗口中转到ChatScript
的顶层目录,然后输入以下命令:
cat cronserver.log
这会将日志文件的内容转储到屏幕上。您可以通过等待 5 分钟,然后在 Xterminal 窗口中执行以下命令,轻松测试您的 cron 表条目是否有效:
netstat --listen
此命令将显示正在侦听端口的进程。您应该看到端口1024正在使用。如果没有,请仔细检查您在上面 cron 表编辑操作中的步骤是否存在错误,尤其是任何路径条目中的拼写错误。您还可以查看文件 /home/[username]_/cronserver.log,查看 cron 作业期间是否发生任何错误。(注意:ChatScript 默认侦听端口1024,但您可以通过配置文件进行更改。有关详细信息,请参阅 ChatScript 服务器手册。)
步骤 7 - 使 ChatScript 可供 Web 使用
我们需要告诉 Azure 将流量从 Web 路由到 ChatScript 正在侦听的内部端口。这是通过 Azure 管理门户的帮助来设置终结点来完成的。请立即按照以下步骤操作:
- 登录 Azure 管理门户
- 门户完全准备就绪后,点击虚拟机图标。
- 从列表中选择您创建的 Ubuntu Linux VM。
- 点击终结点选项以查看当前终结点列表。
- 点击添加按钮添加新终结点。
- 按如下所示的箭头接受默认的添加终结点选项,它将带您进入下一步。
在出现的对话框中,将新终结点命名为ChatScript
,同时保持协议的默认值TCP。将公共端口和私有端口字段都设置为1024
,这是ChatScript
侦听新连接的端口。您的屏幕应如下图所示。
点击检查标记图标进入下一步。您将看到一条状态消息,显示更新进行中,因为 Azure 正在添加您的新终结点。这需要几分钟时间。完成后,您的终结点列表屏幕应如下所示。
步骤 8 - 限制对 ChatScript 元命令的访问
ChatScript
具有可以直接从聊天远程发送到服务器的命令。它们都以冒号(“:”)开头,并且通常对 ChatScript 服务器产生重大影响,例如完全重新加载它、添加新内容等等。它们只应该由您或您指定为系统管理员的人使用。它们绝对不应该对访问您聊天机器人的所有人开放!
在顶层ChatScript目录中有一个名为authorizedIP.txt的文件。此文件应仅包含允许发出元命令的 IP 地址。该文件默认包含“all”一词,这是危险的。在您将用于管理 ChatScript 服务器的 PC 上,访问像 What Is My IP 这样的网站以查找您的 IP 地址,然后将其输入到authorizedIP.txt文件中。如果您想授予访问权限的每台 PC 的 IP 地址不同,请重复此过程。您可以使用Nano编辑器来编辑文件,这是您用来编辑 cron 表的编辑器。要执行编辑,请切换到 Xterminal 窗口,确保您位于顶层ChatScript目录中,然后使用以下命令:
nano authorizedIP.txt
确保您首先删除“all
”关键字。
ChatScript
服务器请求不同,后者源自您的公共网站服务器的 IP 地址。
步骤 9 - 将 ChatScript TCP 请求包装在 HTTP GET 请求中
ChatScript
服务器不响应 HTTP 请求,它只接受它正在侦听的端口上的 TCP 套接字连接。尽管 ASP.NET 网站可以直接打开套接字,但使用 AJAX 请求更方便,特别是当涉及到制作一个与用户交互的漂亮网页,同时在后台代理 ChatScript 服务器时。不幸的是,由于 JavaScript AJAX 请求的浏览器安全限制,它仅适用于 HTTP 请求。那么,诀窍是创建一个可以接受 HTTP GET
请求的网页,建立到ChatScript
服务器的 TCP 连接,使用给定的GET
参数发出聊天请求,并将服务器的响应作为典型的响应页面返回。这正是ChatRelay.cshtml文件所做的。
注意:我添加了一个网站文件的小型版本,名为ChatEssentials.zip,其中包含_SiteLayout.cshtml、ChatMessage.cs、ChatRelay.cshtml和Chat/Default.cshtml,其中除了_SiteLayout.cshtml之外,都将在下面讨论。对_SiteLayout.cshtml的唯一修改是添加了一个网站范围的“帮助”链接,该链接将访问者带到聊天机器人页面。zip 文件中有一个readme.txt文件,其中说明了如何使用这些文件更新您的 KittyDar 照片库副本以添加聊天机器人,或如何使用包含的文件为您自己独特的、与失物招领照片库无关的网站添加聊天机器人。这些文件对于不是用 WebMatrix 3 创建的 ASP.NET 网站也应该能很好地工作。
到ChatScript
服务器的客户端请求包括发送 3 个*null*终止的string
到服务器。以下是ChatMessage.cs中的代码,它显示了ChatMessage
类的属性,该类体现了一个ChatScript
服务器请求。
public class ChatMessage
{
public static string NULL_TERMINATOR = "\0";
public string LoginName { get; set; }
public string BotName { get; set; }
public string Message { get; set; }
...
} // public class ChatMessage
LoginName
是您分配给用户的唯一 ID。这对于聊天机器人能够在会话之间(即,用户多次访问聊天机器人页面)记住特定用户的信息是必需的。BotName
是要使用的机器人的名称。ChatScript
可以支持多个聊天机器人,每个机器人都有自己的“个性”、事实集和对话规则。它可以留空以使用默认聊天机器人。Message
是用户输入的需要聊天机器人响应的文本。
注意:
Message
字段仅在新会话开始时应为空。也就是说,当用户进入网页以与聊天机器人对话时,即使是返回访问。这会让聊天机器人知道这是一个新会话或与当前用户的对话的开始。但是,当用户停留在网页上与聊天机器人聊天时,*您不应向聊天机器人发送空字符串!*如果您查看了我用于促进聊天的网页Chat/Default.cshtml,那么当网页加载时,我做的第一件事就是发送一个消息为空的聊天请求。从那时起,如果用户尝试提交一个空的聊天消息,将显示一个警告消息,阻止这种情况发生。
除了其他有用的方法外,ChatMessage
类还包含ToString()
。此方法构建一个string
,您可以将其直接发送到聊天服务器,其中聊天消息属性已正确格式化为 C string NULL
终止符。
/// <summary>
// Returns a string formatted properly for acceptance by the ChatScript server.
/// </summary>
/// <returns></returns>
public override string ToString()
{
// The ChatScript server expects 3 C-style strings terminated
// with the null terminate character. Ascii(0)
// The 3 strings are the login name (user ID), the chatbot name,
// and the last message from a current
// conversation, or an empty string if this is the
// first message for a new session, where a session is
// defined as the user "entering" the web page to chat with the chat bot.
return this.LoginName + NULL_TERMINATOR + this.BotName +
NULL_TERMINATOR + this.Message + NULL_TERMINATOR;
} // public string ToString()
此类还包含一个名为readChatScriptMessage()
的方便方法,该方法从打开的套接字连接中读取ChatScript
服务器的响应,并将其作为string
返回。ChatMessage
类由ChatRelay.cshtml使用,以与ChatScript
服务器进行通信。ChatRelay.cshtml接受 3 个GET
请求参数,这些参数直接对应于ChatMessage
类中的三个属性,并且为了简单起见,具有完全相同的名称。以下是使用FindAPet
网站的示例GET
请求:
http://findapet.azurewebsites.net/Chat/ChatRelay.cshtml?LoginName=192_58_16_17&BotName=&Message=I%20lost%20my%20cat.%20%20Can%20you%20help%20me?
此请求告诉ChatRelay.cshtml向聊天机器人提问“我丢了我的猫。你能帮我吗?”。目前,用户 IP 地址(句点被替换为下划线)用作其 ID。这是一种有缺陷的方法,因为他们可能共享 IP 地址或拥有动态 IP 地址,但它使得处理访客用户变得容易。风险是聊天机器人记住的信息将在使用相同 IP 地址的人之间混合,或者如果用户使用新的 IP 地址重新访问网站,则会丢失,但现在对于方便的访客用户访问来说,这是一个快速而粗糙的解决方案。更好的解决方案是基于 cookie 的方法,但如果他们阻止或拒绝 cookie,该方法也会失败。最佳解决方案是要求具有已验证电子邮件地址的用户帐户,但这将要求他们创建一个帐户才能与聊天机器人交互。选择您的首选方案,并根据最适合您网站的方法进行。
ChatRelay.cshtml首先获取 Linux VM 的 IP 地址,以便我们可以使用它来打开套接字连接。(注意:您应该将 [azuredns] 替换为您在创建 Linux VM 时为其指定的 DNS 名称。)
// Lookup the IP address for our chatscript server. (Cache this value
// in a later build since GetHostEntry() is reportedly a slow call.)
IPAddress ipAddress = Dns.GetHostEntry("[azuredns].cloudapp.net").AddressList[0];
然后,URL 参数来自GET
请求。LoginName
是必需的,但BotName
和Message
参数不是。请注意,checkForValidURLArgument()
是一个简单的验证 URL 参数的方法,如果参数无效且标记为必需,则会抛出异常。您会在ChatRelay.cshtml中找到它以及其他几个辅助方法。
// LoginName, is mandatory.
strLoginName = checkForValidURLArgument("LoginName", true);
// BotName, is optional.
strBotName = checkForValidURLArgument("BotName", false);
// User message (chatbot input), is optional. But remember,
// only send a blank message to start a new session
// with ChatScript! After that, send the user's input
// each time.
strMessage = checkForValidURLArgument("Message", false);
在参数验证完成后,就可以连接到ChatScript
服务器,使用给定的GET
请求参数创建一个聊天消息对象,将请求发送到聊天机器人,然后等待响应。
// Connect to the ChatScript server.
TcpClient tcpCli = new TcpClient();
tcpCli.Connect(ipAddress, 1024);
// Open the stream
streamChatScript = tcpCli.GetStream();
StreamReader sr = new StreamReader(streamChatScript);
BinaryWriter sw = new BinaryWriter(streamChatScript);
// Create a message to send to the server, using the URL argument values
// passed to us.
ChatMessage cm = new ChatMessage(strLoginName, strBotName, strMessage);
// Send the message to the chat server.
string strSendChatMsg = cm.ToString();
// Translate the passed message into ASCII and store it as a Byte array.
Byte[] data = System.Text.Encoding.ASCII.GetBytes(strSendChatMsg);
for (int i = 0; i < strSendChatMsg.Length; i++)
{
data[i] = (byte)strSendChatMsg[i];
}
// Send the chat message.
streamChatScript.Write(data, 0, data.Length);
strResponseMsg = ChatMessage.readChatScriptMessage(streamChatScript);
网页的核心部分只是显示响应,该响应由formatResponse()
方法以 XML 风格包装在名为response
的标签中。
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<!-- Just output the response message from the C# code above -->
@strResponseMsg
</body>
</html>
来自ChatScript
服务器的示例响应,在渲染的网页中返回,可能如下所示:
<response>Do you have a photo of your cat?</response>
请记住,用户永远看不到此输出。只有与用户交互并向ChatRelay.cshtml发出后台 AJAX 请求的网页才能看到它,如下所述。
步骤 10 - 与用户聊天
与用户交互的页面称为Chat/Default.cshtml。您可以将其用作自己网站的起点。只需稍作修改,您应该就能顺利进行。现在是解释与用户交互的网页和ChatScript
服务器如何工作的时候了。您现在可以使用以下链接与失物招领聊天机器人交谈:
与失物招领聊天机器人交谈
下面是聊天页面的屏幕截图。
这是名为Chat/Default.cshtml的网页。构成网页的活动元素包含在 ID 为“#q_and_a
”的DIV
中。它包含一个 ID 为“#entry
”的文本区域,一个 ID 为“#do_chat
”的提交按钮,一个 ID 为“#user_question
”的DIV
,以及另一个 ID 为“chat_response
”的DIV
。
<div id="q_and_a" >
<br />
<div id="user_input" class="q_and_a_element" >
<h3 class="label">Enter your question or reply in the box below.</h3>
<textarea id="entry" onkeypress="if(event.keyCode==13) return doChat();" rows="2" cols="" name="entry"></textarea>
<br>
<input type="button" value="Ask" id="do_chat" name="do_chat" onclick="doChat()" >
</div>
<div id="you_said" class="q_and_a_element" >
<div class="label">You said:</div>
<div id="user_question" class="q_and_a_text_field" >
<!-- When the user hits the submit button, their question will be echoed here. -->
</div>
</div>
<div id="i_replied" class="q_and_a_element" >
<div class="label">I said:</div>
<div id="chat_response" class="q_and_a_text_field" >
<!-- When the user hits the submit button, the response from the ChatScript server will be placed here. -->
</div>
</div>
</div>
一小段 JQuery JavaScript 添加了一个文档就绪事件处理程序,该处理程序调用 JavaScript 方法doChat()
,并将bFirstMessage
参数设置为TRUE
。如前所述,这会发送一个聊天请求到ChatScript
服务器,其Message
字段为空,这是聊天会话期间唯一发生这种情况的时候。这可以让聊天机器人知道这是一个新会话或与当前用户的对话的开始。
<script>
// When the page is loaded, send a chat with an empty message to let the ChatScript
// server know this is a new session for the current user.
$(document).ready(function ()
{
// Set the bFirstMessage parameter to TRUE so the chatbot knows
// this is the start of a new session.
doChat(true);
});
</script>
聊天会话现在正在进行中,从这一点开始,直到用户离开聊天页面,以下模式会重复。用户在“#entry
”textarea
中输入他们的问题或回复,然后按Enter键或单击提问按钮。这将触发一个调用doChat()
方法的调用,该方法执行所有工作。在这种情况下,会省略bFirstMessage
,导致该参数设置为FALSE
。如果用户未能输入任何内容到“#entry
”textarea
中,则会弹出一个警报框,告知用户输入内容,并且不会提交聊天请求。
// This method is called when the user hits the ASK button. The user's current message
// is sent to the ChatScript server and the server's response is shown in the "You said"
// DIV element.
//
// PARAMETERS:
// bFirstMessage - If TRUE then an empty message is sent to the ChatScript server to
// let it know this is a new chat session for this user. If FALSE, then an empty
// message will trigger an alert telling the user to enter a valid message.
function doChat(bFirstMessage)
{
// The default value for bFirstMessage is FALSE.
typeof bFirstMessage == 'undefined' ? false : bFirstMessage;
var strGuestID = "@strGuestID";
var strMessage = "";
if (bFirstMessage)
{
// First message. Send an empty message but show the word "Hello"
// as the user's question.
$("#user_question").html("Hello.");
}
else
{
// Not first message. Look for the user message in the message entry text area.
strMessage = $("#entry").val().trim();
// Empty messages are not allowed if this is not the first message
// for the session..
if ((!bFirstMessage) && (strMessage.length < 1))
{
alert("Please enter a question or statement first.");
return;
}
// Show the question.
$("#user_question").html(strMessage);
} // else - if (bFirstMessage)
// Build the URL and URL arguments to make the request to the ChatScript server
// via the ChatRelay page.
var strUrl =
"/Chat/ChatRelay.cshtml"
+ "?"
+ "LoginName=" + encodeURI(strGuestID)
+ "&"
+ "BotName=" + encodeURI("")
+ "&"
+ "Message=" + encodeURI(strMessage);
$.ajax(
{
url: strUrl,
// Do not cache the results of a query. We want a fresh response
// from the ChatScript server each time.
cache: false
}).done(function (html) {
var strResponse = "(none)";
// Wipe the text area of the user's last question
// so they can enter something new.
$("#entry").val("");
// Extract the response contained in our "response" XML tags.
var re = /<response>(.*?)<\/response>/im;
var match = re.exec(html);
if (match != null) {
strResponse = match[1].trim();
}
else {
strResponse = "Chat is offline at the moment.
Please try again in 30 minutes.";
} // else if (match != null)
// Show the response returned by the ChatScript server.
// $("#chat_response").val(strResponse);
$("#chat_response").html(strResponse);
});
} // function doChat()
doChat()
首先构建具有正确 URL 参数的GET
请求,以发送到聊天机器人。
LoginName
设置为正确格式化的用户 IP 地址。BotName
留空,因为我们使用的是默认聊天机器人。Message
设置为用户刚刚输入文本。
首先,将用户最近的输入复制到“#user_question
"DIV
中。接下来,使用缓存设置为FALSE
的 AJAX 请求被构造,以便我们总是从chatbot
获得新的响应,并且定义了一个匿名的完成处理程序,该处理程序执行以下操作(*附加到 JQuery AJAXdone()
事件*):
- 清除“
#entry
”textarea
,为用户的新输入腾出空间。 - 使用正则表达式从ChatRelay.cshtml在渲染的网页中嵌入的
<response></response>
元素中提取响应消息。
此模式会一直重复,直到用户完成与聊天机器人的聊天。至此,我们已经创建了一个完整的聊天机器人系统,核心是运行在 Ubuntu Linux VM 上的ChatScript
,一个通过 AJAX 友好的 HTTP 请求使其可用的代理或中继页面,以及一个具有友好用户界面的网页,用户可以通过该界面与之交互,以促进聊天会话。现在是时候研究聊天机器人本身了。
一个简单的失物招领访客聊天机器人
前言:此处描述的聊天机器人是一个基础聊天机器人。它丝毫不能体现 ChatScript 中惊人的功能深度。它仅用于让您初步体验在 ChatScript 中创建聊天机器人是多么容易,特别是对于像您一样的程序员。然而,就像剥洋葱一样,ChatScript 的伟大之处在于,当您需要更大的主题控制灵活性、增强的解析能力以及像宏和更多这样的编程语言风格功能时,在着手更宏伟的项目时,您会发现这些功能以及更多功能。
您可以通过访问“失物招领”网站的 帮助部分 来试用此处描述的聊天机器人。您将在本文的下载列表中找到实现此聊天机器人的文件,文件名为 ChatEssentials.zip。
以下是用户与我在“失物招领”网站上实现的简单聊天机器人之间的交互示例。请注意,我将对话展示为一系列线性的交互,而不是使用实际的 GUI 界面(在第 10 步中显示)的笨重屏幕截图流,该界面只显示最新的用户输入和聊天机器人响应。此外,聊天机器人的内部名称是 Harry
,我随意给访客起了一个名字 CatLover17
。如前所述,用户当前的 IP 地址正被用作访客 ID。
HARRY:_ Hello, welcome to FindAPet. How can I help you?
CatLover17:_> How much does it cost to use the Lost & Found?
HARRY:_ There is no charge to use the Lost & Found system.
CatLover17:_> Do I need to have a credit card?
HARRY:_ No you do not. There is no charge to use the Lost & Found system.
CatLover17:_>
以下是促成此次交流的 ChatScript
规则。
# These rules handle questions from the user regarding
# the cost of using the system.
#
#!x*** COST OF THE SYSTEM
#! How much does it cost to use the system?
u: FREE (how much)
There is no charge to use the Lost & Found system.
#! Is it free?
u: (is * free)
^reuse(FREE)
#! What is the cost to use the system?
u: (what * cost)
^reuse(FREE)
#! Do I need a credit card?
u: (credit card)
No you do not.
^reuse(FREE)
ChatScript
中的注释行以井号“#”开头。但是,以井号后跟感叹号 (“#!”) 开头的行具有非常特殊的含义。它们是规则测试注释,“#!”是规则测试前缀。您可以使用“:verify”元命令让 ChatScript
验证您的主题文件中的规则。ChatScript
会将规则测试前缀后面的示例句子应用于紧随其后的规则,就像用户在会话中输入它一样。如果规则未能匹配示例句子,则会打印出警告。这对于您在对系统进行许多新更改可能破坏旧规则时,对聊天机器人进行单元测试非常有用!
提示:“
:verify [主题名称]
”可用于验证单个主题。例如“:verify ~introductions
”。
规则结构
ChatScript
规则具有以下结构:
[rule type]: {rule label} (match pattern)
[body]
[规则类型]
:规则类型是 4 种不同规则类型中的一个字符,它是必需的。
- “
?
” - 仅当用户提问并且问题的内容与匹配模式匹配时,该规则才会匹配(或“触发”)。 - “
s
” - 仅当用户做出非疑问性响应并且语句的内容与匹配模式匹配时,该规则才会匹配。 - “
u
” - “?
”和“s
”的并集。当匹配模式匹配最后一个用户输入的任何内容时,该规则都会匹配,而无论输入类型如何,因此会考虑问题或陈述的匹配。 - “
t
” - 主题赌注。这是聊天机器人显示给用户的陈述,前提是聊天机器人控制着对话,这发生在它当前没有响应用户提出的问题或陈述时。
{规则标签}
:规则标签是一个常量,仅用于允许其他规则引用它。它是可选的,稍后您会看到一个示例。规则标签是可选的。
(匹配模式)
:匹配模式是 ChatScript
将应用于用户输入的表达式,以决定是否执行该规则。
[主体]
:规则的主体,其中包含规则触发时要采取的操作。这些操作可以包括:设置内部变量、链接到其他规则、向用户显示文本等等。
提示:规则在主题内线性执行。因此,您必须将匹配更具体单词模式的规则放在具有更通用单词模式且也可以匹配相同用户输入的规则之前。否则,尤其是在您使用
keep
关键字时,更通用的规则很可能会阻止更具体的规则触发,因为它如果出现在主题文件中比更具体的规则靠前,就会先匹配。(稍后会详细介绍keep
关键字)。
模式匹配
重要:ChatScript
内置的强大功能之一是其规范化用户输入中单词的能力。这允许您编写简洁的规则,只需少量精力就可以处理多种句子模式,因为 ChatScript
正在帮助您泛化您的匹配模式。例如,动词的规范形式是不定式。如果您在匹配模式中使用单词“be”,它将匹配“[is, was, are, etc.]”,因为它们都是动词“be”的变体。
提示:如果您能记住,请始终在匹配模式中使用单词的规范形式,以便它能很好地泛化。如果您只想匹配单词或短语的精确形式,请使用双引号或单引号。有关规范化和精确匹配的更多详细信息,请参阅
ChatScript
基本用户手册。
让我们来看一下上面规则中响应用户关于失物招领系统成本输入的一条规则。
u: FREE (how much)
There is no charge to use the Lost & Found system.
规则类型是“u
”,表示该规则将响应用户的任何输入。匹配模式就是简单的单词“how”和“much”。只要用户输入包含“how much”这两个单词,并且它们的顺序与匹配模式一致,这条规则就会触发。因此,以下句子以及许多其他句子都会触发此规则:
- 该系统多少钱?
- 使用该系统需要多少钱?
- (任何其他包含“how much”且单词顺序相同的句子)。
提示:要匹配用户句子中的单词,无论它们在句子中出现的顺序如何,请查阅“<< [要匹配的单词] >>”语法。
如果此规则触发,那么句子“使用失物招领系统不收费”将显示给用户,因为这是规则主体中找到的唯一操作。
规则标签和重用关键字
这是响应用户关于系统成本输入的第二条规则。
u: (is * free)
^reuse(FREE)
ChatScript 的匹配模式可以包含通配符,其用法类似于正则表达式。在匹配过程中,它会消耗句子中的 0 个或多个单词。在匹配模式 (is * free) 中,这意味着该模式将匹配任何包含“is”和“free”的句子,即使它们之间有 1 个或多个单词。因此,此规则将匹配以下句子:
- 它是免费的吗?
- 系统是免费的吗?
- 系统对用户是否永远免费?
- (包含“is”后面跟着“free”,中间有 0 个或多个单词的任何句子)
通配符拓宽了匹配模式可以匹配的句子数量,增加了其灵活性,但可能会增加匹配不必要句子的可能性,并增加了规则与其他主题规则发生冲突的可能性。上面关于具体与通用规则的提示在这里也适用。
提示:
ChatScript
有几种有用的通配符变体。有关更多信息,请参阅基本用户手册。高级提示:您甚至可以根据单词的词性来制作匹配模式。例如,您可以有一个匹配模式,它仅匹配名词“run”而不匹配动词“run”。详细信息可以在
ChatScript
文档中找到。
再次快速查看上面关于处理用户关于系统成本问题的规则列表。请注意,第一条规则之后的所有规则都具有相同的正文,其中包含一个操作,即重用标有 FREE 的规则。reuse
关键字消除了在执行相同基本任务以响应用户含义大致相同的输入的多条规则之间重复规则主体的需要。在此上下文中的任务是,当用户询问系统成本时,向用户显示消息“使用失物招领系统不收费
”。如果没有 reuse
关键字,我们就必须在所有规则中复制该操作。但是,每条规则都可以执行自己的操作,正如您在信用卡规则中看到的。
u: (credit card)
No you do not.
^reuse(FREE)
该规则首先向用户显示消息“不,您不需要。
”,然后将控制权交给第一条规则。因此,给用户的净输出变为:“不,您不需要。 使用失物招领系统不收费
”。这为您在创建简洁的规则以处理多种不同情况提供了极大的灵活性,同时最大限度地减少了规则内容之间的不必要重复。
提示:
reuse
关键字及其同类项在规则中出现时必须由帽子字符 (“^”) 前缀。这就是ChatScript
知道它不是常用词的原因。
应答。带上下文的规则。
通常,您会有只在先前的问题或响应的上下文中相关的问题或响应。ChatScript
有一类规则称为应答。请查看下面的规则。请注意,下面的规则属于 ~questions 主题,而不是 ~introductions 主题,这一点由第一行指示。repeat
、keep
和 nostay
关键字将在稍后讨论。
topic: ~questions repeat keep nostay []
# Do we know if the user has a photo?
u: (!$hascatphoto)
Do you have a photo of your cat?
# Rejoinder looking for "yes" response or equivalent.
a: (~yes)
Good. When we are done talking, please upload it to the Lost & Found.
# Remember that they have a photo.
$hascatphoto = true
# Ask the next relevant question.
^respond(~questions)
# Rejoinder looking for "no" response or equivalent.
a: (~no)
OK.
# Remember that they don't have a photo.
$hascatphoto = false
# Ask the next relevant question.
^respond(~questions)
变量和词集
这给了我一个机会来谈论变量和词集。ChatScript 中的变量名以美元符号 (“$b”) 为前缀。ChatScript 还支持否定运算符,用感叹号“!”表示。因此,这里顶层规则的匹配模式匹配,如果 $hascatphoto
变量是未定义的,这表示我们还没有从用户那里得到关于他们是否有丢失的猫的照片的答案。如果此规则触发,它将立即显示消息“您有猫的照片吗?
”并等待用户响应。
这就是应答发挥作用的地方。所有应答规则必须以“a 到 q”范围内的单个字母开头,该字母取代了前面讨论的 4 种规则类型之一。该字母指示应答嵌套深度。应答可以嵌套,如果嵌套,字母应在每个深度递增(即 - “a:”、“b:”、“c:”等)。由于我们的示例只深入一层,因此两条应答都有“a:”作为应答嵌套深度字符。示例显示了两条应答规则,一条用于处理用户是的响应,另一条用于处理用户否的响应。
请注意,单词no和yes前面都有一个波浪号 (“~”) 字符。在 (~yes) 的情况下,这告诉 ChatScript 匹配任何等同于积极响应的响应(yes、sure、OK 等)。在 (~no) 的情况下,这告诉 ChatScript 匹配任何等同于消极响应的响应(no、nope 等)。当单词前面有一个波浪号字符时,它表示该单词实际上是一个概念集的名称。概念集是单词列表,它们代表相同的语义含义,至少在规则相关的情况下,并且可以在您的聊天机器人中重复使用,以消除在创建主题规则时重复常用单词列表。
请注意,每条应答为 $hasphoto
变量分配的值不同。您稍后会看到这如何影响聊天机器人与用户的交互。
Respond 关键字
您还可以看到每条应答都以 respond
语句结束。由于单词 ^respond
前面有一个波浪号,这表明它是一个 ChatScript
函数调用。语句^respond(~questions)告诉 ChatScript
调用~questions主题。但是,如前所述,此处显示的应答规则是~questions主题的成员,因此这相当于对主题进行递归调用。这样做是为了确保每个需要由用户回答的问题都有机会执行。为了巩固我们的知识,这里对~questions主题中的另一条规则进行了简要描述。请看询问用户猫品种的规则。
#!x*** CAT BREED
# TIP: This rule must follow the other cat breed rules becaue it is more general than
# they are and will fire before they do.
#
u: (!$hasbreed)
What breed is your missing cat? If more than one, just list them.
# My cat's breed is.
a: ASKCATBREED (is _*)
$catbreed = '_0
# Just accept the user's last input as the entire answer to the breed question.
a: (_*)
^reuse(ASKCATBREED)
规则的匹配模式表明,如果我们还没有为变量 $hasbreed
获得有效值,它就会触发。这条规则向您介绍了捕获变量的主题,这是从事正则表达式工作的人熟悉的另一个概念。如果您查看此规则使用的应答的匹配模式,您会看到单词“is”后面跟着一个下划线,前缀是通配符 (“_*”)。这告诉 ChatScript 捕获单词“is”之后的所有用户输入。例如,如果用户说“My cat is a tabby cat”(我的猫是虎斑猫),ChatScript 将把单词“is a tabby cat”赋值给 $catbreed
变量。捕获变量在规则的主体中通过使用捕获变量索引标签来访问。这些索引标签从 0 开始编号,并按照从用户输入捕获内容的顺序进行位置对应。由于我们只有一个捕获组,要访问该组的内容并将其值分配给 $catbreed
变量,我们使用“_0
”作为捕获变量索引标签。在这种情况下,捕获组的值是文本“is a tabby cat”。在捕获变量索引标签前加上撇号 (“'”) 的原因是防止 ChatScript
在将捕获组的值交给 $catbreed
变量之前尝试解释或修改它。您将在 ChatScript
基本和高级用户手册中找到大量关于 ChatScript
内置文本处理功能的有趣信息。然而,在这种情况下,我们需要用户输入的原始内容,因为它是一只猫的品种,而不是一个常用词或短语。
整合
我在此处重现整个问题清单的规则,以便能够向您描述交互的整体模式,以确保您获得这里发生情况的全局图景。
#!x*** QUESTIONS GAUNTLET
# This topic keeps asking questions until the user provides the basic
# information needed to help find their cat.
topic: ~questions repeat keep nostay []
#!x*** USER PHOTO OF CAT
# Do we know if the user has a photo?
u: (!$hascatphoto)
Do you have a photo of your cat?
# Rejoinder looking for "yes" response or equivalent.
a: (~yes)
Good. When we are done talking, please upload it to the Lost & Found.
# Remember that they have a photo.
$hascatphoto = true
# Ask the next relevant question.
^respond(~questions)
# Rejoinder looking for "no" response or equivalent.
a: (~no)
OK.
# Remember that they don't have a photo.
$hascatphoto = false
# Ask the next relevant question.
^respond(~questions)
#!x*** CAT'S NAME
# TIP: This rule must follow the other cat name rules becaue it is more general than
# they are and will fire before they do.
#
# Do we know the cat's name?
u: (!$catname)
# Set a variable so we know the last question we asked was the cat's name.
$askcatname = true
What is the name of your cat?
#! My cat's name is Malfie
a: ASKCATNAME (is _*)
# Save the cat's name. We prepend an apostrophe so that
# ChatScript does not attempt to interpret the cat's name
# and will give it to us unaltered as given by the user.
$catname = '_0
# Ask the next relevant question.
^respond(~questions)
#! I call her Trixie.
a: (call* * _*)
^reuse(ASKCATNAME)
# This pattern is the catch-all in case the above patterns don't match since
# the user just said the cat's name as a response.
#! Trixie.
a: (_*)
^reuse(ASKCATNAME)
#!x*** CAT BREED
# TIP: This rule must follow the other cat breed rules becaue it is more general than
# they are and will fire before they do.
#
u: (!$hasbreed)
What breed is your missing cat? If more than one, just list them.
# My cat's breed is.
a: ASKCATBREED (is _*)
$catbreed = '_0
# Just accept the user's last input as the entire answer to the breed question.
a: (_*)
^reuse(ASKCATBREED)
如您所见,问题清单包含三条规则,每条规则从用户那里获取关于他们丢失的猫的一条信息。按顺序,它们是:照片是/否、名字和品种。每次获取时,收到的输入都被分配给正确的变量。按顺序,它们是:$hasphoto
、$catname
和 $catbreed
。
但这些部分是如何组合在一起的呢?一旦问题清单获得控制权,它就会通过前面提到的^respond(~questions)语句不断地递归调用自身,每条规则(最后一条除外)都以该语句结束。最后一条规则不调用 ^respond 的原因是,到它执行并获得所需答案时,所有问题都已回答,并且~questions主题也已完成。如果我们在此主题的 CAT BREED 规则之后添加更多问题,我们也必须在其中添加一个尾随的^respond调用。
最后一块拼图是将我们积累的关于用户猫的知识显示给用户。请看这两条属于 ~introductions 主题的规则,而不是 ~questions 主题。
# If we have successfully collected the basic information
# we need from the user, summarize it and display it back
# to the user with the proper adjustment made for whether
# or not the user has a photo of their lost cat. Otherwise,
# we keep asking questions until we do.
#
#!x*** SUMMARIZE USER INFORMATION AND DISPLAY
# User has provided all the required information and has a photo.
u: ($catname $catbreed $hascatphoto=true)
Ok. You said your cat's name is $catname
and that the breed was $catbreed and that
you have a photo. You are now ready to
use the Lost & Found system.
# User has provided all the required information and does not have
# has photo.
u: ($catname $catbreed $hascatphoto=false)
Ok. You said your cat's name is $catname
and that the breed was $catbreed and that
you do not have a photo. You are now ready to
use the Lost & Found system. However, if you
find a photo of your lost cat, please return here
and upload it to the system.
# User has not provided all the required information yet.
#
u: (!$catname)
^respond(questions)
u: (!$catbreed)
^respond(questions)
u: (!$hasphoto)
^respond(questions)
看看第一条具有匹配模式 ($catname $catbreed $hascatphoto=true) 的规则。当 $catname
和 $catbreed
具有有效值,并且 $hascatphoto
的值为 true
时,此规则将触发。换句话说,当从用户那里获取了所有 3 条信息并且用户表明他们有丢失猫的照片时,此规则将触发。它将回读用户提供的值,并告知他们已准备好使用失物招领系统。
第二条规则几乎相同,只是如果用户表明他们没有丢失猫的照片,它将代替第一条规则触发。在这种情况下,将再次回读用户获取的值,并建议他们如果以后找到照片,则返回并上传猫的照片。
上面最后三条规则用于在任何必需变量尚未具有有效值时触发问题清单。每条规则检查一个特定变量是否尚未具有有效值,如果尚未,则将控制权转移到问题清单。
这就是我们主题文件中的不同规则部分如何交互以及控制如何在主题之间转移。就此而言,这里简要讨论了 keep
、repeat
和 nostay
主题关键字。
避免重复和主题流程控制
这里演示的为失物招领访客提供帮助的聊天机器人是一个简单的聊天机器人。我们不试图讲故事,而只是试图回答用户可能有的几个常见问题,并获取搜索用户猫的附近人道收容所所需的 3 条信息。因此,在合理范围内,我们不在乎是否重复自己,也不试图创造一个真实故事角色的幻觉。更复杂的讲故事聊天机器人非常努力地避免重复,这是我们没有考虑到的。ChatScript 具有多种功能来确保聊天机器人输出的多样性。这些功能在 ChatScript
文档中有详细介绍。但是,为了全面起见,这里对您在本文中看到的重复和主题流程控制关键字进行了简要的讨论。
- repeat - 通常,规则不允许向用户产生重复输出。也就是说,不允许它重复自己。添加此关键字允许规则重复自身,从而更容易创建轻量级聊天机器人。
- keep -
ChatScript
引擎的默认设置是在使用规则后“丢弃”该规则。这确保了聊天机器人中的所有或大部分规则都有机会执行,从而增加了多样性。keep
关键字会阻止此机制,并允许规则无限次重用。 - nostay - 通常,
ChatScript
会留在某个主题内,直到该主题被耗尽。在我们的聊天机器人案例中,我们希望它在获得用户的一条信息后立即退出~questions主题。我们仍然获得该主题中的所有 3 条信息的原因是,我们通过^respond(~questions)语句对~questions主题进行了递归调用。一旦所有 3 次递归调用返回到顶部,控制权就会立即转移回~introductions主题。如果没有nostay
关键字,我们将停留在~questions主题中,这是不希望的行为。
提示:像上面这样的关键字在~topic声明中出现时不带帽子符号前缀,而在规则主体中出现时必须带。
ChatScript
知道它们不是常用词,因为它们出现在主题声明中。提示:您可以使用单词/短语集为聊天机器人添加多样性。例如,在 CAT 品种问题中,我们显示问题“您的[缺失的、丢失的、失踪的]猫是什么品种?”。如果我们使用“您的 [缺失的、丢失的、失踪的] 猫是什么品种?”,那么每次
ChatScript
询问问题时,它都会随机选择一个与“lost”同义的单词。例如:“您的猫是什么品种?”,或者“您的猫是什么品种?”,等等。有关此功能的更多详细信息,请查看ChatScript
文档。
最终结果 - 完整的聊天会话日志
这是一份实际的聊天记录,展示了一个完整的用户会话。阅读时,请尝试将您关于规则如何工作的知识与用户和聊天机器人之间发生的事情联系起来。
HARRY:_ Hello, welcome to FindAPet. How can I help you?
CatLover17:_> How much does it cost to use the Lost & Found?
HARRY:_ There is no charge to use the Lost & Found system.
CatLover17:_> Do I need a credit card?
HARRY:_ No you do not. There is no charge to use the Lost & Found system.
CatLover17:_> What do I do to use it?
HARRY:_ Do you have a photo of your cat?
CatLover17:_> Yes I do.
HARRY:_ Good. When we are done talking, please upload it to the Lost & Found.
What is the name of your cat?
CatLover17:_> Her name is Roxy.
HARRY:_ What breed is your missing cat? If more than one, just list them.
CatLover17:_> Roxy is an Oriental Short-Hair.
HARRY:_ Ok. You said your cat's name is Roxy and that the breed was
an Oriental Short hair and that you have a photo.
You are now ready to use the Lost & Found system.
CatLover17:_>
关于 ChatScript 的最后说明
聊天机器人只有您使其变得有多么复杂和聪明。幸运的是,有了 ChatScript,您可以根据需要使其变得有多么复杂和聪明,因为它具有广泛的功能,而本文只介绍了其中很小一部分。然而,即使是这个简单的聊天机器人也为“失物招领”网站增添了真正的价值,避免用户在回答有关该网站工作方式的关键问题时翻阅帮助页面或常见问题解答。
聊天机器人开发技巧 1:阅读 ChatScript DOCUMENTATION 子目录中的《基本概述》手册,然后阅读《教程》。它们都很简短,易于阅读,并将为您设计和实现聊天机器人提供所需的基本知识。
聊天机器人开发技巧 2:不要试图预测用户将提出的每一种问题或响应。您很容易得到很多相互矛盾的规则,这将使支持和调试您的逻辑变得痛苦。创建真正成熟且功能强大的聊天机器人的有趣且富有成效的方式是,从一个涵盖最基本用户输入开始,并发展它。您将进入的开发周期包括每天分析您的聊天日志,发现用户正在提出的问题或给出的响应未被正确处理。然后添加新的规则和事实来支持这些问题,同时偶尔运行“
:verify
”命令以确保您没有破坏任何东西。(有关“:verify
”命令的信息,请参阅高级用户手册。)您会发现ChatScript
拥有强大的工具来分析您的聊天日志,具体请参阅《分析》手册。
结论
Azure 使配置和设置虚拟机变得容易,而虚拟机是向您的网站添加高功率价值的门户,通过提供超越常见网站或网络服务能力的杀手级网络服务。Azure 在支持非 Windows 操作系统方面同样熟练,因此如果您的首选工具仅在 Linux 或其他一些奇异操作环境中运行,或者在该环境中运行效果最好,那么没有什么可以阻止您实现它。最后,一个需要虚拟机的绝佳具体示例场景是聊天机器人。借助 ChatScript
,您可以轻松地为您的网站添加一个有趣且信息丰富的虚拟助手,该助手托管在 Azure 上,并由 Linux 虚拟机提供支持。您可以通过以下链接与失物招领聊天机器人交谈: