Когда количество доступных LLM инструментов (tool-ов) разрастается, традиционные подходы к tool calling становятся непрактичными — утилизация токенов улетает ещё до начала общения. К тому же, модели становится сложнее выбрать нужный набор tool-ов для решения проблемы.

В новом переводе от команды Spring АйО читаем о паттерне Tool Search Tool, предложенном Anthropic и реализованном в Spring AI с помощью ToolSearchToolCallAdvisor. Он позволяет LLM динамически находить нужные инструменты по мере необходимости, экономя до 64% токенов и повышая точность.


По мере того как AI-агенты подключаются к всё большему числу сервисов — Slack, GitHub, Jira, MCP-серверы — библиотеки инструментов стремительно разрастаются. В типичной конфигурации с несколькими серверами может насчитываться более 50 инструментов, потребляющих свыше 55 000 токенов до начала самого диалога. Ещё хуже то, что точность выбора инструмента снижается, когда модель сталкивается с 30+ инструментами с похожими названиями.

Комментарий от Михаила Поливаха

Здесь наверное для вообще общего понимания статьи нужно сделать пару пояснений.

Довольно часто AI Agent-ы взаимодействуют с какими-либо внешними системами, и всё чаще это происходит через MCP протокол. У какого-то ресурса есть MCP сервер, который может быть написать с помощью Spring AI или ещё чего угодно, и MCP client запрашивает этот MCP server, где последний представляет собой некоторый прокси к этому самому ресурсу. MCP server знает

- О том, что может выполнять ресурс
- О том, какие данные он может возвращать

и т.д.

Проблема в том, что с популяризацией MCP протокола и AI Agent-ов поулчилось так, что абстрактный AI Agent рабоатет с огромным количеством MVP серверов, например с MCP серверами для:

- Мессенджера 
- Почты
- Файловой системы
- Менеджеру пакетов (например npm) и т.д.

И каждый MVP сервер предлагает оргомное количество функционала. Чаще всего, MCP хост не задумывается, и просто все тулы, что предлагает каждый MVP Server запихивает в контекст LLM. 

Как Вы можете догадаться, это вполне себе может привести (и приводит!) к огромному потреблению токенов и, соответственно, сильно удоражает пользование AI Агентами. Это проблема. Её решают.

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

Ключевая идея: хотя этот подход был впервые реализован в Claude от Anthropic, мы можем применить ту же концепцию для любой LLM-модели с помощью Recursive Advisors в Spring AI. Spring AI предоставляет переносимую абстракцию, делающую возможным динамическое обнаружение инструментов для OpenAI, Anthropic, Gemini, Ollama, Azure OpenAI и любых других провайдеров LLM, поддерживаемых Spring AI.

Наши предварительные тесты показывают, что реализация паттерна Tool Search Tool в Spring AI обеспечивает сокращение числа токенов на 34–64% при использовании моделей OpenAI, Anthropic и Gemini, сохраняя при этом полный доступ к сотням инструментов.

Проект Spring AI Tool Search Tool доступен по адресу: spring-ai-tool-search-tool.

Как работает вызов инструментов

Сначала разберёмся, как работает вызов инструментов (он же tool calling) в Spring AI при использовании ToolCallAdvisor — специального рекурсивного советника, который:

  • Перехватывает запрос ChatClient до того, как он попадёт в LLM

  • Включает определения инструментов (т.е. этих самых tool-ов) в промпт, отправляемый модели — для всех зарегистрированных инструментов!

  • Обнаруживает запросы на вызов инструментов в ответе модели

  • Выполняет запрошенные инструменты с помощью ToolCallingManager

  • Повторно обращается к модели с результатами вызова инструментов до тех пор, пока не будет получен окончательный ответ

Вызов инструментов происходит в рекурсивном цикле — советник (advisor) продолжает обращаться к LLM до тех пор, пока не прекратятся запросы на использование инструментов.

Проблема

