> For the complete documentation index, see [llms.txt](https://docs.neuron-ai.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.neuron-ai.dev/neuron-v3-es/flujo-de-trabajo/human-in-the-loop.md).

# Interrupción

### Qué es

El patrón de interrupción de Neuron proporciona un mecanismo integrado *intervención humana en el ciclo* mecanismo que permite\
que los flujos de trabajo pausen su ejecución y esperen entrada externa antes de reanudar.

En esencia, las interrupciones se implementan a través de la clase abstracta `InterruptRequest` clase, un primitivo del framework que los desarrolladores pueden ampliar para crear experiencias de interrupción personalizadas adaptadas a las necesidades específicas de su\
aplicación. El framework incluye una `ApprovalRequest` como una implementación integrada que cubre el caso de uso más común de aprobar acciones (como llamadas a herramientas), pero la arquitectura es intencionalmente flexible: cualquier nodo o middleware del flujo de trabajo puede activar una interrupción, y la capa de persistencia garantiza que el estado se conserve a lo largo del ciclo de pausa/reanudación, lo que lo hace adecuado para procesos de larga duración que requieren puntos de decisión humana en cualquier etapa.

Si la integrada `ApprovalRequest` no se ajusta a tu caso de uso, eres libre de crear tu propia solicitud de interrupción personalizada para crear una experiencia de interfaz de usuario específica.

Así es como funciona:

**Puntos de interrupción**: Cualquier nodo de tu flujo de trabajo puede solicitar una interrupción especificando los datos que quiere presentar al humano. Esto podría ser una simple decisión sí/no, una alerta o cualquier dato estructurado.

**Conservación del estado**: Cuando ocurre una interrupción, Neuron guarda automáticamente el estado completo de tu flujo de trabajo. Tu flujo de trabajo básicamente entra en reposo, esperando la entrada humana.

**Reanudar**: Una vez que un humano responde a la solicitud de interrupción, el flujo de trabajo se reanuda exactamente desde el nodo en el que se quedó. No se pierde ningún dato, no se olvida ningún contexto.

**Integración de comentarios externos**: La solicitud de interrupción editada se inyecta en el nodo interrumpido para continuar su ejecución recibiendo los comentarios humanos.

### Introducción en video

Sabemos que el flujo de interrupción es una función bastante avanzada. Incluso con toda la documentación a continuación, puede que no sea fácil comprender todos los aspectos de esta arquitectura. Nos complace enlazarte a continuación un video introductorio hecho por nuestro miembro de la comunidad [Amitav Roy](https://www.linkedin.com/in/royamitav/).

Puede brindarte información adicional que, combinada con la documentación, puede ayudarte a entender cómo implementar tus casos de uso.

{% embed url="<https://www.youtube.com/watch?v=jjEBjTRDLZE>" %}

### Cómo funciona

Cuando solicitas una interrupción, el flujo de trabajo no simplemente se detiene, preserva todo su estado y espera orientación antes de continuar. Esto te permite crear un sistema de inteligencia híbrida donde la IA se encarga del trabajo computacional pesado mientras los humanos contribuyen a la supervisión estratégica y la toma de decisiones.

La forma más sencilla de solicitar una interrupción es llamando al `interrupt()` método dentro de un nodo, proporcionando una solicitud de interrupción. Aquí hay un ejemplo usando la integrada `ApprovalRequest`:

```php
<?php

namespace App\\Neuron;

use NeuronAI\\Workflow\\Events\\Event;
use NeuronAI\\Workflow\\Interrupt\\Action;
use NeuronAI\\Workflow\\Interrupt\\ApprovalRequest;
use NeuronAI\\Workflow\\Node;
use NeuronAI\\Workflow\\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Interrumpe el flujo de trabajo y espera los comentarios.
        $humanResponse = $this->interrupt(
            new ApprovalRequest(
                message: '¿Debo continuar?'
                actions: [
                    new Action('delete_file', 'Eliminar archivo', 'Eliminar /var/log/old.txt'),
                ],
            )
        );
    
        $action = $humanResponse->getAction('delete_file');
    
        if ($action->isApproved()) {
            $state->set('is_sufficient', true);
            $state->set('user_feedback', $action->feedback);
            return new OutputEvent();
        }
        
        $state->set('is_sufficient', false);
        return new InputEvent();
    }
}
```

Eventualmente puedes implementar tu propia solicitud de interrupción personalizada para pasar la información que necesitas para la interacción humana. Podrás capturar estos datos más tarde, fuera del flujo de trabajo, para poder informar al usuario y pedirle comentarios.

Cuando el flujo de trabajo se reanude, se reiniciará desde el mismo nodo en el que fue interrumpido, y la `$feedback` variable recibirá los datos de respuesta del humano.

El `InterruptRequest` sigue un **patrón solicitud-respuesta** donde:

1. **Fase de solicitud**: Un nodo del flujo de trabajo identifica acciones que requieren aprobación humana y crea una `InterruptRequest` que contiene detalles de esas acciones
2. **Fase de pausa**: El flujo de trabajo lanza una `WorkflowInterrupt` excepción, preservando todo el contexto de ejecución
3. **Fase de decisión**: La aplicación presenta acciones a los usuarios, quienes aprueban, rechazan o editan cada acción
4. **Fase de reanudación**: El flujo de trabajo se reanuda con las decisiones del usuario, continuando la ejecución en función de los comentarios

Este diseño garantiza que el flujo de trabajo pueda pausarse de forma segura en cualquier punto, conservar su estado y reanudarse exactamente donde lo dejó, incluso entre distintas sesiones.

### Solicitud de interrupción personalizada

El `InterruptRequest` es el componente central del patrón de intervención humana en el ciclo (HITL) de Neuron, diseñado para pausar la ejecución del flujo de trabajo y solicitar aprobación o entrada humana para acciones específicas. Proporciona un enfoque estructurado y seguro en cuanto a tipos para crear flujos de trabajo de IA interactivos.

Puedes crear tu propia implementación y pasarla al método interrupt.

```php
class ContentReviewInterrupt extends InterruptRequest
{
    public function __construct(
        protected string $message,
        protected string $content
    ) {
        parent::__construct($message)
    }
    
    public function getContent(): string
    {
        return $this->content;
    }
    
    public function jsonSerialize(): array
    {
        return [
            'message' => $this->message,
            'content' => $this->content,
        ];
    }
    
    public static function fromArray(array $data)
    {
        return new static($data['message'], $data['content']);
    }
}
```

Úsalo para tu caso de uso de interrupción:

```php
class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Generar un artículo
        $response = ContentCreatorAgent::make()
            ->chat(new UserMessage($event->prompt))
            ->getMessage();
    
        // Interrumpe el flujo de trabajo y espera los comentarios.
        $reviewRequest = $this->interrupt(
            new ContentReviewInterrupt(
                message: 'Este es el nuevo artículo. Revisa el contenido antes de guardarlo en la base de datos.'
                $response->getContent()
            )
        );
        
        // Guardar el contenido de la solicitud de interrupción actualizada
        $state->set('content', $reviewRequest->getContent());
        
        return new InputEvent();
    }
}
```

### Captura de la interrupción

Para poder interrumpir y reanudar un flujo de trabajo (también Agent y RAG) necesitas proporcionar la capa de persistencia al crear la instancia del flujo de trabajo:

```php
$workflow = new WorkflowAgent(new FilePersistence(__DIR__));
```

Cuando un nodo solicita una interrupción, el flujo de trabajo lanza un tipo especial de excepción representado por la **`WorkflowInterrupt`** clase. Puedes capturar esta excepción para gestionar la solicitud de interrupción.

```php
$workflow = new WorkflowAgent(
    new FilePersistence(__DIR__),
);

try {
    return $workflow->init()->run();
} catch (WorkflowInterrupt $interrupt) {
    $request = $interrupt->getRequest();
    $workflowId = $interrupt->getWorkflowId();
    
    /*
    * Puedes almacenar la solicitud como un objeto JSON
    * junto con el token de reanudación, y pedir comentarios al usuario.
    */
    $pdo->prepare("INSERT INTO interruption_requests (resume_token, request) VALUES (?, ?)");
    $pdo->execute([
        $workflowId,
        json_encode($request),
    ]);
}
```

Usa la información del `$request` objeto para guiar al humano a proporcionar comentarios. Una vez que finalmente tengas los comentarios del usuario, puedes reanudar el flujo de trabajo pasando la solicitud de interrupción a la `init()` método. Recuerda usar el mismo `workflowId` que obtuviste durante la interrupción.

```php
$workflow = new WorkflowAgent(
    new FilePersistence(__DIR__),
    $workflowId // <- Usa el mismo ID que obtuviste durante la interrupción
);

$request = ContentReviewInterrupt::fromArray($data);

// Reanuda el flujo de trabajo pasando la solicitud procesada como comentarios
$result = $workflow->init($request)->run();

// Obtener la respuesta final
echo $result->get('content');
```

Puedes echar un vistazo al script de abajo como ejemplo de este proceso:

{% @github-files/github-code-block url="<https://github.com/inspector-apm/neuron-ai/blob/main/examples/workflow/workflow-interrupt.php>" %}

### Puntos de control

Cuando el flujo de trabajo se reanuda, reinicia la ejecución desde el nodo donde fue interrumpido. El nodo se volverá a ejecutar por completo, incluido el código presente antes de la interrupción.

Si necesitas solicitar una interrupción no al comienzo del nodo, sino después de realizar otras operaciones, puedes usar puntos de control para guardar el resultado de las sentencias anteriores y usarlo cuando el nodo se reanude. Aquí hay un ejemplo:

```php
<?php

namespace App\\Neuron;

use NeuronAI\\Workflow\\Node;
use NeuronAI\\Workflow\\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // El resultado de este bloque de código se guarda y se devuelve cuando el flujo de trabajo se reanuda.
        $sentiment = $this->checkpoint('agent-1', function () {
            return MyAgent::make()->structured(
                new UserMessage(...),
                SentimentResult::class
            );
        });
        
        // Interrumpe el flujo de trabajo y espera los comentarios.
        if ($sentiment->isNegative()) {
            $feedback = $this->interrupt(
                new ApprovalRequest(
                    message: '¿Debo continuar?'
                    actions: [
                        new Action('review_id', 'Revisión de la respuesta', $sentiment->content),
                    ],
                )
            );
            
            if ($feedback->getAction('review_id')->isApproved()) {
                $state->set('is_sufficient', true);
                $state->set('user_feedback', $feedback->getAction('review_id')->feedback);
                return new OutputEvent();
            }
        }
        
        $state->set('is_sufficient', false);
        return new InputEvent();
    }
}
```

El método checkpoint acepta dos argumentos:

* El **nombre** del punto de control debe ser único en el nodo;
* Una **Closure** para envolver el código cuyo resultado quieres guardar.

Cuando se ejecuta el nodo, el método checkpoint guarda el resultado de la Closure en caso de una interrupción. Cuando el nodo se ejecuta de nuevo después de la interrupción, puede llegar al punto de interrupción con exactamente el mismo estado de la ejecución anterior para obtener los comentarios externos.

### Consumir los comentarios de la interrupción

También puedes consumir los comentarios externos en alguna parte de tu código distinta de donde llamas al `interrupt()` método.

El `consumeResumeRequest()` método te permite obtener el valor de los comentarios externos o null si el nodo simplemente se está ejecutando y no se está reactivando:

```php
<?php

namespace App\\Neuron;

use NeuronAI\\Workflow\\Node;
use NeuronAI\\Workflow\\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Solicitar la solicitud final de reanudación
        $feedback = $this->consumeResumeRequest();
    
        // Si la solicitud aún no está ahí, salta a la interrupción
        if ($feedback !== null && $feedback->getAction('review_id')->isApproved()) {
            $state->set('is_sufficient', true);
            $state->set('user_feedback', $feedback->getAction('review_id')->feedback);
            return new OutputEvent();
        }
        
        $this->interrupt(
            new ApprovalRequest(
                message: '¿Debo continuar?'
                actions: [
                    new Action('review_id', 'Revisión de la respuesta', $state->get('review')),
                ],
            )
        );
        
        $state->set('is_sufficient', false);
        return new InputEvent();
    }
}
```

Esto te permite aplicar condiciones al comienzo del nodo basadas en los comentarios dados.

### Interrupción condicional

También puedes usar `interruptIf()` como ayuda para evaluar una interrupción condicional:

```php
<?php

namespace App\\Neuron;

use NeuronAI\\Workflow\\Node;
use NeuronAI\\Workflow\\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Interrupción condicional
        $this->interruptIf(
            $state->get('is_sufficient') == true, 
            new ApprovalRequest(
                message: '¿Debo continuar?'
                actions: [
                    new Action('review_id', 'Revisión de la respuesta', $state->get('review')),
                ],
            )
        );
        
        // O usa una función de devolución de llamada para evaluar la condición
        $this->interruptIf(
            fn() => $state->get('is_sufficient', false), 
            new ApprovalRequest(
                message: '¿Debo continuar?'
                actions: [
                    new Action('review_id', 'Revisión de la respuesta', $state->get('review')),
                ],
            )
        );
        
        return new InputEvent();
    }
}
```

### Monitorización y depuración

Muchas de las aplicaciones que construyas con Neuron contendrán múltiples pasos con múltiples invocaciones de llamadas a LLM. A medida que estas aplicaciones se vuelven cada vez más complejas, se vuelve crucial poder inspeccionar exactamente qué está ocurriendo dentro de tu sistema agéntico. La mejor manera de hacerlo es con [Inspector](https://inspector.dev/).

{% embed url="<https://docs.inspector.dev/guides/neuron-ai>" %}
