Цель данного документа - предоставить основные инструкции по написанию плагинов для Ирины без углубления в подробности того, как обеспечивается работа той или иной функции.
В простейшем случае, плагин для Ирины представляет собой одиночный файл с расширением .py
. Чтобы файл был успешно распознан в
качестве плагина:
- он должен быть расположен в папке
$IRENE_HOME/plugins
(обычно$HOME/irene/plugins
). Или в другом месте, если настройки плагинаdiscover_plugins
отличаются от стандартных. - его имя должно начинаться на
plugin_
(опять же, можно изменить в настройках плагинаdiscover_plugins
). - в нём должны быть определены следующие переменные:
См. https://semver.org/lang/ru/
# имя плагина. Латиницей, без спец. символов, желательно в snake case name = 'my_plugin' # версия плагина, желательно в формате семантического версионирования version = '1.235.432-rc42'
Так же, желательно добавить docstring с описанием того, что делает плагин в самом начале файла:
"""
Мой замечательный плагин.
Делает кое-что очень полезное.
"""
import
...
...
name = ...
version = ...
В случае необходимости объявить более одного плагина в файле или необходимости определить плагин отдельным классом по
другой причине, можно создать класс, наследующий MagicPlugin
:
from irene.plugin_loader.magic_plugin import MagicPlugin
class MyPlugin(MagicPlugin):
"""
Мой замечательный плагин.
Делает кое-что очень полезное.
"""
# В случае класса, эти переменные не обязательны, но желательны
name = 'my_plugin'
version = '1.235.432-rc42'
# В классе можно объявлять все переменные и функции, описанные далее
# (но у функций добавляется дополнительный первый параметр self)
...
Если плагин состоит из нескольких файлов, то следует создать для него подпапку в папке с плагинами:
$HOME/irene/plugins/my_plugin/
$HOME/irene/plugins/my_plugin/plugin_my.py
$HOME/irene/plugins/my_plugin/helpers.py
$HOME/irene/plugins/my_plugin/...
Поведение плагина может настраиваться посредством изменения конфигурации. Конфигурация каждого плагина хранится на диске в виде YAML или JSON файла и загружается загрузчиком конфигурации при запуске приложения.
Чтобы отметить, что плагину нужна своя конфигурация и задать её значения по-умолчанию нужно объявить
переменную config
:
config = {
"имя_параметра": "значение по-умолчанию",
}
Конфигурация всегда является словарём со строковыми ключами.
Желательно добавить описание поддерживаемых параметров в переменной config_comment
:
config_comment = """
Настройки (моего плагина).
Поддерживаются следующие параметры:
- `(имя_параметра)` - (описание параметра)
(какие-нибудь рекомендации по настройке плагина)
"""
Эта строка будет добавляться в качестве комментария к файлам конфигурации, а так же будет отображаться в графическом интерфейсе настроек.
Загрузчик плагинов будет обновлять значение переменной config
при загрузке конфигурации из файла или обновлении её
через графический интерфейс. Он может как присваивать новое значение переменной config
, так и изменять словарь,
хранящийся в этой переменной.
Если нужно предпринимать какие-то действия при изменении конфигурации, то можно определить функцию reveive_config
:
def receive_config(config: dict[str, Any], *_args, **_kwargs):
# Единственный позиционный параметр - актуальная конфигурация плагина.
# Рекомендуется разрешать передачу дополнительных аргументов
# для сохранения совместимости в будущем.
...
Эта функция будет вызвана как минимум один раз - после попытки загрузить конфигурацию из файла при запуске.
Плагины могут обновлять текущую конфигурацию изменяя значения отдельных ключей в словаре (но не присваивая новое
значение переменной config
). В зависимости от конфигурации загрузчика конфигурации (по сути, так же являющегося
плагином), он может сохранять внесённые плагинами изменения конфигурации обратно в файлы конфигурации.
Чтобы добавить команды голосового ассистента, нужно определить в плагине переменную define_commands
:
define_commands = {
"привет": _say_hi,
}
Переменная должна содержать словарь, ключами в котором являются команды, а значениями - обработчики команд или такие же словари. Текст команды должен быть приведён в нижнем регистре и без знаков препинания. Если команда состоит из нескольких слов, то между ними должен быть ровно один пробел. Пробелов в начале и в конце строки быть не должно.
Вместо переменной можно объявить функцию define_commands
, возвращающую аналогичный словарь:
def define_commands(*_args, **_kwargs):
return {
"привет": _say_hi,
}
Это может быть полезно если набор команд зависит от конфигурации и/или внешних сервисов. Однако, имейте в виду, что функция будет вызвана только один раз, при запуске "мозга" ассистента. Возможно, в будущем будет добавлена возможность перезагружать список команд после первоначального запуска.
Обработчик команды в простейшем случае - функция, принимающая на вход экземпляр API ассистента и дополнительный текст команды:
from irene.brain.abc import VAApiExt
def _say_hi(va: VAApiExt, text: str):
# Просто отвечает пользователю текстом и/или голосом
# См. документацию к VAApiExt чтобы узнать, что ещё можно сделать
va.say("привет")
Для более сложных сценариев можно определить функцию-генератор:
from irene.brain.abc import VAApiExt
def _say_hi(va: VAApiExt, text: str):
# yield задаёт вопрос и ждёт ответа от пользователя
name = yield "Как тебя зовут?"
va.say(f"Привет, {name}")
...или передать функцию, которая будет обрабатывать следующую команду:
from irene.brain.abc import VAApiExt
def _say_hi_to_name(va: VAApiExt, text: str):
va.say(f"Привет, {text}")
def _say_hi(va: VAApiExt, text: str):
va.say("Как тебя зовут?")
# Сигнатура функции аналогична обработчику команды, но вторым аргументом будет передан ответ пользователя полностью
va.context_set(_say_hi_to_name)
Ключ в словаре команд может содержать несколько вариантов команды, разделённых |
:
# пример из плагина plugin_random:
define_commands = {
"подбрось|брось": {
"монету|монетку": _play_coin,
"кубик|кость": _play_dice,
}
}
# Это определение эквивалентно следующему:
define_commands = {
"подбрось монетку": _play_coin,
"подбрось монету": _play_coin,
"брось монетку": _play_coin,
"брось монету": _play_coin,
"подбрось кубик": _play_dice,
"подбрось кость": _play_dice,
"брось кубик": _play_dice,
"брось кость": _play_dice,
}
Определив в плагине следующие функции можно выполнять определённые действия при запуске и завершении приложения:
def init(*_args, **_kwargs):
# Функция будет вызвана при запуске приложения
...
def terminate(*_args, **_kwargs):
# Функция будет вызвана при завершении работы приложения
...
Так же, можно определить функцию run
, которая будет вызвана в отдельном потоке после запуска приложения:
def run(*_args, **_kwargs):
...
Если функция run
не завершается сама по себе в течение некоторого времени, то желательно сделать так, чтобы вызов
функции terminate
завершал её выполнение.
Функции init
, run
и terminate
могут быть асинхронными:
async def init(*_args, **_kwargs):
...
async def run(*_args, **_kwargs):
# Асинхронная функция будет выполняться в основном event loop'е приложения вместо отдельного потока.
# Вызов может быть отменён (см. https://docs.python.org/3/library/asyncio-task.html#task-cancellation) если процесс
# получит сигнал прерывания.
...
async def terminate(*_args, **_kwargs):
...
Если Вам удалось написать работоспособный плагин и Вы считаете, что он может быть полезен другим пользователям Ирины, то стоит подумать о его распространении среди пользователей. Далее приведены инструкции о том, как следует упаковывать плагины так, чтобы пользователям было достаточно просто их устанавливать и использовать.
TODO: Добавить информацию о том, где можно рассказывать о своих плагинах
Если плагин не имеет зависимостей помимо стандартной библиотеки питона, Ирины и пакетов, от которых зависит Ирина, то
плагин можно распространять в виде отдельного .py
файла. Имя этого файла должно начинаться с plugin_
.
Пользователь может положить такой плагин в папку $IRENE_HOME/plugins
(обычно $HOME/irene/plugins
) и использовать
его.
Плагины, состоящие из нескольких файлов и/или имеющие сторонние зависимости рекомендуется публиковать в виде отдельного git-репозитория. В корне такого репозитория должны находиться:
plugin_*.py
файл плагина- дополнительные
.py
файлы если часть компонентов вынесено в отдельные файлы - файл
requirements.txt
со списком зависимостей в стандартном формате, если плагин имеет зависимости - желательно, файл
README.md
с описанием плагина и инструкциями по установке и настройке
Пользователь может склонировать такой репозиторий себе в папку $IRENE_HOME/plugins
(обычно $HOME/irene/plugins
),
установить зависимости из requirements.txt
и пользоваться плагином.
Если плагин достаточно качественен и стабилен, то его можно распространять в виде python-пакета.
К пакетам плагинов предъявляются следующие требования:
- имя пакета должно начинаться на
irene_plugin_
- файлы плагинов должны располагаться в корне пакета и их имя должно начинаться на
plugin_
Пользователь может просто установить такой плагин через pip.