Módulo 4: Herramientas: el agente que actúa sobre sistemas reales

5. Contratos de herramientas y límites de confianza

Descripción

Al terminar esta lección vas a poder escribir el nombre, la descripción y los parámetros de una tool de forma que el agente la use en el momento correcto y con los datos correctos —lo que llamamos el contrato de la tool—, y vas a poder decidir con un criterio concreto qué acciones de esas tools jamás deberían ejecutarse sin que una persona las apruebe primero, usando el mecanismo de revisión humana de n8n.

Esto importa porque en la lección anterior el agente de TuTienda quedó conectado a Gmail, a Google Sheets, a la base de datos de pedidos y a la API HTTP de la transportadora. Técnicamente ya sabe cómo llamar cada una. El problema que queda no es técnico, es de criterio: dos tools con nombres parecidos hacen que el modelo elija la equivocada, y una tool que puede enviar un correo o escribir en la base de datos sin ningún límite puede terminar prometiéndole un descuento a un cliente o modificando el precio de un producto porque nadie le puso un freno. Esto es justo lo que separa un demo que funciona en la llamada de ventas de un agente que puedes dejar corriendo en producción sin supervisión constante.

Conexión con el módulo: la lección 4 te dio el acceso a los sistemas reales. Esta lección te da el control sobre ese acceso —el contrato de cada tool y la barrera de aprobación humana para lo que no se le puede confiar al modelo solo—. La lección 6 toma este mismo criterio y lo aplica a sub-workflows completos expuestos como una sola tool: van a necesitar el mismo contrato claro que practiques acá.

El contrato de una tool

Imagina que delegas una tarea a alguien que se acaba de sumar al equipo, alguien que no puede interrumpirte a media tarea para preguntar algo que no quedó claro. Si le dejas una nota que dice "manda el reporte", esa persona tiene que adivinar: ¿a quién?, ¿en qué formato?, ¿con qué números? Si en cambio la nota dice "envía el reporte de ventas de la semana a maria@empresa.com en PDF, con los números de la hoja 'Cierre semanal', antes de las 5 p. m.", no hay nada que adivinar — todo lo que esa persona necesita saber está en la nota.

El agente está exactamente en esa posición frente a cada tool que conectas. No lee el código del nodo de Gmail ni sabe cómo está armado el API de tu base de datos por dentro. Lo único que tiene para decidir si usa esa tool, cuándo la usa y con qué datos la llama es texto: el nombre del nodo, el campo Description y la descripción de cada parámetro. Ese texto es el contrato completo. Si el contrato es vago, el modelo adivina, igual que la persona nueva del equipo — y adivinar en un sistema que manda correos reales o escribe en una base de datos real sale caro.

El contrato de una tool en n8n tiene tres piezas:

  1. Name — el nombre del nodo tal como aparece en el lienzo. El agente lo usa para referirse a la tool internamente, y es el mismo valor que después vas a ver en la expresión $tool.name cuando armes la revisión humana, más adelante en esta lección.
  2. Description — el campo de texto libre que le dice al modelo qué hace la tool y, tan importante como eso, cuándo NO usarla. Es la parte del contrato que más forma el comportamiento del agente, porque decide en qué momento de la conversación la tool ni siquiera entra en consideración.
  3. Parameters — cada dato que la tool necesita para ejecutarse. En los nodos conectados al Tools Agent, se rellenan con la función $fromAI(key, description, type, defaultValue): key es un identificador de 1 a 64 caracteres (letras, números, guion bajo o guion medio); description es el texto que le dice al modelo qué buscar para ese dato específico; type puede ser string, number, boolean o json (por defecto string); defaultValue es opcional. El modelo usa el key y la description juntos como pista — por ejemplo, con un key de email, el modelo va a buscar una dirección de correo en el contexto de la conversación, en los datos de otras tools o en el input del workflow. Esta función solo funciona conectada a un Tools Agent, no en Code tools ni en otros sub-nodos.

Ejemplo trabajado

TuTienda tiene un nodo Gmail conectado como tool para que el agente responda tickets de soporte. Así se ve el contrato mal escrito:

# Gmail node (usado como Tool) — Name: Send_Email
# Description: "Sends an email."

To:      {{ $fromAI("to") }}
Subject: {{ $fromAI("subject") }}
Message: {{ $fromAI("body") }}

El nombre no dice para qué sirve, la descripción es tan genérica que cabe cualquier correo imaginable, y los tres parámetros no tienen description — el modelo va a inferir el destinatario, el asunto y el cuerpo del mensaje con lo que encuentre en el contexto, sin ninguna guía sobre qué está permitido decir. Así se ve el mismo nodo con el contrato reescrito:

# Gmail node (usado como Tool) — Name: reply_to_support_ticket
# Description: Sends a reply email to the customer who opened the
# current support ticket. Use only to answer questions about order
# status, shipping times, and return policy. Do NOT use this tool
# to offer discounts, refunds, or any form of compensation — those
# require human approval through the request_refund_approval tool.

To: {{ $fromAI("customerEmail", "The email address of the customer who opened the current ticket. Must come from the ticket record, never invented.", "string") }}

Subject: {{ $fromAI("subject", "Short subject line that references the ticket ID, e.g. 'Re: Ticket #4521'", "string") }}

Message: {{ $fromAI("body", "Plain-text reply that answers only the customer's question about order status, shipping, or return policy. No discounts, no refunds, no promises of compensation.", "string") }}

Qué esperar. Un cliente escribe por el chat: "el pedido #4521 llegó dañado, ¿me pueden hacer un descuento en la próxima compra?"

Con el contrato malo, nada le impide al agente interpretar que "resolver la duda del cliente" incluye prometer un descuento, y puede terminar llamando a Send_Email ofreciendo un 15% en la próxima compra — nadie se lo prohibió por escrito. Con el contrato bueno, el agente lee en su propio contrato que reply_to_support_ticket explícitamente no debe usarse para descuentos, y en vez de llamar la tool para prometer algo, responde directamente en el chat sin ejecutar ninguna acción sobre Gmail:

"Lamento el inconveniente con el pedido #4521. Puedo confirmar el estado del envío y la política de devoluciones, pero un descuento o compensación necesita revisión de una persona del equipo — voy a escalar tu caso."

La diferencia entre los dos resultados no vino de un modelo distinto ni de un prompt de sistema distinto — vino solo del texto del contrato de la tool. Pero fíjate en la palabra "escalar" de esa respuesta: que el agente diga que va a escalar el caso no significa que el descuento esté realmente bloqueado. Si en algún otro momento de la conversación el cliente insiste de otra forma, nada impide todavía que el modelo cambie de opinión y llame a la tool de todos modos. Eso nos lleva al segundo problema de esta lección.

Límites de confianza: lo que una descripción sola no resuelve

Piensa en una persona nueva del equipo que puede responder preguntas de clientes y consultar el estado de un pedido sin que nadie la supervise línea por línea — para eso la contrataste. Pero para autorizar un reembolso o firmar un descuento grande, necesita el visto bueno de un supervisor, no porque no confíes en su criterio en general, sino porque el costo de un error en esa acción específica es demasiado alto como para dejarlo en el juicio de una sola persona, humana o no.

Un límite de confianza (trust boundary) es exactamente ese punto: una acción cuyo costo de ejecutarse mal —porque es irreversible, porque mueve dinero, porque compromete a la empresa frente a un cliente, o porque borra datos— es tan alto que necesita la aprobación explícita de una persona antes de que el workflow continúe. Y esto es lo importante: esa aprobación no puede depender solo de una instrucción en el system prompt, como "nunca ofrezcas descuentos sin preguntar". Un texto en el prompt es una instrucción fuerte que el modelo sigue casi siempre — pero sigue siendo texto, compitiendo con el resto del contexto de la conversación. Bajo una formulación inusual, una conversación larga, o un intento deliberado de manipular al agente (el terreno que vas a estudiar de lleno en el módulo de seguridad, más adelante en esta guía), ese texto puede perder. Para lo que de verdad no puede fallar, la barrera tiene que estar en la estructura del workflow, no solo en el criterio del modelo.

