Что это решает
Разработка ИИ-агентов начинается там, где чат перестаёт помогать: данные лежат в базе, действие выполняется в чужом API, а результат надо проверить и записать. В переписке с моделью человек работает шиной передачи — выгрузил, вставил, забрал ответ, руками применил. Агент замыкает круг сам: ходит за данными, вызывает операцию, повторяет шаг после ошибки и останавливается, когда задача закрыта. Плата — новая инженерная работа: описать инструменты, поставить ограничители, научиться читать журнал шагов.
Как устроено
Агент собирается из четырёх частей: цикл, инструменты, память, контроль. Уберите любую — получится либо болтливый чат, либо неуправляемый скрипт.
Цикл
Ядро живёт в коде оркестратора и выглядит как обычный while:
- Собрать запрос: системная инструкция, история, новый вход, описание доступных инструментов.
- Отправить в модель.
- Разобрать ответ. Модель вернёт либо финальный текст, либо структурированный запрос на вызов —
tool_callsс именем функции и аргументами в JSON. - Оркестратор исполняет вызов своим кодом и кладёт результат обратно в историю отдельным сообщением с ролью
tool. - Вернуться к шагу 2 — пока не придёт финальный текст или пока не сработает ограничитель.
Модель ничего не исполняет сама. Она возвращает JSON с именем функции и аргументами, всё остальное делает код, который этот JSON получил. Отсюда главный рычаг: набор инструментов, положенный в запрос, и есть полный список того, что агент физически способен сделать.
Инструменты
Каждый инструмент описывается тремя полями: name, description, parameters (JSON Schema). Модель выбирает по описанию, поэтому описание пишется для читателя, который кроме него ничего не видит: что делает, когда применять, чего не делает, что вернёт.
Правила, которые экономят потом дни отладки:
- Одно действие — один инструмент. Универсальный
do_everythingс полемactionвнутри превращает выбор в угадывание. - Обязательных параметров минимум, у остальных — значения по умолчанию. Каждое обязательное поле повышает шанс, что модель придумает его содержимое.
- Ответ структурированный: статус, данные, и текст ошибки, по которому можно исправиться.
Ошибка 500не лечится,период задан наоборот: date_from позже date_to— лечится на следующем шаге. - Чтение и запись разделены на уровне имени:
get_ordersиcreate_orderникогда не живут в одной функции. - Пагинация и лимиты внутри инструмента, а не в голове модели. Инструмент, способный вернуть десятки тысяч строк, забьёт контекст с первого вызова.
Второй путь — MCP: инструмент описывается один раз на стороне сервера и подключается к любому совместимому клиенту без переписывания адаптеров под каждый рантайм.
Память
Длинная история и память — разные вещи. Слоёв три.
| Слой | Где живёт | Что попадает | Когда чистится |
|---|---|---|---|
| Контекст шага | окно модели | системная инструкция, последние сообщения, результаты вызовов | при переполнении, вытеснением старого |
| Рабочая память | файл или таблица задачи | план, что уже сделано, промежуточные выводы | по завершении задачи |
| Долговременная | БД, векторный индекс, репозиторий заметок | правила домена, факты, прошлые решения | вручную, ревизией |
Типичная ошибка — тащить в каждый шаг всю переписку целиком. Контекст забивается сырыми выгрузками, модель начинает отвечать по началу диалога и терять свежие факты. Рабочий приём: результат тяжёлого вызова сохранить в файл, в историю положить короткую сводку и путь к файлу, а сам файл читать инструментом по требованию.
Долговременная память ценна не объёмом, а тем, что в неё попадают правила. Разобранный сбой заканчивается строчкой в инструкции агента, иначе тот же сбой повторится на следующей неделе.
Контроль
Ограничители ставятся в коде оркестратора, не в промпте. Промпт — рекомендация, код — граница.
- Белый список инструментов на роль или на запуск.
- Подтверждение человека для всего, что пишет наружу.
max_stepsи таймаут на задачу. Без них зацикливание съедает бюджет молча.- Бюджет токенов с остановкой по достижении.
- Песочница для исполнения кода и команд.
- Журнал: номер шага, имя инструмента, аргументы, усечённый результат, длительность, стоимость.
- Идемпотентность записи: ключ операции, чтобы повтор после таймаута не создал дубль.
Правило: модель только предлагает вызов, исполняет его код, а право на запись выдаётся списком, а не доверием.
Как подключить
Шаг 1. Выбрать одну задачу с проверяемым результатом. «Помогать с аналитикой» не проверяется. «По названию компании найти её договоры и вернуть даты окончания» — проверяется на десятке примеров.
Шаг 2. Описать инструменты. Начните с двух-трёх, только чтение. Схема выглядит так:
{
"type": "function",
"function": {
"name": "get_orders",
"description": "Вернуть заказы клиента за период. Только чтение. Максимум 200 строк, дальше — cursor.",
"parameters": {
"type": "object",
"properties": {
"client_id": {"type": "integer"},
"date_from": {"type": "string", "format": "date"},
"date_to": {"type": "string", "format": "date"},
"cursor": {"type": "string"}
},
"required": ["client_id"]
}
}
}
Шаг 3. Написать цикл. Каркас на любом языке одинаковый:
messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": task}]
for step in range(MAX_STEPS):
resp = client.chat(messages=messages, tools=TOOLS)
msg = resp.choices[0].message
messages.append(msg)
if not msg.tool_calls:
return msg.content
for call in msg.tool_calls:
result = registry[call.function.name](json.loads(call.function.arguments))
log(step, call.function.name, call.function.arguments, result)
messages.append({"role": "tool", "tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False)[:MAX_TOOL_CHARS]})
raise StepLimit()
Обрезка результата (MAX_TOOL_CHARS) обязательна с первого дня, иначе один неудачный вызов положит окно.
Шаг 4. Системная инструкция. Четыре блока: роль и предметная область; что запрещено (какие инструменты не трогать, что не выдумывать); формат финального ответа; критерий завершения — по какому признаку задача считается закрытой.
Шаг 5. Журнал с первого запуска. Пишите в файл или таблицу, а не в stdout. Разбор любого странного поведения начинается с чтения последовательности вызовов, и без неё остаётся гадание.
Шаг 6. Ограничители. MAX_STEPS, таймаут на инструмент, таймаут на задачу, лимит суммарных токенов, allowlist.
Шаг 7. Золотой набор. Несколько десятков реальных входов с известным правильным исходом, сложенных в файл. Прогон набора — единственный способ увидеть, что правка промпта улучшила поведение, а не переставила ошибки.
Шаг 8. Права на запись. Добавляются последними, по одной операции, с подтверждением и с ключом идемпотентности.
Полезные сценарии
- Разбор входящей почты: классифицировать письмо, вытащить реквизиты, завести карточку в трекере.
- Дежурный по мониторингу: собрать ошибки за ночь, сгруппировать по причине, отправить сводку с ссылками.
- Сверка данных между двумя системами: найти расхождения и вернуть список пар с пояснением, без записи.
- Подготовка регулярного отчёта: сходить в базу и в рекламный кабинет, собрать цифры, оформить по шаблону.
- Первая линия поддержки: найти ответ в базе знаний, ответить, эскалировать при неуверенности.
- Рутина в трекере: перенести задачи по правилам, проставить метки, собрать список зависших.
Ограничения
Задача с одним детерминированным ответом дешевле решается скриптом: если правило записывается в SQL или в двадцать строк кода, агент добавит только недетерминированность и счёт за токены.
Жёсткая латентность плохо совместима с циклом: каждый шаг — сетевой вызов модели плюс исполнение инструмента, и число шагов заранее неизвестно.
Отладка отличается от обычной: одинаковый вход даёт разные траектории. Тесты пишутся на исход и на инварианты (не более N шагов, ни одного вызова из чёрного списка, схема ответа валидна), а не на точный текст.
Стоимость растёт нелинейно: каждый шаг заново прогоняет всю накопленную историю, поэтому длинная цепочка дорожает быстрее, чем кажется по числу шагов.
Ответственность не делегируется. Всё, что агент делает наружу — письма, платежи, публикации, удаление, — остаётся на владельце процесса, и это диктует, где стоит подтверждение.
Как проверить результат
Прогнать золотой набор и посмотреть на пять чисел из журнала:
- Доля задач, дошедших до финального ответа без исчерпания лимита шагов.
- Доля правильных исходов при ручной сверке с эталоном.
- Среднее и максимальное число шагов. Всплеск максимума — признак зацикливания.
- Доля вызовов, отвергнутых валидацией схемы. Высокая — описание параметров неоднозначно.
- Повторы одного инструмента с одинаковыми аргументами подряд — модель не поняла результат, чинится текстом ответа инструмента.
Отдельно проверить ограничители: подсунуть заведомо неразрешимую задачу и убедиться, что цикл останавливается по max_steps и пишет причину, а не крутится до исчерпания бюджета. Затем убрать один инструмент из allowlist и убедиться, что попытка вызова заканчивается отказом в коде, а не выполнением.
