Ассистенты без MCP
MCP — не единственный способ, которым ассистент вызывает инструменты. Если ваш ассистент работает на GigaChat, YandexGPT или на своей модели в своём контуре, он подключается через слой функций: каталог инструментов тот же, меняется только форма вызова.
Слой функций готовится. Ядро — перевод каталога в формат функций, разбор вызовов, защита от повторов — написано и покрыто тестами. Транспорт для этих клиентов и адаптеры под конкретные платформы пока нет: контракты вызова функций у провайдеров меняются, и писать их по памяти мы не станем. Нужна конкретная платформа — напишите в поддержку, мы уточним контракт и вернёмся с примером.
Как это устроено
Инструмент описан один раз: имя, параметры, область действия, нужно ли подтверждение. Адаптер переводит описание в форму, понятную конкретному клиенту, а проверки, защита от дублей, подтверждения и журнал остаются общими. Иначе поведение начнёт различаться, и документация станет врать одной из половин.
flowchart LR
core["Каталог инструментов:<br/>проверки, дубли,<br/>подтверждения, журнал"]
mcp["Нативный MCP<br/>stdio и HTTP"]
fn["Формат функций<br/>GigaChat, YandexGPT,<br/>свои SDK"]
tag["Разбор из текста<br/>локальные модели"]
api["API Инвойсбокса"]
core --> mcp
core --> fn
core --> tag
core --> apiЧто меняется в описании инструмента
- Схема сводится к общему подмножеству. Плоские объекты, строки, числа, перечисления и массивы объектов — без
anyOfиoneOf: часть платформ отбрасывает такое поле вместе со значением. Пара «единица измерения или её код» превращается в два необязательных поля, а согласованность проверяет сервер. - Описания короче. У провайдеров есть предел длины описания функции и поля; сервер отдаёт сжатый вариант, сохраняя главное.
- Подтверждение только двухфазное. Метки «изменяет данные» в формате функций нет вовсе, и попросить человека посреди вызова платформа не умеет. Поэтому первый вызов возвращает сводку и одноразовый токен, а второй исполняет — то же, что в безопасности.
- Описания по-русски. Русскоязычным моделям описания отдаются на русском: на переводе теряются «счёт-фактура» и «УПД», и модель начинает путать их с инвойсом.
Как выглядит функция
{
"name": "create_order",
"description": "Выставляет счёт покупателю и возвращает ссылку на оплату. Покупателем может быть организация, ИП или физлицо. Подтверждение в два шага: первый вызов возвращает сводку и confirmation_token, второй исполняет.",
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["description", "customer", "basket_items", "amount", "vat_amount", "currency_id", "expiration_date"],
"properties": {
"description": { "type": "string" },
"amount": { "type": "string", "description": "сумма в копейках строкой: 12200 = 122,00 ₽" },
"vat_amount": { "type": "string" },
"currency_id": { "type": "string", "enum": ["RUB", "USD", "EUR", "GBP", "CNY"] },
"customer": {
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["legal", "private"] },
"name": { "type": "string" },
"vat_number": { "type": "string" }
}
},
"confirmation_token": { "type": "string" }
}
}
}Локальные модели, которые вызывают инструмент разметкой
Модели семейства Hermes и родственные вставляют вызов в текст ответа, а сервер вывода разбирает его. Разбор ненадёжен, поэтому сервер отклоняет невалидное вместо того, чтобы дополнять догадками.
<tool_call>
{"name": "get_order", "arguments": {"order_id": "01771534-1a57-f184-dee3-ebeb91dded75"}}
</tool_call>- Невалидный JSON, выдуманное имя функции и аргументы массивом отклоняются с причиной.
- Два одинаковых вызова в одном ответе — реальное поведение части моделей — сводятся в один: иначе получился бы второй счёт.
- В контуре без авторизации и ролей права по умолчанию — только чтение. Запись включается явно, и ответственность за действия ассистента остаётся на владельце контура: сервер не отличает «модель ошиблась» от «оператор попросил» — кроме подтверждений и потолков.
Чего мы не поддерживаем
- Прямое подключение модели к API Инвойсбокса без нашего слоя: тогда исчезают защита от дублей, подтверждения, потолки и журнал — то есть всё, что делает канал безопасным.
- Автоматическое исполнение денежной операции «агентом без человека» — ни в одном протоколе, даже если платформа это позволяет.
- Обещание совместимости с платформой, на которой мы не прогнали сквозной тест на живом ключе.