用SIML实现数据库的自然语言接口
导言
一篇关于使用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。
在里面的Grid的MainWindow.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上进行同样的实验。
不要只是盲目地将我的设置连接到您的某个数据库,很有可能你会把事情搞砸。先要备份你的数据库,然后在设置自然语言接口。
- 分享
- 举报
-
浏览量:1410次2023-02-02 13:12:40
-
浏览量:1848次2018-02-27 19:25:43
-
浏览量:3745次2020-08-30 10:56:46
-
浏览量:7860次2020-12-11 10:42:42
-
浏览量:2416次2020-07-30 18:41:41
-
浏览量:791次2023-03-14 10:09:41
-
浏览量:3953次2021-04-05 12:09:13
-
浏览量:8542次2017-12-01 16:55:09
-
2023-11-23 11:04:00
-
浏览量:5451次2020-12-22 09:20:03
-
浏览量:988次2023-01-12 17:08:25
-
浏览量:2631次2020-12-07 10:07:25
-
浏览量:2069次2023-04-23 09:34:59
-
浏览量:3774次2021-08-09 15:21:20
-
浏览量:1233次2023-03-20 15:51:34
-
浏览量:2940次2018-01-30 19:45:14
-
浏览量:2600次2024-01-25 15:00:06
-
浏览量:2446次2020-07-17 17:00:06
-
浏览量:3305次2022-01-11 09:00:17
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
恬静的小魔龙
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明