Стандартный механизм вызова инструментов (например, ToolCallAdvisor) отправляет все определения инструментов в LLM заранее. Это приводит к трём серьёзным проблемам при работе с большими наборами инструментов:

  • Раздувание контекста — огромное потребление токенов ещё до начала диалога

  • Путаница в инструментах — модели сложно выбрать правильный инструмент при наличии 30+ схожих

  • Повышенные затраты — вы платите за передачу определений инструментов, даже если они не используются в запросе

Решение: Tool Search Tool

Расширив ToolCallAdvisor из Spring AI, мы создали ToolSearchToolCallAdvisor, который реализует динамическое обнаружение инструментов. Он перехватывает цикл вызова инструментов и выборочно подставляет определения только тех инструментов, которые модель определяет как нужные:

Процесс работает следующим образом:

  • Индексация: в начале диалога все зарегистрированные инструменты индексируются в ToolSearcher (но не отправляются в LLM)

  • Первичный запрос: в LLM отправляется только определение Tool Search Tool (TST) — экономия токенов в контексте

  • Запрос на обнаружение: когда LLM нужны определённые возможности, она вызывает TST с поисковым запросом

  • Поиск и расширение: ToolSearcher находит подходящие инструменты (например, «Tool XYZ»), и их определения добавляются в следующий запрос

  • Вызов инструмента: теперь LLM видит как TST, так и определения найденных инструментов и может вызвать нужный

  • Выполнение инструмента: найденный инструмент выполняется, а результат передаётся обратно в LLM

  • Ответ: LLM генерирует окончательный ответ, используя результаты выполнения инструментов

В коде это выглядит следующим образом:

var toolSearchToolCallAdvisor = ToolSearchToolCallAdvisor.builder()
    .toolSearcher(toolSearcher)
    .maxResults(5)
    .build();

ChatClient chatClient = chatClientBuilder
    .defaultTools(new MyTools())  // 100s of tools registered but NOT sent to LLM initially
    .defaultAdvisors(toolSearchToolCallAdvisor) // Activate Tool Search Tool
    .build();

Плагиновые стратегии поиска

Интерфейс ToolSearcher абстрагирует реализацию поиска, поддерживая несколько стратегий (см. tool-searchers для готовых реализаций):

Стратегия

Реализация

Оптимально для

Semantic

VectorToolSearcher

Запросы на естественном языке, нечеткое соответствие

Keyword

LuceneToolSearcher

Точное совпадение терминов, известные названия инструментов

Regex

RegexToolSearcher

Шаблоны названий инструментов (например, get_*_data)

Начало работы

Репозиторий проекта на GitHub: spring-ai-tool-search-tool.

Подробные инструкции по настройке и примеры кода доступны в руководстве Quick Start (v1.x), а также в сопутствующем примере приложения (v1.x).

Maven Central (1.0.1):

<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>tool-search-tool</artifactId>
    <version>1.0.1</version>
</dependency>

<!-- Choose a search strategy -->
<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>tool-searcher-lucene</artifactId>
    <version>1.0.1</version>
</dependency>

Версия v1.0.x совместима с Spring AI 1.1.x и Spring Boot 3, а версия v2.0.x — с Spring AI 2.x и Spring Boot 4.

Комментарий от Михаила Поливаха

Здесь частично речь про будущую совместимость - Spring AI 2.x ещё не вышел и пока непонятно когда выйдет.

Пример использования

@SpringBootApplication
public class Application {

    @Bean
    CommandLineRunner demo(ChatClient.Builder builder, ToolSearcher toolSearcher) {
        return args -> {
            var advisor = ToolSearchToolCallAdvisor.builder()
                .toolSearcher(toolSearcher)
                .build();

            ChatClient chatClient = builder
                .defaultTools(new MyTools())
                .defaultAdvisors(advisor)
                .build();

            var answer = chatClient.prompt("""
                Help me plan what to wear today in Amsterdam.
                Please suggest clothing shops that are open right now.
                """).call().content();
            
            System.out.println(answer);
        };
    }

    static class MyTools {

