35 / 37 · Современные LLM-системы
Использование инструментов и агенты
Языковая модель может вместо ответа выдать структурированный вызов функции; среда выполнения исполняет его и возвращает результат. Заметка описывает этот протокол, цикл, который превращает его в агента (состояние, планирование, остановка), способы разделить работу между специализированными агентами с координатором и шагом подтверждения человеком, ошибки цикла и случаи, когда фиксированный конвейер — лучшее решение.
Генерация с извлечением (RAG) · Обучение на инструкциях и выравнивание
На этой странице 11
RAG-система из предыдущей заметки вызывает поисковую модель на каждый запрос — так устроен конвейер, — ещё до того, как модель что-либо увидит. Модель не решает, нужен ли был поиск, что искать и не искать ли снова, прочитав результаты. Использование инструментов (tool use) даёт ей это право, а агент — то, что получается, когда модели позволяют решать раз за разом. Эта заметка — о механизме, о том, для чего он хорош, и о вопросе, который воодушевление вокруг него обычно пропускает: когда цикл, в котором решает модель, хуже конвейера, где она не решает.
Обозначения. Модель с контекстом (инструкции, диалог, прошлые результаты); набор инструментов с типизированными сигнатурами; вызов инструмента — ; среда выполнения (runtime) возвращает наблюдение , которое добавляется к .
Базовый вариант: модель отвечает из своего контекста#
На вопрос, ответ на который требует сегодняшней погоды, вычисления или запроса к базе данных, обычная модель порождает текст того рода, что следует за такими вопросами: догадку правильной формы. Поиск закрывает случай с запросом к текстовым корпусам. Он не покрывает калькулятор, поисковую систему, интерпретатор кода, систему заказов компании и вообще любое действие с побочным эффектом.
Использование инструментов#
Протокол#
- Системный промпт перечисляет инструменты: имя, что делает, имена и типы аргументов (JSON-схема). Чат-модели обучены на таких перечнях и на примерах вызовов в фиксированном формате, окружённых специальными токенами.
- Модель порождает либо текст на естественном языке, либо вызов. Вызов — это структурированный вывод в смысле заметки о RAG; разбираемым его делает ограниченное декодирование или обучение под конкретный формат.
- Среда выполнения проверяет аргументы по схеме, исполняет функцию и добавляет результат сообщением с ролью инструмента.
- Модель продолжает: она может ответить или вызвать снова.
Протокол даёт то, что последовательность вызовов никто не пишет: с инструментом поиска и инструментом генерации изображений просьбу о фото машины, на которой Джеймс Бонд ездил в последнем фильме, можно выполнить как найти, затем сгенерировать, и эту цепочку модель составляет сама из описаний инструментов и вопроса. Цена в том, что модель решает и то, вызывать ли что-нибудь вообще, и запуски ниже показывают, как плохо она это иногда решает.
Код как интерфейс инструментов#
Один из вариантов заменяет фиксированный формат вызова на Python: модель пишет короткую программу, вызывающую инструменты как функции, среда выполнения исполняет её в песочнице (sandbox), а напечатанный вывод становится наблюдением. Цикл, условие или вызов, чьи аргументы зависят от предыдущего результата, тогда занимают один шаг вместо нескольких, а тот же интерпретатор позволяет модели запустить код, прочитать ошибку и исправить её. Песочница обязательна: модель пишет код, который исполняется.
Три запуска небольшого агента с кодом#
Возьмём вариант с кодом в реализации библиотеки smolagents поверх Qwen3-4B-Instruct-2507, модели с инструкциями на 4 млрд параметров, и дадим ему три запроса. Трассы (traces) поучительнее ответов:
- What is the current weather in Paris? с инструментом веб-поиска. Первая программа ищет «current weather in Paris» и получает посты с форума телефонной компании, совпавшие по слову current. Вторая уточняет запрос до «current weather in Paris, France» и получает страницы погоды, которые расходятся: одна сообщает 13 °C и переменную облачность, другая — ясное небо и +16°. Третий шаг отвечает «Clear sky, around 13-16°C» — склейкой двух источников. Три шага заняли в сумме 9528 входных токенов, потому что перечень инструментов и все прошлые результаты перечитываются на каждом шаге: промпт растёт с 2083 токенов на первом шаге примерно до 4100 на третьем.
- Generate me a photo of the car that James Bond drove in the latest movie с инструментом поиска и инструментом генерации изображений. Модель пропускает поиск: её единственная программа отправляет генератору изображений промпт, начинающийся с «James Bond’s car from the latest Bond movie, high-res, photorealistic», и возвращает картинку. Генератор получает описание роли, а не машины.
- Скрипт с двумя ошибками —
numbers(i)для списка иrange(4)по трём элементам — передан с просьбой отладить его, затем запустить и убедиться, что он работает. Модель за один шаг возвращает исправленную версию,numbers[i]иrange(len(numbers)), не запустив ни одну из версий, так что цикл «запустить — прочитать ошибку — исправить», который допускает интерфейс через код, так и не случается.
Все три ответа выглядят как успех. Только трассы показывают ответ, склеенный из противоречащих источников, инструмент, к которому так и не обратились, и проигнорированную инструкцию, — поэтому агентов оценивают по траекториям, а не только по выходам.
Агенты: цикл вокруг вызовов инструментов#
Одиночный вызов инструмента расширяет модель; цикл, повторяющий решить → вызвать → наблюдать до условия остановки, — это агент. Шаблон ReAct (Yao et al., 2023) чередует с каждым действием записанный шаг рассуждения, чтобы траекторию можно было проверить. Траектория, которой требует просьба про Джеймса Бонда и которой запуск выше не последовал:
thought: I need the car model first, then an image.
action: web_search(query="car James Bond latest movie")
observation: ... Aston Martin DB5 ...
thought: Now generate the image.
action: text_to_image(prompt="Aston Martin DB5")
observation: <image>
final answer: <image>
Цикл агента с инструментами
Что нужно добавить к протоколу, чтобы цикл работал:
Состояние. Контекст накапливает каждый вызов и результат. На длинных задачах он превышает окно, и его приходится сжимать или хранить вне модели (черновик, память о прошлых запусках, файлы); что сохранять и что отбрасывать — проектное решение, напрямую влияющее на поведение. Сама модель между вызовами ничего не хранит; где живёт память — свойство среды выполнения, оно описано после этого списка.
Планирование. На многошаговых задачах модель сначала пишет план (подцели по порядку) и пересматривает его по мере поступления наблюдений. План — это текст, порождённый той же моделью, а не отдельный решатель, и планы часто ошибочны так, что модель этого не замечает, пока наблюдение им не противоречит.
Остановка. Действие «финальный ответ», максимальное число шагов, бюджет токенов или стоимости и обнаружение повторов (тот же вызов дважды с тем же результатом). Без трёх последних цикл, который не может продвинуться, работает, пока его не остановит что-то внешнее.
Специализация и координация. Когда инструментов больше нескольких, один промпт, описывающий всё, работает хуже. Обычная структура — несколько агентов, у каждого узкая роль и свои инструменты, и координатор, который направляет запрос пользователя и сводит результаты. Конкретная форма из системы складских операций: агент-координатор принимает запрос; агенты снабжения, транзита и экспорта обращаются каждый к своим внутренним системам и предлагают действия в своей области; координатор собирает предложение. Каждый подагент видит только свои инструменты и инструкции, так что каждый промпт остаётся коротким, а каждая ошибка — локальной.
Подтверждение человеком. Чтение легко отменить, запись — нет. Та же складская система предлагает операционные действия и не исполняет ни одного критического (перемещение запасов, изменение экспортной декларации) без явного шага подтверждения. Цикл останавливается, показывает предлагаемый вызов с аргументами и продолжает только после одобрения. Это свойство среды выполнения, а не модели, и это самая действенная защита в системах, которые действуют в мире.
Где живёт память разговора#
Языковая модель — это функция от последовательности токенов к распределению следующего токена. От вызова к вызову ничего не сохраняется: веса фиксированы, а KV-кэш одного вызова не переносится в следующий (сервер может переиспользовать его для идентичного префикса — это экономит вычисления и не меняет ни одного выхода). Чат-модель, которая вспоминает имя, названное три реплики назад, вспоминает его потому, что среда выполнения снова отправила ей эти три реплики. На вопрос What’s my name?, заданный отдельным сообщением, чат-модель на 0.5 млрд параметров, Qwen2-0.5B-Instruct, отвечает общим абзацем о том, что такое имена; на тот же вопрос после Hi! I’m Bob и собственного приветствия она отвечает Your name is Bob.
Поэтому память — часть среды выполнения, и состоит она из трёх частей.
- Сохранённое состояние для каждого разговора. Состояние — это запись: список сообщений плюс любые поля, нужные приложению (язык ответа, номер счёта, результат более раннего вызова инструмента). После каждого шага среда выполнения сохраняет контрольную точку (checkpoint) этой записи под идентификатором диалога (thread identifier). Запрос с тем же идентификатором продолжает этот разговор; новый идентификатор начинает с пустой записи. Два разговора никогда не видят историю друг друга, потому что у них разные идентификаторы, а не потому, что модель их разделяет.
- Правило обновления для каждого поля. К списку сообщений дописывают: шаг возвращает свои новые сообщения, и среда выполнения добавляет их к сохранённому списку, а не заменяет его. Остальные поля перезаписываются, когда запрос их передаёт, и сохраняют прежнее значение, когда не передаёт, так что язык ответа, заданный на первом ходу, действует и на десятом без повторения.
- Промпт, собираемый из состояния при каждом вызове. Шаблон ставит первой системную инструкцию, подставляет в неё поля состояния (
answer in {language}) и вставляет после неё сохранённые сообщения, последним — самое новое сообщение пользователя. Результат оформляется шаблоном чата модели в одну обычную последовательность токенов.
В хранилище должен попадать только ответ модели. Если шаг генерации возвращает промпт вместе с продолжением и этот текст сохраняется как сообщение ассистента, следующий промпт содержит предыдущий, вложенный в реплику ассистента, и история быстро растёт, даже когда каждый обмен репликами короткий.
Обрезка. Сохранённая история растёт неограниченно, а контекстное окно — нет, поэтому перед каждым вызовом среда выполнения сокращает сообщения до бюджета токенов, посчитанного собственным токенизатором модели. Обычная политика состоит из четырёх правил:
- оставлять самые новые сообщения — от них текущий обмен зависит сильнее всего;
- всегда оставлять системную инструкцию и учитывать её в бюджете;
- никогда не разрезать сообщение: сообщение, обрезанное на произвольном токене, — это уже другое сообщение;
- начинать оставленную историю с реплики пользователя, чтобы модель никогда не читала ответ, вопрос к которому отрезан.
Разобранный пример (игрушечные числа токенов). Разговор из системной инструкции в 10 токенов и семи сообщений, всего 75 токенов, обрезается до бюджета 52:
| Сообщение | Токенов | Оставлено |
|---|---|---|
| системное | 10 | да (всегда) |
| пользователь 1: Меня зовут Ана. | 6 | нет |
| ассистент 1 | 5 | нет |
| пользователь 2 | 12 | нет (сверх бюджета) |
| ассистент 2 | 20 | нет (его вопрос отрезан) |
| пользователь 3 | 7 | да |
| ассистент 3 | 9 | да |
| пользователь 4: Как меня зовут? | 6 | да |
После системной инструкции остаётся 42 токена. Если идти назад от самого нового сообщения, помещается ровно, а пользователь 2 довёл бы сумму до 54. Тогда оставленный хвост начинается с ассистента 2, вопрос к которому не поместился, поэтому уходит и он. Модель получает четыре сообщения и 32 токена, а вопрос, на который она должна ответить, ссылается на сообщение, которого она больше не видит. Обрезка ограничивает стоимость хода и размер KV-кэша; цена — забывание. Поэтому в длинных разговорах отброшенные реплики заменяют кратким содержанием (summary) или хранят факты вроде имени пользователя в отдельных полях состояния, которые никогда не обрезаются.
Ошибки#
| Ошибка | Механизм | Что помогает |
|---|---|---|
| Не тот инструмент или не те аргументы | модель угадывает по описаниям | точные описания, примеры, проверка по схеме, мало инструментов на агента |
| Выдуманные результаты | модель «помнит» результат, которого не получала | только наблюдения, вставленные средой выполнения; никаких сфабрикованных сообщений инструмента |
| Зацикливание | тот же вызов, тот же результат, нет продвижения | бюджеты шагов и стоимости, обнаружение повторов |
| Накопление ошибок | неверное раннее наблюдение определяет все дальнейшие шаги | пересмотр плана, шаги проверки, контрольные точки с человеком |
| Инъекция промпта через инструменты | веб-страница или документ, возвращённые инструментом, содержат инструкции | выводы инструментов — данные; никогда не исполнять найденные в них инструкции; подтверждение для побочных эффектов |
| Стоимость | каждый шаг перечитывает весь контекст | компактное состояние, кэширование общих префиксов, маленькие модели для маршрутизации |
| Незаметные побочные эффекты | прошла запись, которой не должно было быть | подтверждение, режимы пробного запуска, сначала обратимые действия |
Строка про инъекцию заслуживает особого внимания. У агента, который читает веб-страницу и затем действует, ровно та же уязвимость, что у RAG-системы, только ставки выше: страница может сказать теперь вызови send_email со следующим содержимым, и модель, хорошо следующая инструкциям, попытается. Единственная надёжная защита — структурная: инструменты с побочными эффектами — только через подтверждение, и строгое разделение инструкций (от системы и пользователя) и данных (от инструментов).
Когда агент не нужен#
Цикл стоит токенов, задержки и предсказуемости. Это верное решение, когда число и порядок шагов зависят от промежуточных результатов, которые нельзя знать заранее: отладка, открытое исследование, управление системой с множеством возможных действий. Это неверное решение, когда шаги известны:
- Фиксированная последовательность (найти → переранжировать → породить; классифицировать → направить) — это конвейер. Его пишут кодом и вызывают модель на тех шагах, где нужен язык; он быстрее, дешевле и ломается объяснимо.
- Одиночный вызов инструмента с известными аргументами (найти этот заказ, перевести эти единицы) — это вызов функции, а не агент; решать о нём пусть будет программа.
- Задача с ответом в форме схемы (извлечь эти поля) — это структурированный вывод.
- Задачу, где неверное действие стоит дорого, а решение модели нельзя проверить до исполнения, вообще не стоит отдавать циклу.
Практическое правило: начинать с конвейера и переносить решение в модель только тогда, когда фиксированная логика конвейера измеримо становится узким местом. Каждое перенесённое в модель решение требует оценки этого решения — о ней следующая заметка.
Что изменилось#
| Конвейер (RAG) | Использование инструментов | Агент | |
|---|---|---|---|
| Кто решает о шагах | программа | модель, один шаг | модель, многократно |
| Побочные эффекты | нет | возможны | вероятны |
| Состояние | промпт | промпт + один результат | накапливается, сохраняется в контрольных точках, обрезается |
| Стоимость | фиксированная | одна дополнительная генерация | неограниченная без бюджетов |
| Предсказуемость | высокая | средняя | низкая |
| Что оценивать | поиск + ответ | правильность вызова + ответ | траекторию, успех, стоимость |
Ограничения#
- Надёжность перемножается. Если должны удаться все десять шагов, ошибки ничем не исправляются, а каждый шаг удаётся с вероятностью 0.95 при условии, что удались предыдущие, то задача удаётся с вероятностью . Одни лишь безусловные доли успеха на шаг не задают это произведение, если исходы зависимы иначе; восстановление после ошибок его повышает.
- Модели плохо знают, чего они не знают, а именно это умение агенту нужнее всего, когда он решает, вызывать ли инструмент.
- Бенчмарки для агентов молоды и чувствительны к реализации инструментов; результат плохо переносится между средами.
- Стоимость — ограничение первого порядка; цикл, решивший задачу в десять раз дороже бюджета, её не решил.
- Многоагентные схемы добавляют ошибки координации (потерянный между агентами контекст, противоречащие предложения) поверх ошибок одного агента.
- Память теряет информацию. Обрезка отбрасывает самые старые реплики, а краткие содержания — детали; чего нет в промпте, того для модели не существует.
Что нужно запомнить#
- Использование инструментов: функции описываются схемами; модель выдаёт вызов; среда выполнения исполняет его и добавляет результат; модель продолжает.
- Агент — это цикл с состоянием, планом и правилами остановки (финальный ответ, бюджеты шагов и стоимости, обнаружение повторов).
- Модель не хранит состояния. Разговор — это запись, которую ведёт среда выполнения: она сохраняется в контрольных точках по идентификатору диалога, её список сообщений пополняется на каждом шаге, отправляется заново при каждом вызове и обрезается до бюджета токенов (системная инструкция сохраняется, самые новые сообщения — целиком, начало — с реплики пользователя).
- Большие наборы инструментов делить между специализированными агентами с координатором; каждое действие с побочным эффектом ставить за подтверждение человеком.
- Выводы инструментов — данные; инструкции внутри них — атака.
- Предпочитать конвейер, когда шаги известны; переносить решения в модель, только когда они обязаны зависеть от промежуточных результатов, и оценивать каждое.
- Смотреть на траектории, а не только на ответы: в трёх запусках небольшого агента с кодом все три ответа выглядят успешными.
Ноутбук#
Ноутбук об агентах и диффузии. Номера ячеек считают все ячейки, включая текстовые, начиная с 1. Приведённые числа взяты из прогона, сохранённого в курсе; у ячеек, изменённых с тех пор под текущие версии библиотек, сохранённого выхода нет. Ячейки с агентом следуют готовому руководству (cookbook), и сохранённые трассы поучительнее его текста вокруг них.
- Ячейка 6 строит
CodeAgentс инструментом генерации изображений, загруженным с Hub, и инструментом поиска DuckDuckGo поверхQwen3-4B-Instruct-2507и запускает просьбу про Джеймса Бонда; сохранённая трасса состоит из одного шага — прямого вызова генератора изображений без поиска (2159 входных и 124 выходных токена). Ячейка 7 показывает картинку Aston Martin DB5 из руководства, а не вывод этого запуска. - Ячейка 9 запускает вопрос о погоде только с инструментом поиска: три шага, первый поиск возвращает посторонние посты с форума, 9528 входных и 225 выходных токенов, ответ «Clear sky, around 13-16°C».
- Ячейки 12–14 передают скрипт с ошибками дополнительным аргументом; агент за один шаг, не запуская код, возвращает исправленную версию с обеими исправленными ошибками, и ячейка 14 её печатает. Текстовая ячейка после этого запуска описывает цикл «запустить — прочитать ошибку — исправить» из руководства, которого в этом запуске не было.
- Ячейки 17–19 относятся к заметке о текстовой диффузии.
Описанная выше многоагентная складская структура взята из промышленной системы, кратко описанной на странице о работе; в ноутбуке её нет.
Память из раздела «Где живёт память разговора» шаг за шагом строится в ноутбуке о LLM курса HSE-LAMBDA Deep Generative Models, Transformers-LangChain.ipynb, с LangChain и LangGraph поверх Qwen/Qwen2-0.5B-Instruct. Номера ячеек считают все ячейки, включая текстовые, начиная с 1; выходы ячеек сохранены. Все его ячейки описывает заметка об авторегрессионных трансформерах.
- Ячейки 42–46 отправляют «Hi! I’m Bob» (ячейка 44), затем «What’s my name?» отдельно (ячейка 45) и после предыдущих реплик (ячейка 46); ответы — общий абзац и приведённое выше «Your name is Bob.»
- Ячейки 47–51 строят
StateGraphнадMessagesStateс одним узлом, который вызывает модель, и ребром к нему отSTARTи компилируют его сMemorySaverв роли хранилища контрольных точек (checkpointer). Конфигурация{"configurable": {"thread_id": "abc123"}}выбирает разговор, так что второйinvoke, который передаёт только «What’s my name?», получает ответ по истории, сохранённой в контрольной точке. - Ячейки 52–58 ставят перед моделью
ChatPromptTemplate.from_messagesс системным сообщением иMessagesPlaceholderдля сохранённых сообщений. - Ячейки 59–64 задают состояние как
TypedDictс полемmessages: Annotated[Sequence[BaseMessage], add_messages], чей редьюсер (reducer)add_messagesдописывает сообщения, и полемlanguage: str— обычным, которое перезаписывается. Второйinvokeне передаётlanguage, и сохранённое «Spanish» по-прежнему действует: ответ — «Tu nombre es Bob.» - Ячейки 65–70 строят обрезку через
trim_messages(max_tokens=45, strategy="last", token_counter=chat_model, include_system=True, allow_partial=False, start_on="human")— это четыре правила выше — и сокращают пример из 11 сообщений до 7 (ячейка 67). Ячейка 68 обрезает сохранённые сообщения до того, как шаблон промпта добавляет системное сообщение, так чтоinclude_systemтам ничего не делает. Ячейка 70 читает контрольную точку обратно черезget_state(config).values: язык и два сохранённых сообщения.
Две проблемы и их совместный эффект. Конвейер генерации текста возвращает промпт вместе с ответом, поэтому каждое сохранённое сообщение ассистента начинается со всего промпта, и начиная с ячейки 51 промпты содержат прежние промпты, вложенные в реплики ассистента; это исправляет return_full_text=False в конвейере. token_counter=chat_model считает токены запасным способом LangChain — токенизатором GPT-2, применённым к строкам Human: …/AI: …, — а не токенизатором Qwen. Вместе они ломают граф с обрезкой после первого хода: сообщение ассистента, сохранённое в ячейке 69, вместе с промптом занимает 205 символов и не меньше 66 токенов GPT-2 — больше всего бюджета, — так что на следующем ходу обрезка оставила бы только новый вопрос. Ноутбук делает один ход и этого не показывает.
Дальше#
В каждой системе этого раздела есть решения, которые принимает модель: какой фрагмент релевантен, достоверен ли ответ, какой инструмент вызвать. Чтобы их измерить, мало тестовой выборки с метками. Следующая заметка посвящена оценке систем на языковых моделях: стандартным бенчмаркам задач и их обвязке, метрикам поиска, моделям в роли судей и тому, насколько им доверять, оценке людьми и эксплуатационным мерам (задержка, стоимость, надёжность), которые решают, выйдет ли система в работу.
Литература#
- S. Yao, J. Zhao, D. Yu, N. Du, I. Shafran, K. Narasimhan, Y. Cao. ReAct: Synergizing Reasoning and Acting in Language Models. ICLR, 2023.
- T. Schick et al. Toolformer: Language Models Can Teach Themselves to Use Tools. NeurIPS, 2023.
- Y. Qin et al. Tool Learning with Foundation Models. arXiv, 2023. Обзор использования инструментов.
- X. Wang et al. Executable Code Actions Elicit Better LLM Agents. ICML, 2024. Код как интерфейс действий.
- K. Greshake, S. Abdelnabi, S. Mishra, C. Endres, T. Holz, M. Fritz. Not what you’ve signed up for: Compromising Real-World LLM-Integrated Applications with Indirect Prompt Injection. AISec, 2023.
- Hugging Face. smolagents documentation. Библиотека агентов, используемая в ноутбуке.