用SIML实现数据库的自然语言接口

恬静的小魔龙 2020-12-12 14:56:13 9175

导言

一篇关于使用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应该如下所示。

Image 1

有一个输入框、一个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 实例化BotDatabaseUtility变量,调用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文件夹中。

Image 2

单击文件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“输出应该如下所示。

Image 3

这很有趣,为同一类型的查询提供更多的模式如何?把你的第一个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>

Image 4

基于谓词的列表信息

现在让我们创建一个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岁的所有员工)

Image 5

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

Image 6

更改数据库中的信息

现在,我们将尝试更改给定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岁)

Image 7

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

总结

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

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

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

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 76 6 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
恬静的小魔龙
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区