TL;DR
SchemaShield-Lite — это слой проверки и ремонта вокруг агента с инструментами (function calling, когда модель сама выбирает функцию и заполняет её аргументы). Код проверяет вызов модели по схеме. Если вызов битый, модель получает точный список ошибок и один раз переделывает вызов. Если она не назвала инструмент, ей сначала показывают список доступных и просят выбрать, и только потом чинят аргументы.
Главная боль: после запуска агента интерфейс инструментов меняется. Переименовали параметры, переписали описания, добавили в каталог ещё один похожий инструмент. Точность агента падает, например с 0,79 до 0,58 на тестовом наборе, и модель то не называет инструмент, то забывает обязательные поля. Просто повторить запрос почти не помогает: модель получает тот же вход и делает ту же ошибку. Лучше всего чинит конкретная обратная связь вида «в вызове нет поля X, инструмент Y не выбран». Самый неожиданный вывод: переименования и переписанные описания почти не вредят. Главный вред приносят инструменты-двойники с похожим описанием, то есть разрастание каталога.
Метод двухшаговый. Шаг 1 — валидатор проверяет вызов и формирует отчёт об ошибках. Шаг 2 — модель получает задачу, битый вызов и отчёт и переделывает вызов, причём для ровно того инструмента, который уже выбран.
Схема метода
ШАГ 0 (профилактика): Компактная «карточка инструмента» в промпте → имя, описание, обязательные поля, типы
ШАГ 1: Модель делает вызов → валидатор (код) проверяет имя, обязательные поля, структуру → отчёт об ошибках
ШАГ 2а: Если имя инструмента не названо → повторный запрос со списком ВИДИМЫХ инструментов → конкретный вызов
ШАГ 2б: Ремонт: задача + битый вызов + отчёт ошибок → модель переделывает вызов для выбранного инструмента (один раз)
Ремонт срабатывает только при невалидном вызове. Это отдельные запросы, а не один промпт.
Пример применения
Метод сильнее всего там, где вызов инструмента сломан формально: нет имени, нет обязательного поля, не тот тип. Он слабее там, где вызов валидный, но смысл неверный.
Задача: Вы настроили в n8n Telegram-ассистента для интернет-магазина. У него есть инструменты amoCRM: создать_сделку, найти_контакт, добавить_комментарий. Через месяц коллега добавил создать_заявку с почти тем же описанием, а в создать_сделку переименовали поле телефон в phone_number. Агент стал присылать пустые вызовы или путаться между «сделкой» и «заявкой».
Промпт ремонтного шага. Его n8n отправляет модели после того, как проверка (узел IF/Code) нашла ошибку:
Ты исправляешь вызов инструмента для ассистента интернет-магазина.
<Задача>
Диалог с клиентом:
Клиент: «Хочу заказать чехол на iPhone 15, черный, 2 штуки. Меня зовут Игорь, телефон 8-916-555-01-23, доставка в Казань».
Задача>
<КарточкаИнструмента>
Имя: создать_сделку
Описание: создаёт сделку в amoCRM по подтверждённому заказу клиента
Обязательные поля:
- name (строка) — имя клиента
- phone_number (строка) — телефон клиента
- product (строка) — название товара
- quantity (целое число) — количество
- city (строка) — город доставки
КарточкаИнструмента>
<НевалидныйВызов>
{"tool": "создать_сделку", "arguments": {"name": "Игорь", "телефон": "8-916-555-01-23", "quantity": "две"}}
НевалидныйВызов>
<ОшибкиВалидации>
1. Отсутствует обязательное поле: phone_number
2. Отсутствует обязательное поле: product
3. Отсутствует обязательное поле: city
4. Поле quantity: ожидается целое число, получена строка «две»
5. Неизвестное поле: «телефон»
ОшибкиВалидации>
Исправь вызов. Выведи все обязательные поля из диалога, даже если невалидный вызов был пустым. Используй только поля из карточки. Верни только JSON вызова.
Результат: модель вернёт один исправленный JSON-вызов для создать_сделку с полями из карточки. Значения она возьмёт из диалога, а число запишет цифрой. Разбора и пояснений не будет, потому что промпт просит только JSON. Если ошибка осталась, следующий узел должен отправить диалог человеку, а не крутить цикл дальше.
Почему это работает
Слабость. Модель выбирает инструмент и заполняет аргументы только по тексту: описаниям, схемам, списку кандидатов. Когда текст меняется (старые названия полей в примерах, новый похожий инструмент), модель частично опирается на привычный образ. Отсюда пустой вызов, забытое поле, не тот инструмент. Если просто повторить запрос, она получает тот же вход и делает ту же ошибку. Повтор дал лишь +1–2 пункта точности.
Сильная сторона. Если сказать модели, что именно не так, исправить это ей легко. Она хорошо работает по чёткому списку замечаний и умеет достать нужные значения из диалога. Авторы проверили: «ты ошибся, вот список инструментов» (без самих ошибок) тоже слабо помогает. Тянет вперёд структурированный отчёт об ошибках, а не просто знание, что было неверно.
Как метод это использует. Проверка делается детерминированно, без LLM-судьи, и выдаёт конкретику. Ремонт идёт для одного уже выбранного инструмента, поэтому путаница между похожими инструментами не возвращается. А шаг «сначала выбери инструмент из видимого списка» закрывает самый частый сбой, когда имя не названо.
Рычаги управления: - Карточка инструмента в первом запросе работает как профилактика. На BFCL она подняла точность с 0,64 до 0,81 уже без ремонта. В ремонтном промпте её можно убрать: качество ремонта не падает. - Инструкция «выведи все поля из диалога, даже если вызов пуст» критична, когда агент присылает пустой вызов. - Бюджет токенов на ремонт должен быть больше, чем на первый ответ. Авторы подняли его до 512 токенов, потому что на длинных диалогах ремонтный вывод обрезался. - Один ремонт, не цикл. Больше одного круга авторы не проверяли.
Шаблон промпта
Два промпта, как в оригинале: сначала повтор выбора инструмента (если имя не названо), затем ремонт.
Промпт 1. Повтор выбора инструмента (если имя не названо):
<Роль>
Ты выбираешь инструмент для выполнения задачи пользователя.
Роль>
<Задача>
{диалог_или_запрос}
Задача>
<ДоступныеИнструменты>
{список_инструментов_с_именем_и_кратким_описанием}
ДоступныеИнструменты>
В предыдущем ответе ты не указал имя инструмента. Выбери ОДИН инструмент из списка выше и сформируй конкретный вызов с аргументами. Не придумывай инструменты, которых нет в списке.
Промпт 2. Ремонт вызова:
<Роль>
Ты исправляешь невалидный вызов инструмента.
Роль>
<Задача>
{диалог_или_запрос}
Задача>
<КарточкаИнструмента>
Имя: {имя_инструмента}
Описание: {описание}
Обязательные поля и типы:
{список_полей_с_типами}
КарточкаИнструмента>
<НевалидныйВызов>
{исходный_вызов_модели}
НевалидныйВызов>
<ОшибкиВалидации>
{нумерованный_список_ошибок_валидатора}
ОшибкиВалидации>
Исправь вызов. Выведи ВСЕ обязательные поля из диалога, даже если невалидный вызов был пустым. Используй только поля из карточки. Верни только исправленный вызов в формате {формат_вывода}.
Что подставлять:
- {диалог_или_запрос} — исходный контекст без изменений.
- {список_инструментов_...} — только те инструменты, которые агент видит сейчас, не весь каталог компании.
- {список_полей_с_типами} — из схемы инструмента, только актуальные имена полей.
- {нумерованный_список_ошибок_валидатора} — текст ошибок, которые выдаёт проверка: «нет поля X», «поле Y должно быть числом». Не пишите «ошибка в вызове».
- {формат_вывода} — JSON или то, что принимает ваша автоматизация.
🚀 Быстрый старт — вставьте в чат:
Вот шаблон ремонта вызова инструмента (SchemaShield-Lite). Адаптируй под мою задачу: [опиши агента и его инструменты].
Задавай вопросы, чтобы заполнить поля.
[вставить шаблон выше]
LLM спросит, какие у агента инструменты, какие у них обязательные поля и как выглядит типичная ошибка. Эти данные нужны, чтобы собрать карточку инструмента и список ошибок валидации. Дальше она возьмёт структуру из шаблона и соберёт готовые промпты под вашу автоматизацию.
Ограничения
⚠️ Нужна проверка в коде или автоматизации: сам метод держится на валидаторе, который проверяет имя инструмента и обязательные поля. Без него отчёта об ошибках не будет. В n8n, Make и подобных системах это узел с условием, но его надо собрать.
⚠️ Валидные, но неверные вызовы метод не ловит: если вызов формально корректен, а значение неправильное (не тот товар, не та дата), ремонт не запускается. В тестах таких было мало, но в реальных задачах их может быть больше.
⚠️ Цена в токенах и времени: ремонт добавил примерно треть токенов на одном наборе и почти половину на другом. Ещё около 3,5 с задержки на запрос в среднем. Платишь только при срабатывании проверки, но это 16–32% запросов.
⚠️ Сбои исходного стенда: дрейф в тестах синтетический, придуман авторами. Основные результаты получены на одной небольшой открытой модели (Qwen3.5-9B) с репликой на другой модели того же семейства. Проверки на GPT, Claude, Gemini нет.
⚠️ Не всё чинится: часть случаев остаётся нерешённой, особенно когда модель так и не называет инструмент или упорно не заполняет поля.
Как исследовали
Авторы взяли два набора тестов для вызова функций: диалоговый DICE-BENCH (300 примеров) и разнородный BFCL (200 примеров). В каждый вносили три вида «дрейфа»: устаревшие примеры в описании, переименованные параметры и лишние инструменты-двойники в списке. Для каждого примера мерили точность три раза: на исходном интерфейсе, на изменённом и после ремонта.
Центральный контроль: а не помогает ли просто вторая попытка? Авторы сравнили ремонт с «повторить тот же запрос» и с «сказать, что вызов неверный, и перечислить инструменты». Точность на DICE: повтор 0,727, список инструментов 0,753, SchemaShield-Lite 0,813. На BFCL: 0,610, 0,625 и 0,795. Вывод: помогает именно отчёт об ошибках, а не сам факт повторной попытки.
Любопытный результат даёт разбор по типам дрейфа. Старые примеры в описании снижали точность всего на 0,013, переименование параметров на 0,007. А один добавленный инструмент-двойник дал падение 0,047, почти весь итоговый ущерб (0,050). Авторы сами называют это уточнением к привычному мнению, что виноваты правки описаний.
Ещё один неожиданный итог: карточка инструмента не нужна на этапе ремонта. Вариант без неё иногда ремонтировал даже лучше (на BFCL 0,820 против 0,795). Зато в первом запросе она работает как профилактика. Ремонт срабатывал без вреда: ни одного исходно правильного вызова он не сломал. Результаты на BFCL: с 0,580 до 0,805, на DICE: с 0,717 до 0,813.
Адаптации и экстраполяции
Это не часть метода авторов, а мои переносы вывода в настройку агента без кода.
🔧 Техника: ремонт вручную в чате или в Cursor/Claude Code → дать точный текст ошибки, а не «попробуй ещё»
Когда агент вызвал инструмент неправильно, вставьте ему сообщение вида:
Твой вызов {инструмент} невалиден. Ошибки: 1. Нет поля {поле} 2. Поле {поле2} должно быть {тип} Исправь вызов. Выведи все обязательные поля из контекста диалога. Не меняй инструмент.Идея из статьи: конкретные ошибки + запрет менять инструмент. Саму ручную схему авторы не проверяли.
Правило для инструкции агента (CLAUDE.md, системный промпт), основанное на главном выводе про инструменты-двойники:
Правила работы с инструментами: - Если два инструмента похожи по описанию, выбирай по условию: {условие_разграничения}. - Если не уверен, какой инструмент выбрать, назови кандидатов и спроси уточнение, а не вызывай наугад. - Всегда указывай имя инструмента явно.Статья показала, что вред несут похожие инструменты в списке. Эти правила проверены не были.
Ресурсы
- Работа: Robust Function Calling for LLM Agents Under Interface Drift — Aniruddha Chattopadhyay, Kanishka Halder, Partha Sarathi Roy (независимые исследователи)
- Код и данные: публичный репозиторий
tool-drift(GitHub, AniruddhaChattopadhyay/tool-drift) - Бенчмарки: DICE-BENCH, Berkeley Function Calling Leaderboard (BFCL)
- Связанные работы: Guo et al. (переписывание описаний инструментов), Faghih et al. (чувствительность выбора инструмента к описанию)
