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:
- Pensamiento (Thought): El modelo describe lo que cree que está pasando y qué paso debe dar a continuación.
- Acción (Action): El modelo decide llamar a una herramienta específica con ciertos parámetros.
- Observación (Observation): El sistema ejecuta la herramienta y le devuelve el resultado al modelo.
- 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);
}