Перейти до основного вмісту
Версія: 2.x

Еталонний розмовний workflow

Це один виконуваний еталон тривалої розмови у вбудованому runtime Laravel. Він поєднує можливості, що мають власні посібники: signal для впорядкованого вводу, activity для роботи моделі, query для статусу та continue-as-new для обмеження історії. Worker сервісного режиму використовують ті самі примітиви через свої посібники SDK.

Вихідний код — ConversationWorkflow у прикладі застосунку, у каталозі app/Workflows/Conversation. Він використовує детерміновану імітацію моделі, тому не потребує облікового запису постачальника AI чи секретів. Це еталон шаблону стійкості, а не чат-бот для production, і він не дає жодних обіцянок щодо затримки чи масштабу.

Запуск​

Коли worker черги прикладу застосунку працює (composer run dev):

php artisan app:conversation \
--message="Hello" \
--message="Please refund my order" \
--message="Thanks" \
--turns-per-run=2

Без --message команда запитує ввід. Повідомлення, що містить «refund», змушує імітацію моделі запропонувати дію, і команда запитує схвалення. --decision=decline відповідає на сценарні запити схвалення відмовою. Відкрийте /waterline, щоб переглянути кожен запуск у ланцюжку.

Як працює хід​

#[Signal('message', [['name' => 'text', 'type' => 'string']])]
#[Signal('decision', [
['name' => 'proposalId', 'type' => 'string'],
['name' => 'approved', 'type' => 'bool'],
['name' => 'reviewer', 'type' => 'string'],
])]
class ConversationWorkflow extends Workflow
{
public function handle(array $state = [], int $turnsPerRun = 3, int $inactivitySeconds = 3600): array
{
$this->state = array_replace($this->state, $state);

while (true) {
if ($this->turnsThisRun >= $turnsPerRun) {
return continueAsNew(
array_replace($this->state, ['generation' => $this->state['generation'] + 1]),
$turnsPerRun,
$inactivitySeconds,
);
}

$text = await('message', $inactivitySeconds);

// A null text is the inactivity timeout; "/end" finishes.
// Otherwise one turn runs:
// activity(FakeModelActivity::class, ...) proposes a reply
// uuid4() records an ID for a proposed action (replay-safe)
// await('decision', ...) waits for a decision naming that ID
// activity(RecordReplyActivity::class, ...) stores the reply
}
}
}
  1. Кожен signal message — це один ввід користувача. Workflow застосовує прийняті повідомлення по одному ходу за раз у порядку їхнього прийняття.
  2. Виклик моделі виконується в activity. Відтворення повторно використовує зафіксований результат замість повторного виклику моделі.
  3. Коли модель пропонує дію, workflow призначає їй ID через uuid4(). Він записує значення в історію, тож відтворення повертає той самий ID. Потім workflow чекає на signal decision, що повторює цей ID; query status повідомляє його. Signals буферизуються за назвою, тому рішення щодо будь-якої іншої пропозиції (надіслане зарано, двічі або повторно після невизначеної відповіді) ігнорується, а не застосовується до наступної пропозиції. Лише схвалення пропозиції, що очікує, запускає ApplyApprovedActionActivity. Відмова або тайм-аут записують відповідь і нічого не змінюють.
  4. RecordReplyActivity записує відповідь у таблицю застосунку conversation_records за посиланням, побудованим з ID workflow і номера ходу. Історія зберігає лише це посилання.
  5. Query status повідомляє фазу (waiting_for_input, thinking, awaiting_approval, applying_action, recording_reply, continuing_as_new або completed), покоління, кількість ходів, пропозицію, що очікує на рішення, якщо вона є, і посилання на останню відповідь. Текст відповіді читайте за цим посиланням.

Що переживає втрату worker​

Тести прикладу виконують рушій по одному завданню за раз і зупиняють worker в обраній точці:

ЗбійЩо відбувається
Worker отримує в оренду завдання, що застосовує повідомлення, а потім припиняє роботуНічого не просувається, доки оренда не завершиться. Потім прохід відновлення передає те саме завдання іншому worker, який застосовує повідомлення один раз.
Worker припиняє роботу під час виконання activity моделіПісля завершення оренди activity повторюється на іншому worker. Запуск фіксує одне завершення цієї activity, а відповідь записується один раз.
Worker відповіді записує рядок, а потім припиняє роботу до звітуПовторна спроба знову виконує activity. Оскільки ключем рядка є посилання, залишається одна відповідь.

Activity виконуються щонайменше один раз: повторна спроба може повторити роботу, яка вже відбулася поза рушієм. Стійка історія фіксує кожен результат один раз. Дайте кожному побічному ефекту сталий ключ, як це роблять посилання відповіді та дії, і передавайте той самий ключ будь-якій зовнішній системі. Див. гарантії виконання та завершення оренди й повторну доставку.

Обмеження історії​

Після $turnsPerRun ходів запуск продовжується як новий. Наступний запуск отримує лише явний стан, переданий у continueAsNew(): покоління, кількість ходів, обмежений підсумок і посилання на останню відповідь. Інші властивості об’єкта workflow відновлюються відтворенням і не копіюються.

Повідомлення, які було прийнято, але ще не застосовано, переходять до наступного запуску в початковому порядку. Це стосується оголошених signal, надісланих екземпляру workflow. Signal, надісланий одному конкретному запуску, відхиляється, якщо цей запуск закривається раніше. Див. прийняття та закриття signal і continue-as-new.

Що залишається вашою відповідальністю​

  • Зберігання даних. Приклад зберігає текст відповідей у таблиці застосунку. Оберіть для неї термін зберігання, контроль доступу й резервне копіювання або використайте зовнішнє сховище даних.
  • Підсумовування. Імітація моделі веде поточний підсумок фіксованої довжини. Зі справжньою моделлю ви вирішуєте, який контекст переходить через continue-as-new і наскільки великим він може стати.
  • Хто може надсилати ввід або схвалювати. Рушій записує signal та його аргументи. Автентифікуйте й авторизуйте відправників і рецензентів у своєму застосунку до надсилання signal; див. особу відправника та авторизацію.
  • Зовнішні ефекти. Рушій не забезпечує виконання зовнішнього виклику рівно один раз. Використовуйте ключі ідемпотентності, як це робить activity схваленої дії.
  • Повторне надсилання signal. Якщо відповідь на надсилання signal втрачено, перш ніж надсилати його знову, див. після невизначеної відповіді.

Приклад зберігає відповіді через activity і не використовує потоки повідомлень з коду workflow. У релізах Workflow до 2.20.0 включно outbox()->sendReference() та inbox()->receive() у коді workflow не є безпечними для відтворення (workflow#1010); див. попередження в потоках повідомлень.