Exemple Human-in-the-Loop
Exemple complet d'agent HITL avec approbation interactive, checkpoints et reprise.
Cet exemple démontre un assistant financier qui nécessite une approbation humaine pour les opérations sensibles comme les transferts d'argent, crée des checkpoints pour la récupération, et permet de reprendre depuis des états sauvegardés.
ORKA — HUMAN-IN-THE-LOOP WORKFLOW
👤 User Request
HITL Agent
Analyze request
Plan actions
Plan actions
⚠️ Sensitive Action?
Money transfer, deletion, etc.
No
Execute
Auto-run
Yes
🛑 Pause
Wait for approval
👤 Human Review
Approve/Reject/Modify
✅ Execute
If approved
✨ Result + Checkpoint
1. Définition des Tools
tools.ts
import { Tool } from '@orka-js/agent'; // Tool pour vérifier le solde d'un compteconst checkBalanceTool: Tool = { name: 'check_balance', description: 'Vérifie le solde d'un compte bancaire', parameters: [ { name: 'accountId', type: 'string', description: 'ID du compte', required: true }, ], async execute(input) { const accountId = input.accountId as string; const balances: Record<string, number> = { 'ACC-001': 5420.50, 'ACC-002': 12890.00, 'ACC-003': 850.25, }; const balance = balances[accountId] ?? 0; return { output: `Solde du compte ${accountId}: ${balance}€` }; },}; // Tool pour transférer de l'argent (opération sensible)const transferMoneyTool: Tool = { name: 'transfer_money', description: 'Transfère de l'argent entre deux comptes', parameters: [ { name: 'fromAccount', type: 'string', description: 'Compte source', required: true }, { name: 'toAccount', type: 'string', description: 'Compte destination', required: true }, { name: 'amount', type: 'number', description: 'Montant à transférer', required: true }, ], async execute(input) { const { fromAccount, toAccount, amount } = input; // Simulation du transfert const transactionId = `TXN-${Date.now()}`; return { output: `Transfert de ${amount}€ effectué de ${fromAccount} vers ${toAccount}. Transaction ID: ${transactionId}` }; },}; // Tool pour consulter l'historiqueconst getTransactionHistoryTool: Tool = { name: 'get_transaction_history', description: 'Récupère l'historique des transactions d'un compte', parameters: [ { name: 'accountId', type: 'string', description: 'ID du compte', required: true }, { name: 'limit', type: 'number', description: 'Nombre de transactions', required: false }, ], async execute(input) { const accountId = input.accountId as string; const limit = (input.limit as number) ?? 5; return { output: `Dernières ${limit} transactions du compte ${accountId}: [Achat -45€, Virement +200€, Retrait -100€]` }; },};2. Gestionnaire d'Approbation Interactive
Ce gestionnaire demande à l'utilisateur dans le terminal pour les décisions d'approbation. En production, vous intégreriez avec votre UI, Slack, email, ou système de webhook.
approval-handler.ts
import * as readline from 'readline';import type { InterruptRequest, InterruptResponse } from '@orka-js/agent'; // Gestionnaire d'interruption interactifasync function interactiveApprovalHandler( request: InterruptRequest): Promise<InterruptResponse> { console.log(`\n⚠️ INTERRUPTION REQUISE [${request.reason}]`); console.log(`📋 Message: ${request.message}`); if (request.data.toolName) { console.log(`🔧 Tool: ${request.data.toolName}`); console.log(`📥 Input: ${JSON.stringify(request.data.toolInput, null, 2)}`); } const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { rl.question( '\n👤 Votre décision? (a=approve, r=reject, m=modify): ', (answer) => { rl.close(); const status = answer.toLowerCase(); if (status === 'a') { resolve({ id: request.id, status: 'approved', respondedAt: new Date(), }); } else if (status === 'r') { resolve({ id: request.id, status: 'rejected', feedback: 'Opération refusée par l\'utilisateur', respondedAt: new Date(), }); } else if (status === 'm') { // Exemple: réduire le montant du transfert const modifiedInput = { ...request.data.toolInput }; if (modifiedInput.amount) { modifiedInput.amount = (modifiedInput.amount as number) / 2; console.log(`✏️ Montant modifié: ${modifiedInput.amount}€`); } resolve({ id: request.id, status: 'modified', modifiedData: { toolInput: modifiedInput }, feedback: 'Montant réduit de moitié', respondedAt: new Date(), }); } else { resolve({ id: request.id, status: 'rejected', feedback: 'Réponse invalide', respondedAt: new Date(), }); } } ); });}3. Configuration de l'Agent HITL
agent-setup.ts
import { OpenAIAdapter } from '@orka-js/openai';import { HITLAgent, MemoryCheckpointStore } from '@orka-js/agent'; // Créer le checkpoint storeconst checkpointStore = new MemoryCheckpointStore(); // Créer l'agent HITLconst financialAgent = new HITLAgent( { name: 'financial-assistant', goal: 'Aider l\'utilisateur avec ses opérations bancaires', tools: [checkBalanceTool, transferMoneyTool, getTransactionHistoryTool], maxSteps: 10, temperature: 0.2, verbose: true, hitl: { // Nécessite approbation pour les transferts d'argent requireApprovalFor: ['transfer_money'], // Auto-approuve les consultations (non sensibles) autoApproveTools: ['check_balance', 'get_transaction_history'], // Crée un checkpoint tous les 2 steps checkpointEvery: 2, // Timeout de 5 minutes pour les approbations defaultTimeoutMs: 5 * 60 * 1000, // Gestionnaire d'interruption onInterrupt: interactiveApprovalHandler, // Store de checkpoints checkpointStore, }, }, new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY! }));4. Écouteurs d'Événements
event-listeners.ts
// Écouter les événements de l'agentfinancialAgent.on('step:start', (event) => { console.log(`\n🔄 Step ${event.stepNumber} démarré`);}); financialAgent.on('step:complete', (event) => { console.log(`✅ Step ${event.stepNumber} terminé`); if (event.toolUsed) { console.log(` Tool utilisé: ${event.toolUsed}`); }}); financialAgent.on('checkpoint:created', (event) => { console.log(`💾 Checkpoint créé: ${event.checkpointId}`);}); financialAgent.on('interrupt:requested', (event) => { console.log(`⏸️ Interruption demandée: ${event.reason}`);}); financialAgent.on('interrupt:resolved', (event) => { console.log(`▶️ Interruption résolue: ${event.status}`);}); financialAgent.on('complete', (event) => { console.log(`\n🎉 Agent terminé!`); console.log(` Steps: ${event.totalSteps}`); console.log(` Interruptions: ${event.interruptCount}`);});5. Exécution de l'Agent
main.ts
async function main() { console.log('🤖 Assistant Financier HITL\n'); try { // Scénario 1: Transfert d'argent (nécessite approbation) console.log('=== Scénario 1: Transfert d\'argent ==='); const result1 = await financialAgent.run( 'Transfère 500€ de mon compte ACC-001 vers ACC-002' ); console.log('\n📊 Résultat:'); console.log('Réponse:', result1.output); console.log('Steps:', result1.steps.length); console.log('Interruptions:', result1.interrupts.length); console.log('Interrompu?', result1.wasInterrupted); // Afficher les détails des interruptions if (result1.interrupts.length > 0) { console.log('\nDétails des interruptions:'); result1.interrupts.forEach((interrupt, i) => { console.log(` ${i + 1}. Status: ${interrupt.status}`); if (interrupt.feedback) { console.log(` Feedback: ${interrupt.feedback}`); } }); } // Afficher les checkpoints créés if (result1.checkpoints.length > 0) { console.log('\n💾 Checkpoints créés:', result1.checkpoints); } } catch (error) { console.error('❌ Erreur:', error); }} main().catch(console.error);6. Reprendre depuis un Checkpoint
Si l'agent est interrompu ou plante, vous pouvez reprendre depuis le dernier checkpoint :
resume.ts
async function resumeFromCheckpoint() { // Récupérer tous les checkpoints de l'agent const checkpoints = await financialAgent.getCheckpoints(); if (checkpoints.length === 0) { console.log('Aucun checkpoint disponible'); return; } // Afficher les checkpoints disponibles console.log('\n💾 Checkpoints disponibles:'); checkpoints.forEach((cp, i) => { console.log(` ${i + 1}. ${cp.id} - Step ${cp.stepNumber} - ${cp.createdAt}`); }); // Reprendre depuis le dernier checkpoint const latestCheckpoint = checkpoints[checkpoints.length - 1]; console.log(`\n▶️ Reprise depuis: ${latestCheckpoint.id}`); const result = await financialAgent.run( 'Continue l\'opération précédente', latestCheckpoint.id ); console.log('\n✅ Opération reprise avec succès'); console.log('Repris depuis checkpoint:', result.resumedFromCheckpoint); console.log('Résultat:', result.output);} // UtilisationresumeFromCheckpoint().catch(console.error);7. Interruptions Manuelles
Vous pouvez également demander des confirmations ou revues manuelles pendant l'exécution de l'agent :
manual-interrupts.ts
// Dans votre tool ou logique métierconst complexOperationTool: Tool = { name: 'complex_operation', description: 'Effectue une opération complexe', parameters: [ { name: 'data', type: 'object', description: 'Données', required: true }, ], async execute(input, context) { // Demander une confirmation avant de continuer const confirmation = await context.agent.requestConfirmation( 'Cette opération est irréversible. Continuer?', { operation: 'complex_operation', data: input.data } ); if (confirmation.status !== 'approved') { return { output: 'Opération annulée par l\'utilisateur' }; } // Effectuer l'opération const result = performComplexOperation(input.data); // Demander une revue du résultat const review = await context.agent.requestReview( 'Veuillez vérifier le résultat', context.stepNumber, 'Opération complexe terminée' ); if (review.status === 'rejected') { return { output: 'Résultat rejeté, opération annulée' }; } return { output: `Opération réussie: ${result}` }; },};8. Sortie de l'Exemple Complet
output.txt
🤖 Assistant Financier HITL === Scénario 1: Transfert d'argent === 🔄 Step 1 démarré✅ Step 1 terminé Tool utilisé: check_balance 💾 Checkpoint créé: cp-1234567890 🔄 Step 2 démarré ⚠️ INTERRUPTION REQUISE [tool_approval]📋 Message: Approbation requise pour: transfer_money🔧 Tool: transfer_money📥 Input: { "fromAccount": "ACC-001", "toAccount": "ACC-002", "amount": 500} 👤 Votre décision? (a=approve, r=reject, m=modify): a ▶️ Interruption résolue: approved✅ Step 2 terminé Tool utilisé: transfer_money 💾 Checkpoint créé: cp-1234567891 🔄 Step 3 démarré✅ Step 3 terminé 🎉 Agent terminé! Steps: 3 Interruptions: 1 📊 Résultat:Réponse: J'ai effectué le transfert de 500€ de votre compte ACC-001 vers ACC-002. Transaction ID: TXN-1234567890Steps: 3Interruptions: 1Interrompu? true Détails des interruptions: 1. Status: approved 💾 Checkpoints créés: ['cp-1234567890', 'cp-1234567891']💡 Bonnes Pratiques
- Sécurité : Toujours exiger une approbation pour les opérations sensibles (transferts d'argent, suppression de données, appels API externes)
- Checkpoints : Créer des checkpoints fréquemment pour les opérations longues pour permettre la récupération
- Timeouts : Définir des timeouts appropriés pour les approbations pour éviter de bloquer indéfiniment
- Production : En production, intégrer avec votre UI, Slack, webhooks, ou système de notification au lieu des prompts terminal
- Persistance : Utiliser RedisCheckpointStore ou PostgresCheckpointStore en production au lieu de MemoryCheckpointStore
🚀 Prochaines Étapes
- Explorer la documentation HITL pour les patterns avancés
- Implémenter un CheckpointStore personnalisé pour votre base de données
- Intégrer les workflows d'approbation avec vos systèmes existants
- Ajouter du monitoring et de l'observabilité pour les événements HITL