Руководство Melbis Shop: оглавление
AI-инструмент — это модульный скрипт, который владелец магазина
регистрирует для AI-помощника. При подключении агент узнаёт, сколько их
у магазина, перечень берёт вызовом tool_list и запускает
инструменты по имени — так магазин получает собственные «кнопки»:
завести товар так, как заведено у вас, закрыть заказ по вашим
правилам.
Перечень — данные, а не код. Новый инструмент появляется у агента без
обновления программы: строки в реестре плюс модуль в units.
Реестр владелец ведёт в программе: Проектирование →
AI-компоненты, вкладка «AI-инструменты»; как заполнять окно —
«AI-инструменты» в руководстве
пользователя.
Реестр — единственный источник подписи: агент узнаёт из него и что инструмент делает (описания), и как его звать (команды с полями). Модуль себя не описывает — в нём остались только тела команд.
Права выдаются людям: агент входит в магазин под
логином того сотрудника, что сидит за клавиатурой — отдельного
пользователя ИИ не существует, — и грант считается по нему: строка его
собственная или строка его группы. Отсюда и то, что один и тот же
инструмент у разных сотрудников умеет разное. Право — это команда:
сколько команд, столько прав. Держатель права «Изменение настроек AI»
(PUT_AGENT_OPTION) владеет всеми командами автоматически.
Перечень открыт всем: агент видит все инструменты и все команды —
выданные строкой may:, остальные строкой
also:; пустой may: — он подсказывает
сотруднику, у кого просить доступ.
Один инструмент покрывает одну сущность целиком — «Пользователи»,
«Задачи», — и это один модуль. Команда называет одно её дело, и её имя в
реестре — это имя функции модуля: CmdList,
CmdDone. Приставка Cmd отличает команду от
функций-помощников, которые агенту не видны.
Право выдаётся на команду целиком, со всей её параметрической
широтой, — поэтому команды делят по праву, а не по значениям параметра.
В «Планировщике» CmdNoteAdd только комментирует, и его
выдают всем, кто видит задачу, а двигают задачу CmdState,
CmdPass, CmdDone и CmdClose —
каждую можно выдать отдельно.
Модуль объявляет пространство имён своим именем заглавными, и движок
зовёт функцию команды напрямую — ни точки входа, ни разводящего
switch в модуле нет. Этим же именем агент называет
инструмент: в перечне и в tool_run модуль
melbis_agent_currency.php — это
MELBIS_AGENT_CURRENCY, а рядом перечень показывает путь по
дереву реестра, Business -> Currencies:
namespace MELBIS_AGENT_USER;
/**
* Function CmdList
* The users of the store
**/
function CmdList($mUserId, $mParam)
{
// ...
}$mUserId — id сотрудника, под которым открыта
сессия, то есть человека за клавиатурой. Он всегда настоящий,
безымянной сессии не бывает, и проверять его на ноль незачем. Им
подписывается авторство: задач, записей, изменений.$mParam — поля команды готовым массивом, уже взвешенные
по реестру (см. «Рутина — дело движка»).Функция возвращает массив, и он целиком уезжает агенту — как он устроен, описано ниже, в «Ответе команды».
До модуля движок проверяет всё, что записано в реестре, и модулю достаются только правила предметной области:
null считается неназванным — так агент «снимает» поле;| Тип | Что принимает | Что получает модуль |
|---|---|---|
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. Помощник кладёт туда
строки массивом — прямо в вызов или, когда их много, в файл полей вызова
params_source, который пишет его скрипт. Модуль
получает массив в обоих случаях и файлов не читает вовсе.
Разница не в удобстве, а в возможности: партия в пятьсот строк, набранная помощником в параметрах вызова, — это сотни тысяч знаков в его собственном сообщении, а файл, который написал его скрипт, не проходит через него совсем.
Список вместо одного значения. К любому типу, кроме
json, jsonl и files, дописывается
хвост /json — int/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 — так движок знает, что у файлов есть хозяин;
безхозные он убирает сам.
result=false.apply.
Это и есть подтверждение — в контракте команды, а не в уговорах. Строки
наборов — права, связи — удаляются сразу: модуль сам передаёт
apply.Функция команды возвращает массив. Движок отдаёт его агенту целиком, MCP-сервер раскладывает строки таблиц по файлам, и агент получает ответ одной формы — кто бы ни ответил: команда или движок, отказавший до неё.
Ключей пять, и в ответе они есть всегда:
| Ключ | Что это |
|---|---|
result |
true — сделано, false — отказ; третьего не
бывает |
message |
что произошло и что делать дальше: коротко и по-английски — его читает модель и пересказывает человеку своими словами |
detail |
значения, которые вернула команда: id новой строки,
ids нескольких, found — сколько нашлось,
состояние объекта после действия |
tables |
строки: именованные списки
{"<имя>": [{…}, {…}]} |
files |
вложения, которые команда оставила себе |
result и message пишет команда.
detail, tables и files, которых
она не написала, движок дописывает пустыми: {},
{} и []. Всё, что команда возвращает сверх
строк и вложений, кладут в detail.
Машинных слов-кодов в ответе нет: агенту хватает result
и текста. Значения в detail называют словами предметной
области — id, version_id, found,
— а не data1.
Строки — только в tables. Всё, что
может оказаться длиннее нескольких строк, команда кладёт в
tables. MCP-сервер пишет каждую таблицу в файл на
компьютере агента и оставляет в ответе число строк, путь к файлу и
первые три строки. Список, положенный в detail, агент
получит целиком, прямо в переписку: тысяча товаров — это сотни тысяч
знаков в его памяти. Связанное отдают отдельными таблицами, строки
ссылаются друг на друга по id, а соединяет их агент своим
скриптом. Так отвечает на чтение библиотека
melbis_inc_agent_table:
return [
'result' => true,
'message' => 'The tables asked for',
'tables' => ['currency' => $rows]
];Имя таблицы становится частью имени файла, поэтому это одно слово:
currency, goods, errors.
Вложения. Команда, принимающая файлы, называет
принятые под ключом files (см. «Рутина — дело движка»).
Если она их не назвала — отказала или забыла, — движок убирает все файлы
вызова и дописывает к message:
The 2 file(s) sent with this call are gone: the command [CmdAdd] did not take them.
Отказ — это result: false и message,
который называет следующий шаг. Кто отказал, агенту не важно: движок до
модуля отказывает в той же форме.
| Кто | Когда | Пример message |
|---|---|---|
| движок | незнакомая команда | Unknown command [CmdNope]. The tool has: CmdList, CmdAdd, CmdUpdate, CmdRemove |
| движок | команда не выдана | The command [CmdRemove] of the tool [Business -> Currencies] is not granted to you…
— и где её выдают |
| движок | поле, которого команда не объявила | The command [CmdAdd] has no field [nme] - … — и какие
поля она принимает |
| движок | нет обязательного поля | The command [CmdAdd] needs [name] - … — и описание
поля |
| движок | тип не сошёлся | The [id] takes a whole number, and [abc] is not one |
| команда | правило предметной области | Nothing was named to change |
| команда | таблица занята | The [currency] tables are busy |
| команда | удаление без apply |
Deleting 1 row(s) of <таблица>, and with them 3 <таблица>. Say apply |
Последняя строка — первый шаг сноса со второго вызова: агент
показывает человеку, что уйдёт следом, и после согласия повторяет вызов
с apply: true.
Отказ — это ответ. Падение — когда ответа нет: инструмента с таким
адресом нет, модуль не найден, в нём нет функции команды, она прервалась
исключением или ответила без result. Тогда движок отвечает
текстом ошибки, и агент получает его отказом без JSON:
ACCESS_DENIED: no such tool: <UNIT>;Module not found! [units/<модуль>.php];Function not found! [<UNIT>\<команда>];<UNIT>\<команда> failed: <текст исключения>;<UNIT>\<команда> answered without result.Команда, которая сама ловит ошибку и отвечает
result: false со словами, лучше падения: из текста
исключения агент не поймёт, что делать дальше.
MCP-сервер отдаёт агенту ответ команды с теми же ключами. Строки
каждой таблицы он пишет в файл и ставит на их место, где они лежат, а
рядом добавляет time:
{"result": true, "message": "The tables asked for",
"detail": {},
"tables": {"currency": {"rows": 3,
"file": "D:\\Melbis\\shop.example.com\\mcp\\claude-code\\prices-sept\\tables\\MELBIS_AGENT_CURRENCY.CmdList.currency.jsonl",
"head": ["{\"id\":\"1\",\"code\":\"USD\"}", "…", "…"]}},
"files": [],
"time": {"server": 11, "module": 1, "trip": 46}}tables — по каждой таблице: rows — сколько
строк, file — где они лежат, head — первые три
строки текстом, обрезанные до 300 символов; с заданием into
ещё job и job_rows;time — server, module и
trip; с debug ещё sql и
sql_ms.Файл таблицы перезаписывается следующим вызовом той же команды.
result: false приходит агенту с признаком ошибки и тем же
JSON. Всё, что принимает и отдаёт вызов, — «tool_run», много вызовов сразу — «tool_pool».
Добавление. Модуль возвращает:
return [
'result' => true,
'message' => 'The row of currency is added',
'detail' => ['id' => 15]
];Агент получает:
{"result": true, "message": "The row of currency is added",
"detail": {"id": 15}, "tables": {}, "files": [],
"time": {"server": 14, "module": 3, "trip": 52}}Изменение, в котором ничего не названо — отказ самой команды:
{"result": false, "message": "Nothing was named to change",
"detail": {}, "tables": {}, "files": [],
"time": {"server": 9, "module": 1, "trip": 44}}Удаление. Первый вызов, без apply:
{"result": false, "message": "Deleting 1 row(s) of <таблица>, and with them 3 <таблица>. Say apply",
"detail": {}, "tables": {}, "files": []}Второй вызов, с apply: true, после согласия
человека:
{"result": true, "message": "1 row(s) of <таблица> gone…",
"detail": {}, "tables": {}, "files": []}Отказ движка — модуль не запускался, поэтому в
time нет module:
{"result": false, "message": "Unknown command [CmdNope]. The tool has: CmdList, CmdAdd, CmdUpdate, CmdRemove",
"detail": {}, "tables": {}, "files": [],
"time": {"server": 6, "trip": 47}}Непринятые вложения — команда отказала, и два отправленных файла убраны:
{"result": false,
"message": "… The 2 file(s) sent with this call are gone: the command [CmdAdd] did not take them.",
"detail": {}, "tables": {}, "files": []}Агент собирает вызов из трёх слоёв текста, и у каждого своя глубина:
descr инструмента — что он делает и когда его
звать;descr команды — смысл и оговорки поведения («пароль не
задаётся и возвращается один раз»);descr параметра — что это за значение и каким оно
должно быть; словарь допустимых значений называется здесь словами
(a value of STORE_KIND_KEY).В перечне tool_list едет только начало описания
инструмента — первое предложение — и слова его команд. Описания команд и
параметры агент берёт по одному инструменту, когда собрался им
работать.
Общая часть инструментов — модули-библиотеки
melbis_inc_agent_* (system,
table, query, store,
file). Юнит подключает библиотеку манифестом
(includes: melbis_inc_agent_system.php=1) и объявляет
алиас:
use MELBIS_INC_AGENT_SYSTEM as SYS;
// ...
$mine = SYS\RightOne('topic', $mUserId, 'descr', $topic_id);Публикуемые алиасы библиотека называет в своём манифесте, в поле
param_info — по ним движок сверяет use при
сохранении.
Проектирование → AI-компоненты → Экспорт (право
TOOL_EXPORT; у агента — tool_export) собирает
весь реестр и модули за ним в один архив: index.json с
деревом и md5-суммой каждого файла, tools/<unit>.json
на инструмент — команды с полями, — units/ с модулями,
манифестами и библиотеками, которые нашлись по манифестам. Гранты в
архив не входят: они про людей магазина, а не про инструменты.
Двери импорта нет, и это решение: после установки владелец дорабатывает инструменты под себя, и обновление — всегда сравнение, а не накат. Агент распаковывает контейнер, по суммам индекса находит разошедшееся и переносит точечно: реестр — прямыми запросами, модуль — сохранением файла.
Где лежит актуальный набор дистрибутива и как по нему обновлять инструменты магазина, агенту сказано в документации MCP-сервера — достаточно попросить.
Живые образцы в демонстрационном магазине платформы:
melbis_agent_key_value.php — компактная сущность:
словари реестра настроек, пять команд;melbis_agent_user.php — сущность на четыре команды:
генерация пароля, защита удаления историей, частичный
CmdUpdate;melbis_agent_task.php — зеркало планировщика программы:
открытые задачи и архив по человеку, как их грузит окно; движение задачи
— отдельными командами по праву исполнителя и автора, как в карточке
задачи; приватность одной формулой во всех выборках.