首页 > 技术知识 > 正文

导言

一篇关于使用SIML实现数据库的简单自然语言接口的介绍性文章,SIML是一种为数据库、游戏和网站设计的标记语言,用于数字助手、聊天机器人和NLI。

前提条件

熟悉C#,SQL

设置

实现的思路:

使用VisualStudio,我们将创建一个简单的WPF应用程序(带有文本框、求值按钮和DataGrid) 启动时,应用程序将使用在Employees表格中填写10名员工以及他们的详细资料如下:ID, 名字, 年龄, 工资, 工作) 然后我们将加载一个SIML把我们的SynBot类对象,并从我们的数据库传入一些重要的值。 稍后,我们将创建一个接受SQL字符串并对其进行评估的SQL适配器。 最后,我们将使用SIML与我们的数据库交互的知识库

因此,首先创建一个wpf应用程序并调用NLI-数据库。将项目命名为NLI-Database,因为前面的一些代码片段可能使用命名空间。NLI_Database

在我们进入之前,我们必须在我们的项目中添加一个对Syn.Bot类库的引用。为此,请在VisualStudio中单击TOOLS ->NuGet Package Manager ->Package Manager Console导入Syn.Bot类库。

Install-Package Syn.Bot

一旦完成,我们将在我们的项目中有机器人库。注意,这不仅仅是一个机器人库,它也是一个符合规范的Siml解释器。

现在让我们也导入SQLite数据库。

再次,在Package Manager Console选择:

Install-Package System.Data.SQLite

C#编码

数据库实用程序

让我们创建一个简单的实用程序类,DatabaseUtilty,我们将在应用程序启动过程中使用它来创建一个简单的Employees用表格填写一些数据。将一个新的类文件添加到您的项目中,命名为DatabaseUtility并添加以下代码行。

public class DatabaseUtility { private const string DataSource = “EmployeesTable.db”; public SQLiteCommand Command { get; set; } public SQLiteConnection Connection { get; set; } public void Initialize() { if(File.Exists(DataSource))File.Delete(DataSource); Connection = new SQLiteConnection { ConnectionString = “Data Source=” + DataSource }; Connection.Open(); ExecuteCommand(“CREATE TABLE IF NOT EXISTS EMPLOYEES (ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Name VARCHAR(100) NOT NULL, Job VARCHAR(10), Age INTEGER NOT NULL, Salary INTEGER NOT NULL);”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(1, Lincoln, Manager, 43, 54000)”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(2, George, CEO, 46, 75000)”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(3, Rick, Admin, 32, 18000)”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(4, Jorge, Engineer, 28, 35000)”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(5, Ivan, Tech, 23, 34000)”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(6, Mark, Tech, 25, 34000)”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(7, Vincent, Support, 21, 20000)”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(8, Carl, Support, 20, 20000)”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(9, Marco, Tech, 24, 34000)”); ExecuteCommand(“INSERT INTO EMPLOYEES VALUES(10, Craig, Admin, 25, 18000)”); } public void ExecuteCommand(string commandText) { Command = new SQLiteCommand(Connection) {CommandText = commandText}; Command.ExecuteNonQuery(); } public void Close() { Command.Dispose(); Connection.Dispose(); } }
<

这个Initialize方法在上面的代码中检查文件是否EmployeesTable.db是否存在。如果它存在,那么它只是删除文件并创建一个新的文件。完成后,代码将添加一些虚拟员工的详细信息到Employees表格。

SQL适配器

是时候为SQL创建第一个SIML适配器了。在Solution给它起个名字Adapter。在这个文件夹中添加一个新的类文件并调用它。SqlAdapter。SqlAdapter类必须实现IAdapter接口(在Syn.Bot库中找到),正是这个接口将您的应用程序粘合到SIML。

public class SqlAdapter : IAdapter { private readonly MainWindow _window; public SqlAdapter(MainWindow window) { _window = window; } public bool IsRecursive { get { return true; } } public XName TagName { get { return Specification.Namespace.X + “Sql”; } } public string Evaluate(Context parameter) { _window.UpdateDataGrid(parameter.Element.Value); return string.Empty; } }

是的,这就是SqlAdapter的全部内容。那么它是做什么的呢?

这个适配器允许我们使用<x:Sql>Some SQL here</x:Sql>在我们的SIML代码中。通过解释器进行评估后,将调用评估函数在上面的适配器代码中。您可能会注意到评估函数在上面的代码中对UpdateDataGrid函数,据推测,该函数将位于MainWindow类中。我将在本文后面讨论这一点。

集合

SIML中的集合是单词或句子的集合。一旦创建了一个具有唯一名称的集合,我们就可以使用该集合的名称来指定我们希望在SIML模式中捕获的单词集合。

