бесплатно без регистрации урок 2 из 16

Круг работы: шаг, история, остановка

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

19 мин чтения · 4 главы ·4 задания ·домашняя работа

Что происходит за один шаг

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

1. Сложить историю сообщений и позвать модель2. Разобрать ответ: это просьба об инструменте или итог?просьба → зовём инструмент,результат в историюитог → выходим из кругаи начинаем шаг зановокруг закончен
Шаг всегда один и тот же: собрали историю, спросили модель, разобрали ответ. Разветвление только в конце.

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

Шаг второй: позвать модель. Вместе с историей ей передаётся список доступных инструментов. Это важная деталь: список приходит с каждым вызовом, а не объявляется один раз в начале. Значит, набор инструментов можно менять по ходу — и на этом строятся полезные приёмы, о которых будет отдельный урок.

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

Теперь стоит остановиться на том, что шаг стоит дороже предыдущего. Каждый оборот кладёт в историю минимум два сообщения — просьбу и результат, — и следующий вызов читает всё накопленное. Первый шаг обошёлся в размер поручения; восьмой — в размер поручения плюс семь пар сообщений, среди которых бывают длинные: вывод команды, содержимое файла, простыня ошибок.

Шаг 1Шаг 2Шаг 3Шаг 8стоимость шага растёт вместе с историей, а не стоит одинаково
Каждый шаг кладёт в окно ещё два сообщения. К десятому шагу модель читает всё, что было, — и платите вы за это тоже каждый раз.

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

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

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

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

Правило цикла целиком

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

история = [системное, поручение]
шаг = 0
пока шаг < ПОТОЛОК:
    ответ = модель(история, инструменты)
    история += ответ
    если в ответе нет просьб об инструментах:
        вернуть ответ            # модель закончила
    для каждой просьбы:
        результат = вызвать(просьба)
        история += сообщение_от_инструмента(результат)
    шаг += 1
вернуть ОТКАЗ: упёрлись в потолок

Здесь сразу видно несколько решений, каждое из которых можно принять иначе — и от каждого что-то зависит.

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

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

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

И отдельно про что считать ответом «закончила». В заготовке это «нет просьб об инструментах». Такое правило простое и работает, но у него есть слабое место: модель может закончить, не сделав задачу, — например, решив, что нужных данных нет. Снаружи это выглядит как успешное завершение. Поэтому в серьёзных системах выход из цикла не отдают модели целиком: рядом с её «готово» ставят проверку. Об этом следующая глава.

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

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

Три причины остановиться

Круг может закончиться тремя способами, и путать их нельзя.

Модель сказала «готово»штатноПроверка сошласьштатно, надёжнееУпёрлись в потолокэто отказтретью строку нельзя показывать человеку как результатона означает «не доделал», а не «сделал»
Две из трёх — штатные, третья означает, что что-то пошло не так. Различать их обязательно: иначе провал выглядит как успех.

Первый: модель перестала просить инструменты. Штатный выход, самый частый. Его слабость мы уже назвали: это её мнение о том, что работа сделана, а не факт.

Второй: сошлась проверка. Обвязка после каждого шага (или после «готово») сама запускает то, что считается доказательством: тесты, сборку, сравнение с эталоном. Выход из цикла происходит, когда проверка прошла. Это надёжнее первого настолько, что стоит считать его основным, а первый — запасным.

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

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

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

Полезно завести и второй, более мягкий предел: шаги без продвижения. Если три шага подряд не изменили ни одного файла и не дали нового результата проверки, агент ходит по кругу, даже если формально ещё не исчерпал потолок. Такое условие ловит вырождение раньше и дешевле, чем счётчик шагов, потому что смотрит не на количество, а на результат.

Есть и третья мерка, менее очевидная и очень полезная: повторяющееся действие. Агент второй раз читает тот же файл, третий раз запускает ту же команду с тем же результатом — это почти всегда признак, что он застрял и ходит по кругу в надежде, что в этот раз выйдет иначе. Заметить просто: обвязка помнит, что уже звалось и с какими параметрами. Дальше есть выбор — оборвать круг или сказать модели прямо в истории: «этот вызов уже был и вернул то же самое».

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

Трасса: что видно снаружи

Всё, что мы описали, происходит внутри и снаружи невидимо. Поэтому вместе с циклом пишется трасса — запись того, что было на каждом шаге.

ШагЧто попросила модельЧто вернулосьИтог шага
1прочитать orders.js218 строк
2запустить тесты3 из 40 упали
3записать orders.jsзаписаноизменён 1 файл
4запустить тесты40 из 40 прошлипроверка сошлась
5итоговый ответвыход из круга

Такая таблица — минимум, и она уже отвечает на большинство вопросов при разборе. Видно, сколько шагов ушло, какие инструменты звались, где произошёл поворот и чем всё кончилось. Дальше в курсе будет отдельный урок про наблюдаемость, где мы добавим к этому размеры, время и расход, — но начинать надо с этого минимума, потому что он пишется за полчаса и окупается в первый же разбор.

Есть одна деталь, которую стоит решить заранее: что делать с содержимым. Полная трасса со всеми сообщениями — это иногда мегабайты на задачу, и в ней бывают секреты и чужие данные. Разумный порядок такой: в трассу идут факты (какой инструмент, с какими параметрами, сколько вернулось, сколько заняло, чем кончилось), а содержимое — по отдельному решению и с ограничением по сроку хранения. Об этом подробно в уроке про секреты.

И последнее про круг, что стоит унести с собой. Цикл — это место, где обвязка перестаёт быть «вызовом модели» и становится системой. У него есть состояние, стоимость, время, условия выхода и режимы отказа. Всё, что мы будем изучать дальше — инструменты, контекст, проверка, бюджеты, — это уточнение одного из мест в тех десяти строках, которые вы уже видели.

Напоследок — про то, как трасса меняет разговор в команде. Без неё обсуждение сбоя состоит из мнений: один говорит «модель слабая», другой «поручение плохое», третий «инструменты кривые», и никто не может ни доказать, ни опровергнуть. С трассой обсуждение занимает три минуты и заканчивается фактом: на четвёртом шаге модель попросила файл, которого нет, потому что имя пришло из вывода на втором шаге, где команда напечатала предупреждение. Дальше понятно, что чинить.

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

Задания урока

Каждое проверяется сразу: ответ сверяется с эталоном, и на неверный вариант приходит разбор.

  1. Восьмой шаг агента стоит примерно столько же, сколько первый.средне
  2. Что обязательно попадает в историю после вызова инструмента?средне
  3. Соотнесите причину остановки и то, чем она является.средне
  4. При хорошем ходе дел задача решается за 4 шагов. Какой потолок разумно поставить по правилу «умножить на два с половиной»?средне
Пройти урок с проверкой заданий Курс «ИИ-Инжиниринг» открыт и бесплатен, прогресс сохраняется.
Открыть курс