		@Tool(description = "Get the weather for a given location at a given time")
		public String weather(String location, 
            @ToolParam(description = "YYYY-MM-DDTHH:mm") String atTime) {...}

		@Tool(description = "Get clothing shop names for a given location at a given time")
		public List<String> clothing(String location,
				@ToolParam(description = "YYYY-MM-DDTHH:mm") String openAtTime) {...}

		@Tool(description = "Current date and time for a given location")
		public String currentTime(String location) {...}
        
        // ... potentially hundreds more tools
    }
}

Для приведённого выше примера поток выполнения будет следующим:

  • Запрос пользователя:
    «Помоги мне решить, что надеть сегодня в Амстердаме. Порекомендуй магазины одежды, которые сейчас открыты.»

  • Инициализация:
    Индексируются все инструменты: weather, clothing, currentTime (+ потенциально ещё сотни)

  • Первый вызов LLM — модель видит только toolSearchTool
    → LLM вызывает: toolSearchTool(query="current time date") → ["currentTime"]

  • Второй вызов LLM — модель видит toolSearchTool + currentTime
    → LLM вызывает: currentTime("Amsterdam") → "2025-12-08T11:30"
    → затем: toolSearchTool(query="weather location") → ["weather"]

  • Третий вызов LLM — модель видит toolSearchTool + currentTime + weather
    → LLM вызывает: weather("Amsterdam") → "Ясно, 15°C"
    → затем: toolSearchTool(query="clothing shops") → ["clothing"]

  • Четвёртый вызов LLM — модель видит toolSearchTool + currentTime + weather + clothing
    → LLM вызывает: clothing("Amsterdam", "2025-12-08T11:30") → ["H&M", "Zara", "Uniqlo"]

  • Финальный ответ:
    «С учётом солнечной погоды 15°C в Амстердаме, рекомендую надеть лёгкие слои. Вот магазины одежды, которые сейчас открыты: H&M, Zara, Uniqlo…»

Измерения производительности

 ⚠️ Предупреждение: это предварительные, ручные измерения, полученные после нескольких запусков. Они не усреднены по множеству итераций и должны рассматриваться как иллюстративные, а не как статистически точные.

Для оценки экономии токенов мы провели предварительные тесты с демонстрационным приложением и следующей конфигурацией:

  • Задача:
    «Помоги мне решить, что надеть сегодня в Амстердаме. Порекомендуй магазины одежды, которые сейчас открыты.»

  • Набор инструментов:
    Всего 28 инструментов:
    • 3 релевантных — weather, clothing, currentTime
    • 25 нерелевантных «заглушек» — специально добавлены, чтобы показать, как эффективно поиск отбирает только нужные инструменты среди множества нерелевантных

  • Стратегии поиска:
    • Lucene (на основе ключевых слов)
    • VectorStore (семантический поиск)

  • Тестируемые модели:
    Gemini (gemini-3-pro-preview)
    OpenAI (gpt-5-mini-2025-08-07)
    Anthropic (claude-sonnet-4-5-20250929)

Измерения проводились с использованием специального TokenCounterAdvisor, отслеживающего и агрегирующего использование токенов.

Результаты с использованием Lucene-поиска

Модель

Подход

Всего токенов

Токены запроса

Токены ответа

Запросов

Экономия

Gemini

С TST

2 165

1 412

231

4

60%

Без TST

5 375

4 800

176

3

OpenAI

С TST

4 706

2 770

1 936

5

34%

Без TST

7 175

5 765

1 410

3

Anthropic

С TST

6 273

5 638

635

5

64%

Без TST

17 342

16 752

590

4

Результаты с использованием VectorStore-поиска

Модель

Подход

Всего токенов

Токены запроса

Токены ответа

Запросов

Экономия

Gemini

С TST

2 214

1 502

234

4

57%

Без TST

5 122

4 767

73

3

OpenAI

С TST

3 697

2 109

1 588

4

47%

Без TST

6 959

5 771

1 188

3

Anthropic

С TST

6 319

5 642

677

5

63%

Без TST

17 291

16 744

547

4

