随着最近全球范围内的大流行和全家待诊订单,我一直在寻找可以代替我平时活动的方法。 我开始更新家用电子设备的设置,并且其中一部分是深入研究家庭自动化。 我的一些朋友使用亚马逊的Alexa打开和关闭房屋中的灯,这在某种程度上很有吸引力。 但是,我是一个注重隐私的人,所以我从来都不会对Google或Amazon一直在听家人的设备感到真正的不舒服(在本次对话中,我将不理会手机)。 我已经了解开源语音助手Mycroft大约四年了,但是由于与该项目的较早斗争,我从未对其进行过仔细的研究。 自从我第一次偶然发现该项目以来,该项目已经走了很长一段路,并且为我检查了很多盒子:
- 自托管
- 易于入门(通过Python)
- 开源的
- 注重隐私
- 互动聊天频道
在本系列的第一篇文章中,我介绍了Mycroft,在第二篇文章中 ,我谈到了人工智能技能的概念。 在最基本的形式中,一项技能是一段代码,可以执行该代码以实现意图所需的结果。 意图试图确定你想要什么 ,和技能是迈克罗夫特响应的方式。 如果您可以想到结果,则可能有一种方法可以创造出使之实现的技能。
从本质上讲,Mycroft技能只是Python程序。 通常,它们分为三个或四个部分:
- 导入部分是您加载完成任务所需的任何Python模块的地方。
- 可选功能部分包含在主类部分之外定义的代码段。
- 课堂部分是所有魔术发生的地方。 一个类应始终将
MycroftSkill
作为参数。 - Mycroft使用create_skill()部分来加载您的技能。
在编写技能时,我通常会先编写一个标准的Python文件,以确保我的代码能够实现我认为的功能。 我之所以这样做,主要是因为我习惯的工作流程(包括调试工具)在Mycroft生态系统之外。 因此,如果需要逐步执行代码,我会发现使用我的IDE( PyCharm )及其内置工具更加熟悉,但这是个人喜好。
该项目的所有代码都可以在我的GitLab存储库中找到。
关于意图解析器
Padatious和Adapt意图解析器,我在上一篇文章中进行了介绍。 为什么? 首先,本教程旨在提供一些具体示例,您可以根据自己的技能考虑使用某些功能。 其次,Padatious意图非常简单,但是不支持正则表达式 ,而Adapt则很好地使用了正则表达式。 同样,Padatious意向不是上下文感知的,这意味着,尽管您可以提示用户一个响应,然后按照一些决策树矩阵对其进行解析,但最好使用Mycroft内置的Adapt意向解析器上下文处理程序。 请注意,默认情况下,Mycroft假定您正在使用Padatious意向处理程序。 最后,很高兴注意到Adapt是关键字意图分析器。 如果您不是正则表达式忍者,这会使繁琐的解析变得麻烦。 (我不是。)实施3T
在开始写技能之前,请考虑以下3个T: 仔细考虑 ! 与撰写论文大纲时类似,当您开始发展一项技能时,写下您想要的技能。
本教程将逐步编写Mycroft技能,以将项目添加到OurGroceries应用程序(与我无关)。 实际上,这是我妻子的主意。 她想要一个可以在手机上使用的应用程序来管理购物清单。 我们尝试了将近十二种应用程序来满足我们的个性化需求-我需要一种API或一种易于与后端交互的方法,而且她拥有大量的标准,其中最重要的标准之一就是易于使用她的电话。 在她列出了必备物品,必备物品和愿望清单之后,我们选择了OurGroceries。 它没有API,但确实有一种通过JSON与之交互的方式。 在PyPI中甚至有一个方便的库,称为py-our-groceries
(我为此贡献了少量)。
一旦有了目标和目标平台,我就开始概述所需的技能:
- 登录/验证
- 获取当前杂货清单的列表
- 将商品添加到特定的杂货清单
- 将项目添加到特定列表下的类别
- 添加类别(因为OurGroceries允许将项目放置在类别中)
考虑到这一点,我开始草绘所需的Python。 这是我想出的。
创建Python草图
通过阅读py-our-groceries
库的示例,我发现只需要导入两件事: asyncio
和ourgroceries
。
很简单。 接下来,我知道我需要使用username
和password
进行身份验证,并且知道该程序需要执行的任务。 所以我的草图最终看起来像这样:
import asyncio
from ourgroceries
import OurGroceries
import
datetime
import json
import
os
USERNAME
=
""
PASSWORD
=
""
OG
= OurGroceries
( USERNAME
, PASSWORD
)
def fetch_list_and_categories
(
) :
pass
def return_category_id
(
) :
pass
def add_to_my_list
(
) :
pass
def add_category
(
) :
pass
我不会深入探讨此草图的全部细节,因为这不在本系列的讨论范围之内。 但是,如果需要,您可以查看整个工作大纲 。
开始编程之前,您需要输入用户名,密码和列表ID。 用户名和密码很明显。 单击链接后,可以从URL中检索列表ID,或者可以通过编程方式进行更多选择,您可以使用所选浏览器的Developer Tools并检查对象。 这是Firefox中开发人员工具的外观:

