OrkaJS
Orka.JS

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
⚠️ 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 compte
const 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'historique
const 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 interactif
async 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 store
const checkpointStore = new MemoryCheckpointStore();
 
// Créer l'agent HITL
const 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'agent
financialAgent.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);
}
 
// Utilisation
resumeFromCheckpoint().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étier
const 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 500de votre compte ACC-001 vers ACC-002. Transaction ID: TXN-1234567890
Steps: 3
Interruptions: 1
Interrompu? 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