Акты Становления

012: Агент/План

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

Сообщение План — это главный способ заставить агента работать над задачей шаг за шагом, запоминая, что он уже сделал. Хотя система может выполнять и простые, одноразовые команды, наличие Плана в контексте означает, что мы переключаемся на более длительный процесс. Когда агент видит План, его Цикл выполнения понимает: «Ага, этот план и все результаты (Состояние) нужно запомнить и использовать на следующих шагах».

Эта «память» очень важна для создания умных агентов, которые могут адаптироваться. Когда языковая модель (LLM) получает текущий План вместе с актуальным объектом Состояния, она полностью понимает, что происходит и на каком она этапе. Это позволяет ей с умом следовать существующему плану, добавлять в него новые Вызовы инструментов или даже полностью отбросить старый план и придумать новый, если что-то пошло не так. Без сообщения План любая команда считается разовой, и никакой информации о стратегии или результатах на следующие шаги не переносится.

Как формируется План

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

Так устанавливается чёткая зависимость: второй Вызов инструмента не может начаться, пока первый не закончит свою работу и не запишет результат в Состояние.

Например, План, чтобы получить профиль пользователя, а затем сделать его краткое описание, будет состоять из двух Вызовов инструментов:

записывает в

читается

состояние.профиль_пользователя

получитьПрофиль

сделатьКраткийОбзор

[
  {
    "_tool": "fetchUserProfile",
    "userName": "Alice",
    "_outputPath": "†state.userProfileData"
  },
  {
    "_tool": "summarizeProfile",
    "profile": "†state.userProfileData",
    "_outputPath": "†state.profileSummary"
  }
]

Здесь вызов summarizeProfile зависит от результата fetchUserProfile, что создаёт план из двух шагов. Эту зависимость можно представить в виде простой схемы.

Содержимое Плана: Граф потока данных

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

состояние.солнечно

состояние.не_солнечно

состояние.предложение

состояние.предложение

Узнать погоду

Солнечно?

Найти парк

Найти кино

Предложить вариант

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

Но такая структура в виде графа подходит не только для описания планов действий. Агента можно попросить создать граф Вызовов инструментов, который представляет что-то совсем другое — например, схему связей в социальной сети, рабочий процесс для GitHub Actions или структуру базы данных.

Очень важно отличать такие результаты от Плана. Хотя они используют ту же структуру графа, они не являются «планами» в архитектурном смысле, пока их не передадут в следующий Запрос как контекстное сообщение План с намерением его выполнить. Это различие помогает не путать создание схемы существующей системы с созданием стратегии, которую нужно выполнить.

Хотя содержимое Плана может быть мощным инструментом для мозгового штурма, обсуждений и «размышлений вслух», его основное применение в этой системе — определять выполняемые рабочие процессы. Для этой цели мы используем особый тип графа, который называется Направленный ациклический граф (НАГ), где каждый узел — это Вызов инструмента.

У НАГ есть несколько ключевых свойств, которые делают его идеальным для выполнения задач:

Чтобы реализовать итеративную логику, такую как цикл «for», используется шаблон вложенного, делегированного выполнения. Внешний План управляет состоянием цикла (например, счётчиком итераций) и для каждой итерации вызывает подзапрос через Делегата. Этот подзапрос содержит свой собственный отдельный, ациклический План, который выполняет логику для одной итерации. Это гарантирует, что циклы создаются явно и безопасно.

  • Граф: «Граф» — это всё содержимое сообщения План: совокупность всех Вызовов инструментов (узлов) и зависимостей по данным, которые их связывают (рёбер).
  • Направленный: Связи односторонние, как улица с односторонним движением. Они определяются потоком данных. Шаг, который создаёт данные, должен идти перед шагом, который их использует.
  • Ациклический: В процессе не может быть круговых зависимостей, что гарантирует наличие чёткого начала и конца. Это как в игре-бродилке, где нельзя, пройдя по кругу, вернуться на старт. Это очень важная мера безопасности, чтобы языковая модель случайно не создала бесконечный цикл. Система проверяет, что План является ациклическим, прежде чем его выполнять.

Стратегии планирования и выполнения

Самая мощная особенность этой архитектуры — это связь между декларативным Планом и его выполнением. Это контролируется свойством mode (режим) внутри самого сообщения План, что позволяет использовать разные стратегии.

Языковая модель (LLM) всегда выступает в роли планировщика. А режим mode определяет, должна ли LLM также немедленно выполнять внутренние задачи.

Нетерпеливое выполнение (по умолчанию)

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

  • Если задача требует внутреннего инструмента (например, кратко пересказать текст), LLM сгенерирует и вызов, и его _output (результат) в одном потоке мыслей.
  • Если задача требует внешнего Действия, LLM генерирует вызов, который затем немедленно отправляется исполнителю действий через организатор Цикла выполнения.