使用“EMP-NAME“将允许我们捕获员工的姓名。同样,一组”EMP-JOB“将允许我们捕获员工在Employees表格。

继续在解决方案中创建一个文件夹并命名为Sets。向它添加一个新的类文件并调用NameSet类,这个NameSet类必须实现ISet接口(在Syn.Bot库中找到)。

public class NameSet : ISet { private readonly HashSet<string> _nameSet; public NameSet(DatabaseUtility databaseUtility) { _nameSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); databaseUtility.Command.CommandText = “SELECT * FROM EMPLOYEES”; var reader = databaseUtility.Command.ExecuteReader(); while (reader.Read()) { _nameSet.Add(reader[“name”].ToString()); } reader.Close(); } public bool Contains(string item) { return _nameSet.Contains(item); } public string Name { get { return “Emp-Name”; }} public IEnumerable<string> Values { get { return _nameSet; } } }

每个SIML集都有一个唯一的名称,并返回可枚举的字符串值。由于SIML集不允许保存重复值,因此我们将使用HashSet存储Employees桌子。是的,一家公司的2名或更多员工可以有类似的名字,但当SIML集的唯一目的是促进模式匹配时,这并不重要。

就像NameSet我们将为ID、工作、年龄和薪资再创建4组。

年龄集合AgeSet

public class AgeSet : ISet { private readonly HashSet<string> _ageSet; public AgeSet(DatabaseUtility databaseUtility) { _ageSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); databaseUtility.Command.CommandText = “SELECT * FROM EMPLOYEES”; var reader = databaseUtility.Command.ExecuteReader(); while (reader.Read()) { _ageSet.Add(reader[“Age”].ToString()); } reader.Close(); } public bool Contains(string item) { return _ageSet.Contains(item); } public string Name { get { return “Emp-Age”; }} public IEnumerable<string> Values { get { return _ageSet; } } }

ID集合IdSet

public class IdSet : ISet { private readonly HashSet<string> _idSet; public IdSet(DatabaseUtility databaseUtility) { _idSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); databaseUtility.Command.CommandText = “SELECT * FROM EMPLOYEES”; var reader = databaseUtility.Command.ExecuteReader(); while (reader.Read()) { _idSet.Add(reader[“ID”].ToString()); } reader.Close(); } public bool Contains(string item) { return _idSet.Contains(item); } public string Name { get { return “Emp-ID”; }} public IEnumerable<string> Values { get { return _idSet; } } }

工作集合JobSet

public class JobSet : ISet { private readonly HashSet<string> _jobSet; public JobSet(DatabaseUtility databaseUtility) { _jobSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); databaseUtility.Command.CommandText = “SELECT * FROM EMPLOYEES”; var reader = databaseUtility.Command.ExecuteReader(); while (reader.Read()) { _jobSet.Add(reader[“Job”].ToString()); } reader.Close(); } public bool Contains(string item) { return _jobSet.Contains(item); } public string Name { get { return “Emp-Job”; }} public IEnumerable<string> Values { get { return _jobSet; } } }

工资集合SalaySet

public class SalarySet : ISet { private readonly HashSet<string> _salarySet; public SalarySet(DatabaseUtility databaseUtility) { _salarySet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); databaseUtility.Command.CommandText = “SELECT * FROM EMPLOYEES”; var reader = databaseUtility.Command.ExecuteReader(); while (reader.Read()) { _salarySet.Add(reader[“Salary”].ToString()); } reader.Close(); } public bool Contains(string item) { return _salarySet.Contains(item); } public string Name { get { return “Emp-Salary”; }} public IEnumerable<string> Values { get { return _salarySet; } } }

GUI界面

GUI应该如下所示。

有一个输入框、一个Evaluate评估按钮和一个DataGrid。

在里面的GridMainWindow.xaml 添加以下内容。里面会有一些未定义的符号,但是我们会在您完成输入之后修复它们。

<TabControl> <TabItem Header=”Interaction”> <Grid> <Grid.RowDefinitions> <RowDefinition Height=”35″/> <RowDefinition Height=”53*”/> <RowDefinition Height=”232*”/> </Grid.RowDefinitions> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width=”414*”/> <ColumnDefinition Width=”100″/> </Grid.ColumnDefinitions> <TextBox Name=”InputBox” TextAlignment=”Center” CharacterCasing=”Upper” KeyDown=”InputBox_OnKeyDown”/> <Button Name=”ExecuteButton” Content=”Evaluate” Grid.Column=”1″ Click=”ExecuteButton_OnClick”/> </Grid> <Label Grid.Row=”1″ Name=”ResponseLabel” Content=”No Response Yet” VerticalContentAlignment=”Center”/> <DataGrid Name=”EmployeeGrid” Grid.Row=”2″ FontSize=”14″ /> </Grid> </TabItem> </TabControl>

