Tool calling в AI-агентах: как модель вызывает реальные функции
Модель предлагает вызов; приложение решает, выполнять ли его
Схема, права, idempotency, read-back и маршруты отказа
Авторский TOOL-GATE-8: учебный контракт, а не клиентские метрики
tool calling AI agents, архитектура вызова функций и контроль инструментов

$ call --contract TOOL-GATE-8
> propose: tool / typed arguments
> gate: identity / tenant / policy
> execute: bounded adapter / idempotency
> verify: read-back / receipt / routeTool calling — это договор между моделью, приложением и внешним инструментом. Модель предлагает имя функции и аргументы; приложение проверяет схему, личность, разрешения и риск, выполняет допустимое действие и возвращает результат. Модель не получает прямой доступ к базе, почте или CRM. Эта граница важнее синтаксиса вызова.
Разберём технический путь от запроса до проверенного результата. Пример ниже синтетический: он показывает проектное решение, не измеренную производительность клиентской системы. Если вы ещё выбираете, нужен ли агент, начните с дерева решений. Широкие вопросы внедрения относятся к AI-специалисту в Армении.
Проблема и требования
Представим запрос: «Найди актуальную карточку поставщика и подготовь изменение адреса». Агенту нужны чтение записи, проверка источника изменения и черновик. Запись в CRM — отдельная операция с разрешением человека. Ответ модели «адрес обновлён» ничего не доказывает: требуется подтверждение системы назначения и повторное чтение. Для задачи задают владельца, tenant, идентификатор объекта, лимит времени, допустимые инструменты и критерий завершения.
Функции полезны там, где нужен внешний факт или действие. Обычный текстовый ответ не должен маскировать отсутствие вызова. И наоборот, не давайте модельному выбору функцию записи только потому, что она доступна в API. Каталог инструментов должен содержать минимальные разрешённые операции для данной стадии.
Архитектура: предложение, шлюз, исполнение, квитанция
Поток начинается с аутентифицированного запроса. Оркестратор формирует контекст с задачей и ограниченным набором схем. Модель выдаёт предложение вызова. Шлюз принимает его как недоверенный ввод: проверяет название, строгую схему, размер, tenant и права пользователя, а также политику для типа действия. Только после этого адаптер вызывает внешний сервис. Результат нормализуется, секреты удаляются, а оркестратор сохраняет квитанцию и передаёт модели только нужный фрагмент.
| Узел | Контракт | Что проверять |
|---|---|---|
| Модель | имя инструмента и JSON аргументы | Схема, допустимые значения, лимит шагов |
| Policy gateway | личность, tenant, действие, объект | Права независимо от модели |
| Адаптер | таймаут, idempotency key, код результата | Повтор, отмена, неизвестный исход |
| Хранилище состояния | request ID, версия, маршрут, квитанция | Восстановление после сбоя |
| Проверка результата | read-back из системы назначения | Действительно ли действие принято |
Эта схема не обещает безопасную автономию сама по себе. Она делает место отказа видимым. Для интеграции процесса смотрите AI-автоматизацию, для управления форматом ответа — prompt engineering.
Авторский пример: TOOL-GATE-8
Минимальный пример использует supplier.lookup как read-only вызов и supplier.draft_change как создание черновика. Запись supplier.commit_change не предлагается модели до одобрения. Все идентификаторы и значения здесь учебные; код показывает контракт, а не готовый SDK.
type ProposedCall = { name: string; args: unknown; requestId: string };
async function execute(call: ProposedCall, ctx: Context) {
const tool = registry.get(call.name);
if (!tool) return deny("unknown_tool");
const args = tool.schema.parse(call.args);
if (!policy.allows(ctx.user, ctx.tenant, call.name, args)) return deny("forbidden");
if (tool.risk === "write" && !ctx.approvalFor(call.requestId)) return hold("approval_required");
const key = `${ctx.tenant}:${call.requestId}:${call.name}`;
const result = await withTimeout(tool.run(args, { key, tenant: ctx.tenant }), tool.timeoutMs);
const checked = tool.risk === "write" ? await tool.readBack(args) : result;
await receipts.save({ key, tool: call.name, route: "verified", resultRef: checked.ref });
return redact(checked);
}В рабочей системе ошибка парсинга возвращает структурированный отказ, а не второй вызов с более широкими правами. Idempotency key нужен, чтобы повтор сетевого запроса не создал второе изменение. При таймауте результат может быть неизвестным: сначала запросите статус по тому же ключу, затем решайте о повторе. Журнал хранит ссылки и reason codes, не полные секретные payload.
Ключевые компоненты и границы
Схема инструмента. Имя, назначение, поля, допустимые значения и тип результата должны быть версионированы. Проверка на сервере обязательна даже при строгом ответе модели. Пустая строка, неверный тип, неожиданный tenant и слишком большой payload отклоняются до адаптера.
Права и подтверждение. Пользовательская сессия определяет область данных. Инструкции в документе или ответе инструмента не меняют права. Чтение, создание черновика и окончательная запись — разные capability. Не передавайте сервисный ключ модели и не полагайтесь на текст «пожалуйста, не записывай» как на контроль.
Состояние. Сохраните request ID, выбранный инструмент, версию схемы, idempotency key, причину отказа и терминальный маршрут. Для длинной задачи нужен механизм возобновления, который не повторит уже выполненную запись.
Результат. Нормализуйте ошибки внешнего API. Различайте denied, invalid, timeout, unknown_outcome, completed и verified. После записи прочитайте объект из целевой системы или получите эквивалентное подтверждение. Успех HTTP-вызова сам по себе не доказывает бизнес-результат.
Failure modes: где агент ошибается
- Несуществующий инструмент. Модель предлагает имя, которого нет. Возвращайте явный отказ и ограничивайте число повторных предложений.
- Неверные аргументы. Схема принимает только объявленные поля; объект или tenant из контекста сервера нельзя подменить текстом.
- Prompt injection в результате чтения. Текст поставщика может содержать команды. Рассматривайте его как данные, а не новую инструкцию или разрешение.
- Таймаут после записи. Состояние неизвестно. Поиск по idempotency key и read-back предшествуют повтору.
- Устаревшая запись. При изменении версии между чтением и записью требуется конфликт и новая проверка, а не тихое перезаписывание.
- Ложное завершение. Модель сообщает об успехе без квитанции. UI должен показывать проверенный статус из приложения.
- Цепочка расходов. Лимиты шагов, времени и стоимости останавливают цикл независимо от желания модели продолжать.
Тестирование и production-чек
Сначала соберите fixture-набор: корректный вызов, неизвестное имя, неверный тип, чужой tenant, недостаток прав, injected текст результата, таймаут до и после выполнения, дублированный request ID, конфликт версии и невозможность read-back. Для каждой фикстуры укажите ожидаемый маршрут и запрещённый побочный эффект. Проверяйте адаптер и policy gateway отдельно от модели, затем весь цикл с записью квитанции.
Перед релизом зафиксируйте версии схем и политик, владельца инструмента, технический бюджет, процедуру остановки и восстановления, правила redaction и срок хранения журнала. Прогоните staging с тестовыми правами. Пилотируйте только ограниченное множество задач; расширяйте доступ после проверки ошибок и ручной приёмки. Технический аудит может выявить, нужна ли вообще агентная оркестрация: иногда достаточно детерминированного workflow.
Если нужно спроектировать конкретный набор функций, права и проверку результатов для вашего процесса, запросите архитектурное ревью. Дайте схему систем, список допустимых действий и один пример неуспешного сценария.
require(schema.valid && policy.allows(user, tenant, tool));
if (tool.isWrite && !approval.valid) return hold;
result = await executeOnce(idempotencyKey);
if (result.unknown) return reconcileBeforeRetry;
return verifyByReadBack(result);