Этот режим оптимизирован для скорости и самостоятельности. Главный компромисс, особенно на первом шаге плана, заключается в том, что любая логика с ветвлением решается немедленно. Схема инструмента может позволять ветвление _outputPath (например, †state.sunny || †state.rainy), но в режиме eager непрерывный процесс мышления LLM сводит эту возможность к одному конкретному пути в решении, которое она генерирует.

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

Ленивое выполнение (Обдуманное планирование)

Пользователь может выбрать более обдуманный рабочий процесс, установив mode в lazy (ленивый). Эта стратегия вводит строгое разделение между планированием и выполнением. В этом режиме LLM получает инструкцию действовать исключительно как планировщик.

  • Она не будет выполнять внутренние задачи.
  • Её единственная цель — сгенерировать полный, декларативный граф потока данных из Вызовов инструментов. Если схема инструмента допускает ветвление _outputPath, LLM сохранит это выражение в плане, отложив выбор до этапа выполнения.

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

  • Проверить: Система может проверить граф на наличие циклических зависимостей или других структурных ошибок.
  • Сымитировать: Можно провести «пробный запуск», чтобы предсказать поведение рабочего процесса.
  • Представить на утверждение: План можно показать человеку для проверки, изменения или одобрения перед выполнением (Участие человека).

Выполнением занимается Цикл выполнения, который интерпретирует сообщение План и запускает Вызовы инструментов в правильном порядке на основе их зависимостей, заполняя объект Состояние по мере продвижения.

План как развивающаяся стратегия

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

Этот цикл превращает одноразовый результат в постоянную стратегию:

  • Контекст для запроса содержит объект Состояние и сообщение План с предыдущего шага.
  • Решение, сгенерированное LLM, содержит новый набор Вызовов инструментов, который становится новым Планом для следующего шага.

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

Пример: Планирование на один шаг вперёд

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

Сценарий: Агенту поддержки клиентов нужно обработать возврат средств. Стандартная процедура, запущенная запросом пользователя, — сначала проверить историю платежей для контекста, а затем оформить возврат.

1. Начальный запрос

Цикл начинается с запроса клиента. На основе этого ввода LLM формулирует стандартный двухэтапный План для обработки возврата. Это представляет собой идеальный, наиболее распространённый рабочий процесс.

Контекст и схема для запроса

// Agent.Request(config, schema, context)
{
  "schema": {
    "type": "object",
    "properties": {
      "calls": { "type": "array" },
      "output": {
        "type": "object",
        "nullable": true,
        "properties": {
          "confirmationId": { "type": "string" },
          "message": { "type": "string" }
        }
      }
    }
  },
  "context": [
    {
      "type": "input",
      "request": "Я бы хотел вернуть деньги за последний заказ.",
      "customerId": "cust_123",
      "amount": 50.0
    }
  ]
}

`Решение` LLM

{
  "calls": [
    {
      "_tool": "checkBillingHistory",
      "customerId": "†input.customerId"
    },
    {
      "_tool": "issueRefund",
      "customerId": "†input.customerId",
      "amount": "†input.amount"
    }
  ],
  "output": null
}

2. Следующий запрос в цикле

Цикл выполнения выполняет вызов checkBillingHistory и заполняет Состояние. История показывает некоторые сложности (например, предыдущий возвратный платёж). На этом этапе агент без чётких указаний мог бы с большой вероятностью выбрать другой доступный инструмент, escalateToSupervisor (передать супервайзеру).

Однако сообщение План в контексте обеспечивает необходимую структуру. Сопоставляя то, что он знает (сложное Состояние), с тем, что он должен делать (План), LLM точно понимает свою позицию в рабочем процессе и придерживается «идеального сценария».

Контекст

[
  {
    "type": "state",
    "billingHistory": {
      "orders": 5,
      "lastChargeback": "2025-09-10"
    }
  },
  {
    "type": "plan",
    "plan": [
      {
        "_tool": "checkBillingHistory",
        "customerId": "†input.customerId"
      },
      {
        "_tool": "issueRefund",
        "customerId": "†input.customerId",
        "amount": "†input.amount"
      }
    ]
  }
]

`Решение` LLM

{
  "calls": [
    {
      "_tool": "issueRefund",
      "customerId": "†input.customerId",
      "amount": "†input.amount"
    }
  ],
  "output": {
    "confirmationId": "refund_xyz789",
    "message": "Возврат был успешно обработан."
  }
}

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

Пример: Корректировка плана

Этот пример показывает, как агент может изменить существующий План в ответ на новую информацию, выбрав другой инструмент.

Контекст

Агенту даётся существующий «идеальный» План и новый Ввод от пользователя, который вводит новое ограничение.

[
  { type: 'tool', tool: Tool.bookFlight },
  { type: 'tool', tool: Tool.bookHotel },
  { type: 'tool', tool: Tool.findPetFriendlyHotel },
  {
    type: 'plan',
    plan: [
      {
        _tool: 'bookFlight',
        destination: '†input.destination',
      },
      {
        _tool: 'bookHotel',
        destination: '†input.destination',
      },
    ],
  },
  {
    type: 'input',
    destination: 'Berlin',
    instruction: "Кстати, я поеду со своей собакой.",
  },
];

