# Llamada a herramienta

> Un paso de un agente de IA: qué hizo, su resultado y Permitir o Rechazar. Componente de Pyzed para GUIs de escritorio en Python, con ejemplos y API.

Source: https://albakore.github.io/pyzed/es/library/tool-call/

:::tip[En pocas palabras]
Una cosa que hizo un asistente de IA, como leer o cambiar un archivo. Púlsala para ver los detalles; si necesita tu permiso, pregunta con botones de Permitir y Rechazar.
:::

`ui.ToolCall` es un componente para una acción de un agente de IA (leer un archivo, editarlo, ejecutar un comando), dibujado como en Zed: una línea con el estado, el título (`title`) y sobre qué trabaja (`detail`), y un clic despliega la entrada (`input`) y la salida (`output`), más las imágenes (`images`) si las hay. Las ediciones, los comandos (`kind` `edit`, `delete`, `move` o `execute`) y las llamadas que esperan son tarjetas. `diffs` muestra los cambios que hizo como diferencias de colores y, mientras se ejecuta, `progress` muestra las últimas líneas que imprimió un comando. Mientras `status` es `"waiting"` pregunta en la misma tarjeta: Permitir siempre, Permitir o Rechazar, que llegan a `on_allow`, `on_allow_always` y `on_deny`. El componente recuerda si está desplegado; tu app solo guarda los datos.