太棒了!您已经添加了组成GUI的XAML代码。现在用下面的代码替换代码隐藏。

using System.Data; using System.Data.SQLite; using System.IO; using System.Linq; using System.Windows; using System.Windows.Input; using System.Xml.Linq; using NLI_Database.Adapter; using NLI_Database.Sets; using Syn.Bot; namespace NLI_Database { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow { public DatabaseUtility DatabaseUtility { get; private set; } public SynBot Bot { get; private set; } public MainWindow() { InitializeComponent(); Bot = new SynBot(); DatabaseUtility = new DatabaseUtility(); DatabaseUtility.Initialize(); UpdateDataGrid(“SELECT * From Employees”); Bot.Sets.Add(new NameSet(DatabaseUtility)); Bot.Sets.Add(new JobSet(DatabaseUtility)); Bot.Sets.Add(new SalarySet(DatabaseUtility)); Bot.Sets.Add(new AgeSet(DatabaseUtility)); Bot.Sets.Add(new IdSet(DatabaseUtility)); Bot.Adapters.Add(new SqlAdapter(this)); var simlFiles = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), “SIML”), “*.siml”, SearchOption.AllDirectories); foreach (var simlDocument in simlFiles.Select(XDocument.Load)) { Bot.AddSiml(simlDocument); } } public void UpdateDataGrid(string sql) { var dataSet = new DataSet(); var dataAdapter = new SQLiteDataAdapter(sql, DatabaseUtility.Connection); dataAdapter.Fill(dataSet); EmployeeGrid.ItemsSource = dataSet.Tables[0].DefaultView; } private void ExecuteButton_OnClick(object sender, RoutedEventArgs e) { var result = Bot.Chat(string.IsNullOrEmpty(InputBox.Text) ? “clear” : InputBox.Text); ResponseLabel.Content = result.BotMessage; InputBox.Clear(); } private void InputBox_OnKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Return) { ExecuteButton_OnClick(null, null); } } }
<

在上面的代码中,这个Constructor 实例化Bot和DatabaseUtility变量,调用DatabaseUtilityObject的Initialization方法,然后添加SqlAdapter并将先前创建的Siml设置为Bot对象,并最终加载在SIML文件夹,它位于应用程序的根目录中。

这个UpdateDataGrid方法接受一个SQL字符串并计算它。一旦计算完成,它将刷新ItemSource的属性EmployeeGrid.

SIML编码

由于模式识别远远超出了本文的范围,我将在我的SIML中演示一些简单的SQL命令用法。但是没有什么好担心的,因为本文附带的项目有很多预先声明的模式,可以通过在Chatbot Studio中打开SIML项目来引用。

在应用程序的根目录中创建一个名为SIML的文件夹。项目的输出路径设置:Bin/Debug or Bin/Release。

如果你没有Chatbot Studio,可以从这里下载从这里开始。通过按下Ctrl+Shift+N创建一个新项目。填写所需的详细信息并选择English Minimum作为默认模板。将项目保存在您刚才创建的SIML文件夹中。

单击文件Hello Bot特你会看到一个简单的SIML文档。这个文档只有一个SIML模型。与模式匹配的Hello Bot并生成响应Hello User!。通过选择该模型并按“Delect”来删除该模型。

添加新的命名空间控件之前右键单击Siml文档的根元素。标记和选择Insert -> Attributes -> Xmlns:X\.

名称空间xmlns:x=”http://syn.co.in/2014/siml#external”将被插入。这个命名空间具有非常重要的意义,因为我们将严格使用命名空间在我们的SIML代码中。

简单的与员工相关的查询

现在按ALT+M插入新的SIML模型并在模式标记中添加(EMP-名称)的年龄是多少?.

以上模式匹配:

Rike几岁了? Lincoln的年龄是多少? Jorge的年龄是多少?以此类推。

如果您还记得我们以前创建了一个NameSet类派生的ISet接口。我们选择返回的集合的名称是Emp-Name。在SIML中,您可以通过将集合的名称括在方括号中来指定集合(在SIML模式中)。这正是我们在这里要做的。

在响应元素类型中Age of Employee 。其次是:

<x:Sql>SELECT DISTINCT Age FROM Employees WHERE UPPER(Name) LIKE UPPER(%<Match />%)</x:Sql>

新SIML模型现在应该类似于:

<Model> <Pattern>WHAT IS THE AGE OF [EMP-Name]</Pattern> <Response> Age of the employee <Match />. <x:Sql>SELECT DISTINCT Age FROM Employees WHERE UPPER(Name) LIKE UPPER(%<Match />%)</x:Sql> </Response> </Model>