`Решение` LLM

LLM понимает, что первоначальный план больше не подходит. Он отбрасывает старый план и генерирует новый, заменяя bookHotel на более специализированный инструмент.

{
  "calls": [
    {
      "_tool": "bookFlight",
      "destination": "†input.destination"
    },
    {
      "_tool": "findPetFriendlyHotel",
      "destination": "†input.destination"
    }
  ],
  "output": null
}

Агент не просто меняет параметр; он коренным образом изменяет свою стратегию, выбирая более подходящий инструмент (findPetFriendlyHotel) на основе новых требований. Этот новый набор Вызовов инструментов становится Планом для следующего шага в Цикле выполнения.

Пример: Обработка сбоя

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

1. Начальный план

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

Начальный контекст

Agent.Request(config, {
  schema: {
    type: 'object',
    properties: {
      calls: { type: 'array' },
      output: {
        type: 'object',
        nullable: true,
        properties: {
          status: {
            type: 'string',
            enum: ['Success', 'Failed'],
          },
        },
      },
    },
  },
  context: [
    { type: 'tool', tool: 'Tool.processPayment' },
    { type: 'tool', tool: 'Tool.confirmOrder' },
    { type: 'tool', tool: 'Tool.reportFailure' },
    { type: 'input', amount: 50.0 },
  ],
});

Начальное решение

{
  "calls": [
    {
      "_tool": "processPayment",
      "amount": "†input.amount",
      "_outputPath": "†state.receipt || †state.error"
    },
    {
      "_tool": "confirmOrder",
      "receipt": "†state.receipt"
    }
  ],
  "output": null
}

2. Сбой и перепланирование

Цикл выполнения пытается запустить processPayment, но инструмент даёт сбой. Система заносит ошибку в †state.error. На следующей итерации LLM видит это новое состояние ошибки вместе с исходным (теперь устаревшим) планом и генерирует новое решение для обработки сбоя.

Контекст для следующего запроса

[
  {
    "type": "state",
    "error": { "code": "card_declined", "message": "Ваша карта отклонена." }
  },
  // Оригинальный, теперь устаревший план всё ещё в контексте
  {
    "type": "plan",
    "plan": [
      { "_tool": "processPayment", "_outputPath": "†state.receipt || †state.error" },
      { "_tool": "confirmOrder", "receipt": "†state.receipt" }
    ]
  }
]

Новое решение (перепланированное)

{
  "calls": [
    {
      "_tool": "reportFailure",
      "error": "†state.error"
    }
  ],
  "output": { "status": "Failed" }
}

Агент распознал ошибку в Состоянии, проигнорировал устаревший «идеальный» План и сгенерировал новый, одношаговый план для сообщения о сбое. Это демонстрирует способность агента реагировать на неожиданные результаты.

Пример: Планирование по схеме

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

Контекст со схемой состояния

Вызывающая сторона предоставляет Ввод и сообщение Состояние, которое содержит только схему. Эта схема определяет предполагаемый поток данных, указывая «переменные», которые должен использовать план.

[
  { "type": "tool", "tool": "Tool.detectLanguage" },
  { "type": "tool", "tool": "Tool.isEnglish" },
  { "type": "tool", "tool": "Tool.translateText" },
  {
    "type": "input",
    "text": "Bonjour le monde"
  },
  {
    "type": "state",
    "schema": {
      "type": "object",
      "properties": {
        "language": { "type": "string" },
        "isEnglish": { "type": "boolean" },
        "translatedText": { "type": "string" }
      }
    }
  }
]

`Решение` LLM

LLM использует схему Состояния как руководство для построения действительного плана, правильно связывая Путь для вывода одного инструмента со входом следующего.

{
  "calls": [
    {
      "_tool": "detectLanguage",
      "text": "†input.text",
      // LLM знает, что нужно использовать этот путь из схемы.
      "_outputPath": "†state.language"
    },
    {
      "_tool": "isEnglish",
      // Она правильно ссылается на вывод предыдущего шага.
      "language": "†state.language",
      "_outputPath": "†state.isEnglish"
    },
    {
      "_tool": "translateText",
      "text": "†input.text",
      "isEnglish": "†state.isEnglish",
      "_outputPath": "†state.translatedText"
    }
  ],
  "output": null
}

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

Этот итеративный цикл планирования и выполнения является ядром Процесса. Он представляет собой самодостаточный снимок рабочего процесса, фиксирующий доступные Инструменты, текущее Состояние и сам План.

От единого плана к многоразовым рабочим процессам

Сообщение План определяет последовательность действий для конкретной задачи. Чтобы сделать эти рабочие процессы по-настоящему мощными, нам нужен способ упаковывать их в многоразовые компоненты, которые можно вызывать из других Планов.

Протокол для такого параллельного выполнения описан в 013: Агент/Экземплирование.