Blog

Agentes de IA con confirmación humana: por qué la IA no debería actuar sola sobre tus datos

Anderson Sinaluisa4 min de lectura
Un patrón práctico para construir agentes de IA que proponen acciones y esperan tu confirmación antes de ejecutarlas: tool calling, acciones pendientes, idempotencia y auditoría.

Los agentes de IA son la evolución natural del chat: en lugar de solo responder, hacen cosas. Registran un gasto, crean un presupuesto, mandan un correo o actualizan un registro. Y ahí aparece la pregunta incómoda: ¿qué pasa cuando se equivocan?

En Fino tomé una decisión desde el diseño: Fino AI puede registrar gastos, crear presupuestos e importar estados de cuenta desde el chat, pero siempre con tu confirmación antes de cada acción. En este artículo te explico el patrón detrás de esa decisión y cómo implementarlo.

El problema: el modelo no sabe cuándo duda

Un LLM puede leer “gasté 20 en el super” y registrar $20 en “Supermercado”… o $200, o en la cuenta equivocada. Y lo hará con el mismo tono seguro en ambos casos. Cuando la acción toca dinero, datos personales o algo difícil de deshacer, un error silencioso es mucho peor que un paso extra.

El patrón: proponer → confirmar → ejecutar

La idea es separar dos cosas que normalmente van juntas:

  1. El modelo propone una acción estructurada (qué herramienta, con qué parámetros).
  2. La app la muestra al usuario de forma legible y la guarda como pendiente.
  3. El usuario confirma (o corrige, o cancela).
  4. El servidor ejecuta, validando todo de nuevo.

El modelo nunca ejecuta nada directamente: solo produce intenciones.

Diagrama de secuencia entre usuario, app, modelo y servidor con el paso de confirmación humana

1. Define herramientas con parámetros estrictos

Con tool calling (o function calling), le describes al modelo qué acciones existen y qué parámetros aceptan:

const tools = [
  {
    name: 'create_expense',
    description: 'Registra un gasto del usuario.',
    parameters: {
      type: 'object',
      properties: {
        amount: { type: 'number', description: 'Monto positivo' },
        currency: { type: 'string', enum: ['USD'] },
        category: { type: 'string' },
        accountId: { type: 'string' },
        date: { type: 'string', format: 'date' },
      },
      required: ['amount', 'currency', 'category', 'accountId'],
    },
  },
];

Mientras más cerrado sea el esquema (enums, formatos, campos requeridos), menos espacio tiene el modelo para inventar.

2. Convierte la llamada en una acción pendiente

Cuando el modelo responde con una llamada a herramienta, no la ejecutes. Valídala y guárdala:

import { z } from 'zod';
import { randomUUID } from 'node:crypto';

const CreateExpense = z.object({
  amount: z.number().positive().max(100_000),
  currency: z.literal('USD'),
  category: z.string().min(1),
  accountId: z.string(),
  date: z.string().date().optional(),
});

async function proposeAction(userId: string, toolName: string, rawArgs: unknown) {
  const args = CreateExpense.parse(rawArgs); // falla si el modelo inventó algo

  // Verifica contra tus datos, no contra lo que dijo el modelo
  await assertAccountBelongsToUser(userId, args.accountId);

  return db.pendingActions.insert({
    id: randomUUID(),
    userId,
    tool: toolName,
    args,
    status: 'pending',
    expiresAt: Date.now() + 10 * 60 * 1000, // 10 minutos
  });
}

3. Muestra la acción en lenguaje humano

La confirmación solo sirve si la persona entiende lo que va a aprobar. Nada de JSON en pantalla:

Registrar gasto $20.00 · Supermercado · Cuenta principal · Hoy [Confirmar] [Editar] [Cancelar]

Permitir editar antes de confirmar es clave: convierte un error del modelo en una corrección de dos segundos.

Chat donde el usuario escribe gasté 20 en el súper y la IA muestra una tarjeta con el gasto propuesto y botones para confirmar o editar

4. Ejecuta con idempotencia

Cuando llega la confirmación, el servidor ejecuta usando el ID de la acción pendiente como llave de idempotencia. Si el usuario toca dos veces “Confirmar” o la red reintenta, la acción se aplica una sola vez:

async function confirmAction(userId: string, actionId: string) {
  return db.transaction(async (tx) => {
    const action = await tx.pendingActions.findForUpdate(actionId);

    if (!action || action.userId !== userId) throw new Error('Acción no encontrada');
    if (action.status === 'done') return action.result; // idempotente
    if (action.expiresAt < Date.now()) throw new Error('La acción expiró');

    const result = await executeTool(tx, action.tool, action.args);
    await tx.pendingActions.update(actionId, { status: 'done', result });
    await tx.auditLog.insert({ userId, actionId, tool: action.tool, args: action.args });

    return result;
  });
}

El usuario confirma dos veces la misma acción y el servidor detecta que ya se ejecutó: un solo registro

Estados de una acción: propuesta, pendiente, confirmada y ejecutada, o cancelada y expirada

5. Deja auditoría

Cada acción ejecutada queda registrada: quién, qué, cuándo y con qué parámetros. Si algo sale mal, puedes explicarlo y revertirlo.

¿Siempre hay que confirmar?

No necesariamente. Mi criterio:

Tipo de acción¿Confirmación?
Leer o consultar datosNo
Sugerir algo (sin efecto)No
Crear o modificar datos del usuarioSí
Mover dinero, enviar mensajes, borrarSí, y con detalle

La fricción debe ser proporcional al riesgo. Confirmar una lectura es molesto; no confirmar un borrado es peligroso.

Conclusión

Los agentes de IA más útiles no son los más autónomos, sino los más confiables. Separar proponer de ejecutar te da lo mejor de ambos mundos: la comodidad de pedir las cosas en lenguaje natural y la tranquilidad de que nada pasa sin tu visto bueno.

Comparto más sobre IA y desarrollo de productos en TikTok: @andydev22.