Разработка ИИ-агентов · Модуль 3 · Урок 3.2

Декомпозиция задач: prompt chaining vs динамическая

Разбить большую задачу на шаги

Сложную задачу почти всегда выгодно разложить на части: каждая меньше, проще для модели и легче проверяется. Вопрос в том, кто и когда определяет эти части. Здесь два полюса: статическая декомпозиция (prompt chaining) и динамическая.

Prompt chaining: фиксированная цепочка

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

Сильные стороны: предсказуемость, простая отладка, дешевизна. Между шагами удобно ставить программные gate'ы — проверки, которые решают, идти дальше, повторить шаг или прерваться (например, «если план пустой — переспросить»). Это повышает надёжность: ошибка ловится рано, а не в финале. Берите prompt chaining, когда задачу можно разбить на устойчивые шаги заранее — это большинство практических случаев.

Динамическая декомпозиция: план на лету

Когда состав и число подзадач заранее неизвестны, шаги планирует сама модель во время выполнения. Агент-координатор смотрит на задачу, придумывает подзадачи, раздаёт их (себе же на следующих оборотах или субагентам) и адаптируется по ходу. Это нужно для открытых задач: исследование, отладка, работа с непредсказуемым вводом.

Платой за гибкость становятся непредсказуемость, выше стоимость и риск зацикливания — поэтому здесь особенно важны safety-cap, проверки прогресса и грунтинг. Правило выбора: если можете разложить задачу на шаги заранее — делайте prompt chaining (он надёжнее). Динамику включайте, только когда статикой не обойтись. Гибридный вариант тоже частый: фиксированный каркас с одним динамическим шагом внутри.

Prompt chaining: фиксированная цепочка с gate между шагами
// Шаг цепочки: преобразует вход в выход.
type Step func(in string) (string, error)

// chain прогоняет вход через шаги по очереди; gate проверяет промежуточный
// результат и может остановить цепочку (вернув ошибку).
func chain(in string, gate func(stepName, out string) error, steps ...struct {
    name string
    fn   Step
}) (string, error) {
    cur := in
    for _, s := range steps {
        out, err := s.fn(cur)
        if err != nil {
            return "", fmt.Errorf("шаг %s: %w", s.name, err)
        }
        if gate != nil {
            if err := gate(s.name, out); err != nil { // программный gate
                return "", fmt.Errorf("gate после %s: %w", s.name, err)
            }
        }
        cur = out // выход шага — вход следующего
    }
    return cur, nil
}

Anti-patterns

Анти-паттернПочему плохоКак правильно
Динамический план там, где хватит цепочкиЛишняя непредсказуемость и стоимостьЕсли шаги известны заранее — prompt chaining
Цепочка без проверок между шагамиОшибка раннего шага тихо тащится в финалПрограммные gate'ы между шагами: проверить/повторить/прервать
Слишком мелкая декомпозицияМного лишних вызовов, накладные расходыДробить до проверяемых, но осмысленных шагов
Динамика без safety-cap и проверки прогрессаЗацикливание, расход токеновЛимит шагов + контроль, что есть продвижение к цели

Практическое задание

  • Возьмите задачу и разложите её на 2–4 фиксированных шага (prompt chaining).
  • Реализуйте цепочку, где выход шага становится входом следующего.
  • Вставьте между шагами хотя бы один программный gate (например, проверку непустого/валидного промежуточного результата).
  • Придумайте задачу, которую заранее НЕ разложить, и опишите, как её решал бы динамический координатор.
  • Сформулируйте для своей задачи правило: когда хватает цепочки, а когда нужна динамика.

Проверка знаний

Чем prompt chaining отличается от динамической декомпозиции?

  • A Ничем, это синонимы
  • B В prompt chaining шаги и их порядок заданы заранее разработчиком; в динамической — план составляет модель во время выполнения
  • C Prompt chaining не использует LLM
  • D Динамическая всегда дешевле

Зачем ставить программные gate'ы между шагами цепочки?

  • A Чтобы замедлить выполнение
  • B Чтобы ловить ошибку раннего шага сразу (проверить/повторить/прервать), а не тащить её в финал
  • C Это требование компилятора
  • D Чтобы увеличить число вызовов модели

Когда оправдана динамическая декомпозиция?

  • A Всегда — она «умнее»
  • B Когда состав и число подзадач заранее неизвестны (исследование, отладка, открытый ввод)
  • C Когда шаги строго фиксированы
  • D Никогда