获得列表ID后,登录OurGroceries并获取Cookie。 为此,创建一个OurGroceries 对象 ,然后将其传递给asyncio
。 在使用它的同时,您还可以定义列表ID:
OG
= OurGroceries
( USERNAME
, PASSWORD
)
asyncio.
run
( OG.
login
(
)
)
MY_LIST_ID
=
"a1kD7kvcMPnzr9del8XMFc"
就本项目而言,您需要定义两种对象类型来帮助组织代码: groceries
和categories
。 fetch_list_and_categories
方法非常简单:
def fetch_list_and_categories
( object_type
=
None
) :
if object_type
==
"groceries" :
list_to_return
= asyncio.
run
( OG.
get_list_items
( list_id
= MY_LIST_ID
)
)
elif object_type
==
"categories" :
list_to_return
= asyncio.
run
( OG.
get_category_items
(
)
)
else :
list_to_return
=
None
return
( list_to_return
)
OurGroceries允许您添加多个具有相同名称的类别或项目。 例如,如果您已经在列表中添加了“肉类”并再次添加,则将看到一个名为“肉类(2)”的类别(只要您创建一个具有相同名称的类别,此数字就会增加)。 对我们来说,这是不受欢迎的行为。 我们还想尽可能避免重复,所以我做了初步的尝试来检测复数。 例如,我的代码同时检查“肉”和“肉”。 我敢肯定,有一种更智能的方式来执行这些检查,但是此示例突出了您在进行过程中可能要考虑的一些事项。 为简便起见,我将省略这些检查,因此return_category_id
方法看起来像这样:
def return_category_id
( category_to_search_for
, all_categories
) :
category_to_search_for_lower
= category_to_search_for.
lower
(
)
category_id
=
None
if
len
( all_categories
[
'list'
]
[
'items'
]
)
is
not
0 :
for category_heading
in all_categories
[
'list'
]
[
'items'
] :
# Split the heading because if there is already a duplicate it
# presents as "{{item}} (2)"
category_heading_lowered
= category_heading
[
'value'
] .
lower
(
) .
split
(
)
[
0
]
if category_to_search_for_lower
== category_heading_lowered:
category_id
= category_heading
[
'id'
]
break
return
( category_id
)
要将项目添加到列表,您需要:
- 检查项目是否不存在
- 获取类别ID
- 将项目添加到特定类别下的列表中(如果指定)
add_to_my_list
方法的最终结果如下:
def add_to_my_list
( full_list
, item_name
, all_categories
, category
=
"uncategorized"
) :
# check to make sure the object doesn't exist
# The groceries live in my_full_list['list']['items']
# Start with the assumption that the food does not exist
food_exists
=
False
toggle_crossed_off
=
False
category_lowered
= category.
lower
(
)
for food_item
in full_list
[
'list'
]
[
'items'
] :
if item_name
in food_item
[
'value'
] :
print
(
"Already exists"
)
food_exists
=
True
if
not food_exists:
category_id
= return_category_id
( category_lowered
, all_categories
)
asyncio.
run
( OG.
add_item_to_list
( MY_LIST_ID
, item_name
, category_id
)
)
print
(
"Added item"
)
最后, add_category
运行asyncio
命令创建一个类别(如果尚不存在):
def add_category
( category_name
, all_categories
) :
category_id
= return_category_id
( category_name
, all_categories
)
if category_id
is
None :
asyncio.
run
( OG.
create_category
( category_name
)
)
refresh_lists
(
)
print
(
"Added Category"
)
else :
print
(
"Category already exists"
)
现在,您应该能够测试您的草图,以确保每个功能中的所有功能都可以正常工作。 对草图满意后,您可以继续思考如何以Mycroft技能实施它。
计划Mycroft技能
您可以应用与绘制Python相同的原理来开发Mycroft技能。 官方文档建议使用称为Mycroft Skills Kit的交互式帮助程序来设置技能。 mycroft-msk create
要求您:
- 命名你的技能
- 输入一些通常用来触发您的技能的短语
- 确定Mycroft应该以什么对话框回应
- 创建技能描述
- 从
fontawesome.com/cheatsheet
选择一个图标 - 从
mycroft.ai/colors
或color-hex.com
选择一种颜色 - 定义技能所属的一个或多个类别
- 指定代码的许可证
- 说明技能是否具有依赖性
- 指示您是否要创建GitHub存储库
这是mycroft-msk create
原理的演示:

