Tool Calling

Tool calling (function calling) — механизм, которым LLM «действует»: модель возвращает JSON с именем функции и аргументами, а рантайм исполняет реальный вызов (API, shell, БД, web). Это «руки» агента из Agent Anatomy.

Суть

Модель не вызывает функцию сама — она выдаёт структурированный запрос на вызов (JSON), который выполняет код вокруг неё, и результат возвращается в контекст следующим шагом цикла ReAct.

Зачем это нужно

Без инструментов LLM только генерирует текст. Tool calling превращает рассуждение в действие в системах. Точность вызова (правильный инструмент + правильные аргументы) — одна из критичных способностей мозга-агента.

Как работает

  • Спецификация — JSON Schema: name, description, parameters. Хороший description инструмента решает больше, чем выбор модели.
  • Типы: API-вызовы, shell/bash, чтение/запись файлов, запросы к БД, вычисления, web-поиск.
  • Анти-паттерн: 50+ инструментов в одном агенте → падает accuracy. Дробите на subagents (узкий набор tools на агента). Ориентиры сверху вниз: своему MCP-серверу выставляют 3-7 укрупнённых бизнес-действий (MCP Business Facade); когда инструментов объективно больше сотни и укрупнить нельзя — их не показывают модели все сразу, а отбирают 3-5 кандидатов до вызова (Tool Retrieval). То есть падение accuracy лечится не «моделью получше», а сокращением набора, который она видит за один раз.
  • Единый стандарт подключения внешних инструментов к любой модели — это MCP.
  • Tool = контракт и точка контроля: описывай что делает / какие входы / что возвращает / какие ограничения. Инструмент — точка контроля безопасности, стоимости и предсказуемости: find_customers(query, limit=5) лучше, чем database(query) (модель понимает, что можно). «Инструмент не прощает плохой подготовки — документация должна быть идеальная».
  • Детерминизм: хороший инструмент детерминирован; @tool-декоратор — лишь способ «научить» модель вызывать действие, а не сама логика.
  • Уточнение про надёжность: function/tool calling задаёт схему как hint — это уровень 2 (≈95–99%), модель может его нарушить. 100%-ю гарантию формата даёт только constrained decoding на уровне генерации (Native Structured Output) — см. Structured Output. То есть «нативный вызов надёжнее промпта» верно, но это ещё не жёсткий контракт.
  • В LangGraph: модель получает инструменты через model.bind_tools([...]) и возвращает tool_calls; их исполняет готовый узел ToolNode, результат возвращается в цикл (см. LangGraph ReAct Loop). Альтернатива «MCP-как-tool» — подключить инструмент как узел графа (LangGraph MCP as Node).

Пример

{ "name": "get_weather",
  "arguments": { "city": "Berlin", "units": "metric" } }

Реестр как единственная дверь к инструментам

Между «модель попросила вызвать» и «функция выполнилась» должен стоять один проход, а не россыпь if по коду. Реестр даёт такой проход и совмещает две обязанности: разрешение имени в функцию и проверку, что вызов вообще позволен.

class ToolRegistry:
    def __init__(self):
        self._tools: dict[str, Callable] = {}
        self.allowlist: set[str] = set()

    def register(self, name, fn, allowed=True):
        self._tools[name] = fn
        if allowed:
            self.allowlist.add(name)

    def call(self, name, **kwargs):
        if name not in self.allowlist:
            raise ToolError(f"Tool '{name}' is not allowed.")   # ← гейт до исполнения
        return self._tools[name](**kwargs)

Регистрация и разрешение разведены намеренно: инструмент может быть зарегистрирован, но выключен. Это то, что позволяет держать в системе отладочные и опасные вызовы, не давая модели до них дотянуться, — и переключать доступ конфигурацией, а не правкой кода.

Проверка стоит до исполнения и опирается на список разрешённого, а не на список запрещённого. Разница принципиальна: чёрный список защищает только от того, что вы предусмотрели, белый — от всего, что не предусмотрели (тот же принцип для доменов и путей — Tool Hijacking, Guardrails).

Отдельный класс ошибки ToolError вместо обычного исключения нужен, чтобы наверху отличить «инструмент отказал» от «в коде баг»: у первого реакция — ретрай, фолбэк или сообщение модели, у второго — падение и починка (см. Agent Failure Modes).

Отказ — тоже результат

Из предыдущего раздела следует правило, которое легко нарушить незаметно: у вызова инструмента ровно один допустимый исход — вернуть значение. Отказ, блокировка, нехватка прав, требование подтверждения — всё это возвращается строкой, а не отсутствием ответа.

Причина в том, как модель обходится с пробелом. Вызов, не вернувший ничего, она не считает неудачей — она достраивает результат сама и докладывает об успехе (Agent Failure Modes).

Практическое следствие для проектирования гейтов: проверка ставится внутри исполнения инструмента, где гарантированно есть куда вернуть строку. Пометка «этот вызов требует подтверждения» на уровне описания инструмента гейтом не является — это сигнал обвязке, и без второй половины, доводящей запрос до человека, вызов исчезает молча.

Текст отказа при этом пишется для модели, а не для журнала: он должен называть, что именно заблокировано, чтобы модель передала это пользователю, а не пересказала своими словами.

Инструмент как источник истины

Правило, которое стоит держать рядом с предыдущим: модель оркестрирует, но не вычисляет. Цифры в ответе берутся из результата инструмента, а не из того, что модель «помнит» или посчитала в уме.

Практическое следствие жёстче, чем кажется: если агент не вызвал инструмент, он не имеет права делать выводы о данных. Ответ, собранный без вызова, — это ответ на весах модели, и выглядит он ровно так же уверенно, как настоящий (см. голодание RAG в Agent Failure Modes).

Отсюда требования к самому инструменту. Он должен делать строгую фильтрацию сам — «продажи категории X за диапазон дат» возвращает именно их, а не всё подряд с надеждой, что модель отберёт нужное. И бизнес-логика внутри него — детерминированная: группировка, суммирование, округление обязаны давать один и тот же результат на одних и тех же данных, иначе проверить ответ нечем.

Это та же граница, что и в Deterministic Veto, только с другой стороны: там код запрещает действие модели, здесь код поставляет ей факты.

Связано с

  • Tool Gateway — тот же проход, но с субъектом, классом риска и подтверждением
  • Agent Anatomy — tool calling = компонент «руки»
  • ReAct — результат вызова = ground truth следующего шага
  • MCP — стандартизация инструментов под все модели
  • Structured Output — function calling = уровень 2; жёсткий контракт даёт constrained decoding
  • LangGraph ReAct Loop — bind_tools + ToolNode в графе LangGraph
  • Slot Filling — что делать, когда пользователь называет аргументы не за один ход
  • Tool Hijacking — чем рискует система, если гейта перед вызовом нет
  • Deterministic Veto — обратная сторона той же границы: код запрещает, а не поставляет
  • Agent Sandboxing — где исполняется то, что реестр пропустил