Saltar al contenido principal

Agentes

Un agente de IA es un sistema que utiliza un LLM como "cerebro" para planificar y ejecutar una secuencia de acciones de forma autónoma para alcanzar un objetivo, sin que el programador defina explícitamente cada paso.

La diferencia clave con un chatbot o una cadena simple es que el agente puede decidir qué herramientas usar, en qué orden, y cuándo ha terminado.

Chatbot:

Cadena simple (pasos fijos):

Agente:

El bucle ReAct

El patrón más común en agentes es ReAct (Reasoning + Acting). Este ciclo permite al modelo "pensar en voz alta" antes de realizar cualquier acción:

  1. Pensamiento (Thought): El modelo describe lo que cree que está pasando y qué paso debe dar a continuación.
  2. Acción (Action): El modelo decide llamar a una herramienta específica con ciertos parámetros.
  3. Observación (Observation): El sistema ejecuta la herramienta y le devuelve el resultado al modelo.
  4. Respuesta (Final Answer): Una vez que el modelo tiene información suficiente, genera la respuesta para el usuario.
PENSAMIENTO: Necesito saber el precio actual del dólar para calcular la conversión.
ACCIÓN: obtener_tipo_cambio({ moneda: "USD", base: "EUR" })
OBSERVACIÓN: { tipo: 1.08, fecha: "2026-03-09" }

PENSAMIENTO: Ahora puedo calcular 500 USD en EUR.
ACCIÓN: calcular({ operacion: "500 / 1.08" })
OBSERVACIÓN: { resultado: 462.96 }

PENSAMIENTO: Ya tengo el resultado, puedo responder al usuario.
RESPUESTA: 500 dólares equivalen a aproximadamente 462,96 euros.

Agente simple con OpenAI

Para construir un agente "manual" con OpenAI, necesitamos una clase que gestione el historial de mensajes y un bucle que analice los tool_calls del modelo hasta que este decida que ha terminado la tarea.

import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// === Definición de herramientas ===
const HERRAMIENTAS = [
{
type: 'function',
function: {
name: 'buscar_en_web',
description: 'Busca información actualizada en internet',
parameters: {
type: 'object',
properties: {
consulta: { type: 'string', description: 'Término de búsqueda' }
},
required: ['consulta']
}
}
},
{
type: 'function',
function: {
name: 'leer_archivo',
description: 'Lee el contenido de un archivo del sistema',
parameters: {
type: 'object',
properties: {
ruta: { type: 'string', description: 'Ruta del archivo' }
},
required: ['ruta']
}
}
},
{
type: 'function',
function: {
name: 'escribir_archivo',
description: 'Escribe contenido en un archivo',
parameters: {
type: 'object',
properties: {
ruta: { type: 'string' },
contenido: { type: 'string' }
},
required: ['ruta', 'contenido']
}
}
},
{
type: 'function',
function: {
name: 'ejecutar_consulta_sql',
description: 'Ejecuta una consulta SELECT en la base de datos',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Consulta SQL (solo SELECT)' }
},
required: ['query']
}
}
}
];

// === Implementaciones ===
const acciones = {
buscar_en_web: async ({ consulta }) => {
// Integración real con Brave Search API, SerpAPI, etc.
return { resultados: [`Resultado simulado para: ${consulta}`] };
},
leer_archivo: async ({ ruta }) => {
const fs = await import('fs/promises');
try {
return { contenido: await fs.readFile(ruta, 'utf-8') };
} catch {
return { error: 'Archivo no encontrado' };
}
},
escribir_archivo: async ({ ruta, contenido }) => {
const fs = await import('fs/promises');
await fs.writeFile(ruta, contenido);
return { ok: true, bytes: contenido.length };
},
ejecutar_consulta_sql: async ({ query }) => {
// Solo SELECTs por seguridad
if (!query.trim().toUpperCase().startsWith('SELECT')) {
return { error: 'Solo se permiten consultas SELECT' };
}
// return await db.query(query);
return { filas: [] }; // Simulado
}
};

// === Clase Agente ===
class Agente {
constructor({ objetivo, systemPrompt, maxPasos = 10 }) {
this.historial = [
{
role: 'system',
content: systemPrompt || `Eres un agente inteligente. Tu objetivo es completar tareas
usando las herramientas disponibles. Piensa paso a paso y usa las herramientas necesarias.
Cuando hayas terminado, proporciona un resumen claro del resultado.`
}
];
this.maxPasos = maxPasos;
this.pasos = 0;
}

async ejecutar(objetivo) {
this.historial.push({ role: 'user', content: objetivo });
console.log(`\n🎯 Objetivo: ${objetivo}\n`);

while (this.pasos < this.maxPasos) {
this.pasos++;
console.log(`--- Paso ${this.pasos} ---`);

const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: this.historial,
tools: HERRAMIENTAS,
tool_choice: 'auto'
});

const mensaje = response.choices[0].message;
this.historial.push(mensaje);

// Agente terminó
if (response.choices[0].finish_reason === 'stop') {
console.log('\n✅ Agente completó la tarea');
return mensaje.content;
}

// Ejecutar herramientas
if (mensaje.tool_calls) {
for (const call of mensaje.tool_calls) {
const nombre = call.function.name;
const args = JSON.parse(call.function.arguments);

console.log(`🔧 Herramienta: ${nombre}`, args);

const resultado = await acciones[nombre]?.(args) ?? { error: 'Herramienta no encontrada' };
console.log(`📊 Resultado:`, resultado);

this.historial.push({
role: 'tool',
tool_call_id: call.id,
content: JSON.stringify(resultado)
});
}
}
}

return 'El agente alcanzó el límite de pasos sin completar la tarea.';
}
}

// === Uso ===
const agente = new Agente({});
const resultado = await agente.ejecutar(
'Busca información sobre las mejores prácticas de seguridad en Node.js, ' +
'résume los 5 puntos más importantes y guárdalos en /tmp/seguridad-nodejs.md'
);
console.log('\n📝 Resultado final:\n', resultado);

Agentes con Vercel AI SDK

Al igual que con el function calling, el AI SDK facilita enormemente la creación de agentes. Al definir maxSteps, el SDK se encarga de re-alimentar al modelo con los resultados de las herramientas automáticamente hasta alcanzar una respuesta final o el límite de pasos.

import { generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const resultado = await generateText({
model: openai('gpt-4o'),
system: 'Eres un asistente analítico. Usa las herramientas disponibles para responder.',
prompt: 'Analiza las ventas del último mes y genera un informe ejecutivo.',

tools: {
obtenerVentas: tool({
description: 'Obtiene datos de ventas de un período',
parameters: z.object({
fechaInicio: z.string(),
fechaFin: z.string(),
agruparPor: z.enum(['dia', 'semana', 'mes']).default('dia')
}),
execute: async ({ fechaInicio, fechaFin, agruparPor }) => {
// return await db.ventas.findMany(...)
return {
total: 45230,
ventas: [{ fecha: '2026-02', importe: 45230, pedidos: 312 }]
};
}
}),

obtenerTopProductos: tool({
description: 'Obtiene los productos más vendidos',
parameters: z.object({ limite: z.number().default(5) }),
execute: async ({ limite }) => {
return {
productos: [
{ nombre: 'Producto A', unidades: 120, importe: 12000 },
{ nombre: 'Producto B', unidades: 95, importe: 9500 }
]
};
}
}),

guardarInforme: tool({
description: 'Guarda el informe generado en la base de datos',
parameters: z.object({
titulo: z.string(),
contenido: z.string(),
tipo: z.enum(['ejecutivo', 'detallado'])
}),
execute: async ({ titulo, contenido, tipo }) => {
// await db.informes.create(...)
return { id: 'inf-001', guardado: true };
}
})
},

maxSteps: 8 // el agente puede dar hasta 8 pasos
});

console.log(resultado.text);
console.log(`Pasos usados: ${resultado.steps.length}`);

Patrones de agentes avanzados

Agentes paralelos (multi-agent)

En sistemas complejos, no es eficiente tener un solo agente con 50 herramientas. Es mejor tener agentes especialistas (por ejemplo, un agente para ventas y otro para soporte técnico) y un agente coordinador que dirija la consulta al especialista adecuado.

// Varios agentes especializados trabajando en paralelo
async function analizarConMultiplesAgentes(datos) {
const [analisisTecnico, analisisNegocio, analisisRiesgos] = await Promise.all([
ejecutarAgente('analista-tecnico', datos),
ejecutarAgente('analista-negocio', datos),
ejecutarAgente('analista-riesgos', datos)
]);

// Agente coordinador sintetiza los resultados
return await ejecutarAgente('coordinador', {
tecnico: analisisTecnico,
negocio: analisisNegocio,
riesgos: analisisRiesgos
});
}

Agente con memoria persistente

Por defecto, los agentes olvidan todo al terminar el script. Para aplicaciones reales, necesitamos persistir el historial (el "contexto") en una base de datos o caché como Redis, asociándolo a un ID de sesión.

class AgenteConMemoria {
constructor(sessionId) {
this.sessionId = sessionId;
}

async cargarHistorial() {
return await redis.get(`agente:${this.sessionId}`) ?? [];
}

async guardarHistorial(historial) {
await redis.set(
`agente:${this.sessionId}`,
JSON.stringify(historial),
{ EX: 3600 } // TTL 1 hora
);
}

async ejecutar(mensaje) {
const historial = await this.cargarHistorial();
historial.push({ role: 'user', content: mensaje });

// ... lógica del agente ...

await this.guardarHistorial(historial);
return respuesta;
}
}

Consideraciones de seguridad en agentes

Dado que los agentes pueden realizar acciones autónomas (como borrar archivos o ejecutar SQL), la seguridad es crítica. Nunca confíes ciegamente en los argumentos generados por el LLM.

Los agentes pueden ejecutar acciones reales en tu sistema. Es importante:

// ✅ Valida y sanitiza los argumentos antes de ejecutar
async function ejecutarHerramientaSegura(nombre, args) {
// Lista blanca de herramientas permitidas
const herramientasPermitidas = ['buscar', 'leer', 'calcular'];
if (!herramientasPermitidas.includes(nombre)) {
throw new Error(`Herramienta no autorizada: ${nombre}`);
}

// Valida esquemas con Zod antes de ejecutar
const esquema = esquemasHerramientas[nombre];
const argsValidados = esquema.parse(args);

// Loguea todas las acciones del agente
await auditLog.registrar({ herramienta: nombre, args: argsValidados });

return await acciones[nombre](argsValidados);
}

Implementa:

  • Límite de pasos para evitar bucles infinitos.
  • Timeout global por ejecución.
  • Nunca permitas SQL sin restricciones.
  • Separa herramientas de solo lectura de las de escritura.
  • Requiere confirmación humana para acciones destructivas (Human-in-the-Loop).

Human-in-the-Loop (HITL)

El patrón HITL consiste en introducir una interrupción manual en el flujo del agente. Cuando el agente intenta ejecutar una acción de alto riesgo, el sistema se pausa y solicita la aprobación de un humano antes de continuar.

Para acciones críticas, pausa el agente y pide confirmación:

async function ejecutarConConfirmacion(herramienta, args) {
// Herramientas que requieren confirmación humana
const requierenConfirmacion = ['eliminar_datos', 'enviar_email_masivo', 'ejecutar_pago'];

if (requierenConfirmacion.includes(herramienta)) {
// Notifica al usuario (WebSocket, email, Slack...)
const aprobado = await esperarAprobacion({
herramienta,
args,
timeout: 60_000 // 1 minuto para aprobar
});

if (!aprobado) return { cancelado: true };
}

return await acciones[herramienta](args);
}