n8n resuelve esto con el patrón de revisión humana para tools, disponible directo en el conector Tools del nodo AI Agent:

  1. Haces clic en el conector Tools del nodo AI Agent para abrir el panel de tools.
  2. Ahí encuentras la sección Human review y eliges tu canal de aprobación preferido — Slack, Discord, Telegram, Microsoft Teams, Gmail, WhatsApp Business Cloud, Google Chat, Microsoft Outlook o el Chat propio de n8n.
  3. Conectas las tools que requieren aprobación al conector de tools de ese paso de revisión humana — no directamente al agente.

Cuando el agente intenta llamar una tool conectada de esa forma, el workflow se detiene y espera. Dentro del paso de revisión tienes disponible la variable $tool, con dos propiedades: $tool.name (el nombre de la tool que el agente está tratando de llamar) y $tool.parameters (los parámetros con los que la está tratando de llamar). Si la persona aprueba, la tool se ejecuta normalmente y el resultado vuelve al agente. Si la deniega, la acción se cancela y el agente recibe el rechazo — por eso el system prompt también necesita decirle qué hacer con esa negativa: informar al cliente, sugerir una alternativa, o pedir más contexto.

Antes de construirlo, conviene tener un criterio explícito de qué sí se le confía al agente sin supervisión y qué no:

Tipo de acción¿Se ejecuta sola?Ejemplo en TuTienda
Consultar o leer datosBuscar el estado de un pedido, leer el precio de un producto en Sheets
Responder con información ya validadaConfirmar la política de devoluciones
Acción reversible de bajo impactoNormalmente síMarcar un ticket como "en revisión"
Acción irreversibleNo — requiere aprobaciónCancelar un pedido, borrar una fila
Impacto financieroNo — requiere aprobaciónReembolso, descuento, cambio de precio
Comunicación externa que compromete a la empresaNo — requiere aprobaciónPrometer una compensación, confirmar un plazo legal

Ejemplo trabajado

TuTienda tiene una tool issue_refund —un HTTP Request Tool que llama al API de pagos— conectada detrás de una revisión humana por Slack en vez de ir directo al agente:

# Slack node (paso de revisión humana, conectado al conector Tools
# del AI Agent — issue_refund se conecta a ESTE nodo, no directo
# al agente)

Message:
El agente quiere usar {{ $tool.name }} con estos parámetros:
{{ JSON.stringify($tool.parameters, null, 2) }}

Ticket: {{ $json.ticketId }}

Y un fragmento agregado al system prompt del agente, para que sepa qué hacer si Slack deniega la acción:

# Fragmento del system prompt del agente
Si una tool requiere aprobación humana y es denegada, no la
reintentes. Dile al cliente que su solicitud necesita revisión
adicional y que alguien del equipo le va a dar seguimiento; deja
el ticket marcado como "pendiente de revisión manual".

Qué esperar. El agente decide llamar a issue_refund con { orderId: "4521", amount: 15000, reason: "producto dañado en tránsito" }. El workflow se detiene ahí — el HTTP Request Tool todavía no se ejecutó. En el canal de Slack de aprobaciones aparece: "El agente quiere usar issue_refund con los siguientes parámetros: { orderId: '4521', amount: 15000, reason: 'producto dañado en tránsito' }. Ticket: 4521", con botones para aprobar o denegar. Si una persona del equipo aprueba, el HTTP Request Tool se ejecuta contra el API de pagos y el resultado —reembolso confirmado— vuelve al agente, que se lo comunica al cliente. Si deniega, la acción se cancela, y el agente, siguiendo el fragmento de system prompt de arriba, le dice al cliente que su caso quedó en revisión manual, sin insistir ni inventar una alternativa por su cuenta.

La diferencia con el ejemplo anterior es la que importa: ahí, todo lo que impedía el descuento era una frase en la descripción de la tool. Acá, aunque el agente decidiera llamar issue_refund sin dudar, la ejecución real contra el API de pagos no ocurre hasta que una persona lo aprueba. El contrato le dice al agente qué hacer; el límite de confianza garantiza qué pasa aunque el agente se equivoque.