Ключевые наблюдения

  • Значительная экономия токенов во всех моделях:
    Паттерн Tool Search Tool обеспечил снижение общего потребления токенов на 34–64% в зависимости от модели и стратегии поиска.

  • Основной фактор — токены запроса:
    Экономия достигается в первую очередь за счёт сокращения токенов промпта — с TST в контекст подставляются только обнаруженные инструменты, а не все 28 сразу.

  • Компромисс: больше запросов, меньше токенов:
    Подход с TST требует 4–5 запросов против 3–4 без него, но суммарная стоимость токенов значительно ниже.

Комментарий от Евгения Сулейманова

Очень важное утверждение о том, что главный "налог" tool-calling - это не сами вызовы, а раздувание промпта всеми схемами инструментов, и TST логично режет его за счет lazy-discovery, параллельно улучшая выбор среди однотипных tools. Но это не бесплатная магия: вы меняете "один жирный запрос" на 4–5 тонких, поэтому важно мерить не только токены, а end-to-end стоимость (latency + число round-trip-ов) и добавить кэш результатов поиска/подобранных инструментов между итерациями. Качество будет упираться в качество метаданных инструментов: стабильные имена/ID, хороший description с синонимами, явные входы/выходы, плюс фильтрация "опасных"/дорогих инструментов на уровне searcher.

Для прода я бы добавил guardrails: fallback на "полный список" при провале поиска, лимиты на расширение контекста, и A/B-валидацию точности выбора инструмента (не только успешность ответа). В целом это правильный шаг к масштабируемым агентам в MCP-мире, и ценность Spring AI здесь именно в переносимой рекурсивной архитектуре advisor-ов, которую можно докручивать под разные модели и качества tool-библиотек.

  • Обе стратегии поиска эффективны:
    Lucene и VectorStore показали сравнимую производительность, при этом VectorStore дал немного лучшую эффективность для OpenAI в данном тесте.

  • Все модели корректно завершили задачу:
    Все три модели (Gemini, OpenAI, Anthropic) поняли, что нужно сначала вызвать currentTime, прежде чем обращаться к другим инструментам — это демонстрирует корректное понимание зависимостей между инструментами.

  • Разные стратегии обнаружения:
    Модели продемонстрировали различные подходы: некоторые запрашивали все нужные инструменты сразу, другие — по одному. Тем не менее, все они использовали параллельный вызов инструментов, где это было возможно, чтобы оптимизировать выполнение.

  • Старые модели могут испытывать трудности:
    Более ранние версии моделей могут не справляться с паттерном поиска инструментов: пропускать нужные инструменты или принимать неэффективные решения. В таких случаях рекомендуется:

    • Добавить systemMessageSuffix для дополнительной инструктивности

    • Попробовать разные конфигурации tool-searcher

    • Использовать паттерн LLM as Judge для обеспечения надёжного поведения на разных моделях

Когда использовать

Подход Tool Search Tool

Традиционный подход

Более 20 инструментов в системе

Небольшая библиотека инструментов (<20)

Определения инструментов занимают более 5 000 токенов

Все инструменты регулярно используются в каждом сеансе

Разработка MCP-систем с несколькими серверами

Очень компактные определения инструментов

Наблюдаются проблемы с точностью выбора инструментов

Заключение

Паттерн Tool Search Tool — это шаг к созданию масштабируемых AI-агентов. Объединяя инновационный подход Anthropic с переносимой абстракцией Spring AI, мы можем строить системы, которые эффективно управляют тысячами инструментов и при этом сохраняют высокую точность вне зависимости от провайдера LLM.

Сила рекурсивной архитектуры советников Spring AI заключается в том, что она позволяет реализовывать сложные сценарии обнаружения инструментов, которые работают универсально — будь то модели GPT от OpenAI, Claude от Anthropic, локальные модели Ollama или любой другой LLM, поддерживаемый Spring AI. Вы получаете все преимущества динамического обнаружения инструментов — без привязки к конкретной реализации или провайдеру.


Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Комментарии (0)