012: Агент/План
Контекстное сообщение, содержащее граф потока данных из Вызовов Инструментов, который представляет стратегию агента. Оно передается между шагами для обеспечения итеративного выполнения и адаптации.
Сообщение План — это основной механизм для обеспечения итеративного выполнения с сохранением состояния. Хотя система агентов способна обрабатывать простые, одноразовые запросы, предоставление сообщения Plan в контексте означает переход к постоянному рабочему процессу. Его наличие указывает Циклу Выполнения сохранять сгенерированный план и итоговое Состояние на протяжении нескольких шагов.
Это постоянство является краеугольным камнем создания адаптивных агентов. Когда LLM получает текущий План вместе с актуальным объектом Состояния, она получает полное ситуационное понимание своего положения в рабочем процессе. Это позволяет ей разумно следовать существующему плану, генерировать новые Вызовы Инструментов для его расширения или полностью отбрасывать его и перепланировать в ответ на неожиданные результаты. Без сообщения Plan запрос рассматривается как операция без сохранения состояния, и никакое состояние или стратегия не переносятся на последующие шаги.
Как формируется План
Связи в графе создаются не с помощью явных указателей, а через простую и мощную конвенцию потока данных, использующую объект Состояния.
-
Узлы (Вызовы Инструментов): Каждый шаг в рабочем процессе — это Вызов Инструмента, представляющий действие, которое необходимо выполнить.
-
Рёбра (Объект Состояния): Связи между шагами создаются путем записи в объект Состояния и чтения из него. Один Инструмент записывает свой результат по определенному пути в Состоянии, используя мета-свойство Путь Вывода. Следующий Инструмент может затем использовать этот результат в качестве входных данных, ссылаясь на тот же путь с помощью Ссылки на Переменную.
Это устанавливает четкую зависимость: второй Вызов Инструмента не может быть выполнен, пока первый не завершится и не заполнит Состояние.
Например, План для получения профиля пользователя и его последующего суммирования будет состоять из двух Вызовов Инструментов:
[
{
"_tool": "fetchUserProfile",
"userName": "Alice",
"_outputPath": "†state.userProfileData"
},
{
"_tool": "summarizeProfile",
"profile": "†state.userProfileData",
"_outputPath": "†state.profileSummary"
}
]
Здесь вызов summarizeProfile зависит от результата fetchUserProfile, создавая двухэтапный план. Эту взаимосвязь можно представить в виде простого графа.
Содержимое Плана: Граф Потока Данных
План не ограничивается линейными последовательностями. Он может представлять сложные рабочие процессы с условной логикой, где путь выполнения зависит от результата предыдущего шага:
Содержимое сообщения План — это граф потока данных. Эта структура используется для представления стратегии агента в виде последовательности взаимосвязанных Вызовов Инструментов. Представляя рабочий процесс в виде графа, система может четко определить зависимости между шагами, где результат одного Вызова Инструмента становится входными данными для другого. Этот формат на основе графа обеспечивает четкую, машиночитаемую структуру, которую Цикл Выполнения агента может интерпретировать и выполнять.
Базовая структура графа не ограничивается только определением исполняемых рабочих процессов. Агенту можно дать команду сгенерировать граф Вызовов Инструментов, который представляет что-то совершенно иное — визуализацию социальной сети, рабочий процесс GitHub Actions или схему базы данных.
Крайне важно отличать эти результаты от Плана. Хотя они используют ту же структуру графа, они не являются «планами» в архитектурном смысле, если только они не передаются в последующий Запрос как контекстное сообщение Плана с намерением быть выполненными. Это различие предотвращает путаницу между созданием представления существующей системы и созданием исполняемой стратегии.
Хотя содержимое Плана может быть мощным инструментом для мозгового штурма, обсуждения и «размышлений вслух», его основное применение в этой системе — определение исполняемых рабочих процессов. Для этой цели мы используем особый тип графа, называемый Направленным Ациклическим Графом (DAG), где каждый узел — это Вызов Инструмента.
У DAG есть несколько ключевых свойств, которые делают его идеальным для выполнения:
Для реализации итеративной логики, такой как «цикл for», используется шаблон вложенного, делегированного выполнения. Внешний План управляет состоянием цикла (например, счетчиком итераций), и на каждой итерации он вызывает подзапрос через Делегата. Этот подзапрос содержит свой собственный, отдельный, ациклический План, который выполняет логику для одной итерации. Это гарантирует, что циклы создаются явно и безопасно.
- Граф: «Граф» — это всё содержимое сообщения Плана — совокупность всех Вызовов Инstrumenta (узлов) и зависимостей по данным, которые их соединяют (рёбер).
- Направленный: Связи односторонние, они определяются потоком данных. Шаг, который создает данные, должен идти перед шагом, который их использует.
- Ациклический: Рабочий процесс не может иметь циклических зависимостей, что обеспечивает ему четкое начало и конец. Это критически важная функция безопасности для предотвращения генерации LLM рабочего процесса с бесконечным циклом. Система проверяет, что План является ациклическим перед выполнением.
Стратегии Планирования и Выполнения
Самая мощная особенность этой архитектуры — это взаимосвязь между декларативным Планом и его выполнением. Это контролируется свойством mode внутри самого сообщения Плана, что позволяет использовать различные стратегии.
LLM всегда выступает в роли планировщика. mode определяет, должна ли LLM также выступать в роли немедленного исполнителя для скрытых задач.
Немедленное Выполнение (по умолчанию)
По умолчанию План работает в режиме eager. В этом режиме планирование — это выполнение. Нет никакого обмена данными между созданием плана и действием по нему. Когда LLM генерирует решение, она выполняет единый, непрерывный акт рассуждения и выполнения для любых скрытых шагов, которые она может разрешить.
- Если задача требует скрытого инструмента (например, для суммирования текста), LLM сгенерирует
callи его_outputв одном мыслительном процессе. - Если задача требует явного Действия, LLM генерирует
call, который затем немедленно отправляется исполнителю действий оркестратором Цикла Выполнения.
Этот режим оптимизирован для скорости и автономности. Ключевой компромисс, особенно на первой итерации плана, заключается в том, что логика ветвления разрешается немедленно. Схема инструмента может допускать ветвящийся _outputPath (например, †state.sunny || †state.rainy), но в режиме eager непрерывный процесс рассуждения LLM сводит эту возможность к единственному конкретному пути в генерируемом ею решении.
Следовательно, решение на первом шаге немедленного выполнения никогда не будет содержать скрытого вызова с неразрешенным выражением ветвления. Выбор делается как часть начального этапа планирования/выполнения. Именно это делает выполнение линейным на данном этапе процесса. На последующих итерациях LLM имеет доступ к состоянию, созданному на предыдущих шагах, и может принимать более сложные решения о ветвлении на основе этого существующего состояния.
Отложенное Выполнение (Обдуманное Планирование)
Пользователь может выбрать более обдуманный рабочий процесс, установив mode в значение lazy. Эта стратегия обеспечивает строгое разделение планирования и выполнения. В этом режиме LLM получает инструкцию действовать как чистый планировщик.
- Она не будет выполнять скрытые операции.
- Ее единственная цель — сгенерировать полный, декларативный граф потока данных из Вызовов Инструментов. Если схема инструмента допускает ветвящийся
_outputPath, LLM сохранит это выражение в плане, откладывая выбор на этап выполнения.
Результатом является чистая структура данных, представляющая всю стратегию, которая не выполняется. Это создает критическую контрольную точку, где план может быть:
- Проверен: Система может проверить граф на наличие циклических зависимостей или других структурных ошибок.
- Симулирован: Можно выполнить «пробный запуск», чтобы предвидеть поведение рабочего процесса.
- Представлен для утверждения: План может быть показан человеку для проверки, изменения или утверждения перед выполнением (HITL).
Выполнение обрабатывается Циклом Выполнения, который интерпретирует сообщение Плана и запускает Вызовы Инструментов в правильном порядке на основе их зависимостей, заполняя объект Состояния по мере продвижения.
План как Развивающаяся Стратегия
План не статичен; это живая стратегия, которая может адаптироваться на каждом шаге цикла выполнения. Ключевое различие заключается в том, что План — это не просто любой результат от LLM. Когда агент впервые генерирует набор Вызовов Инструментов, это просто предложенная последовательность действий. Он становится настоящим Планом только тогда, когда передается как контекстное сообщение в следующий запрос в цикле.
Этот цикл превращает одноразовый результат в непрерывную стратегию:
- контекст для запроса содержит объект Состояния и сообщение Плана с предыдущего шага.
- решение, сгенерированное LLM, содержит новый набор Вызовов Инструментов, который становится новым Планом для следующего шага.
Этот итеративный процесс позволяет агенту быть как проактивным, так и реактивным. Он может следовать существующему Плану, но также может изменять его в ответ на результаты предыдущего шага. Например, если Вызов Инструмента завершается неудачей, агент может сгенерировать новый План, включающий шаги по обработке ошибок. Это делает систему устойчивой и адаптируемой.
Пример: Планирование на один шаг вперед
Этот пример демонстрирует, как План предоставляет структурный «идеальный сценарий», который направляет агента, не позволяя ему отклоняться от стандартной процедуры, даже когда доступны другие правдоподобные действия.
Сценарий: Агенту службы поддержки необходимо обработать возврат средств. Стандартная процедура, запускаемая запросом пользователя, заключается в том, чтобы сначала проверить историю платежей для получения контекста, а затем произвести возврат.
1. Первоначальный Запрос
Цикл начинается с запроса клиента. На основе этого input 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": "I'd like a refund for my last order.",
"customerId": "cust_123",
"amount": 50.0
}
]
}
`solution` от LLM
{
"calls": [
{
"_tool": "checkBillingHistory",
"customerId": "†input.customerId"
},
{
"_tool": "issueRefund",
"customerId": "†input.customerId",
"amount": "†input.amount"
}
],
"output": null
}
2. Следующий Запрос в Цикле
Цикл Выполнения выполняет вызов checkBillingHistory и заполняет Состояние. История выявляет некоторую сложность (например, предыдущий чарджбэк). На этом этапе агент без четких указаний мог бы правдоподобно выбрать другой доступный инструмент, escalateToSupervisor.
Однако сообщение Plan в контексте предоставляет необходимую структуру. Сопоставляя то, что она знает (сложное Состояние), с тем, что она должна сделать (План), 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"
}
]
}
]
`solution` от LLM
{
"calls": [
{
"_tool": "issueRefund",
"customerId": "†input.customerId",
"amount": "†input.amount"
}
],
"output": {
"confirmationId": "refund_xyz789",
"message": "The refund has been processed successfully."
}
}
План обеспечивает процедурную последовательность, предотвращая преждевременную эскалацию и удерживая агента на намеченном пути.
Пример: Корректировка Плана
Этот пример демонстрирует, как агент может изменять существующий План в ответ на новую информацию, выбирая другой инструмент.
Контекст
Агенту предоставляется существующий «идеальный сценарий» в виде Плана и новый Input от пользователя, который вводит новое ограничение.
[
{ 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` от 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": "Your card was declined." }
},
// Оригинальный, теперь устаревший план все еще находится в контексте
{
"type": "plan",
"plan": [
{ "_tool": "processPayment", "_outputPath": "†state.receipt || †state.error" },
{ "_tool": "confirmOrder", "receipt": "†state.receipt" }
]
}
]
Новое Решение (Перепланированное)
{
"calls": [
{
"_tool": "reportFailure",
"error": "†state.error"
}
],
"output": { "status": "Failed" }
}
Агент распознал error в Состоянии, проигнорировал устаревший «идеальный сценарий» Плана и сгенерировал новый, одношаговый план для reportFailure. Это демонстрирует способность агента реактивно справляться с неожиданными результатами.
Пример: Планирование на основе Схемы
Этот пример демонстрирует, как предоставление схемы для объекта Состояния действует как чертеж, направляя LLM к созданию структурно правильного Плана.
Контекст со Схемой Состояния
Вызывающая сторона предоставляет Input и сообщение State, которое содержит только схему. Эта схема определяет предполагаемый поток данных, указывая «переменные», которые должен использовать план.
[
{ "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` от 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: Агент/Экземплирование.