3,583 papers
arXiv:2509.18076 87 22 сент. 2025 г. FREE

Template-based Function Calling: структурированное рассуждение вместо чёрного ящика

КЛЮЧЕВАЯ СУТЬ
LLM постоянно ломают вызовы функций API: передают "2" (строку) вместо 2 (числа), выбирают не ту функцию из десятка похожих, теряют обязательные параметры. Ты не видишь почему модель решила передать именно это значение. Template-based Function Calling превращает вызов функции в 7 прозрачных шагов: identify (какие функции подходят?) → decide (точно релевантны?) → examine (изучить документацию) → extract (вытащить параметры из запроса) → convert (поменять типы если надо) → draft (черновик вызова) → revalidate (финальная проверка). Модель не может пропустить проверку типов или забыть про обязательный параметр — каждый шаг явно прописан. Вместо чёрного ящика получаешь полную трассировку рассуждений в теге , а финальный вызов в .
Адаптировать под запрос

TL;DR

Template-based Function Calling — метод, который переносит рассуждения о вызове функций внутрь промпта через 7 чётких шагов: от идентификации нужной функции до финальной проверки параметров. Работает двумя способами: через структурированный промпт (template prompting) или через файнтюнинг на датасете примеров (ToolGT). Вместо того чтобы модель сразу выдавала вызов функции, она проходит curriculum — пошаговый чеклист, где каждый шаг проверяет свою часть задачи.

LLM часто проваливают function calling: передают неправильные параметры (вместо строки — число), выбирают не ту функцию из десятка похожих, или неверно понимают что хочет пользователь. Проблема в том, что модели делают это как чёрный ящик — выдают function_name(param1="value") без объяснений. Ты не видишь почему модель выбрала именно эту функцию, откуда взяла значение параметра, или учла ли она все ограничения из документации. Free-form CoT ("подумай шаг за шагом") помогает слабо — модель может думать вслух, но без чёткой структуры она всё равно пропускает критические проверки.

Template решает это через жёсткий чеклист из 7 шагов, который модель обязана пройти: (1) какие функции подходят по описанию, (2) точно ли они релевантны запросу, (3) внимательно изучить документацию, (4) извлечь параметры из запроса пользователя, (5) конвертировать типы если нужно, (6) составить черновик вызова, (7) перепроверить всё ещё раз. Каждый шаг фокусирует внимание модели на конкретной части задачи — как мысленный чек-лист перед отправкой важного письма.


🔬

Схема метода

ШАГ 1: Identify → какие функции подходят по описанию
ШАГ 2: Decide → точно ли они релевантны запросу 
ШАГ 3: Examine → изучить документацию функции
ШАГ 4: Extract → извлечь параметры из запроса
ШАГ 5: Convert → конвертировать типы параметров
ШАГ 6: Draft → составить черновик вызова
ШАГ 7: Revalidate → финальная проверка

Все шаги выполняются в одном промпте через тег <THINKING>, после чего модель выдаёт финальный вызов в <FUNCTION>.


🚀

Пример применения

Задача: Ты ищешь квартиру в Москве. Хочешь снять двушку до 100к в месяц недалеко от метро Маяковская. У тебя есть доступ к API Авито с функциями поиска.

Промпт:

У меня есть API Авито для поиска квартир. Найди мне двушку в Москве, 
до 100 тысяч в месяц, желательно рядом с метро Маяковская.

Доступные функции:
- search_apartments(city, rooms, max_price, metro_station=None)
- get_apartment_details(apartment_id)

Перед тем как вызвать функцию, пройди по шагам:
1. Определи какая функция подходит
2. Проверь что она точно отвечает на запрос
3. Изучи документацию функции
4. Извлеки параметры из моего запроса
5. Проверь нужна ли конвертация типов
6. Составь черновик вызова
7. Перепроверь всё ещё раз

Выдай рассуждения в <THINKING>, вызов в <FUNCTION>.

Результат:

