> 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/agente/tool-approval.md).

# Aprobación de herramientas

Revisa, aprueba o deniega las llamadas a herramientas seleccionadas antes de que se ejecuten.

{% hint style="warning" %}

#### Habilidad del agente de programación

Usa **`/neuron-tool-approval`** para enseñar a tu agente de programación cómo implementar un flujo completo de aprobación de herramientas en tu aplicación.

[Desarrollo asistido por IA](/neuron-v3-es/resumen/agentic-development.md)
{% endhint %}

Neuron te ofrece compatibilidad completa con los patrones de intervención humana en el proceso, incluida la aprobación de herramientas. El framework intercepta la llamada a la herramienta y se pausa, esperando la decisión final del usuario. Para activar la aprobación de herramientas necesitas equipar a tu Agent con dos componentes:

* Un componente persistente [MessageStore](/neuron-v3-es/agente/chat-history-and-memory.md#message-store)
* El [Persistencia](/neuron-v3-es/flujo-de-trabajo/persistence.md) componente

```php
use NeuronAI\Agent\Middleware\ToolApproval;
use NeuronAI\Chat\History\FileChatHistory;
use NeuronAI\Workflow\Persistence\FilePersistence;

class MyAgent extends Agent
{
    ...
    
    /**
     * Registrar herramientas
     */
    protected function tools(): array
    {
        return [
            BuyTicketTool::make(),
        ];
    }
    
    /**
     * Historial de chat persistente
     */
    protected function chatHisotry(): ChatHistoryInterface
    {
        return new FileChatHistory(
            directory: '/path-to-filesystem',
            key: 'ID de la conversación',
        );
    }
    
    /**
     * Persistencia del flujo de trabajo
     */
    protected function persistence(): PersistenceInterface
    {
        return new FilePersistence(
            directory: '/path-to-filesystem'
        );
    }
}
```

El framework gestionará todo el flujo de aprobación automáticamente. Solo tienes que encargarte de la interfaz de usuario. Pero antes de profundizar en este proceso, veamos cómo puedes definir políticas de aprobación para tus herramientas.

Simplemente sobrescribe el `approvalPolicy()` método en la clase Tool para determinar si la herramienta debería requerir aprobación. El método recibirá las entradas que el modelo quiere usar para llamar a la herramienta. Devolver una cadena cuenta como true y también funciona como el motivo de la solicitud de aprobación.

```php
class BuyTicketTool extends Tool
{
    ...,
    
    protected function approvalPolicy(): bool|string
    {
        // La herramienta requiere aprobación si el importe es mayor que 100
        return $this->getInput('amount') > 100;
    }
}
```

También puedes sobrescribir la política predeterminada cuando adjuntes la herramienta al agente:

```php
class MyAgent extends Agent
{
    ...,
    
    protected function tools(array $inputs): array
    {
        return [
            BuyTicketTool::make()->requireApproval(true|false),
            
            /*
             * para cuando se ejecute la callback, la instancia de herramienta que recibe 
             * no es la herramienta del registro sin más, es un clon con las entradas de esta llamada 
             * ya vinculadas.
             */
            BuyTicketTool::make()->withApprovalPolicy(
                fn(ToolInterface $tool): bool|string => $tool->getInput('amount') > 100
            )
        ];
    }
}
```

### Gestionar el flujo de aprobación

**El historial de chat es el sistema de registro para la aprobación de herramientas.** Todo lo que necesita tu interfaz de usuario: qué herramientas están esperando una decisión, *por qué* cada una la está solicitando y qué ya se decidió, vive en el **último mensaje del hilo**.

Mientras hay una aprobación pendiente, el framework garantiza que el último `ToolCallMessage` es la cola del hilo. Así que la lógica de renderizado de tu interfaz solo mira ese último mensaje.

#### Detectar la aprobación en la interfaz

Cuando cargues un hilo, comprueba el último mensaje:

1. `tipo` es `"tool_call"`
2. al menos una entrada en `tools[]` tiene `"approval": "pending"`

Si ambas se cumplen, la conversación queda suspendida: representa la interfaz de aprobación y **bloquea el cuadro de entrada**, no se pueden enviar nuevos mensajes del usuario hasta que se tomen todas las decisiones.

```javascript
const last = thread.messages.at(-1);
const pendingTools = last?.type === 'tool_call'
    ? last.tools.filter(t => t.approval === 'pending')
    : [];
const isSuspended = pendingTools.length > 0;
```

#### El JSON con el que trabajarás

Este es un JSON serializado real `ToolCallMessage` en espera de aprobación: dos herramientas con bloqueo, una todavía pendiente y otra ya rechazada en un envío parcial anterior:

```json
{
    "__id": "msg_7488585683808067584",
    "resume_token": "workflow_6650a1b2c3d4e",
    "role": "assistant",
    "content": [],
    "type": "tool_call",
    "tools": [
        {
            "callId": "toolu_01A2B3C4D5E6F7",
            "name": "delete_file",
            "description": "Eliminar un archivo del sistema de archivos",
            "parameters": [],
            "inputs": { "path": "C:/old_logs.txt" },
            "result": null,
            "approval": "pending",
            "approvalReason": "Eliminar un archivo es irreversible",
            "rejectReason": null
        },
        {
            "callId": "toolu_08G9H0I1J2K3L4",
            "name": "send_email",
            "description": "Enviar un correo electrónico a un destinatario",
            "parameters": [],
            "inputs": { "to": "team@example.com", "subject": "Limpieza de registros" },
            "result": null,
            "approval": "rejected",
            "approvalReason": "El correo saliente llega a personas fuera de este espacio de trabajo",
            "rejectReason": "No envíes un correo al equipo completo"
        }
    ]
}
```

`approvalReason` es *la herramienta hablando con el humano* ("aquí está por qué lo estoy pidiendo"), `rejectReason` es *el humano respondiendo al modelo* ("aquí está por qué dije que no").

#### Guía de renderizado

* Representa una tarjeta por cada entrada en `tools[]` que **tiene** una `aprobación` campo: el nombre de la herramienta, sus `entradas`, el `approvalReason` si está presente, y las acciones Aceptar / Denegar. Denegar debe ofrecer una razón opcional en texto libre: el modelo la recibe literalmente, así que una buena razón produce una mejor respuesta del modelo.
* Las herramientas ya `"approved"`/`"rejected"` se muestran como decididas — pero siguen **sujetas a revisión hasta que se decida el conjunto completo** (last-write-wins). Una vez que llega la última decisión, el flujo continúa inmediatamente y los estados pasan a ser históricos.
* Las aprobaciones son simples: no existe un "aprobar con comentario".

### Un único endpoint para toda la conversación

Un turno normal y una reanudación de aprobación son la misma operación desde el punto de vista del endpoint: construye el agente a partir del hilo, aliméntalo con lo que envió el cliente y devuelve el nuevo estado del hilo. El framework se encarga del resto — con un `carga útil`, el token de reanudación se toma del final del historial de chat; con un mensaje, comienza un nuevo turno.

Tu endpoint solo necesita el `threadId`:

```php
class ChatController
{
    public function chat(ChatRequest $request, string $threadId)
    {
        $agent = MyAgent::make($threadId);
    
        $state = $request->has('decisions')
            ? $agent->submitApprovalDecisions($request->get('decisions'))->run()
            : $agent->chat(new UserMessage($request->get('message')));
        
        return [
            'status'  => $state->isInterrupted() ? 'awaiting_approval' : 'completed',
            'message' => $state->getMessage()?->jsonSerialize(),
        ];
    }
}
```

### Enviando decisiones

Hilo suspendido con herramientas pendientes. Aquí está el cuerpo que necesitas enviar para resolver la aprobación pendiente:

```
POST /threads/thread_42/chat
    { "decisions": { "toolu_01A2B3C4D5E6F7": "approve" } }
```

Las decisiones viajan como un mapa simple indexado por el `callId`. Cada valor es una de exactamente tres formas:

| Tú envías               | Significado                           | Lo que ve el modelo                                                                                              |
| ----------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| "approve"               | Ejecuta la herramienta                | La salida real de la herramienta, nada más. Las aprobaciones son simples: no existe un "aprobar con comentario". |
| "reject"                | Omite la herramienta                  | La plantilla de rechazo con *"No se proporcionó ninguna instrucción específica."*                                |
| \["reject", "tu razón"] | Omite la herramienta, con comentarios | La plantilla de rechazo con tu razón textual.                                                                    |