`ui.ToolCall` · Categoría: [Agentes de IA](https://albakore.github.io/pyzed/es/library/#agentes-de-ia) · [Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#toolcall)

**Código:**

```python
pz.column([
    ui.ToolCall(title="Read file", detail="app/storage.py", kind="read", output="def save(path): ..."),
    ui.ToolCall(title="Search", detail="def save", kind="search", output="app/storage.py:12"),
    ui.ToolCall(title="Edit file", detail="app/storage.py", kind="edit", status="waiting",
                diffs=[{"path": "app/storage.py", "old": "def save(path):\n    write(path)\n",
                        "new": "def save(path, data):\n    write(path, data)\n"}],
                on_allow=on_allow, on_allow_always=on_allow_always, on_deny=on_deny),
    ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status="running",
                progress="collected 24 items\n........................"),
], spacing=4, width=560)
```

:::note[Nuevo]
Este componente se añadió hace poco. Cuéntanos qué tal te funciona en el [gestor de incidencias](https://github.com/albakore/pyzed/issues).
:::

## Cuándo usarlo

- En el chat de un agente, una por cada paso que da.
- Con `status="waiting"`, para preguntar antes de un paso delicado como escribir archivos o ejecutar un comando.
- Para un cambio suelto, fuera de una llamada a herramienta, usa la [Vista de diferencias](https://albakore.github.io/pyzed/es/library/diff-view/).

## Uso

```python
import pyzed as pz
from pyzed import ui

class Chat(pz.App):
    def __init__(self):
        self.status = "waiting"

    def answer(self, allowed):
        self.status = "done" if allowed else "rejected"

    def view(self):
        return ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status=self.status,
                           input={"command": "pytest -q"}, output="24 passed",
                           on_allow=lambda e: self.answer(True), on_deny=lambda e: self.answer(False))
```

## Variantes

:::note[Sobre estas capturas]
Cada captura la graba pyzed mismo, fuera de pantalla a 2x, en One Dark o One Light según el tema de este sitio, y recortada al componente para que se vea a su tamaño real. El puntero pasa por encima, pulsa o escribe en él; un componente que no reacciona al puntero recorre algunos temas. Con movimiento reducido ves el primer fotograma. Abre **Código** bajo una captura para ver el fragmento que la dibuja; los nombres libres como `on_save` son tus propios callbacks.
:::

### Estado

Pendiente y en marcha muestran un spinner, esperando pregunta con Permitir, Permitir siempre y Rechazar, y fallida y rechazada lo dicen en rojo.

```python
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="pending"),
             width=360)
```

```python
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="running"),
             width=360)
```

```python
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="done"),
             width=360)
```

```python
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="waiting",
                         on_allow=on_allow, on_allow_always=on_allow_always, on_deny=on_deny),
             width=360)
```

```python
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="failed"),
             width=360)
```

```python
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="rejected"),
             width=360)
```

### Tipo

El tipo elige el icono de una llamada terminada.

```python
pz.container(ui.ToolCall(title="Read file", detail="app.py", kind="read"), width=300)
```

```python
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit"), width=300)
```

```python
pz.container(ui.ToolCall(title="Delete file", detail="old.py", kind="delete"), width=300)
```

```python
pz.container(ui.ToolCall(title="Move file", detail="a.py → b.py", kind="move"), width=300)
```

```python
pz.container(ui.ToolCall(title="Search", detail="def save", kind="search"), width=300)
```

```python
pz.container(ui.ToolCall(title="Run", detail="pytest -q", kind="execute"), width=300)
```

```python
pz.container(ui.ToolCall(title="Think", kind="think"), width=300)
```

```python
pz.container(ui.ToolCall(title="Fetch", detail="https://example.com", kind="fetch"),
             width=300)
```

```python
pz.container(ui.ToolCall(title="Switch mode", detail="plan", kind="switch_mode"),
             width=300)
```

```python
pz.container(ui.ToolCall(title="Tool", detail="custom", kind="other"), width=300)
```

### Contenido

Las desplegadas se dibujan como quedan tras un clic en la cabecera.

```python
pz.container(ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status="running",
                         progress="collected 24 items\n............\n............"),
             width=360)
```

```python
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="running",
                         diffs=[{"path": "app.py", "old": "x = 1\ny = 2\n", "new": "x = 10\ny = 2\n"}]),
             width=360)
```

```python
pz.container(ui.ToolCall(title="Run", detail="rm -rf build", kind="execute", status="waiting",
                         allow_always=False, on_allow=on_allow, on_deny=on_deny),
             width=360)
```

```python
pz.container(ui.ToolCall(title="Read file", detail="app.py", kind="read",
                         input={"path": "app.py"}, output="import pyzed as pz\n\npz.run(App)"),
             width=360)
```

```python
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit",
                         diffs=[{"path": "app.py", "old": "x = 1\ny = 2\n", "new": "x = 10\ny = 2\n"}]),
             width=360)
```

## Eventos

Llamada a herramienta tiene **3 eventos propios**, en la tabla de abajo. Como todo widget, también tiene los **18 eventos comunes** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), que se conectan con `.on(...)`: mira [Eventos](https://albakore.github.io/pyzed/es/guides/events/) y [Eventos de cada componente](https://albakore.github.io/pyzed/es/library/events/).

Es un `pz.Component`, así que también lanza `on_mount` y `on_unmount`, y sus handlers reciben un `pz.Event` cuyo `value` trae el dato.

| Evento | El handler recibe | Cuándo |
| --- | --- | --- |
| `on_allow` | `(event)` | Se pulsó Permitir. |
| `on_allow_always` | `(event)` | Se pulsó Permitir siempre. |
| `on_deny` | `(event)` | Se pulsó Rechazar. |

**El handler recibe** es lo que Pyzed le pasa a tu función: `()` es nada. Cualquier handler también puede ser un mensaje (un valor que no se puede llamar), que va a `update()`.

### Manejar los eventos

Los mismos eventos escritos de tres formas. **Imperativa**: métodos de la app que cambian `self`. **Declarativa**: los handlers producen mensajes y un único `update()` decide. **Funcional**: una función pura dibuja un estado inmutable y pide cambios con `set_state(...)`. Mira [Imperativo, declarativo y funcional](https://albakore.github.io/pyzed/es/guides/code-styles/).

**Imperativo:**

```python
class Chat(pz.App):
    def __init__(self):
        self.status = "waiting"

    def answer(self, status):
        self.status = status

    def view(self):                      # a pz.Component: handlers get a pz.Event
        return ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status=self.status,
                           input={"command": "pytest -q"},
                           on_allow=lambda e: self.answer("done"),
                           on_allow_always=lambda e: self.answer("done"),
                           on_deny=lambda e: self.answer("rejected"))
```

**Declarativo:**

```python
class Chat(pz.App):
    status = "waiting"

    def update(self, message):
        match message:
            case "allow":
                self.status = "done"
            case "deny":
                self.status = "rejected"

    def view(self):
        return ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status=self.status,
                           on_allow="allow", on_deny="deny")   # messages work for components too
```

**Funcional:**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class State:
    status: str = "waiting"

def view(state, set_state):
    return ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status=state.status,
                       on_allow=lambda e: set_state(status="done"),
                       on_deny=lambda e: set_state(status="rejected"))
```

## Consejos

:::tip
`status` es tuyo: ponlo según lo que diga el agente (`pending`, luego `running`, luego `done` o `failed`; `rejected` tras un Rechazar). `kind` solo elige el icono: `read`, `edit`, `delete`, `move`, `search`, `execute`, `think`, `fetch`, `switch_mode` u `other`. La cabecera solo se despliega cuando hay algo que mostrar (`input`, `output`, `images` o `diffs`).
:::

## Propiedades y API

### `ui.ToolCall`

```python
class ToolCall
```

Un componente para una acción de un agente de IA, dibujada como en el panel Agent de Zed: una línea con su icono, lo que hace y dónde (se despliega para mostrar la entrada y la salida), o una tarjeta para las ediciones y los comandos, con el diff o la salida. Mientras `status` es `"waiting"` pregunta en la propia tarjeta: Permitir siempre, Permitir o Rechazar. Mira [Agentes de IA](https://albakore.github.io/pyzed/es/editor/ai-agents/).

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `title` | `""` | Lo que hace ("Editar archivo"). |
| `detail` | `""` | Sobre qué trabaja, en letra monoespaciada. |
| `kind` | `""` | El tipo de ACP: `read`, `edit`, `delete`, `move`, `search`, `execute`, `think`, `fetch` u `other` (su icono). |
| `status` | `"done"` | `pending`, `running`, `waiting`, `done`, `failed`, `rejected` o `canceled`. |
| `input` | `None` | Los argumentos (un dict o texto). |
| `output` | `None` | La respuesta (texto o un dict). |
| `images` | `None` | Bytes PNG que se muestran bajo la salida. |
| `diffs` | `None` | Los cambios que hizo: dicts `{"path", "old", "new"}`, que se muestran como diffs de colores (mira `ui.diff_view`). |
| `progress` | `""` | Lo que imprimió hasta ahora (la salida de un comando); sus últimas líneas se ven bajo la cabecera mientras corre. |
| `allow_always` | `True` | Muestra Permitir siempre. |
| `theme` | `None` | Tema. |

Eventos: `on_allow`, `on_allow_always`, `on_deny` ([eventos](#eventos)).

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#toolcall)

## Relacionados

- [Vista de diferencias](https://albakore.github.io/pyzed/es/library/diff-view/): Un cambio como líneas de colores: lo añadido en verde, lo quitado en rojo.
- [Plan](https://albakore.github.io/pyzed/es/library/plan/): El plan de un agente como una lista que se pliega.
- [Barra de cambios](https://albakore.github.io/pyzed/es/library/edits-bar/): Los archivos que cambió un agente, con Conservar todo y Rechazar todo.
- [Medidor de contexto](https://albakore.github.io/pyzed/es/library/context-meter/): Cuánto se llenó la ventana de contexto de un modelo: un anillo y `48k / 200k`.
