Глава 2.1: Примеры инструкций
Пример 1: Планирование процесса
Этот пример показывает, как структурировать сложный, многоэтапный процесс, который включает в себя разные контексты выполнения (LLM, сервер, пользователь). Процесс предназначен для планирования встречи и требует определения участников, проверки их доступности, выбора временного слота и управления приглашениями. Каждое переключение контекста явно смоделировано, а зависимости данных между шагами четко определены через ссылки. Процесс показывает, как обрабатывать и немедленные операции LLM, и длительные задачи сервера в рамках единого, согласованного рабочего процесса.
Вложенное выражение:
// Send the approved invitation
SendInvitation_Activity(
// Get user approval for the drafted invitation
ConfirmInvitation_User(
// Create invitation with participants and selected time slot
DraftInvitation(
// Extract participant info from user's request
participants: IdentifyParticipants(prompt),
// Find a time that works for both participants
slot: FindCommonSlot(
// Get calendar data for identified participants
FetchAvailability_Activity(IdentifyParticipants(prompt)))
)
)
)
)
Или альтернативно:
// Extract participant info from user's request
const participants = IdentifyParticipants(prompt);
// Get calendar data for identified participants
const availabilityParams = FetchAvailability(participants);
const availabilityData = FetchAvailability_Activity(availabilityParams);
// Find a time that works for both participants
const commonSlot = FindCommonSlot(availabilityData);
// Create invitation with participants and selected time slot
const invitation = DraftInvitation(participants, commonSlot);
// Get user approval for the drafted invitation
const approval = ConfirmInvitation_User(invitation);
// Send the approved invitation
const result = SendInvitation_Activity(approval);
Упрощенное выражение в псевдокоде:
Process(
// LLM определяет участников и готовит параметры запроса к календарю
LLM_Context({
participants: IdentifyParticipants(prompt),
availabilityParams: FetchAvailability({ participants }),
}),
// Сервер получает актуальные данные календаря
Server_Context({
availabilityData: FetchAvailability_Activity({ availabilityParams }),
}),
// LLM анализирует доступность и создает приглашение
LLM_Context({
commonSlot: FindCommonSlot({ availabilityData }),
invitation: DraftInvitation({ participants, commonSlot }),
}),
// Пользователь просматривает и одобряет приглашение
User_Context({
approval: ConfirmInvitation_User({ invitation }),
}),
// Сервер отправляет одобренное приглашение
Server_Context({
result: SendInvitation_Activity({ invitation, approval }),
})
);
Этот пример демонстрирует, как сложный, многоэтапный процесс запускается с помощью простой, сфокусированной инструкции. У процесса много шагов (определение участников, получение доступности, поиск слотов, создание и подтверждение приглашений), но начальная инструкция фокусируется только на том, что необходимо для старта.
Важно отметить, что только первый контекст LLM (LLM_Context
с IdentifyParticipants
и FetchAvailability
) служит точкой входа для всего процесса — это начальное взаимодействие с LLM становится запускающей логикой, которая стартует рабочий процесс. После этого оно становится частью Vibe процесса, который переносит состояние через весь его жизненный цикл, поддерживая контекст и зависимости данных между шагами. Это гарантирует, что каждое последующее действие (выполняемое сервером, LLM или пользователем) имеет доступ к необходимой информации с предыдущих шагов. Vessel лишь облегчает это взаимодействие, но сам не становится процессом.
Схема процесса
Эта схема является частью Vibe aug:processes/schedule-meeting:1
, который существует как прототип полного рабочего процесса планирования встречи со всеми его шагами и зависимостями данных. Далее в этом объяснении мы покажем, как Vessel может создать экземпляр этого процесса, завершив его первый шаг в работе с контекстом LLM.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Процесс: Планирование встречи",
"description": "Надежный процесс для планирования встречи, включающий шаги LLM и сервера.",
"type": "object",
"properties": {
"llmContext1": {
"title": "Начальная настройка и подготовка параметров",
"description": "LLM определяет участников и готовит параметры для операций сервера",
"type": "object",
"properties": {
"identifyParticipants1": {
"title": "Определение участников встречи",
"type": "object",
"properties": {
"organizer": {
"type": "string",
"description": "Человек, инициирующий встречу."
},
"attendee": {
"type": "string",
"description": "Человек, которого приглашают."
}
},
"required": ["organizer", "attendee"]
},
"fetchAvailability": {
"title": "Подготовка параметров для запроса доступности",
"references": ["identifyParticipants"],
"type": "object",
"properties": {
"organizerId": {
"type": "string",
"description": "Системный идентификатор организатора, полученный из identifyParticipants.organizer."
},
"attendeeId": {
"type": "string",
"description": "Системный идентификатор участника, полученный из identifyParticipants.attendee."
},
"timeRange": {
"type": "object",
"properties": {
"start": {
"type": "string",
"format": "date-time",
"description": "Начало временного диапазона для проверки доступности."
},
"end": {
"type": "string",
"format": "date-time",
"description": "Конец временного диапазона для проверки доступности."
}
},
"required": ["start", "end"]
}
},
"required": ["organizerId", "attendeeId", "timeRange"]
}
},
"required": ["identifyParticipants", "fetchAvailability"]
},
"serverContext1": {
"title": "Получение и обработка данных",
"description": "Сервер получает и обрабатывает данные о доступности из календаря",
"type": "object",
"properties": {
"FetchAvailability_Activity": {
"title": "Результаты доступности в календаре",
"references": ["llmContext1.fetchAvailability"],
"type": "object",
"properties": {
"organizerSlots": {
"type": "array",
"items": { "type": "string", "format": "date-time" },
"description": "Доступные временные слоты для организатора."
},
"attendeeSlots": {
"type": "array",
"items": { "type": "string", "format": "date-time" },
"description": "Доступные временные слоты для участника."
}
},
"required": ["organizerSlots", "attendeeSlots"]
}
},
"required": ["FetchAvailability_Activity"]
},
"llmContext2": {
"title": "Выбор времени встречи и черновик сообщения",
"description": "LLM анализирует доступность и готовит приглашение на встречу",
"type": "object",
"properties": {
"findCommonSlot": {
"title": "Найти общий доступный слот",
"references": [
"llmContext1.identifyParticipants",
"serverContext1.FetchAvailability_Activity"
],
"type": "object",
"properties": {
"selectedSlot": {
"type": "string",
"format": "date-time",
"description": "Выбранное время встречи, которое подходит обоим участникам."
},
"reasoning": {
"type": "string",
"description": "Объяснение, почему был выбран этот слот."
}
},
"required": ["selectedSlot", "reasoning"]
},
"draftInvitation": {
"title": "Черновик приглашения",
"references": ["llmContext1.identifyParticipants", "findCommonSlot"],
"type": "object",
"properties": {
"subject": { "type": "string" },
"body": {
"type": "string",
"description": "Полный текст электронного письма с приглашением."
}
},
"required": ["subject", "body"]
}
},
"required": ["findCommonSlot", "draftInvitation"]
},
"userContext": {
"title": "Точка принятия решения пользователем",
"description": "Ожидает одобрения пользователем предложенных деталей встречи",
"type": "object",
"properties": {
"confirmInvitation": {
"title": "Получить подтверждение пользователя",
"references": ["llmContext2.draftInvitation"],
"type": "object",
"properties": {
"decision": {
"type": "string",
"enum": ["Approve", "Reject"],
"description": "Решение пользователя по предложенной встрече."
}
},
"required": ["decision"]
}
},
"required": ["confirmInvitation"]
},
"serverContext2": {
"title": "Окончательная отправка сообщения",
"description": "Сервер отправляет одобренное приглашение на встречу",
"type": "object",
"properties": {
"sendInvitation": {
"title": "Отправить приглашение",
"references": [
"llmContext1.identifyParticipants",
"llmContext2.draftInvitation",
"userContext.confirmInvitation"
],
"type": "object",
"properties": {
"messageId": { "type": "string" },
"status": { "type": "string" }
},
"required": ["messageId", "status"]
}
},
"required": ["sendInvitation"]
}
},
"required": ["llmContext1", "serverContext1", "llmContext2", "userContext", "serverContext2"]
}
Целенаправленный поток данных с references
Мета-свойство references
, показанное в схеме выше, является ключом к управлению потоком данных и размером контекста. Движок рабочего процесса использует это свойство, чтобы добавлять в промпт для LLM только необходимые данные с предыдущих шагов для текущего шага.
Давайте рассмотрим конкретный пример. Представьте себе шаг step5_draft_email
, которому нужно имя пользователя с предыдущего шага, step4_find_user
. Схема будет выглядеть так:
"step5_draft_email": {
"description": "Составить персонализированное письмо пользователю, найденному на предыдущем шаге.",
"references": [ "step4_find_user.output" ],
"type": "object",
"properties": {
"recipientName": {
"description": "Используйте поле 'userName' из объекта 'step4_find_user.output', предоставленного в контексте промпта.",
"type": "string"
},
"emailBody": { "type": "string" }
}
}
Движок рабочего процесса обрабатывает это так:
- Он видит
references: [ "step4_find_user.output" ]
. - Он извлекает весь выходной объект
{ "userId": "...", "userName": "..." }
из своего состояния. - Он создает минимальный контекст и добавляет его в промпт для LLM.
- Он отправляет схему для
step5_draft_email
в LLM, но без свойстваreferences
. Затем LLM использует предоставленный контекст, чтобы следовать инструкциям в поляхdescription
. Это обеспечивает чистый и мощный механизм для передачи данных от действий на стороне сервера обратно в процесс рассуждений LLM.
Это гарантирует, что LLM получает только необходимую информацию, предотвращая перегрузку контекста и сохраняя эффективность взаимодействия. Большая схема Процесс: Планирование встречи
широко использует этот шаблон для передачи информации между контекстами LLM, сервера и пользователя.
Модульная схема
Вместо того чтобы показывать LLM всю сложную схему процесса, мы создаем инструмент специально для запуска процесса. Этот инструмент создается путем взятия первого контекста LLM из Примера 1 и его улучшения дополнительными когнитивными шагами через композицию Инструкций.
Давайте проследим, как работает эта композиция, начиная с исходной структуры процесса.
Использование инструкций для когнитивного улучшения
Инструкции могут компоноваться друг с другом, потому что их схемы сливаются подобно объектам JavaScript, а JSON Schema сохраняет порядок свойств. Это позволяет создавать макро-подобные Инструкции, которые оборачивают другие Инструкции, добавляя при этом дополнительные шаги:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Introspection_Mixin",
"description": "Инструкция, которая улучшает любую схему, добавляя анализ перед действием и рефлексию после действия через естественную композицию",
"type": "object",
"properties": {
"_considerations": {
"type": "string",
"description": "Перед выполнением действия проанализируйте запрос и выделите ключевые моменты."
},
"default": {
"type": "object",
"description": "Это заполнитель, который будет заменен схемой цели во время выполнения."
},
"_feedback": {
"type": "string",
"description": "После завершения действия предоставьте обратную связь о принятых решениях."
}
},
"required": ["_considerations", "default", "_feedback"]
}
Когда эта Инструкция компонуется с другой, свойство default
действует как слот, который заменяется свойствами другой схемы. Поскольку порядок свойств сохраняется, мы сначала получаем _considerations
, затем все свойства из скомпонованной схемы, а затем _feedback
— создавая макро-подобную обертку, которая добавляет шаги «мышления» вокруг основной задачи.
Компоновка инструмента
Вот как мы превращаем первый контекст LLM из Примера 1 в наш инструмент:
Исходный фрагмент первого контекста LLM из процесса выглядит так:
const llmContext1 = FetchAvailability(IdentifyParticipants(prompt));
Vessel оборачивает его с запуском процесса и интроспекцией:
StartProcess({
name: 'ScheduleMeeting',
llmContext1: Introspect_Mixin(FetchAvailability(IdentifyParticipants(prompt))),
});
Introspect_Mixin — это миксин-инструкция, которая принимает именованные свойства:
StartProcess({
name: 'ScheduleMeeting',
llmContext1: Introspect_Mixin({
default: {
identifyParticipants: IdentifyParticipants(prompt),
fetchAvailability: FetchAvailability({ participants }),
},
}),
});
Это приводит к композиции JSON-схем, где инструкции компонуются вместе. Обратите внимание, как мы используем поле description
для указания, как передавать аргументы между шагами. Например, описание fetchAvailability
указывает, что оно должно повторно использовать email-адреса участников с шага идентификации. Этот подход обеспечивает ясный поток данных между компонентами инструкции, не требуя явного объявления параметров.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Запустить процесс планирования",
"description": "Запускает рабочий процесс планирования встречи с определением участников и проверкой доступности",
"type": "object",
"allOf": [
{ "$ref": "aug:/vibes/macros/StartProcess:1" },
{
"properties": {
"name": { "const": "ScheduleMeeting" },
"llmContext1": {
"type": "object",
"allOf": [
{ "$ref": "aug:/vibes/macros/Introspect_Mixin" },
{ "$ref": "aug:/vibes/macros/Loop_Mixin", "description": "Цикл не более 3 раз" },
{
"properties": {
"default": {
"type": "object",
"properties": {
"identifyParticipants": {
"allOf": [
{ "$ref": "aug:instructions/identifyParticipants" },
{ "description": "Извлекает и идентифицирует информацию об участниках из промпта пользователя, определяя, кто должен быть включен во встречу" }
]
},
"fetchAvailability": {
"allOf": [
{ "$ref": "aug:processes/schedule-meeting:1#fetchAvailability" },
{ "description": "Получает данные о доступности из календаря для определенных участников, повторно используя их email-адреса с шага идентификации для проверки расписаний: llm_context_1.identifyParticipant" }
]
}
}
}
}
}
]
}
}
}
]
}
Вот как это выглядит, когда миксин раскрыт, и LLM выполняет свою работу
Промпт: Ассистент, запланируй встречу между Алисой и Бобом!
StartProcess({
name: 'ScheduleMeeting',
llmContext1: {
_considerations: "Нужно проверить доступность обоих участников в рабочее время",
identifyParticipants: {
organizer: 'alice@example.com',
attendee: 'bob@example.com',
},
fetchAvailability: {
organizerEmail: 'alice@example.com', // Повторно используется из identifyParticipants
attendeeEmail: 'bob@example.com', // Повторно используется из identifyParticipants
timeRange: {
start: '2024-03-20T09:00:00Z',
end: '2024-03-20T17:00:00Z',
},
},
_feedback:
'Подготовлено к проверке доступности для Алисы и Боба в течение их рабочего дня. После завершения процесс продолжится поиском общих слотов.',
},
});
Это показывает, как структура схемы направляет LLM:
- Продумать важные моменты перед действием (
_considerations
) - Предоставить полные, хорошо структурированные данные для начальных операций
- Повторно использовать email-адреса участников напрямую с шага идентификации
- Сделать выводы как о немедленном шаге, так и о продолжении процесса (
_feedback
) - Явно запустить процесс
ScheduleMeeting
на сервере и продолжить его там
Скомпонованная схема инструмента
Это окончательная схема, представленная LLM, со всеми разрешенными ссылками. Она объединяет собственные корректировки Vessel с определениями инструментов (например, инструкции о том, как передавать вывод одного шага на вход другого)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Инструментарий Vessel: Планировщик встреч",
"description": "Набор инструментов, доступных Vessel для планирования встреч.",
"type": "object",
"properties": {
"name": {
"type": "string",
"const": "ScheduleMeeting",
"description": "Определяет запускаемый процесс"
},
"llmContext1": {
"type": "object",
"properties": {
"_considerations": {
"type": "string",
"description": "Перед выполнением действия проанализируйте запрос и выделите ключевые моменты."
},
"identifyParticipants": {
"title": "Определение участников встречи",
"type": "object",
"description": "Определяет участников встречи из ввода пользователя\nИзвлекает и идентифицирует информацию об участниках из промпта пользователя, определяя, кто должен быть включен во встречу",
"properties": {
"organizer": {
"type": "string",
"format": "email",
"description": "Email человека, инициирующего встречу."
},
"attendee": {
"type": "string",
"format": "email",
"description": "Email человека, которого приглашают."
}
},
"required": ["organizer", "attendee"]
},
"fetchAvailability": {
"title": "Получить доступность участников",
"type": "object",
"description": "Получает доступность в календаре для указанных участников\nПолучает данные о доступности из календаря для определенных участников, повторно используя их email-адреса с шага идентификации для проверки расписаний",
"properties": {
"organizerEmail": {
"type": "string",
"format": "email",
"description": "Email организатора"
},
"attendeeEmail": {
"type": "string",
"format": "email",
"description": "Email участника."
},
"timeRange": {
"type": "object",
"properties": {
"start": {
"type": "string",
"format": "date-time",
"description": "Начало временного диапазона для проверки доступности."
},
"end": {
"type": "string",
"format": "date-time",
"description": "Конец временного диапазона для проверки доступности."
}
},
"required": ["start", "end"]
}
},
"required": ["organizerEmail", "attendeeEmail", "timeRange"]
},
"_feedback": {
"type": "string",
"description": "После завершения действия предоставьте обратную связь о принятых решениях."
}
},
"required": ["_considerations", "identifyParticipants", "fetchAvailability", "_feedback"]
}
},
"required": ["name", "llmContext1"]
}