按动Ctrl+S以保存文档。

现在继续启动您的应用程序并键入“What is the age of Rick“输出应该如下所示。

这很有趣,为同一类型的查询提供更多的模式如何?把你的第一个SIML模型改为:

<Model> <Pattern> <Item>WHAT IS THE AGE OF [EMP-NAME]</Item> <Item>HOW OLD IS [EMP-NAME]</Item> <Item>$ AGE OF [EMP-NAME]</Item> </Pattern> <Response> Age of the employee <Match />. <x:Sql>SELECT DISTINCT Age FROM Employees WHERE UPPER(Name) LIKE UPPER(%<Match />%)</x:Sql></Response> </Model>

下面的查询将产生相同的结果。

Rike多大了? Rike几岁了? Rike的年龄?

现在可以继续创建许多与薪资、职务和ID相关的查询模式。(本文附带的下载有很多预定义的模式)

下面是另一个响应如下问题的Siml代码示例Rike是谁?, **Jorge是谁?***

<Model> <Pattern>WHO IS [EMP-NAME]</Pattern> <Response> Employee(s) with the name <Match />. <x:Sql>SELECT * FROM Employees WHERE UPPER(Name) LIKE UPPER(%<Match />%)</x:Sql> </Response> </Model>

基于谓词的列表信息

现在让我们创建一个Operator准备好了。

正如我前面提到的,SIML集是一个独特的词或句子集合,它有助于模式匹配。在File Explorer(在Chatbot Studio中)选择Sets文件并添加以下内容:

<Set Name=”operator”> <Item>equal to</Item> <Item>less than</Item> <Item>greater than</Item> <Item>less than or equal to</Item> <Item>greater than or equal to</Item> </Set>

现在点击Maps并添加以下内容。

<Map Name=”operator”> <MapItem Content=”equal to” Value=”=” /> <MapItem Content=”less than” Value=”<” /> <MapItem Content=”greater than” Value=”>” /> <MapItem Content=”less than or equal to” Value=”<=” /> <MapItem Content=”greater than or equal to” Value=”>=” /> </Map>

另一方面,SIML Map使我们能够在运行时将给定的值映射到其他值。在上面的代码中等于获得映射到符号= , 少于被映射到<,大于映射到符号>等等..。

现在,让我们添加一个新的SIML模型,它将允许我们获取年龄或薪资大于、小于或等于某个指定值的员工的详细信息。

列出年龄小于40岁的所有员工 列出薪资高于18000的所有员工 <Model> <Pattern>LIST ALL EMPLOYEES * (AGE|SALARY) IS [OPERATOR] *</Pattern> <Response> <x:Sql>select * from Employees where <Match Index=”2″ /><Map Get=”operator”><Match Index=”3″ /></Map><Match Index=”4″ /></x:Sql> </Response> </Model>

按Ctrl+S保存文档,运行WPF应用程序并键入List all employees whose age is greater than 30(列出年龄大于30岁的所有员工)

现在试试List all Employees whose salary is less than 30000(列出所有工资低于30000的雇员)

更改数据库中的信息

现在,我们将尝试更改给定ID的雇员的年龄(使用名称为一家公司的2名或2名以上的雇员可以有相同的名字)员工的Value。

将以下SIML模型添加到您的SIML文档中。

<Model> <Pattern>(CHANGE|SET|UPDATE) THE AGE OF ID [EMP-ID] TO *</Pattern> <Response> Age of ID <Match Index=”2″ /> has now changed to <Match Index=”3″ />. <x:Sql>UPDATE EMPLOYEES SET AGE=<Match Index=”3″ /> WHERE ID=<Match Index=”2″ />;</x:Sql><x:Sql>SELECT * FROM EMPLOYEES WHERE ID=<Match Index=”2″ />;</x:Sql> </Response> </Model>

保存文档,重新启动WPF应用程序,然后尝试键入Change the age of ID 3 to 34(将ID3的年龄改为34岁)

好的,这就为这个初始版本包装好了。你最好自己尝试和试验SIML。您可以使用附加的项目为您自己的目的。

总结

使用SIML作为接口的优点是,更新模式的时候,不必对应用程序代码进行任何更改。SIML自然语言接口和数据库之间的抽象层非常牢固,并且在许多情况下都能很好地支持。

即使由于某种原因更改了代码的结构,SIML代码仍然是可重用的。最重要的是,Siml解释器是独立于平台的,所以可以在Linux或Mac环境下在Mono上进行同样的实验。

不要只是盲目地将我的设置连接到您的某个数据库,很有可能你会把事情搞砸。先要备份你的数据库,然后在设置自然语言接口。

猜你喜欢