在 C# 应用程序中添加聊天机器人(使用 SIML - 综合智能标记语言)






4.95/5 (24投票s)
在 C# 应用程序中使用 SIML(综合智能标记语言)
引言
简而言之,聊天机器人是能够通过文本或语音方式与人类用户进行对话的软件实体。多年来(数十年),开发人员和研究人员一直在研究聊天机器人架构,试图实现和改进现有架构,以进一步推动智能机器的研究。
SIML 或 Synthetic Intelligence Markup Language 就是这样一种架构,它允许开发人员为聊天机器人编写简单和复杂的知识库。最棒的是,SIML 提供了复杂的模式识别、条件模式,甚至 JavaScript,确保我们不再受限于许多现有架构所使用的简单模式识别系统。
熟悉 AIML 的人会觉得 SIML 的 XML 结构更容易理解和实现,因为 SIML 是 AIML 的超集,并且考虑周全且功能强大。它还拥有自己的 IDE,专门用于创建智能 SIML 知识库,并提供自动完成、代码分析、JavaScript 编辑器、正则表达式测试器以及脚本评估器。
- 对于聊天机器人或 XML 的新手,我强烈建议您在整个教程中保持耐心,并给自己更多时间进行实验。
- 解释一个规范是一项艰巨的任务,但我会尽力使教程尽可能简单。
如果您已经参考了本文,可以继续阅读下一篇文章 - 第二部分。
如果您更喜欢构建一个由机器学习驱动的智能机器人,可以看看我的另一篇文章 使用 Oscova 构建数据库机器人。
必备组件
- 对 XML 及其语法有良好的了解
- 具备 XAML 和 WPF 用于 GUI 的初级经验
- 具备相当 C# 开发的平均经验
- 具备 SIML 的基本知识(可从SIML 快速入门文档中获得)
入门
首先,启动 Visual Studio 创建一个新项目。我目前使用的是 Visual Studio Community Edition 2013(更新 4)。在本教程中,我们将使用 Syn.Bot
类库,该库可作为 **NuGet** 包使用。
设置 GUI
现在我们开始模仿游戏。首先,我们将创建一个(非常)简单的 C# WPF 应用程序与我们的聊天机器人进行交互。单击 **文件 -> 新建 -> 项目...**,然后在 Visual C# 模板下,选择 *WPF 应用程序*,并确保您的项目 **目标是 .NET 4.5** 或更高版本。为简化起见,将您的项目命名为 **SIML Chatbot Demo**。
为了与我们的聊天机器人交互,我们需要将以下组件添加到我们的 WPF 应用程序中。
- 用于发送我们输入消息的输入框。
- 一个发送按钮(显而易见),用于提交我们的文本消息。
- 以及一个输出框,用于显示聊天机器人生成的响应。
我本可以选用 WinForms 来帮助初级 C# 开发人员,但使用 WPF 也不难,而且如今比 WinForms 更受欢迎。双击 *MainWindow.xaml* 并用以下 XAML 代码替换。如果您没有按上述方式命名您的项目,可能需要自行进行更改。
<Window x:Class="SIML_Chatbot_Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SIML Chatbot Demo" Height="350" Width="600">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="260*"/>
<RowDefinition Height="5*"/>
<RowDefinition Height="40*"/>
</Grid.RowDefinitions>
<TextBox Name="OutputBox" IsReadOnly="True" />
<DockPanel Grid.Row="2">
<TextBox Width="450" Name="InputBox" />
<Button Content="Send"
Name="SendButton" Click="SendButton_OnClick"/>
</DockPanel>
</Grid>
</Window>
当您粘贴上述代码时,Click="SendButton_OnClick"
将显示为 **红色**,因为事件处理程序不存在于我们的 C# 代码中。因此,右键单击 *MainWindow.xaml*,**选择“查看代码”**,然后为我们 SendButton
的单击事件添加以下事件处理程序。
private void SendButton_OnClick(object sender, RoutedEventArgs e)
{
//Our code will go here
}
太棒了!现在,我们的 GUI 已经准备就绪。
导入 Syn.Bot NuGet 包
要从 NuGet 导入可移植类库,请单击 **工具 -> NuGet 包管理器 -> 包管理器控制台**,然后键入 Install-Package Syn.Bot
。该类库现在将添加到您的项目中并被正确引用。Syn.Bot
是 SIML 的官方解释器,并且会定期更新。
很好!现在我们有了库,我们将继续编写我们的聊天机器人知识库,稍后将其导入到我们的 C# 应用程序中。
获取 SIML 开发 IDE
要创建 SIML 知识库,我们将使用 Syn Chatbot Studio,这是一个免费的 SIML 开发 IDE。要下载 Syn Chatbot Studio,请在 **Google** 中键入“**下载 Syn Chatbot Studio**”并按照链接操作。在本教程中,我们将创建一个非常简单的知识库,但如果您想磨练 SIML 技能,请尝试 SIML 官方网站上提供的 SIML 快速入门教程。
安装并启动 Chatbot Studio 后,单击 **文件 -> 新建 -> 项目** 并填写详细信息,但请确保在“模板”部分选择 **Empty** 模板。
将您的 SIML 项目保存到您喜欢的任何目录(此时,*Project* 目录无关紧要)。创建项目后,您将看到项目概述。概述面板提供了项目统计信息的快速预览。
SIML 基础知识
在深入探讨 SIML 之前,我必须坦诚 SIML 非常强大,但为了在本教程中更好地理解,我将尽量只触及表面。
您的 SIML 项目包含 2 个主要部分
- 设置
- 概念
这些分组在 **Files Explorer** 中,右侧,分别在 **Settings** 和 **Files** 类别下。
SIML 的核心由概念(Concepts)和模型(Models)组成。SIML 中的概念是主题或话题,在该主题下声明相应的模型。SIML 中的模型是知识的基本单位,包含一个唯一的模式(Pattern)和一个响应(Response)。
在本教程中,我们将编写一些简单的 SIML 模型,并在此过程中学习一些基础知识。单击 **Console** 选项卡,键入 hello bot
并按 *Send*。
您将收到响应“hello user!
”
现在单击 Console 选项卡下的 **Stats**。您看到的是机器人响应的速度以及处理响应所花费的毫秒数。目前,这并不重要,让我们继续。
单击 **Files Explorer** 下 **Files** 类别中的 **Hello Bot** 文件。您将看到一个入口点模型。
您看到的上图是一个简单的 SIML **Concept** 文件,只有一个 **Model**。
- **Concept** - 每个 SIML 知识库都应以一个主题或话题开始。概念允许您将模型分组到特定的名称下。在上面的示例代码中,概念的名称是 **Hello Bot**。
- **Model** - 是 SIML 中的基本知识单位,包含模式和响应。单个概念可能包含数千个模型,但一个模型包含 1 个响应元素,无限数量的模式,以及可选的
<Previous>
元素。 - **Pattern** - 模式是一组标记/单词,在运行时与用户输入进行检查,以查看用户输入是否与指定的标记/单词序列匹配。
- **Response** - 一旦模式被检查并满足所有指定的条件,响应部分就会被评估并发送回用户。
以上所有元素都放在 <Siml>
元素内(它始终是任何 SIML 文档的根元素)。这些元素的名称区分大小写。
SIML 中有两种概念 - Public
和 Private
。Public
概念包含所有模型,并且在运行时可公开访问;而 Private
概念必须由其他 SIML 代码激活。现在您可以停止猜测 public
和 private
概念,因为在本教程中我们将只使用 Public
概念。
简单的原子模式
删除现有模型(XML 元素),然后在 **Editor** 中键入以下 SIML 代码。
<Model>
<Pattern>
<Item>HELLO THERE</Item>
<Item>HI</Item>
<Item>HOLA</Item>
<Item>HI</Item>
</Pattern>
<Response>Hi there!</Response>
</Model>
按 F5,然后单击 **Console 选项卡**,键入“Hello there
”,您将收到响应“Hi there
!”。这很简单。在上面的 SIML 代码中,我们添加了一个 SIML 模型,其中包含一个 Pattern
元素,在该元素内我们使用 <Item>
元素声明了多个原子模式,最后添加了一个带有简单文本的 Response
元素。
- 要定义多个模式,请始终在
<Pattern>
内使用<Item>
元素 - 在使用控制台之前,请始终刷新您的项目,以便将更改加载到 Bot 内存中。
- 我们使用的模式称为“原子”模式,因为它们不包含任何符号/通配符。
从上面的示例中,我们了解到要声明一个 SIML 模型,我们使用 <Model>
元素,该元素又接受 <Pattern>
和 <Response>
元素作为其子元素。要声明多个模式,我们只需要在 <Pattern>
本身内使用 <Item>
元素即可。
使用实时集合
我们现在来尝试一些非原子模式。在 Concept
元素内添加以下 SIML 模型。
<Model>
<Pattern>DO YOU LIKE (BACON|PANCAKE)</Pattern>
<Response>Yes I love <Match /></Response>
</Model>
按 F5 并键入“do you like bacon?
”。响应将是“Yes I love bacon
”。在我们的新 SIML 模型模式中,我们使用了一个实时集合,它包含两个项目:bacon
和 pancake
。因此,如果用户键入 do you like bacon
或 do you like pancake
,我们的 SIML 模型就会被评估。
要创建实时集合,我们使用**圆括号**,并用 **|** 字符分隔每个单词。我们声明的 Response 元素包含一个新元素 <Match>
。该元素捕获我们在 SIML 模型模式中使用的任何通配符、正则表达式或 SIML 集合的值。在我们的例子中,当我们在 do you like bacon
中输入时,实时集合 (bacon|pancake)
匹配单词 bacon
。
Match
元素<Match/>
可用于捕获模式中通配符、集合(在上面的例子中是单词bacon
和pancake
)或正则表达式的值。Match
元素还有一个Index
属性,用于指定我们模式中项的索引。
使用静态集合
让我们通过创建一个 SIML 集合来进一步挑战自己,该集合将帮助我们匹配用户输入中的颜色数量。假设我们想检测用户在输入中提到的颜色。例如,“I like the color red
”。
要创建集合,请在 **Files Explorer** 下的 **Settings** 类别中单击 *Sets* 文件,然后添加以下 SIML 代码
<Set Name="COLOR">
<Item>Red</Item>
<Item>Green</Item>
<Item>Blue</Item>
</Set>
在上面的 SIML 代码中,我们使用 <Set>
标签创建了一个 SIML 集合,并使用“Name
”属性为集合赋予了一个唯一的名称,该名称稍后可在我们的 SIML 代码中使用。按 Ctrl+S 保存我们刚刚编写的 SIML 代码,并单击 **Files Explorer** 下的 *Hello Bot* 文件,然后添加以下 SIML 代码。
<Model>
<Pattern>MY FAVOURITE COLOR IS [COLOR]</Pattern>
<Response>I see so you like the color <Match />.</Response>
</Model>
在上面的 SIML 代码中,我们将所需**集合**的名称用**方括号**括起来,要获取捕获集合的值,我们当然使用 <Match>
元素。我稍后会在教程中回到这个元素。按 F5 刷新您的项目,然后在控制台中键入“My favourite color is Green
”,响应将是“I see so you like the color Green
”。
- 集合通过允许我们在模式中使用分组的项目集合来节省大量时间,从而消除了冗余,并有助于维护大型知识库。
- SIML 集合速度极快,较大的集合不会对您的项目产生任何影响。唯一的区别是存储较大的集合需要更多的物理内存。
使用正则表达式
由于出于显而易见的原因,不允许将 Regex 直接嵌入到我们的 Pattern 元素中,因此我们必须在一个单独的文件中创建一个正则表达式,为模式指定一个唯一名称,然后使用我们为正则表达式指定的唯一名称在 Pattern 元素中使用 Regex。为此,在 **Settings** 类别下,单击 **Regular Expressions** 文件并添加以下 SIML 代码。
<Regex Name="number" Pattern="\b(\d+)\b" />
在上面的代码中,我们创建了一个简单的 Regex 元素,然后使用 Name
属性为正则表达式指定了一个唯一名称,并在 Pattern
属性中编写了一个简单的 Regex 模式来捕获整数。按 **Ctrl+S** 将代码保存到文件,然后移至我们的 *Hello Bot* 文件并键入以下 SIML 代码。
<Model>
<Pattern>I AM @NUMBER YEARS OLD</Pattern>
<Response>
<Match /> years is good enough.</Response>
</Model>
上面的代码不言自明,除了我们没有像使用集合时那样使用*方括号*,而是使用了*@*符号作为正则表达式名称的前缀,这就是您可以在 SIML 模式中使用先前声明的正则表达式的确切方式。
要测试代码,请按 F5(刷新项目)并单击控制台选项卡,然后键入“I am 26 years old
”,即可生成所需的响应。
使用通配符
SIML 中有许多通配符。这些通配符就像用于捕获您想要的单词的容器。使得使用松散模式成为可能。我将举一个例子。
SIML 中有 4 种通配符符号
%
匹配零个或多个单词_
匹配一个或多个单词$
匹配零个或多个单词,但优先级低于%
*
匹配一个或多个单词,但优先级低于_
有人可能会问,为什么 SIML 中存在两个或多个功能相同的符号。最简单的答案是 SIML 中的模式存储在混合图结构中,因此您编写的每个模式实际上都以树状方式存储。我现在不深入探讨。但如果您仍然对此感到困惑,我只能建议您随着时间的推移,您会发现有时我们需要使用更高优先级的通配符来覆盖现有模式。
现在让我们使用两个通配符,一个用于*零个或多个单词*,另一个用于*一个或多个单词*。首先,让我们尝试*一个或多个单词*通配符,即 **\*** 符号。在您的 Hello Bot
概念中键入以下 SIML 代码。
<Model>
<Pattern>ARE YOU *</Pattern>
<Response>I am not sure if I am <Match /></Response>
</Model>
在 Chatbot Studio 中按 F5,然后在 Console Tab 中键入“Are you a human
”?机器人将回复“I am not sure if I am a human
”,稍后键入“are you
”,则不会有响应。这是因为 *
通配符匹配“1
”个或多个单词,而输入“are you
”没有剩余的单词供 *
符号匹配。
现在让我们尝试*零个或多个单词*通配符。为此,请键入以下 SIML 代码。
<Model>
<Pattern>HOW ARE YOU $</Pattern>
<Response>I am fine thank you!</Response>
</Model>
$
符号在模式中确保即使在用户输入中不存在 $
符号的位置上的单词,该模型也会被评估。因此,按 F5 并键入“How are you my friend
”或仅仅“How are you?
”,响应将始终相同。
将关键字用作模式
IMHO,基于关键字的模式是普通人的模式。它们包含一组单词,这些单词必须存在于用户输入中,然后才能接受底层响应。例如,您希望在用户何时以及如何编写“play music
”这两个词时做出响应。
<Model>
<Pattern>{PLAY MUSIC}</Pattern>
<Response>Playing music.</Response>
</Model>
在上面的代码(在 Pattern
元素内)中,我们指定了两个单词“play
”和“music
”,并用**花括号**将它们括起来。这告诉 SIML 解释器,每当用户提交包含这两个单词(play
和 music
)的聊天请求时,就生成底层响应。
因此,如果您键入“play music
”或“can you play music
”或甚至“play some rock music for me
”,响应将始终是“Playing Music
”。这告诉我们,如果我们使用基于关键字的模式,那么指定关键字*之前、之间和之后*的任何内容都对我们的模式没有任何影响。
- 关键字模式应始终作为模式识别的最后手段使用。
在模式和响应中使用 JavaScript
JavaScript 也可以作为模式用在 Pattern
元素内部,用于在 Response
元素内部生成复杂的 string
。其做法是在 Pattern
元素中引用一个 JavaScript 函数,如果指定的 Js 函数在评估时返回 True
,则激活模型。类似地,Response
元素内的 JavaScript 由 JavaScript 解释器评估,返回的值被添加到响应 string
中。
在响应元素中使用 JavaScript 的示例代码
在 **Settings** 类别下选择 *Scripts* 文件,然后键入以下代码
<Script Type="JavaScript">
function test(){return "Js string response";}
</Script>
当调用函数 test();
时,上面的 JavaScript 返回“Js string response
”。现在,我们将需要回到我们的 *Hello Bot* 文件并键入以下 SIML 代码。
<Model>
<Pattern>TEST JAVASCRIPT</Pattern>
<Response>
<Js>test();</Js>
</Response>
</Model>
请注意,我们在 Response
中使用了一个新的 <Js>
元素。这是为了告诉 SIML 解释器将此元素的值视为 JavaScript。键入“Test JavaScript
”,您将收到我们 JavaScript 函数作为 string
返回的值。
SIML 中的脚本适用于硬核和经验丰富的开发人员。因此,目前我将把这项功能留给开发人员自行尝试。
- SIML 解释器 (
Syn.Bot
) 内置了 Js 解释器,因此不需要引用任何外部库即可使 SIML 代码正常工作。
处理 Bot 和 User 变量
既然我们已经练习了模式,是时候学习 SIML 中的变量了。您开发的每个聊天机器人肯定都有一些 Bot 特有的属性或特征。这些属性可以在开发阶段设置,并在聊天会话期间检索。
SIML 中的变量可以是**单值**的,也可以是**值集合**。在这里,我们将了解如何 Get
和 Set
Bot 相关变量。为此,请选择 **Settings** 类别下的 *Bot-Settings* 文件。您将看到默认声明的数十个 Bot 变量,它们位于 <BotSettings>
元素下。滚动到 Name
属性值为“Name
”的变量。这是为您的 Bot 指定名称的变量。要在运行时获取名称,让我们尝试一段 SIML 代码。
在 *Hello Bot* 文件中,键入
<Model>
<Pattern>WHAT IS YOUR NAME</Pattern>
<Response>My name is <Bot Get="NAME" /></Response>
</Model>
在上面的代码中,我们使用 <Bot>
标签来获取先前存储的 Bot 变量的值。为此,我们使用 Get
属性并指定变量的名称。每当您希望处理 Bot 相关变量时,都应使用 Bot
元素。
刷新项目,然后在 Console Tab 中键入“what is your name
”,瞧,Bot 的名称已从 Bot 变量中提取并显示在响应中。
现在,要Set
一个变量,让我们使用一段 SIML 代码。
<Model>
<Pattern>CHANGE YOUR NAME TO *</Pattern>
<Response>Alright my name is now <Bot Set="NAME"><Match /></Bot></Response>
</Model>
要为 Bot 设置变量,我们使用 Set
属性并指定我们要修改的变量的名称。<Bot>
元素的内部值现在用于为“name
”变量设置值。
要测试上述代码,请在控制台中键入“what is your name
”,然后键入“Change your name to Juliet
”,然后再次尝试询问“what is your name
”。您会注意到 Bot 的名称现已更改为 Juliet
,但 *Bot-Settings* 文件中的内容保持不变。
同样,您可以使用 <User>
元素和 Get
/Set
属性来设置或获取用户变量。
例如,您可以使用以下 SIML 代码设置用户的名称
<Model>
<Pattern>MY NAME IS *</Pattern>
<Response>Alright I will remember your name
<User Set="NAME"><Match /></User></Response>
</Model>
同样,您可以使用 Get
属性检索用户名。
规范化用户输入
并非所有与您的聊天机器人交互的用户都是英语教授。为了应对一些互联网/短信俚语并过滤掉垃圾词,总是需要进行*规范化*。
规范化包含 2 个易于理解的步骤。第一步是通过检测句子中的“点”或“句号”等字符来分割用户输入,这些字符表示句子已结束;第二步是将单词和文本替换为其最简单的可识别形式。
分割用户输入是规范化最简单的形式。这种规范化是针对用户输入执行的,以检测多个句子并为每个句子生成单独的响应。假设用户键入“How are you today. What are you upto”。生成两个句子的响应会更*人性化*。这要求我们分离这两个句子,为此,我们使用 SIML 分割器。
为简单起见,我建议您在处理英语聊天机器人项目时,保持 Splitter
不变。但是,如果您足够好奇,Splitter
是使用 <Splitter>
元素声明的,其子元素指定了用于分割用户输入的字符或单词。典型的 Splitter
可能如下所示
<Splitter>
<Text>.</Text>
<Text>?</Text>
<Text>!</Text>
<Text>;</Text>
</Splitter>
在上面的 SIML 代码中,声明了一个 Splitter
元素,在其内部指定了多个子 <Text>
元素。这些元素强制 SIML 解释器在检测到符号 . ? !
或 ;
时分割用户输入。
但是我们可以在遇到特定单词时分割句子吗?是的,我们可以。
让我们看一个例子
<Splitter>
<Text>.</Text>
<Text>?</Text>
<Text>!</Text>
<Text>;</Text>
<Word>and</Word>
</Splitter>
现在根据我们新的 Splitter
设置,每当在用户输入中遇到单词 and
时,输入将被分割。例如,如果用户说“play music and make it loud
”,用户输入将被分割成两个句子“play music
”和“make it loud
”。这两个句子将被单独处理,并向用户发送合并的输出。请注意,单词 and
不会包含在第二个句子中。
除了 <Word>
元素外,在 splitter
中还有一个元素可用于进行基于正则表达式的高级用户输入分割。但我找不到任何真正需要这样做的情况。
无论如何,如果我要使用 <Regex>
元素替换上面的 <Word>
示例,我会这样做:
<Splitter>
<Text>.</Text>
<Text>?</Text>
<Text>!</Text>
<Text>;</Text>
<Regex>\b(and)\b</Regex>
</Splitter>
<Regex>
元素告诉解释器将其内部值视为正则表达式。此设置将在正则表达式定义的点分割用户输入。
接下来是过滤器...
SIML 提供了许多过滤技术,这些技术针对用户输入和 Bot 本身的输出。我们将在(本教程中)尝试过滤一个单词,并将其转换为其*规范化*形式,然后再传递给 SIML 解释器。单击并选择 **Settings** 类别下的 **Normalizations**。归一化文件必须包含所有(当然)归一化,如过滤器和分割器。假设您想过滤(转换)单词“whats
”为“what is
”。
<Filter Value="what is">
<Word>whats</Word>
</Filter>
SIML 中创建 Filter
的语法并不难理解。您使用 <Filter>
元素,在该元素内包含 <Word>
、<Text>
或 <Regex>
(每个都有自己的行为)等子元素,最后使用 Value
属性指定替换值。
映射用户输入
SIML 中的映射使用预定义的 Map 将指定文本转换为不同的值。当您希望在运行时将一段文本更改为其他期望值时,Map 非常有用。例如,可以将第一人称代词替换为第二人称代词。假设用户键入“I think you are awesome
”或“I think I am awesome
”,而我们期望的响应应该是“Yes I am awesome
”或“Yes you are awesome
”.
我们将创建一个名为“2ndPerson
”的 Map
,并在我们的 SIML 模型中使用此 Map
。
<Map Name="2ndPerson">
<MapItem Content="you are" Value="i am" />
<MapItem Content="i am" Value="you are" />
<MapItem Content="you" Value="me" />
<MapItem Content="me" Value="you" />
<MapItem Content="your" Value="my" />
</Map>
如上面的 SIML 代码所示,声明 SIML Map 需要使用一个带有 Name
属性的 <Map>
元素来为我们的 Map
提供唯一标识符,然后我们创建单独的 SIML MapItem
(s)。这些项的 Content
属性指定您希望更改的值,而 Value
部分不言自明。
为了测试我们新的 Map
,我们将创建一个 SIML 模型,其中包含一个模式,该模式将从用户输入中提取所需部分,然后我们在响应中将其*映射*到不同的值。
在 *Hello Bot* 文件中键入以下 SIML 代码。
<Model>
<Pattern>I THINK * AWESOME</Pattern>
<Response>Yes <Map Get="2ndPerson"><Match /></Map> awesome.</Response>
</Model>
显然,我们在这里所做的只是捕获 *
通配符的值,并在我们的响应中使用 Map
元素,通过指定我们希望对提取值执行的转换的名称。现在,如果您保存并刷新项目,您可以测试 Bot 对输入“I think you are awesome
”的输出,这将是“yes I am awesome
”。
随机响应
很明显,有时随机响应可能最适合某些类型的用户输入。在 SIML 中,<Random>
标签用于定义随机响应的集合。解释器从集合中选择一个项目并将其发送回用户。
让我们看一个例子。
<Model>
<Pattern>BYE</Pattern>
<Response>
<Random>
<Item>See Ya</Item>
<Item>Adios amigo!</Item>
<Item>Talk to you later</Item>
<Item>Nice talking to you.</Item>
</Random>
</Response>
</Model>
在上面的例子中,我所做的就是添加了一个 <Random>
元素,在该元素中,我将 4 个 <Item>
元素组合在一起,这些元素的值包含说 good bye
的不同方式。
按 F5,然后在控制台中重复键入 Bye
,您应该会看到随机的答案。
预测性响应
既然我们已经习惯了随机响应,那么有没有一些随机响应试图使输出与用户输入匹配呢?为此,我们将需要将以下 SIML 代码添加到编辑器中。
<Model>
<Pattern>
<Item>DO YOU LIKE CUPCAKES</Item>
<Item>DO YOU LOVE CUPCAKES</Item>
<Item>DO YOU WANT CUPCAKES</Item>
</Pattern>
<Response>
<Phrase>
<Item>I do like cupcakes</Item>
<Item>Yes I love cupcakes</Item>
<Item>I do want cupcakes</Item>
</Phrase>
</Response>
</Model>
哇哦,这是我们遇到的最长的 SIML 代码了。让我带您了解一下。在上面的 SIML 代码中,我们有一个带有多个选项的 Pattern
,然后是我们的 Response
元素,它包含一个带有多个 <Item>
元素的 <Phrase>
元素。
Phrase
元素就像 <Random>
元素一样,除了当 Model
被评估时(在匹配任何指定的 Pattern
之后),输出将与用户输入匹配。
现在,如果您键入 do you love cupcakes?
,机器人的回答将是 Yes I love cupcakes
;如果您键入 do you want cupcakes?
,回答将是 I do want cupcakes
。
Phrase
元素只能与原子模式和响应一起使用。*在许多情况下,如果找不到与用户输入的具体相似性,则相同的答案会重复出现。*
上一句话 - That's What She Said
基于对话的系统要求在处理用户输入时要考虑 Bot 的上一句话。想象一个场景,您告诉*您的计算机关闭一个关键应用程序*,而 Bot 应该*确认命令*。
<Model>
<Pattern>Close Application</Pattern>
<Response>Are you sure?</Response>
<Model>
<Model>
<Pattern>YES</Pattern>
<Previous>are you sure</Previous>
<Response>Application closed</Response>
</Model>
现在,如果您键入 Close application
,Bot 将回复 Are you sure?
;如果您说 Yes
,则响应将是 application closed
*。
这种情况是通过在后面的 Model
中使用 <Previous>
元素实现的。Previous
元素告诉解释器,除了 Yes
模式之外,上一句话应该是 are you sure
。只有当 Bot 的上一句话是 are you sure
时,底层响应才会被接受。
嵌套建模
SIML 的另一件事是,当我试图弄清楚“Model
”的实际含义时,每个 Model
都可以*神奇地*嵌套在另一个 Model
中,这样做会对 Syn.Bot
类库如何解释您的代码产生一些行为影响。我将使用上面的示例来展示如何通过使用嵌套建模技术来减少冗余。
上面的 SIML 代码可以使用嵌套建模重写为..
<Model>
<Pattern>CLOSE APPLICATION</Pattern>
<Response>Are you sure?</Response>
<Model>
<Pattern>Yes</Pattern>
<Response>Application closed.</Response>
</Model>
<Model>
<Pattern>No</Pattern>
<Response>Application shut-down cancelled.</Response>
</Model>
</Model>
上面的代码不言自明,或者*可能不是*。我在上面的 SIML 代码中所做的是将一个 Model
添加到另一个 Model
中,也就是说,我将它们嵌套了。因此,如果用户说“Close application
”,Bot 将回复“Are you sure?
”;如果用户说“no
”,则输出将是“Application shut-down cancelled
”。请注意,这次我没有使用任何 <Previous>
元素,这显而易见,因为嵌套模型就像通过某种隐藏的 <Previous>
元素链接一样。我注意到的这种技术的唯一缺点是,您的代码的可读性略有下降,但这是避免冗余的一种权衡。
随机答案和上一句话
当我在基于对话的场景中使用第一种处理 Bot 上一句的方法时,我问了一个奇怪的问题(我自己问的):**如果上一句话涉及随机答案怎么办?**我是否需要为随机响应中的每个项目编写一个 Model
?如果是这样,那我最好去数天上的星星了。幸运的是,有天赐的 <Label>
元素。
<Label>
元素会覆盖 Previous
元素对 Model
的 string
值,用一个唯一的标识符,该标识符以后可以在任何后续 Model
的 <Previous>
元素中引用。
天哪!我觉得我无法进一步解释我刚才写的内容,但也许一个例子会有帮助。
<Model>
<Pattern>RESTART COMPUTER</Pattern>
<Response>
<Random>
<Item>Are you sure Sir?</Item>
<Item>Shall I restart the Computer now?</Item>
<Item>Say yes if you wish to restart your computer now.</Item>
<Item>Please confirm that you intend to restart your computer</Item>
</Random>
<Label>computer-restart:</Label>
</Response>
</Model>
<Model>
<Pattern>Yes</Pattern>
<Previous>computer-restart:</Previous>
<Response>Restarting computer. Please Standby..</Response>
</Model>
让我来为您解答!从上面的代码可以很容易地理解,由于我在前面的模型中使用了随机响应,我无法再假定运行时的实际输出是什么。因此,而不是为我的随机响应中的每个项目编写多个模型,我为响应添加了一个 <Label>
。这个 Label
确保我可以通过唯一的标签“computer-restart:
”来引用模型中的任何响应。Voilà,我节省了代码膨胀。
- 创建标签时,尝试使用连字符分隔单个单词,并在标签后加上冒号。
- 避免含糊不清的标签,并确保您的标签能说明问题。
数学表达式
我很难想象为所有可能的数学表达式编写文本模式来取悦用户。这时 <Math>
标签就派上用场了,它提供了现代计算器中常见的有用功能集。
假设您想评估一个数学表达式。例如“What is the sine of 1
”。
<Model>
<Pattern>what is the sine of *</Pattern>
<Response>It's <Math Get="sin"><Match /></Math></Response>
</Model>
要实现我刚才提到的功能,我们只需要使用 <Math>
元素,并在 Get
属性中指定我们希望调用的数学函数。SIML 的 Math
元素有很多内置函数,如*sin、cos、acos、tan、tanh、pi、random、floor* 等等。您需要参考规范以获取更多信息。链接已在下方提供。
关于 <Math>
元素还有一个好消息是,当省略 Get
属性时,它的作用就像一个*数学表达式求值器*。
<Model>
<Pattern>CALCULATE *</Pattern>
<Response>The answer is <Math><Match /></Math></Response>
</Model>
在上面的代码中,我使用了 <Match/>
元素来提取 *
通配符的值,并将其指定为 <Math>
元素的值。
现在,如果您按 F5 并键入控制台中的 Calculate 1+2+7
,Bot 将回复 The answer is 10
;或者如果您键入 calculate sin(1)
*,Bot 将回复 The answer is 0.841470984807897
*。
文本操作
在某些时候,您肯定需要操作文本语料库。例如,将文本转换为大写或小写,甚至将文本拆分成间隔均匀的字符。
为了完成此类文本相关的任务,可以使用 <Text>
元素,该元素可以在我们的 Response
元素中使用。请记住,您在 Splitters 和 Filters 中使用的 Text 元素与在 <Response>
元素中使用的 Text 元素的行为完全不同。
示例
<Model>
<Pattern>CHANGE * TO UPPERCASE</Pattern>
<Response><Text Get="Uppercase"><Match /></Text></Response>
</Model>
因此,如果您键入 Change this to uppercase
,响应将是 THIS
*。同样,还有 Lowercase
、Titlecase
、Explode
、Join
、Sentence
、Length
、WordCount
等等。
例如,您想查找单词中的字母数。以下 SIML 代码就足够了。
<Model>
<Pattern>HOW MANY LETTERS ARE THERE IN THE WORD *</Pattern>
<Response>There are <Text Get="Length"><Match /></Text> letters in the word <Match /></Response>
</Model>
现在如果您问 how many letters are there in the word Bacon
,响应将是 There are 5 letters in the word bacon
*。
日期和时间
您的用户可能会通过询问与日期和时间相关的问题让您的 Bot 感到惊讶。对于日期和时间检索,SIML 都提供了 <Date>
元素。该元素的 Get
属性用于检索特定的日期和时间相关值。例如,用户询问“what day is it?
”或“what year is it?
”。虽然 JavaScript 可能适合此任务,但我建议您坚持使用 SIML 的内置元素,因为它们已针对此类任务进行了大量优化(至少规范是这么说的)。
检索星期几的示例 SIML 代码
<Model>
<Pattern>WHAT DAY IS IT</Pattern>
<Response>It's <Date Get="weekday" /></Response>
</Model>
检索年份的示例 SIML 代码
<Model>
<Pattern>WHAT YEAR IS IT</Pattern>
<Response>It's <Date Get="year" /></Response>
</Model>
Date
元素中的 Get
属性的一些可接受值包括
weekday
month
year
hour
minute
second
meridiem
timezone
时间
format
Get
属性的另一个值(我个人最喜欢的是)是 format
。在我阅读规范时,它似乎与 C# 的 Date.ToString("format")
函数类似。例如,在 C# 中,自定义格式说明符“dddd
”会给出星期几。
因此,一个非常简单的例子是...
<Model>
<Pattern>WHAT ERA IS IT</Pattern>
<Response>It's <Date Format="gg"/></Response>
</Model>
根据 .NET 的自定义日期和时间格式字符串,这将返回纪元或时期。如果我们通过说“what era is it?
”来测试上面的例子,答案将是“It's A.D
”。
EmotionML - 聊天机器人中的情感
SIML 中的情感是使用 W3C 标准 Emotion Markup Language 创建的。我建议您在 SIML 中使用它之前,先阅读 EmotionML
规范。
但是...单击 **Settings** 类别下的 EmotionML
项目。正是在这个文件中创建了所有的 EmotionML
情感。SIML 要求您使用 Siml
命名空间并为每个详细的情感赋予唯一的标识符,以便您以后可以在 SIML 项目中使用该情感,或者最重要的是在 SIML 模型中使用。
这些情感实际上是为了让硬核开发人员通过 3D 化身或语音合成来模拟真正的人类表情。深入研究 EmotionML
超出了本教程的范围,但我以后一定会尝试扩展这一部分。
导出到 SIML 包
好了,现在我们已经了解了一些 SIML 基础知识(是的,它们仍然是基础知识),我们将导出我们的第一个知识库包。单击 **Project -> Export to Package**。将您的包命名为“Knowledge
”,并将其保存在 C# WPF 项目的*Bin* 目录(在*Debug* 或 *Release* 文件夹内)中,然后关闭 Studio。
回到我们的 C# 应用程序(呼!)
我们确实沉迷于 SIML 基础知识,但现在是最后的润色。将 *MainWindow.xaml.cs* 代码替换为以下内容
using System.IO;
using System.Windows;
using Syn.Bot;
namespace SIML_Chatbot_Demo
{
public partial class MainWindow
{
public SynBot Chatbot;
public MainWindow()
{
InitializeComponent();
Chatbot = new SynBot();
Chatbot.PackageManager.LoadFromString(File.ReadAllText("Knowledge.simlpk"));
}
private void SendButton_OnClick(object sender, RoutedEventArgs e)
{
var result = Chatbot.Chat(InputBox.Text);
OutputBox.Text = string.Format("User: {0}\nBot: {1}\n{2}",
InputBox.Text, result.BotMessage, OutputBox.Text);
InputBox.Text = string.Empty;
}
}
}
是时候运行 WPF C# 应用程序了,所以请按 **Start**(在 Visual Studio 中),您应该会看到一个类似于下图的窗口
尝试一些您刚写的 SIML 代码,例如 What is your name
或 How are you?
*,并且将会显示所需的响应。
资源
- SIML 规范 - Syn.Bot 实现的完整 SIML 1.0 规范
- GitHub SIML English Base - 英语聊天机器人的完整 SIML 知识库
- Syn.Bot NuGet 包 - 本教程中使用的 NuGet 包链接
- Visual Studio Community Edition 2013 - 我用于该项目的 IDE。
- https://github.com/SynHub/syn-bot-samples - 项目示例集合
历史
- 2015 年 1 月 19 日星期一 - 初始发布
- 2015 年 1 月 21 日星期三 - 添加了以下主题(相应更新了项目文件)
Date
和Time
(在EmotionML
上面)- 嵌套建模(在
Previous Utterance
下) Label
s(在Previous Utterance
下)<Word>
和<Regex>
(在Normalization
下)
- 2017 年 10 月 9 日星期一 - 小编辑
关注点
虽然本教程只涵盖了 SIML 的一小部分,但还有很多内容值得学习,但到目前为止您所学的应该足以让您开始您的聊天机器人之旅。SIML 规范有数十个功能元素供您在开发过程中使用,因此如果您在某个时候感到迷茫,您就确切知道该去哪里查找。另外,顺便说一下,SIML 2.0 规范(具有机器学习功能)已发布。
如果您还想了解更多,可以参考下一篇文章 - 第二部分。