010: Агент/План
- Требуется:
- 004: Агент/Вызов
- 005: Агент/Цикл
- 008: Агент/Переменные
- 009: Агент/Состояние
- 011: Агент/Экземплирование
Это своего рода инструкция для агента, похожая на схему или рецепт. В ней шаг за шагом расписано, какие инструменты нужно использовать и в каком порядке. Агент смотрит на этот план, чтобы понимать, что делать дальше, и может менять его на ходу.
План — это как карта для путешественника. Когда умная программа (языковая модель) видит эту карту (План) и знает, где она находится сейчас (объект Состояния), она может понять, что делать дальше. Это и делает агента по-настоящему умным и гибким. Он может следовать существующему плану, добавлять в него новые шаги или даже полностью отбросить его и составить новый, если что-то пошло не так. Это позволяет ему действовать как по строгому сценарию, так и свободно импровизировать, исследуя новые пути.
Как формируется План
Связи между шагами в плане создаются не стрелочками, как на бумаге, а с помощью простого, но мощного правила: все обмениваются информацией через специальный «контейнер» — объект Состояния.
- Узлы (вызовы инструментов): Каждый шаг в плане — это «вызов инструмента», то есть какое-то действие, которое нужно выполнить.
- Связи (объект Состояния): Связи между шагами появляются, когда один инструмент записывает результат своей работы в «контейнер» Состояния, а другой читает его оттуда. Один инструмент кладёт результат в определённое место (используя мета-свойство
_outputPath
). Следующий инструмент может взять этот результат для своей работы, просто заглянув в то же самое место (используя ссылку на переменную).
Так появляется чёткая зависимость: второй инструмент не может начать работу, пока первый не закончит и не оставит свой результат в «контейнере».
Например, план по получению профиля пользователя и его краткому изложению будет состоять из двух вызовов инструментов:
[
{
"_tool": "fetchUserProfile",
"userName": "Alice",
"_outputPath": "†state.userProfileData"
},
{
"_tool": "summarizeProfile",
"profile": "†state.userProfileData",
"_outputPath": "†state.profileSummary"
}
]
Здесь вызов summarizeProfile
(сделать краткий пересказ) зависит от результата fetchUserProfile
(получить профиль), что создаёт план из двух шагов. Эту зависимость можно представить в виде простой схемы.
Содержание Плана: Граф потока данных
План не ограничен только линейными последовательностями. Он может представлять сложные рабочие процессы с условной логикой, где путь выполнения зависит от результата предыдущего шага:
По своей сути, План — это граф потока данных. Эта структура используется, чтобы представить стратегию агента как последовательность связанных между собой вызовов инструментов. Представляя рабочий процесс в виде графа, система может чётко определить зависимости между шагами, где результат одного вызова инструмента становится входными данными для другого. Этот формат на основе графа предоставляет понятную, машиночитаемую структуру, которую цикл выполнения агента может интерпретировать и выполнить.
Структура на основе графа не ограничивается только созданием исполняемых рабочих процессов. Агента можно попросить сгенерировать граф вызовов инструментов, который представляет что-то совершенно другое — визуализацию социальной сети, рабочий процесс GitHub Actions или схему базы данных.
Очень важно отличать такие результаты от Плана. Хотя они используют ту же структуру графа, они не являются «планами» в архитектурном смысле, если только они не передаются в последующий Запрос как контекстное сообщение Плана с намерением быть выполненными. Это различие помогает избежать путаницы между созданием представления существующей системы и созданием исполняемой стратегии.
Хотя содержание Плана может быть мощным инструментом для мозгового штурма, обсуждения и «размышлений вслух», его основное применение в этой системе — определение исполняемых рабочих процессов. Для этой цели мы используем определённый тип графа, называемый Направленным ациклическим графом (DAG), где каждый узел — это вызов инструмента.
У DAG есть несколько ключевых свойств, которые делают его идеальным для выполнения задач:
Чтобы реализовать логику повторений, например, цикл «for», используется шаблон вложенного, делегированного выполнения. Внешний План управляет состоянием цикла (например, счётчиком итераций), и для каждой итерации он вызывает подзапрос через Делегата. Этот подзапрос содержит свой собственный, отдельный ациклический План, который выполняет логику для одной итерации. Это гарантирует, что циклы создаются явно и безопасно.
- Граф: «Граф» — это всё содержимое сообщения Плана: набор всех вызовов инструментов (узлы) и зависимости по данным, которые их соединяют (рёбра).
- Направленный: Связи односторонние, как улица с односторонним движением. Направление определяется потоком данных. Шаг, который создаёт данные, должен идти до шага, который их использует.
- Ациклический: В рабочем процессе не может быть круговых зависимостей (петель). Это гарантирует, что у него есть чёткое начало и конец. Это важнейшая мера безопасности, чтобы языковая модель случайно не создала бесконечный цикл. Система проверяет План на ацикличность перед выполнением.
Разделение планирования и выполнения
Самая мощная особенность этой архитектуры — полное разделение планирования и выполнения. Поскольку План — это просто декларативная структура данных, агент может сгенерировать весь граф вызовов инструментов до того, как будет запущен какой-либо код.
Языковая модель выступает в роли планировщика, составляя массив вызовов, который представляет собой задуманный рабочий процесс. Эту структуру данных затем можно:
- Проверить: Система может проверить граф на наличие циклических зависимостей или других структурных ошибок.
- Сымитировать: Можно провести «пробный запуск», чтобы предсказать поведение рабочего процесса.
- Представить для утверждения: План можно показать человеку для проверки, изменения или одобрения перед выполнением, создавая важный уровень безопасности и совместной работы.
Выполнением занимается Цикл выполнения, который интерпретирует сообщение Плана и запускает вызовы инструментов в правильном порядке на основе их зависимостей, заполняя объект Состояния по мере продвижения.
План как развивающаяся стратегия
План не статичен; это живая стратегия, которую можно адаптировать на каждом шаге цикла выполнения. Ключевое отличие заключается в том, что Plan
— это не просто любой вывод от языковой модели. Когда агент впервые генерирует набор вызовов инструментов, это просто предлагаемая последовательность действий. Она становится настоящим Планом только тогда, когда передаётся как контекстное сообщение в следующий запрос в цикле.
Этот цикл превращает одноразовый вывод в постоянную стратегию:
- Контекст для запроса содержит объект Состояния и сообщение Плана с предыдущего шага.
- Решение, сгенерированное языковой моделью, содержит новый набор вызовов инструментов, который становится новым Планом для следующего шага.
Этот итеративный процесс позволяет агенту быть одновременно проактивным и реактивным. Он может следовать существующему Плану, но также может изменять его в ответ на результаты предыдущего шага. Например, если вызов инструмента завершился неудачно, агент может сгенерировать новый План, включающий шаги по обработке ошибок. Это делает систему устойчивой и адаптируемой.
Пример: Планирование на один шаг вперёд
Этот пример показывает, как План
задаёт основной «правильный путь», который направляет агента и не даёт ему отклониться от стандартной процедуры, даже если доступны другие возможные действия.
Сценарий: Агенту поддержки клиентов нужно обработать возврат средств. Стандартная процедура, запускаемая запросом пользователя, — сначала проверить историю платежей для получения контекста, а затем оформить возврат.
1. Первоначальный запрос
Цикл начинается с запроса клиента. На основе этого input
языковая модель формулирует стандартный двухэтапный План
для обработки возврата. Это представляет собой идеальный, наиболее распространённый рабочий процесс.
Контекст и схема для запроса
// 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
}
]
}
`solution` от языковой модели
{
"calls": [
{
"_tool": "checkBillingHistory",
"customerId": "†input.customerId"
},
{
"_tool": "issueRefund",
"customerId": "†input.customerId",
"amount": "†input.amount"
}
],
"output": null
}
2. Следующий запрос в цикле
Цикл выполнения запускает вызов checkBillingHistory
и заполняет Состояние. История показывает некоторые сложности (например, предыдущий возвратный платёж). В этот момент агент без чёткого плана мог бы выбрать другой доступный инструмент, например, escalateToSupervisor
(передать супервайзеру).
Однако сообщение Plan
в контексте обеспечивает необходимую структуру. Сопоставляя то, что он знает (сложное Состояние
), с тем, что он должен делать (План
), языковая модель понимает своё точное положение в рабочем процессе и придерживается «правильного пути».
Контекст
[
{
"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"
}
]
}
]
`solution` от языковой модели
{
"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: "Кстати, я буду путешествовать со своей собакой.",
},
];
`solution` от языковой модели
Языковая модель понимает, что первоначальный план больше не подходит. Она отбрасывает старый план и генерирует новый, заменяя 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
. На следующей итерации языковая модель видит это новое состояние ошибки вместе с первоначальным (теперь устаревшим) планом и генерирует новое решение для обработки сбоя.
Контекст для следующего запроса
[
{
"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" }
}
Агент распознал ошибку
в Состоянии
, проигнорировал устаревший «правильный» План
и сгенерировал новый, одношаговый план для сообщения о сбое
. Это демонстрирует способность агента реагировать на неожиданные результаты.
Пример: Планирование по схеме
Этот пример показывает, как предоставление схемы
для объекта Состояния действует как чертёж, направляя языковую модель к созданию структурно правильного Плана.
Контекст со схемой Состояния
Вызывающая сторона предоставляет Ввод
и сообщение Состояния
, которое содержит только схему
. Эта схема определяет предполагаемый поток данных, указывая «переменные», которые должен использовать план.
[
{ "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" }
}
}
}
]
`solution` от языковой модели
Языковая модель использует схему Состояния
как руководство для построения действительного плана, правильно связывая _outputPath
одного инструмента с входом следующего.
{
"calls": [
{
"_tool": "detectLanguage",
"text": "†input.text",
// Языковая модель знает, что нужно использовать этот путь из схемы.
"_outputPath": "†state.language"
},
{
"_tool": "isEnglish",
// Она правильно ссылается на вывод предыдущего шага.
"language": "†state.language",
"_outputPath": "†state.isEnglish"
},
{
"_tool": "translateText",
"text": "†input.text",
"isEnglish": "†state.isEnglish",
"_outputPath": "†state.translatedText"
}
],
"output": null
}
Предоставляя схему
, вызывающая сторона даёт языковой модели чёткий чертёж потока данных. Языковой модели не нужно угадывать имена переменных или последовательность; она просто заполняет предопределённые ячейки, что приводит к более надёжному и предсказуемому Плану
.
Этот итеративный цикл планирования и выполнения является ядром Процесса. Он представляет собой автономный снимок рабочего процесса, охватывающий доступные Инструменты, текущее Состояние и сам План.
От одного плана к многоразовым рабочим процессам
Сообщение Плана определяет последовательность действий для конкретной задачи. Чтобы сделать эти рабочие процессы по-настояшему мощными, нам нужен способ упаковывать их в многоразовые компоненты, которые можно вызывать из других Планов.
Протокол для такого параллельного выполнения описан в документе 011: Агент/Экземплирование.