Référence de workflow conversationnel
Cette page est une référence exécutable unique pour une conversation de longue durée dans le runtime Laravel embarqué. Elle combine des fonctionnalités qui disposent chacune de leur propre guide : les signaux pour les entrées ordonnées, les activités pour le travail du modèle, les requêtes de lecture pour le statut et la continuation dans une nouvelle exécution pour borner l’historique. Les workers en mode service utilisent les mêmes primitives via leurs guides SDK.
La source est ConversationWorkflow dans l’application exemple,
sous app/Workflows/Conversation.
Elle utilise un modèle factice déterministe : elle ne nécessite donc ni compte
chez un fournisseur d’IA ni secret. C’est une référence du schéma de durabilité,
pas un chatbot de production, et elle ne promet rien en matière de latence ni de
montée en charge.
Exécuter l’exemple
Avec le worker de file de l’application exemple en cours d’exécution (composer run dev) :
php artisan app:conversation \
--message="Hello" \
--message="Please refund my order" \
--message="Thanks" \
--turns-per-run=2
Sans --message, la commande demande une saisie. Un message contenant « refund »
amène le modèle factice à proposer une action, et la commande demande alors une
approbation. --decision=decline répond par un refus aux approbations scriptées.
Ouvrez /waterline pour inspecter chaque exécution de la chaîne.
Déroulement d’un tour
#[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
}
}
}
- Chaque signal
messagecorrespond à une entrée utilisateur. Le workflow applique les messages acceptés un tour à la fois, dans l’ordre de leur acceptation. - L’appel au modèle s’exécute dans une activité. La relecture réutilise le résultat enregistré au lieu d’appeler de nouveau le modèle.
- Lorsque le modèle propose une action, le workflow lui attribue un identifiant
avec
uuid4(), qui enregistre la valeur dans l’historique : la relecture renvoie donc le même identifiant. Il attend ensuite un signaldecisionqui reprend cet identifiant ; la requête de lecturestatusl’indique. Les signaux sont mis en mémoire tampon par nom : une décision portant sur une autre proposition (envoyée trop tôt, deux fois, ou renvoyée après une réponse incertaine) est donc ignorée au lieu de s’appliquer à la proposition suivante. Seule une approbation de la proposition en attente exécuteApplyApprovedActionActivity. Un refus ou une expiration enregistre la réponse et ne modifie rien. RecordReplyActivityécrit la réponse dans la table applicativeconversation_records, sous une référence construite à partir de l’ID du workflow et du numéro de tour. L’historique ne stocke que cette référence.- La requête de lecture
statusindique la phase (waiting_for_input,thinking,awaiting_approval,applying_action,recording_reply,continuing_as_newoucompleted), la génération, le nombre de tours, toute proposition en attente et la référence de la dernière réponse. Lisez le texte de la réponse à l’aide de cette référence.
Ce qui survit à la perte d’un worker
Les tests de l’exemple exécutent le moteur une tâche à la fois et arrêtent un worker à un point choisi :
| Défaillance | Ce qui se passe |
|---|---|
| Un worker prend sous bail la tâche qui applique un message, puis s’arrête brutalement | Rien ne progresse avant l’expiration du bail. Le passage de réparation remet ensuite la même tâche à un autre worker, qui applique le message une seule fois. |
| Un worker s’arrête brutalement pendant l’exécution de l’activité du modèle | Après l’expiration du bail, l’activité fait l’objet d’une nouvelle tentative sur un autre worker. L’exécution enregistre un seul achèvement pour cette activité, et la réponse est enregistrée une seule fois. |
| Le worker de réponse écrit la ligne, puis s’arrête brutalement avant d’en rendre compte | La nouvelle tentative exécute de nouveau l’activité. Comme la ligne a pour clé la référence, une seule réponse subsiste. |
Les activités s’exécutent au moins une fois : une nouvelle tentative peut répéter un travail déjà effectué hors du moteur. L’historique durable enregistre chaque résultat une seule fois. Donnez à chaque effet de bord une clé stable, comme le font les références de réponse et d’action, et transmettez cette même clé à tout système externe. Consultez les garanties d’exécution et l’expiration du bail et la redistribution.
Borner l’historique
Après $turnsPerRun tours, l’exécution continue dans une nouvelle exécution.
L’exécution suivante reçoit uniquement l’état explicite transmis à
continueAsNew() : la génération, le nombre de tours, un résumé borné et la
référence de la dernière réponse. Les autres propriétés de l’objet workflow sont
reconstruites par relecture et ne sont pas copiées.
Les messages acceptés mais pas encore appliqués passent à l’exécution suivante dans leur ordre d’origine. Cela s’applique aux signaux déclarés envoyés à l’instance de workflow. Un signal envoyé à une exécution précise est rejeté si cette exécution se clôt avant. Consultez acceptation et clôture des signaux et la continuation dans une nouvelle exécution.
Ce qui reste à votre charge
- Stockage des charges utiles. L’exemple conserve le texte des réponses dans une table applicative. Choisissez sa rétention, son contrôle d’accès et sa sauvegarde, ou utilisez le stockage externe des charges utiles.
- Résumé. Le modèle factice conserve un résumé courant de longueur fixe. Avec un modèle réel, c’est à vous de décider quel contexte franchit la continuation dans une nouvelle exécution et jusqu’à quelle taille il peut croître.
- Qui peut envoyer des entrées ou approuver. Le moteur enregistre le signal et ses arguments. Authentifiez et autorisez les émetteurs et les approbateurs dans votre application avant d’envoyer le signal ; consultez identité de l’émetteur et autorisation.
- Effets externes. Le moteur ne garantit pas qu’un appel externe a lieu exactement une fois. Utilisez des clés d’idempotence, comme le fait l’activité d’action approuvée.
- Envois de signaux réessayés. Si la réponse à un envoi de signal est perdue, consultez après une réponse incertaine avant de le renvoyer.
L’exemple stocke les réponses via une activité et n’utilise pas les flux de
messages depuis le code du workflow. Dans les versions de Workflow jusqu’à
2.20.0 incluse, outbox()->sendReference() et inbox()->receive() ne sont pas
sûrs à la relecture dans le code du workflow
(workflow#1010) ;
consultez l’avertissement dans les flux de messages.