Модель покажет пошаговое рассуждение:

  • Шаг 1: "Подходят обе функции, но search_apartments — для поиска, get_apartment_details — для деталей конкретной квартиры"
  • Шаг 2: "Мне нужен поиск, значит search_apartments"
  • Шаг 3: "Смотрю параметры: city (обязательный), rooms (обязательный), max_price (обязательный), metro_station (опциональный)"
  • Шаг 4: "Из запроса: city='Москва', rooms=2, max_price=100000, metro_station='Маяковская'"
  • Шаг 5: "rooms должен быть int, max_price тоже int — конвертирую '100 тысяч' в 100000"
  • Шаг 6: search_apartments(city="Москва", rooms=2, max_price=100000, metro_station="Маяковская")
  • Шаг 7: "Проверяю: все обязательные параметры заполнены, типы совпадают, metro_station опциональный но указан"

Финальный вызов точно соответствует API, все параметры корректны.


🧠

Почему это работает

Слабость LLM: Модели часто ошибаются в function calling, потому что обрабатывают всю задачу одним махом — одновременно выбирают функцию, извлекают параметры, конвертируют типы. Это как пытаться сделать 7 дел за раз — что-то обязательно забудешь. Особенно страдают мелкие детали: модель может выбрать правильную функцию, но передать rooms="2" (строку) вместо rooms=2 (число), или вообще пропустить опциональный параметр который критичен.

Сильная сторона LLM: Модели отлично следуют пошаговым инструкциям. Если разбить задачу на мелкие шаги и явно сказать "сейчас делаем только это" — они фокусируются. Это как готовить по рецепту: не "сделай торт", а "шаг 1: взбей яйца, шаг 2: добавь муку...". Каждый шаг простой, результат надёжный.

Как template использует это: Template превращает размытую задачу "вызови функцию" в чек-лист из 7 конкретных действий. Модель не может перескочить через проверку типов (шаг 5) или пропустить изучение документации (шаг 3) — каждый шаг явно прописан. Это как дорожная полиция на каждом этапе: не проверил параметры на шаге 4 — не пройдёшь дальше.

Рычаги управления:

  • Число шагов: 7 шагов — для сложных API. Для простых функций можно сократить до 4-5 (убрать Convert, Revalidate)
  • Формат вывода: Уберите <THINKING> теги → модель выдаст только финальный вызов без рассуждений (быстрее, но менее прозрачно)
  • Глубина проверки: На шаге 7 (Revalidate) можно добавить "проверь edge cases" → модель подумает о граничных значениях
  • Язык рассуждений: Можно попросить русский или английский — меняет только язык <THINKING>, логика та же

📋

Шаблон промпта

Ты эксперт по вызову функций. У тебя есть запрос пользователя и набор функций.

Доступные функции:
{описание_функций_в_JSON}

Перед тем как вызвать функцию, пройди по этим шагам в <THINKING>:

1. IDENTIFY: Какие функции подходят по описанию к запросу?
2. DECIDE: Точно ли эти функции релевантны? Можно ли решить задачу?
3. EXAMINE: Внимательно изучи документацию выбранной функции. 
 Какие параметры обязательные? Какие опциональные? Какие типы?
4. EXTRACT: Извлеки параметры из запроса пользователя. 
 Все ли обязательные параметры есть?
5. CONVERT: Нужна ли конвертация типов? 
 (например "100 тысяч" → 100000, "два" → 2)
6. DRAFT: Составь черновик вызова функции
7. REVALIDATE: Перепроверь всё: правильная функция? все параметры? 
 корректные типы? соответствует документации?

Выдай финальный вызов в формате:
<FUNCTION>[function_name(param1=value1, param2=value2)]

Запрос пользователя: {запрос}

Что подставлять:

  • {описание_функций_в_JSON} — документация API в JSON-формате (как в OpenAI function calling)
  • {запрос} — то что хочет пользователь на естественном языке

🚀 Быстрый старт — вставь в чат:

