AI-инструменты

AI-инструмент — это модульный скрипт, который владелец магазина регистрирует для AI-помощника. Агент видит перечень инструментов при подключении и запускает их по имени — так магазин получает собственные «кнопки»: завести товар так, как заведено у вас, закрыть заказ по вашим правилам.

Перечень — данные, а не код. Новый инструмент появляется у агента без обновления программы: строки в реестре плюс модуль в units. Реестр владелец ведёт в программе: Проектирование → AI-инструменты.

Реестр

Таблица Что хранит
agent_tool инструмент: название, описание, модуль (unit); дерево с папками
agent_tool_command команды инструмента: имя — оно же имя функции, — описание, порядок
agent_tool_param параметры команды: имя, описание, тип, обязательность, умолчание, порядок
agent_tool_right гранты: строка выдаёт одну команду (command_id) человеку (user_id) или группе (group_id)

Реестр — единственный источник подписи: агент узнаёт из него и что инструмент делает (описания), и как его звать (команды с полями). Модуль себя не описывает — в нём остались только тела команд.

Права выдаются людям: агент входит в магазин под логином того сотрудника, что сидит за клавиатурой — отдельного пользователя ИИ не существует, — и грант считается по нему: строка его собственная или строка его группы. Отсюда и то, что один и тот же инструмент у разных сотрудников умеет разное. Право — это команда: сколько команд, столько прав. Держатель права «Изменение настроек AI» (PUT_AGENT_OPTION) владеет всеми командами автоматически. Перечень открыт всем: агент видит все инструменты и все команды, а выданные — отдельной строкой may:; пусто — он подсказывает сотруднику, у кого просить доступ.

Инструмент — это сущность, команда — её функция

Один инструмент покрывает одну сущность целиком — «Пользователи», «Задачи», — и это один модуль. Команда называет одно её дело, и её имя в реестре — это имя функции модуля: CmdList, CmdTaskAdd. Приставка Cmd отличает команду от функций-помощников, которые агенту не видны.

Право выдаётся на команду целиком, со всей её параметрической широтой: CmdNoteAdd пишет и комментарий, и движение задачи — одна дверь, как в программе. Хочется разделить «комментировать» и «двигать» грантом — это две команды, а не два значения параметра.

Контракт вызова

Модуль объявляет пространство имён своим именем заглавными, и движок зовёт функцию команды напрямую — ни точки входа, ни разводящего switch в модуле нет:

namespace MELBIS_AGENT_USER;

/**
 * Function CmdList
 * The users of the store
 **/
function CmdList($mUserId, $mParam)
{
    // ...
}

Ответ — массив, он целиком уезжает агенту. Соглашение: result — однозначный true или false, message — человеческий текст, который агент прочтёт сотруднику, остальные ключи — полезная нагрузка (id, users, password…). Машинных слов-кодов в ответе нет: агенту хватает булева результата и текста. Отказы движка приходят агенту в том же контракте — один язык отказов, кто бы ни отказал.

Рутина — дело движка

До модуля движок проверяет всё, что записано в реестре, и модулю достаются только правила предметной области:

Тип Что принимает Что получает модуль
str любое одиночное значение строка
int целое число или его запись словом "12" int
float число float
bool true/false, yes/no, 1/0, on/off bool
date день как YYYY-MM-DD строка в форме базы
time час как HH:MM:SS, секунды можно опустить строка в форме базы
datetime день, и час после него; час можно опустить строка в форме базы
json список или объект; скаляр — отказ массив
jsonl поток строк: список объектов или файл с ними (см. ниже) массив строк
files вложения вызова, не поле (см. ниже) строки таблицы файлов в $mParam['files']

Дни и часы приводятся к той форме, в которой их держит база, и «31 февраля» отбивается, а не сползает на третье марта. Проверять их в модуле не нужно и не следует: база строгая, и слово, которого она не прочтёт, губит не поле, а весь ответ.

Поток строк. Команда, которая грузит пачку однородных строк — товары импорта, значения характеристик, цены прайса, — объявляет параметр типа jsonl. Помощник кладёт туда либо сами строки массивом, либо путь к jsonl-файлу на своей машине: {"file": "..."}, строка на запись. Модуль получает массив в обоих случаях — файл разворачивается на стороне помощника, до вызова, и читать файлы модулю не нужно вовсе.

Разница не в удобстве, а в возможности: партия в пятьсот строк, набранная помощником в параметрах вызова, — это сотни тысяч знаков в его собственном сообщении, а файл, который написал его скрипт, не проходит через него совсем.

Список вместо одного значения. К любому типу, кроме json, jsonl и files, дописывается хвост /jsonint/json, str/json, datetime/json. Такое поле принимает и одно значение, и список, а модулю всегда достаётся массив, так что развилки «один или несколько» в коде не появляется:

$list = implode(',', $mParam['store_id']);   // int/json: элементы уже взвешены

Каждый элемент взвешивается тем же типом, что и одиночное значение, — поэтому int/json и есть то, что делает безопасной подстановку списка в IN ( … ). Пустой список и список внутри списка — отказ.

Множественность — свойство строки реестра, а не поля вообще: у одной и той же price в читающей команде может стоять float/json (диапазон, поиск), а в пишущей — float, потому что записать в колонку список нельзя.

Файлы. Команда, принимающая вложения, объявляет один параметр типа files. В подписи он показывается не полем, а пометкой команды — файлы прикладываются к вызову, а не передаются в параметрах. Движок раскладывает их до модуля, и команда читает готовые строки из $mParam['files']; обязательный files без вложений — отказ, вложения в команду без files — отказ. Команда, принявшая файлы, называет их в ответе под ключом files — так движок знает, что у файлов есть хозяин; безхозные он убирает сам.

Внутри команды

Описания делят работу

Агент собирает вызов из трёх слоёв текста, и у каждого своя глубина:

Первые два едут в перечне при каждом подключении, параметры агент берёт по одному инструменту, когда собрался им работать.

Библиотеки

Общая часть инструментов — модули-библиотеки melbis_inc_agent_* (util, tree, store, info, file). Юнит подключает библиотеку манифестом (includes: melbis_inc_agent_util.php=1) и объявляет алиас:

use MELBIS_INC_AGENT_UTIL as UTIL;
// ...
$lock = UTIL\Lock([$table]);

Публикуемые алиасы библиотека называет в своём манифесте, в поле param_info — по ним движок сверяет use при сохранении.

Резервная копия и обновление

Проектирование → AI-инструменты → Экспорт (право TOOL_EXPORT; у агента — tool_export) собирает весь реестр и модули за ним в один архив: index.json с деревом и md5-суммой каждого файла, tools/<unit>.json на инструмент — команды с полями, — units/ с модулями, манифестами и библиотеками, которые нашлись по манифестам. Гранты в архив не входят: они про людей магазина, а не про инструменты.

Двери импорта нет, и это решение: после установки владелец дорабатывает инструменты под себя, и обновление — всегда сравнение, а не накат. Агент распаковывает контейнер, по суммам индекса находит разошедшееся и переносит точечно: реестр — прямыми запросами, модуль — сохранением файла.

Эталоны

Живые образцы в демонстрационном магазине платформы: