Danila (Dayfing)
Volver a publicaciones
2872 palabras14 min

Arquitectura de un agente de IA en producción: herramientas, estado, aprobaciones y evaluaciones

Un agente de IA en producción no es un prompt con una ventana de contexto más grande. Es un servicio que permite que un modelo elija acciones, observe sus resultados y continúe hasta alcanzar un resultado acotado o pedir ayuda a una persona. El modelo es solo un componente. El plano de control decide qué puede ver, qué herramientas puede invocar, qué acciones requieren aprobación, cómo sobrevive el estado a un reinicio y qué pruebas justifican publicar una nueva versión.

Este artículo presenta una arquitectura de referencia para un agente con pocas herramientas. Las mismas fronteras sirven para un workflow de pasos fijos y para una futura arquitectura multiagente. Empiece por el ciclo más pequeño que resuelva la tarea. La guía de Anthropic sobre agentes eficaces distingue también los workflows predecibles de los sistemas en los que el modelo dirige dinámicamente el uso de herramientas. La autonomía es una decisión de producto, no un valor predeterminado de arquitectura.

Arquitectura de referencia

Separe el camino de la solicitud, el ciclo de decisión y los efectos que cambian el mundo exterior. Un gateway autentica al llamador, crea los identificadores de solicitud y traza, aplica cuotas y elimina o clasifica campos sensibles. Un orquestador es dueño del ciclo. Un adaptador de modelo convierte el estado neutral en una solicitud al proveedor y la respuesta del proveedor en un tipo pequeño de decisión. Un gateway de herramientas valida argumentos, autoriza al principal, aplica la política y llama a un conector aislado. El estado y el registro de eventos deben estar fuera del proceso para que un worker pueda reanudar después de un fallo.

[Usuario o cliente API]
          |
[Gateway: identidad, límites, request ID]
          |
[Orquestador: policy -> model -> tool loop]
       /       |              \
[State]   [Approval service]  [Tool gateway]
   |             |              |
[Memory/RAG] [Decisión humana] [APIs, archivos, colas]
                         |
                  [Eventos y trazas]

No entregue al modelo credenciales sin acotar, acceso de red irrestricto ni escritura directa en la base. El gateway debe exponer operaciones estrechas como "search_orders", "draft_refund" o "send_message", no un cliente HTTP genérico ni una consola SQL. Cada operación necesita un responsable, un esquema de entrada, un permiso, una clase de riesgo, un tiempo límite y un formato de resultado documentado.

Use una envoltura explícita para cada solicitud:

Campo Propósito
tenantId y actorId Vincular cada lectura y efecto con un principal autenticado.
requestId y traceId Correlacionar reintentos, aprobaciones, llamadas de herramientas y logs.
goal Guardar el resultado solicitado por el usuario separado de los mensajes del modelo.
policyVersion y modelVersion Hacer la ejecución suficientemente reproducible para investigarla.
deadline y stepBudget Limitar tiempo de pared y turnos de modelo-herramienta.

El perfil de IA generativa de NIST AI RMF ayuda a organizar los riesgos durante todo el ciclo de vida. Convierta sus preguntas en controles concretos. Un framework no demuestra seguridad.

Ciclo de herramientas

El ciclo debe tener un único dueño y una transición de estado visible en cada turno:

  1. Cargar la ejecución, la política, el resumen de conversación y el catálogo permitido.
  2. Construir una entrada de modelo acotada. Marcar texto del usuario, texto recuperado, salida de herramienta e instrucciones de sistema como clases de confianza distintas.
  3. Pedir al modelo una respuesta final o una llamada de herramienta tipada. Rechazar nombres y argumentos mal formados antes de ejecutar.
  4. Resolver autorización y riesgo fuera del modelo. Que el modelo diga que una acción es segura no es una decisión de autorización.
  5. Si la acción cruza una barrera de aprobación, guardar la acción pendiente y detenerse. Reanudar desde la acción guardada después de una decisión explícita.
  6. Ejecutar el conector con un límite y una clave de idempotencia. Registrar un resultado redactado y añadirlo al estado.
  7. Comprobar los presupuestos de pasos, tokens, coste y tiempo. Continuar solo si la política permite otro turno.
  8. Devolver una respuesta que diga qué ocurrió, qué no ocurrió y qué aprobación o incertidumbre queda.

No coloque un bucle "while" ilimitado alrededor de una llamada al modelo. Un presupuesto de pasos impide una cadena costosa. Un límite protege al cliente y al grupo de workers. Un detector descubre acciones que fallan repetidamente. Un circuit breaker puede desactivar un conector degradado sin quitar lecturas.

La especificación de Model Context Protocol estandariza resources, prompts y tools, pero no sustituye el consentimiento, la autorización ni el aislamiento del host. Trate un servidor MCP como una dependencia externa. Fije su identidad, inspeccione sus herramientas anunciadas, limite los datos enviados y aplique la misma política del gateway que a un conector propio.

Estado, memoria y contexto

El estado es el registro durable de una ejecución. Guarde el objetivo, mensajes o referencias, llamadas y resultados de herramientas, aprobaciones, decisiones de política, versiones de modelo y herramientas, fechas, estado y clasificación del error. Añada eventos primero y derive una vista actual después. La recuperación y la auditoría son más fáciles que con un único blob JSON opaco. Cifre campos sensibles, aísle tenants, defina retención y haga que la eliminación cubra snapshots, índices, trazas y cachés.

El historial de conversación no es memoria. Mantenga un contexto de trabajo de corta duración para la ejecución actual. Conserve memoria de usuario o negocio solo si existen propósito, regla de retención, fuente y una forma de corregirla. Guarde hechos con procedencia y un campo de confianza o frescura. No escriba suposiciones del modelo en la memoria duradera solo porque parecen plausibles.

La recuperación es una herramienta con una frontera de confianza. Filtre por tenant y autorización antes de ordenar resultados. Lleve identificadores y fechas al contexto. Indique al modelo que el texto recuperado son datos, no instrucciones. Limite fragmentos y registre identificadores sin contenido privado. En PostgreSQL, compare candidatos léxicos y vectoriales y haga rerank después de autorizar. La guía interna de RAG híbrido con pgvector desarrolla esta frontera.

La compactación del contexto debe ser suficientemente determinista para depurarla. Resuma turnos antiguos con un esquema fijo que conserve decisiones, preguntas abiertas, efectos de herramientas, citas y restricciones del usuario. Mantenga el registro de eventos original fuera del prompt. Si un resumen cambia el sentido de una acción pendiente, deténgase y solicite revisión en lugar de continuar en silencio.

Herramientas y fronteras de amenaza

El esquema de una herramienta es un contrato de seguridad, no solo metadatos del modelo. El gateway debe validar tipos, longitudes, valores de enumeración, propiedad de recursos y relaciones entre campos. Resuelva nombres a identificadores internos después de autorizar. Separe herramientas de lectura y escritura. Entregue a los conectores credenciales de corta duración limitadas a un tenant y operación. Ejecute código o trabajo con archivos de riesgo en un worker aislado con listas de autorización para sistema de archivos y red.

Considere hostil todo contenido externo. Un correo, página web, ticket, documento, descripción de herramienta o recurso MCP puede contener prompt injection indirecto. Delimítelo en la entrada y deje que la aplicación decida permisos. Valide las salidas independientemente. Compruebe importe, moneda, actor y máximo antes de un reembolso.

El modelo de amenazas debe cubrir:

Frontera Falla que se debe evitar
Usuario hacia gateway Toma de cuenta, solicitudes enormes e identificadores de otro tenant.
Modelo hacia gateway de herramientas Inyección de argumentos, confusión de privilegios y agencia excesiva.
Recuperación o MCP hacia modelo Prompt injection indirecto e instrucciones maliciosas dentro de datos.
Conector hacia servicio externo Fuga de credenciales, SSRF, replay y exfiltración de datos.
Worker hacia state store Eventos manipulados, política obsoleta y auditoría incompleta.

El OWASP GenAI LLM Top 10 describe prompt injection, agencia excesiva, manejo inseguro de salidas y consumo sin límite como riesgos que requieren mitigaciones de aplicación. La guía interna de prompt injection y seguridad MCP contiene una lista centrada en amenazas. Un system prompt es una guía útil, pero no es un sandbox, una capa de autorización ni un almacén de secretos.

Barreras de aprobación y control humano

Coloque las aprobaciones alrededor de los efectos, no del razonamiento inocuo. Leer el calendario propio del usuario puede ser automático. Enviar un mensaje externo, cambiar un registro, emitir dinero, borrar datos o desplegar código suele requerir una decisión basada en actor, objetivo, importe, reversibilidad y confianza. Mantenga la barrera en código de aplicación para que ningún prompt pueda saltarla.

Persista una solicitud de aprobación con la herramienta propuesta, argumentos normalizados, recursos afectados, motivo, versión de política, caducidad y hash del estado relevante. El revisor debe ver los mismos datos. Vincule su decisión al hash de la acción y al actor. Tras aprobar, vuelva a comprobar autorización, frescura y presupuesto antes de ejecutar. Tras rechazar o caducar, registre la decisión e informe al modelo de que la acción no ocurrió. Una aprobación antigua no debe autorizar un payload cambiado.

Los modos human-in-the-loop son varios:

Modo Uso adecuado
Observe Registrar o muestrear acciones de bajo riesgo manteniendo el agente automático.
Confirm Pedir aprobación justo antes de un efecto irreversible.
Review Permitir que una persona revise un borrador completo y sus evidencias.
Take over Transferir la ejecución a un operador con estado y lock actuales.

Diseñe la pausa como un estado normal, no como una excepción. Una cola puede entregar aprobaciones pendientes, una notificación puede caducar y un worker puede reanudar en otro host. El usuario debe saber si el agente está razonando, esperando datos, esperando aprobación, reintentando o terminado.

Reintentos, idempotencia y recuperación

Clasifique los errores antes de reintentar. Un error de validación necesita una llamada corregida o una pregunta al usuario. Los errores de autenticación y autorización deben detenerse. Los límites de tasa deben respetar la señal de reintento del proveedor. Un timeout o una conexión rota son ambiguos para una escritura porque el servicio remoto pudo aplicar el efecto. Reintente solo una operación cuya semántica o clave de idempotencia haga segura la repetición. La sección 9.2.2 de RFC 9110 explica por qué un cliente no debe repetir automáticamente métodos no idempotentes sin una forma de establecer que el efecto es seguro.

Genere una clave estable a partir de la ejecución y la acción lógica, no del número de intento. El conector guarda la clave y el resultado final durante la ventana de reintento. Si llega la misma clave con argumentos distintos, rechácela. Use backoff exponencial con jitter y un número máximo pequeño de intentos. Un reintento no es una decisión nueva del modelo. Persista la llamada original, número de intento, clase de respuesta e identificador del conector.

La recuperación pertenece a una máquina de estados. Use estados como "running", "waiting_for_approval", "retrying", "failed", "completed" y "cancelled". Un lease impide que dos workers ejecuten el mismo run a la vez. Tras perderlo, deténgase antes del siguiente efecto. Un reconciler puede comparar acciones pendientes con registros del conector después de un crash. La cancelación debe propagarse a solicitudes del modelo, llamadas, colas y aprobaciones cuando el proveedor lo permita.

Observabilidad

Instrumente una traza por solicitud de usuario y spans para llamadas de modelo, recuperación, controles de política, aprobaciones y herramientas. Registre duración, estado, número de reintentos, tokens de entrada y salida si están disponibles, versiones de modelo y prompt, nombre de herramienta, clase de riesgo y coste estimado. Redacte secretos y contenido sensible antes de exportar. Use un identificador de run estable para enlazar una aprobación o ticket con la traza sin poner datos personales en baggage. OpenTelemetry context propagation describe cómo unir contexto de traza entre servicios y advierte sobre headers no confiables y baggage sensible.

Mida finalización con una rúbrica, llamadas exitosas, fallos de validación, aprobaciones, reintentos, timeouts, latencia p50 y p95, coste por tarea, cancelaciones y bloqueos. Segmente por versión, herramienta, tenant y release. Pocos errores pueden ocultar respuestas incorrectas. Relacione trazas con transcripciones muestreadas y evaluadores. No registre chain-of-thought. Guarde metadatos y citas visibles permitidos por la política.

La guía interna de observabilidad para agentes de IA muestra cómo usar estas señales sin convertir los logs en otra base de secretos.

Evaluaciones antes y después del release

Una evaluación de agente es un escenario con estado inicial, herramientas permitidas, invariantes esperados y regla de puntuación definidos. La respuesta final no basta. Compruebe que el agente usó una herramienta autorizada, mantuvo el tenant, pidió aprobación cuando hacía falta, no repitió una escritura, citó la fuente correcta y se detuvo en el presupuesto. Incluya escenarios adversariales: texto recuperado malicioso, conector no disponible, timeout después de una escritura, aprobación obsoleta, solicitud ambigua y datos mal formados de una herramienta.

Use una suite por capas:

  1. Pruebas unitarias deterministas para esquemas, autorización, redacción, idempotencia, transiciones de estado y presupuesto.
  2. Pruebas de replay con respuestas de herramientas registradas y decisiones de modelo fijas para verificar recuperación.
  3. Pruebas de escenarios contra un modelo con rúbrica de resultado, seguridad y comunicación.
  4. Pruebas red team para inyección directa e indirecta, fuga de datos, agencia excesiva y denegación de servicio.
  5. Muestreo en producción con controles de privacidad, revisión humana y conversión de fallos en casos de regresión.

Versione el escenario, herramientas, política, prompts, modelo y evaluador. Guarde fallos con la traza y la entrada reproducible más pequeña. Compare una release candidata con una baseline y no permita regresiones en invariantes de seguridad estrictos, aunque mejore la calidad media. La guía de Anthropic sobre evaluaciones de agentes explica por qué el uso de herramientas en varios turnos requiere evaluar la trayectoria. La guía interna de evaluaciones de agentes IA aporta una matriz práctica.

Decisiones de coste y latencia

Cada turno de modelo, token recuperado, llamada, pausa de aprobación y reintento añade tiempo o dinero. Defina presupuestos por clase de tarea y no un número global. Use un modelo pequeño para routing, extracción y comprobaciones previas si su precisión medida basta. Reserve un modelo más potente para planificación ambigua o síntesis final. Ponga en caché catálogos estables y embeddings. Resuma el contexto antes de que crezca, pero mida si el resumen causa turnos extra o pérdida de hechos.

Paralelice llamadas de lectura independientes y fusione los resultados con procedencia explícita. Mantenga las escrituras secuenciales salvo que el conector ofrezca una transacción o compensación diseñada. Muestre progreso sin exponer secretos. Para trabajos largos, persista un job y deje que un worker continúe después de terminar la petición HTTP. Un modelo más rápido no es más barato si sus errores activan revisión humana, escrituras compensatorias o ejecuciones repetidas. Mida el coste total por resultado correcto y conforme a la política.

Un ciclo ejecutable mínimo

El siguiente ejemplo TypeScript independiente del protocolo usa un modelo ficticio y herramientas locales, por lo que funciona sin SDK de proveedor ni red. Muestra un ciclo de decisión tipado, aprobación de escritura, reintentos acotados, clave de idempotencia estable y defensa contra efectos duplicados. Un adaptador real puede reemplazar model manteniendo las fronteras de policy y tool.

type ToolCall = { id: string; name: string; input: unknown };
type Message =
  | { role: "user"; content: string }
  | { role: "assistant"; content: string; toolCall?: ToolCall }
  | { role: "tool"; callId: string; content: string };
type Decision =
  | { kind: "answer"; text: string }
  | { kind: "call"; call: ToolCall };
type Tool = {
  sideEffect: "read" | "write";
  run(input: unknown, idempotencyKey: string): Promise<string>;
};

const issued = new Set<string>();
const tools: Record<string, Tool> = {
  getBalance: {
    sideEffect: "read",
    async run(): Promise<string> {
      return JSON.stringify({ account: "demo", cents: 4200 });
    },
  },
  sendInvoice: {
    sideEffect: "write",
    async run(input: unknown, idempotencyKey: string): Promise<string> {
      if (issued.has(idempotencyKey)) return "already-sent";
      if (typeof input !== "object" || input === null) throw new Error("invalid input");
      issued.add(idempotencyKey);
      return "invoice-sent";
    },
  },
};

const model = {
  async decide(messages: readonly Message[]): Promise<Decision> {
    const toolCount = messages.filter((message) => message.role === "tool").length;
    if (toolCount === 0) {
      return { kind: "call", call: { id: "balance-1", name: "getBalance", input: {} } };
    }
    if (toolCount === 1) {
      return { kind: "call", call: { id: "invoice-1", name: "sendInvoice", input: { cents: 1200 } } };
    }
    return { kind: "answer", text: "The balance was checked and the invoice was sent." };
  },
};

const wait = (milliseconds: number) => new Promise((resolve) => setTimeout(resolve, milliseconds));

async function execute(call: ToolCall, tool: Tool, key: string): Promise<string> {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    try {
      return await tool.run(call.input, key);
    } catch (error) {
      if (attempt === 2) throw error;
      await wait(10 * 2 ** attempt);
    }
  }
  throw new Error("unreachable");
}

async function run(): Promise<string> {
  const state: { messages: Message[]; completed: Set<string> } = {
    messages: [{ role: "user", content: "Check the balance and send the invoice." }],
    completed: new Set<string>(),
  };
  const sessionId = "session-demo";
  for (let step = 0; step < 6; step += 1) {
    const decision = await model.decide(state.messages);
    if (decision.kind === "answer") return decision.text;
    const tool = tools[decision.call.name];
    if (!tool) throw new Error("unknown tool");
    const key = sessionId + ":" + decision.call.id;
    if (tool.sideEffect === "write" && !process.argv.includes("--approve")) {
      return "Paused for approval: " + decision.call.name;
    }
    if (state.completed.has(key)) continue;
    const result = await execute(decision.call, tool, key);
    state.completed.add(key);
    state.messages.push(
      { role: "assistant", content: "", toolCall: decision.call },
      { role: "tool", callId: decision.call.id, content: result },
    );
  }
  throw new Error("step budget exceeded");
}

run().then(console.log).catch((error: unknown) => {
  console.error(error);
  process.exitCode = 1;
});

Compile el código con el toolchain TypeScript del proyecto y ejecute el JavaScript emitido sin la opción para observar la pausa de aprobación, y después con --approve para permitir la escritura. El ejemplo mantiene el estado en memoria de forma deliberada. En producción, el estado debe ser durable, limitado al tenant, cifrado cuando corresponda y recuperado por un worker que respete leases.

Orden de construcción

Defina el resultado y los efectos prohibidos antes de seleccionar un modelo. Implemente una herramienta de lectura y una escritura reversible detrás de esquemas y autorización. Añada eventos, presupuestos, aprobaciones e idempotencia antes de más herramientas. Instrumente la primera traza. Cree escenarios desde fallos reales y ejecútelos en cada cambio. Añada MCP o delegación multiagente solo cuando un requisito medido justifique la frontera adicional.

Más publicaciones