Вот шаблон Template-based Function Calling. Адаптируй под мою задачу: 
[опиши твою задачу — какой API, что нужно сделать]. 
Задавай вопросы, чтобы заполнить поля.

[вставить шаблон выше]

LLM спросит какие у тебя функции и их параметры, как они документированы — это нужно чтобы правильно заполнить {описание_функций_в_JSON}. Модель возьмёт структуру из шаблона и подставит твои функции.


⚠️

Ограничения

⚠️ Вложенные вызовы: Метод слабо работает на nested function calls (когда результат одной функции передаётся в другую). Исследование показало падение качества на таких задачах даже после файнтюнинга. Датасет ToolACE не покрывал сложные композиции функций.

⚠️ Маленькие модели: Модели типа Mistral-7B с трудом следуют template через промпт — им нужен файнтюнинг. Без дообучения они ломают формат (забывают теги <THINKING>, путают шаги).

⚠️ Токены: 7 шагов рассуждений = примерно в 3 раза больше токенов чем прямой вызов. Для частых простых запросов это накладно. Можно сократить template до 3-4 шагов для лёгких задач.

⚠️ Переобучение на паттернах: Если файнтюнить только на простых одиночных вызовах (как ToolACE), модель деградирует на сложных задачах. Нужен разнообразный датасет.


🔍

Как исследовали

Команда взяла два бенчмарка: BFCL-v2 (Berkeley Function Calling Leaderboard) и Nexus — вместе примерно 1000+ задач на вызов функций разной сложности. Тестировали на 9 моделях от GPT-4o до Mistral-7B.

Логика эксперимента: Сравнили три подхода для каждой модели:

  1. No Thought — модель сразу выдаёт вызов функции
  2. CoT — free-form "подумай шаг за шагом"
  3. Template — структурированные 7 шагов

Потом взяли те же модели и дообучили на датасете ToolGT (10,830 примеров с рассуждениями по template). Датасет генерировали так: GPT-4o-mini создавал рассуждения по шагам, потом валидировали что финальный вызов совпадает с ground truth.

Что удивило: Template prompting почти всегда бил CoT и No Thought на больших моделях (GPT-4o, LLaMA-70B). Но Mistral-7B провалился с template в промптинге — модель не умела следовать формату без дообучения. После файнтюнинга та же Mistral-7B показала лучший результат со всех стратегий (+4.89% на BFCL).

Неожиданность №2: На Qwen-2.5-14B добавление любых рассуждений (CoT или Template) ухудшало результат на BFCL по сравнению с No Thought (77.33 → 73.28). Гипотеза: модель натренирована делать function calling напрямую, рассуждения сбивают её с толку. Но на Nexus Template всё равно выиграл (+5.7% над No Thought).

Инсайт для практики: Если модель уже хорошо делает function calling "из коробки" (как Qwen) — template prompting может навредить. Но template файнтюнинг всё равно улучшает результат (+1-2%), потому что учит модель внутренне структурировать рассуждения без явного вывода.


📄

Оригинал из исследования

Исследователи использовали три варианта template: Simple, Claude, Detail. Detail показал лучший результат и стал основным. Вот как выглядел полный промпт для генерации данных (этап Stage-1):

You are an expert in composing functions. You are given a question, 
a set of possible functions and the ground truth function call(s). 
Based on the question and the ground truth function call(s), 
you will need to generate the analysis and thought following 
the given curriculum steps by steps.

Here is a list of functions in JSON format that you can invoke:
{FUNCTIONS}

When composing your analysis, you SHOULD follow this curriculum 
in <THINKING> tags:

1. Identify which function or set of functions best fit the user query 
 based on function descriptions.
2. Pick that function or set of functions to fulfill the user's request.
3. After selecting the function(s), carefully examine the function 
 documentation.
4. Analyze the provided parameters, considering descriptions, 
 parameter types, and optionality.
5. Extract relevant information from user queries, performing 
 necessary type conversions.