(史蒂夫烤箱, CC BY-SA 4.0 )
回答这些问题后,Mycroft在mycroft-core/skills/<skill name>
下创建以下结构:
├── __init__.py
├── locale
│ └── en-us
│ ├── ourgroceries.dialog
│ └── ourgroceries.intent
├── __pycache__
│ └── __init__.cpython-35.pyc
├── README.md
├── settings.json
└── settingsmeta.yaml
您现在可以忽略其中的大多数文件。 在尝试进入Mycroft特定的疑难解答之前,我希望确保我的代码能正常工作。 这样,如果以后出现问题,您就会知道这与Mycroft技能的构建方式有关,而与代码本身无关。 与Python草图一样,看看Mycroft在__init__.py
创建的轮廓。
所有Mycroft技能都应具有__init__.py
。 按照惯例,所有代码都应放在该文件中,尽管如果您是熟练的Python开发人员并且知道此文件的工作原理,则可以选择将代码拆分。
from mycroft
import MycroftSkill
, intent_file_handler
class OurGroceries
( MycroftSkill
) :
def
__init__
(
self
) :
MycroftSkill.
__init__
(
self
)
@ intent_file_handler
(
'ourgroceries.intent'
)
def handle_test
(
self
, message
) :
self .
speak_dialog
(
'ourgroceries'
)
def create_skill
(
) :
return OurGroceries
(
)
理论上,此代码将基于您在msk create
过程中创建的触发器执行。 Mycroft首先尝试查找扩展名为.dialog
的文件,该文件与传递给selfspeak_dialog()
的参数匹配。 在上面的示例中,Mycroft将查找一个名为ourgroceries.dialog
的文件,然后说出在其中找到的短语之一。 如果失败,它将说出文件名。 我将在有关响应的后续文章中对此进行更多介绍。 如果您想尝试此过程,请随时探索在技能创建过程中可以想到的各种输入和输出短语。
尽管脚本是一个很好的起点,但我更愿意自己考虑__init__.py
。 如前所述,该技能将同时使用Adapt和Padatious意向处理程序,并且我还想演示对话上下文处理 (我将在下一篇文章中进行深入介绍)。 因此,从导入它们开始:
from mycroft
import intent_file_handler
, MycroftSkill
, intent_handler
from mycroft.
skills .
context
import adds_context
, removes_context
如果您想知道,在Python中指定导入语句的顺序无关紧要。 导入完成后,查看类结构。 如果您想了解有关类及其使用的更多信息, Real Python对此主题有很好的入门。
如上所述,首先使用其预期功能模拟代码。 本部分使用与Python草图相同的目标,因此继续进行一些插入,这次添加一些注释以帮助您:
class OurGroceriesSkill
( MycroftSkill
) :
def
__init__
(
self
) :
MycroftSkill.
__init__
(
self
)
# Mycroft should call this function directly when the user
# asks to create a new item
def create_item_on_list
(
self
, message
) :
pass
# Mycroft should also call this function directly
def create_shopping_list
(
self
, message
) :
pass
# This is not called directly, but instead should be triggered
# as part of context aware decisions
def handle_dont_create_anyways_context
(
self
) :
pass
# This function is also part of the context aware decision tree
def handle_create_anyways_context
(
self
) :
pass
def stop
(
self
) :
pass
__init__
和initialize
方法
一项技能具有一些您应该了解的“特殊”功能。 首次实例化此技能时,将调用__init__(self)
方法。 在Python IDE中,在__init__
部分之外声明的变量通常会引起警告。 因此,它们通常用于声明变量或执行设置操作。 然而,尽管你可以声明变量,旨在(后面有更多此)相匹配的技能设置文件,你不能使用迈克罗夫特方法(如self . settings . get)
来检索值。 通常不适合尝试通过__init__
与外界建立联系。 另外,在Mycroft中__init__
函数被认为是可选的。 大多数技能都选择拥有一种技能,这被认为是“ Pythonic”的做事方式。
在技能完全构建并向系统注册后,将调用initialize
方法。 它用于执行技能的任何最终设置,包括访问技能设置。 但是,它是可选的,我选择创建一个获取身份验证信息的函数。 如果您好奇并想_create_initial_grocery_connection
,我将其称为_create_initial_grocery_connection
。 当我开始逐步创建技能代码时,我将在下一篇文章中重新介绍这两个特殊功能。
最后,有一个特殊的函数stop()
,我没有使用过。 每当用户说“停止”时,都会调用stop方法。 如果您有一个长时间运行的过程或音频播放,则此方法很有用。
结语
因此,您现在已经有了要完成的任务的大纲。 随着时间的流逝,这肯定会增长。 随着技能的发展,您将发现技能最佳工作所需的新功能。
下次,我将讨论您将使用的意图类型,如何设置它们以及如何处理正则表达式。 我还将探讨会话上下文的概念,该上下文用于获取用户的反馈。
您有任何意见,问题或疑虑吗? 发表评论,在Twitter @linuxovens上访问我,或通过Mycroft技术聊天频道停止。
翻译自: https://opensource.com/article/20/6/mycroft-voice-assistant-skill