Human-in-the-Loop (HITL)
Agents Human-in-the-Loop capable de pause pour approbation, de création de checkpoints et de modification en temps réel. Essentiel pour lesworkflows critiques.
Approbation d'Outils
Security GateImpose une validation manuelle stricte pour les opérations sensibles comme l'écriture en base.
Checkpoints d'État
ResiliencePersistance atomique de la mémoire de l'agent. Revenez ou reprenez depuis n'importe quelle étape.
Modification d'Actions
SupervisionPermet aux utilisateurs de modifier les paramètres d'outils en plein vol avant l'exécution.
# Utilisation de Base
Le HITLAgent étend l'agent de base avec des capacités human-in-the-loop. Configurez quels outils nécessitent une approbation et fournissez un gestionnaire d'interruption pour traiter les décisions humaines.
import { HITLAgent, MemoryCheckpointStore } from '@orka-js/agent';import { OpenAIAdapter } from '@orka-js/openai';import type { Tool, InterruptRequest, InterruptResponse } from '@orka-js/agent'; const llm = new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY! }); // Define tools - some will require approvalconst searchTool: Tool = { name: 'web_search', description: 'Search the web for information', parameters: [{ name: 'query', type: 'string', description: 'Search query', required: true }], execute: async (input) => ({ output: `Results for: ${input.query}` })}; const sendEmailTool: Tool = { name: 'send_email', description: 'Send an email', parameters: [ { name: 'to', type: 'string', description: 'Recipient', required: true }, { name: 'subject', type: 'string', description: 'Subject', required: true }, { name: 'body', type: 'string', description: 'Email body', required: true } ], execute: async (input) => ({ output: `Email sent to ${input.to}` })}; const deleteFileTool: Tool = { name: 'delete_file', description: 'Delete a file', parameters: [{ name: 'path', type: 'string', description: 'File path', required: true }], execute: async (input) => ({ output: `Deleted: ${input.path}` })}; // Create HITL agentconst agent = new HITLAgent( { goal: 'Help users with tasks while requiring approval for sensitive operations', tools: [searchTool, sendEmailTool, deleteFileTool], verbose: true, hitl: { // Tools that require human approval requireApprovalFor: ['send_email', 'delete_file'], // Tools that are always auto-approved autoApproveTools: ['web_search'], // Create checkpoint every N steps checkpointEvery: 3, // Timeout for human response defaultTimeoutMs: 60000, // Handler for interrupt requests onInterrupt: async (request) => { // Your approval logic here (UI, CLI, API, etc.) console.log(`Approval needed: ${request.message}`); return { id: request.id, status: 'approved', respondedAt: new Date() }; } } }, llm); const result = await agent.run('Search for AI news and email a summary to team@example.com'); console.log(result.output);console.log(`Was interrupted: ${result.wasInterrupted}`);console.log(`Interrupts: ${result.interrupts.length}`);console.log(`Checkpoints: ${result.checkpoints.length}`);# Gestionnaire d'Interruption
Le gestionnaire d'interruption est appelé chaque fois que l'agent a besoin d'une entrée humaine. Vous pouvez implémenter n'importe quel workflow d'approbation : prompts CLI, interface web, intégration Slack, etc.
import type { InterruptRequest, InterruptResponse } from '@orka-js/agent'; // Interactive CLI handlerasync function cliInterruptHandler(request: InterruptRequest): Promise<InterruptResponse> { console.log('\n' + '='.repeat(50)); console.log('🔔 HUMAN APPROVAL REQUIRED'); console.log('='.repeat(50)); console.log(`Reason: ${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)}`); } if (request.data.thought) { console.log(`Agent reasoning: ${request.data.thought}`); } // Get user input (use readline, inquirer, etc.) const answer = await promptUser('Approve? (y/n/m for modify): '); if (answer === 'y') { return { id: request.id, status: 'approved', respondedAt: new Date() }; } else if (answer === 'm') { const newInput = await promptUser('Enter modified input (JSON): '); return { id: request.id, status: 'modified', modifiedData: { toolName: request.data.toolName, toolInput: JSON.parse(newInput) }, respondedAt: new Date() }; } else { const feedback = await promptUser('Reason for rejection: '); return { id: request.id, status: 'rejected', feedback, respondedAt: new Date() }; }} // Web API handler (for async approval via webhooks)async function webhookInterruptHandler(request: InterruptRequest): Promise<InterruptResponse> { // Store pending request in database await db.pendingApprovals.create({ id: request.id, agentId: request.agentId, data: request.data, createdAt: request.createdAt }); // Send notification (Slack, email, etc.) await slack.send({ channel: '#agent-approvals', text: `🔔 Approval needed: ${request.message}`, actions: [ { type: 'button', text: 'Approve', value: 'approve' }, { type: 'button', text: 'Reject', value: 'reject' } ] }); // Wait for response (with timeout) const response = await waitForApproval(request.id, request.timeoutMs); return response;}📋 Structure de la Requête d'Interruption
interface InterruptRequest { id: string; // Unique request ID agentId: string; // Agent that triggered the interrupt reason: InterruptReason; // 'tool_approval' | 'checkpoint' | 'review' | 'confirmation' | 'custom' message: string; // Human-readable message data: { toolName?: string; // Tool being called toolInput?: Record<string, unknown>; // Tool parameters stepNumber?: number; // Current step thought?: string; // Agent's reasoning context?: Record<string, unknown>; // Additional context }; createdAt: Date; timeoutMs?: number; // How long to wait for response}Types de Réponse
Exécution immédiate. L'agent poursuit son intention initiale.
{ status: 'approved' }Exécution stoppée. L'agent reçoit un feedback pour replanifier.
{ status: 'rejected', feedback: '...' }Interception et pivot. Exécute l'outil avec des entrées corrigées.
{ status: 'modified', modifiedData: { ... } }Suspension automatique. Évite les tâches fantômes sans révision.
{ status: 'timeout' }# Checkpoints & Récupération
Les checkpoints sauvegardent l'état de l'agent à des intervalles spécifiques, vous permettant de reprendre l'exécution depuis n'importe quel point sauvegardé. C'est crucial pour les tâches longues et la récupération d'erreurs.
import { HITLAgent, MemoryCheckpointStore } from '@orka-js/agent'; // Use the built-in memory store (or implement your own)const checkpointStore = new MemoryCheckpointStore(); const agent = new HITLAgent( { goal: 'Process a large dataset', tools: [/* ... */], hitl: { checkpointEvery: 5, // Save state every 5 steps checkpointStore, onInterrupt: myHandler } }, llm); // Run the agentconst result = await agent.run('Analyze all customer data'); // List all checkpointsconst checkpoints = await agent.getCheckpoints();console.log('Saved checkpoints:', checkpoints.map(cp => cp.id)); // Resume from a specific checkpointconst resumedResult = await agent.run('Continue analysis', checkpoints[2].id);console.log(`Resumed from: ${resumedResult.resumedFromCheckpoint}`);Store de Checkpoints Personnalisé
Implémentez l'interface CheckpointStore pour persister les checkpoints vers Redis, PostgreSQL, ou tout backend de stockage :
import type { Checkpoint, CheckpointStore } from '@orka-js/agent'; class RedisCheckpointStore implements CheckpointStore { constructor(private redis: Redis) {} async save(checkpoint: Checkpoint): Promise<void> { await this.redis.hset( `checkpoints:${checkpoint.agentId}`, checkpoint.id, JSON.stringify(checkpoint) ); } async load(checkpointId: string): Promise<Checkpoint | null> { // Scan all agents for this checkpoint const keys = await this.redis.keys('checkpoints:*'); for (const key of keys) { const data = await this.redis.hget(key, checkpointId); if (data) return JSON.parse(data); } return null; } async loadLatest(agentId: string): Promise<Checkpoint | null> { const all = await this.redis.hgetall(`checkpoints:${agentId}`); const checkpoints = Object.values(all).map(v => JSON.parse(v) as Checkpoint); return checkpoints.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() )[0] ?? null; } async list(agentId: string): Promise<Checkpoint[]> { const all = await this.redis.hgetall(`checkpoints:${agentId}`); return Object.values(all).map(v => JSON.parse(v)); } async delete(checkpointId: string): Promise<void> { const keys = await this.redis.keys('checkpoints:*'); for (const key of keys) { await this.redis.hdel(key, checkpointId); } }}# Patterns d'Approbation
Exiger l'Approbation pour Tous les Outils
hitl: { requireApprovalFor: ['*'], // Wildcard: all tools need approval autoApproveTools: ['web_search'], // Except these}Approbation pour des Outils Spécifiques Seulement
hitl: { requireApprovalFor: ['send_email', 'delete_file', 'execute_code'], // All other tools run without approval}Interruptions Manuelles
Vous pouvez également déclencher des interruptions programmatiquement pour des flux de confirmation personnalisés :
// Request confirmation before a critical actionconst response = await agent.requestConfirmation( 'About to process 10,000 records. Continue?', { recordCount: 10000, estimatedTime: '5 minutes' }); if (response.status === 'approved') { await processRecords();} // Request review of agent's reasoningconst reviewResponse = await agent.requestReview( 'Please review my analysis before I proceed', currentStep, 'I believe the data shows a 15% increase...');# Structure du Résultat
interface HITLAgentResult extends AgentResult { // Standard agent result fields input: string; output: string; steps: AgentStepResult[]; totalLatencyMs: number; totalTokens: number; toolsUsed: string[]; metadata: Record<string, unknown>; // HITL-specific fields interrupts: InterruptResponse[]; // All interrupt responses checkpoints: string[]; // IDs of created checkpoints wasInterrupted: boolean; // True if any interrupts occurred resumedFromCheckpoint?: string; // Checkpoint ID if resumed} // Usageconst result = await agent.run('...'); if (result.wasInterrupted) { console.log(`Agent was interrupted ${result.interrupts.length} times`); for (const interrupt of result.interrupts) { console.log(`- ${interrupt.status}: ${interrupt.feedback ?? 'No feedback'}`); }} if (result.resumedFromCheckpoint) { console.log(`Resumed from checkpoint: ${result.resumedFromCheckpoint}`);}Bonnes Pratiques
1. Classifiez les Outils par Niveau de Risque
Auto-approuvez les outils en lecture seule (recherche, récupération). Exigez une approbation pour les opérations d'écriture (envoi, suppression, modification). Exigez toujours une approbation pour les actions irréversibles.
2. Définissez des Timeouts Appropriés
Utilisez des timeouts courts pour le CLI interactif (30-60s). Utilisez des timeouts plus longs pour l'approbation asynchrone (heures/jours). Gérez le timeout gracieusement — soit réessayez, soit ignorez l'action.
3. Persistez les Checkpoints en Production
MemoryCheckpointStore est uniquement pour le développement. Utilisez Redis, PostgreSQL ou S3 pour la production. Incluez le nettoyage des checkpoints dans vos routines de maintenance.
4. Fournissez du Contexte dans les Messages d'Interruption
Incluez le raisonnement de l'agent (thought) pour que les humains comprennent pourquoi l'action est entreprise. Montrez l'entrée complète de l'outil pour que les humains puissent prendre des décisions éclairées.
Imports Tree-shakeable
// ✅ Import only HITL componentsimport { HITLAgent, MemoryCheckpointStore } from '@orka-js/agent';import type { InterruptRequest, InterruptResponse, Checkpoint, CheckpointStore, HITLConfig } from '@orka-js/agent'; // ✅ Or import from agent indeximport { type InterruptRequest, type InterruptResponse, type Checkpoint, type CheckpointStore, type HITLConfig, HITLAgent, MemoryCheckpointStore,} from '@orka-js/agent';