6. Draft the function call(s) fulfilling the user's request.
7. Revalidate the composed functions, ensuring they satisfy both 
 documentation and the user's query.

The output format: 
<THINKING>[Put your thought based on the curriculum step by step]

User request: {user_request}
Ground truth function calling(s): {GROUND_TRUTH}

Контекст: Этот промпт использовали для генерации датасета ToolGT, а не для inference. В реальном применении ground truth не нужен — модель рассуждает только по запросу и функциям. На этапе создания данных ground truth помогал гарантировать что рассуждения ведут к правильному ответу.


💡

Адаптации и экстраполяции

💡 Адаптация для упрощённых API:

Для простых функций (1-2 параметра, нет опциональных) можно сократить template до 4 шагов:

1. IDENTIFY: Какая функция подходит?
2. EXTRACT: Какие параметры нужны?
3. DRAFT: Составь вызов
4. VALIDATE: Проверь типы параметров

Меньше токенов, быстрее ответ. Подходит для частых простых запросов (погода, курс валют, поиск по одному параметру).


🔧 Техника: Убрать <THINKING> теги → чистый вывод

Если тебе не нужна прозрачность (например в продакшене где важна скорость), убери требование выводить <THINKING>:

Выполни эти шаги МЫСЛЕННО (не выводи рассуждения):
1. Identify...
2. Decide...
[остальные шаги]

Выдай ТОЛЬКО финальный вызов в <FUNCTION>.

Модель всё равно следует структуре, но не тратит токены на вывод рассуждений. Работает на моделях после файнтюнинга на ToolGT.


🔧 Техника: Добавить шаг EXPLAIN → обучающий режим

Для отладки или обучения сотрудников добавь 8-й шаг:

8. EXPLAIN: Объясни простыми словами почему выбрал именно эту функцию 
 и эти параметры. Как будто объясняешь новичку.

Модель выдаст человекочитаемое объяснение после технического рассуждения. Полезно для онбординга людей в работу с API.


💡 Экстраполяция: Template для multi-tool сценариев

Текущий template для одного вызова. Для цепочки вызовов (результат функции_1 → вход функции_2):

<THINKING>
ROUND 1:
1-7. [шаги для первой функции]
8. OUTPUT: что получится после вызова function_1?

ROUND 2: 
1. IDENTIFY: какая функция нужна для обработки OUTPUT из Round 1?
2-7. [шаги для второй функции, используя OUTPUT как входные данные]


<FUNCTION>
[function_1(...)]
[function_2(param=<output_from_function_1>)]

Важно: Это расширение оригинального метода под задачу которую исследование не покрыло. В статье nested calls показали слабый результат — возможно нужна другая структура template для композиций.


🔗

Ресурсы

Improving Large Language Models Function Calling and Interpretability via Guided-Structured Templates

Авторы: Hy Dang, Tianyi Liu, Zhuofeng Wu, Jingfeng Yang, Haoming Jiang, Tao Yang, Pei Chen, Zhengyang Wang, Helen Wang, Huasheng Li, Bing Yin, Meng Jiang

Организации: University of Notre Dame, Amazon

Датасет: ToolGT (10,830 примеров) построен из ToolACE Бенчмарки: BFCL-v2, Nexus


📋 Дайджест исследования

Ключевая суть

LLM постоянно ломают вызовы функций API: передают "2" (строку) вместо 2 (числа), выбирают не ту функцию из десятка похожих, теряют обязательные параметры. Ты не видишь почему модель решила передать именно это значение. Template-based Function Calling превращает вызов функции в 7 прозрачных шагов: identify (какие функции подходят?) → decide (точно релевантны?) → examine (изучить документацию) → extract (вытащить параметры из запроса) → convert (поменять типы если надо) → draft (черновик вызова) → revalidate (финальная проверка). Модель не может пропустить проверку типов или забыть про обязательный параметр — каждый шаг явно прописан. Вместо чёрного ящика получаешь полную трассировку рассуждений в теге , а финальный вызов в .