Errores comunes

Confundir una restricción en el system prompt con una barrera estructural (conceptual). Qué pasa: alguien agrega "nunca ofrezcas descuentos sin autorización" al system prompt y da por cerrado el tema, sin conectar ninguna tool sensible detrás de una revisión humana. Por qué pasa: el modelo respeta esa instrucción la enorme mayoría de las veces, así que en las pruebas normales todo funciona y parece suficiente. Cómo detectarlo: prueba con una conversación adversarial deliberada — un cliente insistente, una formulación distinta de la misma petición, varios turnos de presión — y revisa si en algún punto el agente termina llamando la tool sensible de todos modos. Cómo corregirlo: cualquier acción irreversible, con impacto financiero o que compromete a la empresa frente a un cliente va detrás del patrón de revisión humana de esta lección, no solo detrás de una frase en el prompt; el prompt sigue siendo útil para que el agente sepa cómo responder cuando la aprobación es denegada, pero no reemplaza la barrera.

Dejar que dos tools con descripciones parecidas se disputen la misma intención del usuario (conceptual). Qué pasa: TuTienda tiene update_order (cambia cualquier campo de un pedido) y update_order_status (cambia solo el estado), con descripciones casi idénticas — "Updates an order" y "Updates the status of an order" — y el agente empieza a llamar la que no corresponde, o alterna entre las dos en conversaciones parecidas. Por qué pasa: el modelo elige qué tool llamar comparando semánticamente la petición del usuario contra el texto de cada descripción; si dos descripciones se superponen, la probabilidad de elegir se reparte entre ambas en vez de resolverse con claridad. Cómo detectarlo: revisa los logs de ejecución del agente buscando casos donde la tool invocada no corresponde a lo que pidió el usuario, o donde la misma petición dispara tools distintas en corridas separadas. Cómo corregirlo: haz que las descripciones sean mutuamente excluyentes de forma explícita — "Use this tool only to change the order status field (pending, shipped, delivered, canceled). To change any other field of the order, use update_order instead" — en vez de dejar que la diferencia quede implícita en el nombre del nodo.

Dejar $fromAI() sin description en un parámetro sensible (práctico). Qué pasa: un parámetro como refundAmount se define solo como {{ $fromAI("refundAmount") }}, sin segundo argumento, y el modelo tiene que adivinar de dónde sacar ese número — a veces acierta con un valor que mencionó el cliente en la conversación, a veces inventa uno razonable pero incorrecto. Por qué pasa: sin description, el único indicio que tiene el modelo es el nombre del key, que rara vez alcanza para un dato numérico donde equivocarse cuesta dinero real. Cómo detectarlo: compara el JSON que efectivamente llegó al nodo real —no lo que el agente dijo que iba a hacer— contra los datos que sí aparecían en la conversación; un monto que no coincide con nada que el cliente o el pedido mencionaron es la señal. Cómo corregirlo: en todo parámetro que mueva dinero, borre datos o identifique a una persona, agrega siempre description explícita y type correcto, y usa defaultValue cuando exista un valor seguro por omisión — por ejemplo, $fromAI("refundAmount", "The refund amount in the store's currency, must never exceed the order's original total", "number", 0).

Ejercicios

Ejercicio 1 — Reescribe el contrato. TuTienda tiene una tool conectada a Google Sheets con este contrato: Name update_inventory, Description "Updates a product.", y un único parámetro {{ $fromAI("value") }}. La tool solo debería poder ajustar la cantidad en stock de un producto después de una devolución confirmada — nunca el precio ni el nombre del producto. Reescribe el Name, la Description y el (o los) parámetro(s) con $fromAI() siguiendo el patrón de esta lección.

Ver solución
# Google Sheets node (usado como Tool) — Name: adjust_stock_after_return

# Description: Adjusts the stock quantity of a product after a
# confirmed return. Use only to increase stock when a returned
# item has been verified as received. Do NOT use this tool to
# change price, product name, or any other column.

Row match: {{ $fromAI("productId", "The product ID (SKU) of the returned item, taken from the order record", "string") }}