Принцип работы

Не заставляй модель делать всё сразу — разбей на микрозадачи. LLM проваливаются когда пытаются одновременно выбрать функцию + извлечь параметры + проверить типы + учесть ограничения. Это как готовить борщ держа в голове все 15 шагов — обязательно что-то забудешь. Template делает так: модель сначала только идентифицирует подходящие функции (шаг 1), потом только проверяет релевантность (шаг 2), потом только читает документацию (шаг 3)... Каждый шаг — отдельная фокусировка внимания. Модель физически не может перескочить через конвертацию типов (шаг 5), потому что это явный пункт чек-листа.

Почему работает

LLM хороши в следовании пошаговым инструкциям, но плохи в многозадачности. Когда модель обрабатывает вызов функции одним махом — она жонглирует 7 задачами одновременно и роняет мячи. Разбиение на шаги снижает когнитивную нагрузку: на шаге 4 модель думает ТОЛЬКО об извлечении параметров, на шаге 5 ТОЛЬКО о типах. Это работает как рецепт торта: не "сделай торт за раз", а "шаг 1: взбей яйца, шаг 2: добавь муку...". Каждый шаг простой, результат надёжный. Бонус: ты видишь ГДЕ модель ошиблась — если на шаге 4 она извлекла параметр неправильно, это видно в , можно поправить промпт.

Когда применять

API с 10+ функциями → особенно когда функции похожи по названию но разные по параметрам. Критично для задач где type mismatch ломает всё: банковские API (сумма должна быть float, не строка "100 тысяч"), базы данных (ID = integer, не "123"), бронирования (дата в формате ISO, не "завтра"). НЕ подходит для вложенных вызовов (когда результат одной функции передаётся в другую) — исследование показало падение качества на nested calls. НЕ подходит для частых простых запросов — 7 шагов это примерно в 3 раза больше токенов, для лёгких задач сократи до 3-4 шагов.

Мини-рецепт

1. Добавь шаблон в system prompt: Скопируй 7 шагов (identify → decide → examine → extract → convert → draft → revalidate) и попроси модель выдавать рассуждения в , финальный вызов в
2. Передай документацию функций: В формате JSON опиши каждую функцию: название, параметры (обязательные/опциональные), типы данных, ограничения. Чем подробнее документация — тем лучше шаг 3 (examine)
3. Для простых задач урежь шаги: Если API из 2-3 функций и параметры простые — оставь только identify → extract → draft. Экономия токенов в 2 раза
4. Проверь на edge cases: На шаге 7 (revalidate) добавь "проверь граничные значения" — модель подумает что если пользователь передал 0 или пустую строку

Примеры

[ПЛОХО] : У меня API Авито. Найди двушку в Москве до 100к, рядом с Маяковской — модель может выдать search_apartments(city="Москва", rooms="2", max_price="100000") где rooms и max_price строки вместо чисел, вызов упадёт с type error
[ХОРОШО] : Перед вызовом функции пройди 7 шагов в : 1) какие функции подходят? 2) точно релевантны? 3) изучи документацию — какие параметры обязательные, какие типы? 4) извлеки параметры из запроса 5) конвертируй типы если нужно ("100к" → 100000, "два" → 2) 6) черновик вызова 7) перепроверь всё. Финальный вызов в . Запрос: найди двушку в Москве до 100к рядом с Маяковской — модель покажет рассуждения по каждому шагу, на шаге 5 явно конвертирует типы, выдаст корректный search_apartments(city="Москва", rooms=2, max_price=100000, metro_station="Маяковская")
Источник: Improving Large Language Models Function Calling and Interpretability via Guided-Structured Templates
ArXiv ID: 2509.18076 | Сгенерировано: 2026-01-12 01:03

Работа с исследованием

Адаптируйте исследование под ваши задачи или создайте готовый промпт на основе техник из исследования.

0 / 2000
~0.5-2 N-токенов ~10-30с
~0.3-1 N-токенов ~5-15с