Column to update: quantity_in_stock

New value: {{ $fromAI("newQuantity", "The updated stock quantity after adding back the returned item. Must be greater than or equal to the current quantity.", "number") }}

Por qué funciona: el Name ya no es genérico, la Description dice qué hace y qué prohíbe explícitamente (precio y nombre quedan fuera), y el parámetro tiene su propia description que ancla el número a un caso concreto — devolución confirmada — en vez de dejar "value" abierto a cualquier interpretación.

Ejercicio 2 — Clasifica las acciones. El agente de TuTienda tiene disponibles estas cinco acciones: (a) consultar el estado de un pedido, (b) cancelar un pedido, (c) responder una pregunta frecuente sobre envíos, (d) aplicar un código de descuento a una compra, (e) actualizar la dirección de envío de un pedido que todavía no salió del centro de distribución. Para cada una, decide si el agente puede ejecutarla sola o si necesita pasar por revisión humana, y justifica con el criterio de la tabla de esta lección (reversibilidad, impacto financiero, comunicación externa).

Ver solución

(a) Sola — es una lectura, no cambia nada. (b) Revisión humana — es irreversible una vez que el pedido entra en proceso de cancelación con la transportadora. (c) Sola — es información ya validada, no una acción sobre un sistema. (d) Revisión humana — tiene impacto financiero directo. (e) Depende del estado: si el pedido de verdad no salió del centro de distribución, es una acción reversible de bajo impacto y puede ir sola; si hay alguna duda sobre si ya salió, el costo de equivocarse (el paquete termina en la dirección vieja) empuja esta acción hacia revisión humana o, al menos, hacia una verificación adicional antes de ejecutarla.

Por qué funciona: el criterio no es "qué tan compleja es la acción técnicamente" — actualizar una dirección es tan simple como consultar un estado —, es cuánto cuesta deshacer el error si el agente se equivoca, y quién paga ese costo.

Ejercicio 3 — El parámetro de un reembolso. Escribe la expresión $fromAI() completa para el parámetro refundAmount de la tool issue_refund de esta lección. Debe ser de tipo numérico, tener una descripción que reduzca el margen de error del modelo, y un defaultValue razonable.

Ver solución
{{ $fromAI("refundAmount", "The refund amount in the store's currency (COP). Must never exceed the order's original total and must match an amount the customer or the order record actually mentions — never estimate or round up.", "number", 0) }}

Por qué funciona: el type: "number" evita que el valor llegue como texto; la description le pone un techo explícito (no exceder el total del pedido) y prohíbe inventar el número; el defaultValue: 0 es una salvaguarda razonable si el modelo no encuentra ningún monto claro en el contexto, en vez de dejar que adivine uno.

Resumen y siguiente paso

Ya tienes las dos piezas que le faltaban al acceso que construiste en la lección 4: el contrato de una tool —Name, Description y cada parámetro con su propia descripción vía $fromAI()— que le dice al agente qué hace cada tool y cuándo NO usarla, y el límite de confianza —el patrón de revisión humana en el conector Tools del AI Agent, con $tool.name y $tool.parameters disponibles en el paso de aprobación— que garantiza que las acciones irreversibles, con impacto financiero o que comprometen a la empresa no dependan solo del criterio del modelo.

Antes de avanzar deberías poder: escribir la Description de una tool de forma que no se superponga con otra tool parecida; usar $fromAI() con una description explícita para cada parámetro sensible; clasificar una acción como "el agente la ejecuta sola" o "requiere aprobación humana" usando el criterio de reversibilidad e impacto, no la intuición; y conectar una tool detrás del paso de revisión humana en vez de directo al agente.

La lección 6 toma este mismo criterio de contrato y lo lleva un paso más allá: en vez de exponer un nodo individual como tool, vas a encapsular una secuencia completa de pasos en un sub-workflow y exponer ese sub-workflow como una sola tool del agente. Va a necesitar exactamente el mismo cuidado que practicaste acá —un nombre y una descripción sin ambigüedad—, solo que ahora detrás de esa descripción hay varios pasos en vez de uno.

Recursos