Agentes de IA en Producción
Un agente de IA es un sistema que usa un LLM para razonar, planificar y ejecutar acciones de forma autónoma hasta completar una tarea. A diferencia del function calling simple, los agentes mantienen un loop de razonamiento-acción que puede durar múltiples pasos.
Patrón ReAct: Reason + Act
Pregunta del usuario
│
▼
┌─ LOOP ──────────────────────┐
│ │
│ 1. REASON (pensar) │
│ ¿Qué necesito hacer? │
│ │
│ 2. ACT (actuar) │
│ Ejecutar una herramienta │
│ │
│ 3. OBSERVE (observar) │
│ Analizar el resultado │
│ │
│ 4. ¿Tengo la respuesta? │
│ SÍ → Responder │
│ NO → Volver a 1 │
│ │
└──────────────────────────────┘
Implementación del loop ReAct
interface AgentMessage {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string;
tool_call_id?: string;
}
class Agent {
private tools: Tool[];
private maxIterations: number;
constructor(tools: Tool[], maxIterations = 10) {
this.tools = tools;
this.maxIterations = maxIterations;
}
async run(task: string): Promise<string> {
const messages: AgentMessage[] = [
{ role: 'system', content: this.buildSystemPrompt() },
{ role: 'user', content: task },
];
for (let i = 0; i < this.maxIterations; i++) {
console.log(`\n--- Iteración ${i + 1} ---`);
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages,
tools: this.tools.map(t => t.definition),
tool_choice: 'auto',
});
const choice = response.choices[0];
// Si el agente decide responder directamente
if (choice.finish_reason === 'stop') {
console.log('✅ Agente completó la tarea');
return choice.message.content!;
}
// Ejecutar herramientas
if (choice.message.tool_calls) {
messages.push(choice.message);
for (const call of choice.message.tool_calls) {
const tool = this.tools.find(t => t.name === call.function.name);
if (!tool) {
messages.push({
role: 'tool',
tool_call_id: call.id,
content: `Error: tool "${call.function.name}" not found`,
});
continue;
}
console.log(`🔧 ${call.function.name}(${call.function.arguments})`);
try {
const result = await tool.execute(JSON.parse(call.function.arguments));
console.log(` → ${JSON.stringify(result).slice(0, 200)}`);
messages.push({
role: 'tool',
tool_call_id: call.id,
content: JSON.stringify(result),
});
} catch (error) {
messages.push({
role: 'tool',
tool_call_id: call.id,
content: `Error: ${(error as Error).message}`,
});
}
}
}
}
return 'El agente alcanzó el límite de iteraciones sin completar la tarea.';
}
private buildSystemPrompt(): string {
return `Eres un agente de IA capaz de completar tareas complejas.
INSTRUCCIONES:
- Analiza la tarea paso a paso
- Usa las herramientas disponibles para obtener información y ejecutar acciones
- Si una herramienta falla, intenta otra estrategia
- Cuando tengas toda la información necesaria, genera tu respuesta final
- Sé preciso y verifica tus resultados`;
}
}
Memoria del Agente
Memoria a corto plazo (conversación)
class AgentMemory {
private shortTerm: Message[] = [];
private summaries: string[] = [];
private maxMessages = 20;
add(message: Message) {
this.shortTerm.push(message);
// Cuando hay demasiados mensajes, resumir los antiguos
if (this.shortTerm.length > this.maxMessages) {
this.summarizeOldMessages();
}
}
private async summarizeOldMessages() {
const oldMessages = this.shortTerm.splice(0, 10);
const summary = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: 'Resumen conciso de esta conversación en 3-5 puntos clave:',
},
...oldMessages,
],
max_tokens: 200,
});
this.summaries.push(summary.choices[0].message.content!);
}
getContext(): string {
let context = '';
if (this.summaries.length > 0) {
context += `RESUMEN PREVIO:\n${this.summaries.join('\n')}\n\n`;
}
return context;
}
}
Memoria a largo plazo (vector store)
class LongTermMemory {
async store(content: string, metadata: Record<string, any>) {
const embedding = await getEmbedding(content);
await vectorDB.insert({
content,
embedding,
metadata: { ...metadata, timestamp: Date.now() },
});
}
async recall(query: string, limit = 5): Promise<string[]> {
const results = await vectorDB.search(
await getEmbedding(query),
limit
);
return results.map(r => r.content);
}
}
Agente con herramientas prácticas
// Herramientas para un "Research Agent"
const researchTools: Tool[] = [
{
name: 'web_search',
definition: {
type: 'function',
function: {
name: 'web_search',
description: 'Busca información en internet',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Término de búsqueda' },
},
required: ['query'],
},
},
},
execute: async ({ query }) => {
const results = await searchAPI.search(query);
return results.slice(0, 5).map(r => ({
title: r.title,
snippet: r.snippet,
url: r.url,
}));
},
},
{
name: 'read_webpage',
definition: {
type: 'function',
function: {
name: 'read_webpage',
description: 'Lee el contenido de una página web',
parameters: {
type: 'object',
properties: {
url: { type: 'string', description: 'URL de la página' },
},
required: ['url'],
},
},
},
execute: async ({ url }) => {
const response = await fetch(url);
const html = await response.text();
return extractText(html).slice(0, 3000);
},
},
{
name: 'save_note',
definition: {
type: 'function',
function: {
name: 'save_note',
description: 'Guarda una nota con información relevante',
parameters: {
type: 'object',
properties: {
title: { type: 'string' },
content: { type: 'string' },
},
required: ['title', 'content'],
},
},
},
execute: async ({ title, content }) => {
await prisma.note.create({ data: { title, content } });
return { saved: true };
},
},
];
// Uso
const researcher = new Agent(researchTools, 15);
const result = await researcher.run(
'Investiga las últimas novedades de React 19 y crea un resumen con las 5 features más importantes'
);
Orquestación multi-agente
class MultiAgentOrchestrator {
private agents: Map<string, Agent>;
constructor() {
this.agents = new Map([
['researcher', new Agent(researchTools)],
['coder', new Agent(codeTools)],
['reviewer', new Agent(reviewTools)],
]);
}
async execute(task: string): Promise<string> {
// Paso 1: Router decide qué agente usar
const routerResponse = await openai.chat.completions.create({
model: 'gpt-4o-mini',
response_format: { type: 'json_object' },
messages: [{
role: 'user',
content: `Dado esta tarea: "${task}"
Decide qué agentes necesitan ejecutarse y en qué orden.
Agentes disponibles: researcher, coder, reviewer.
Responde JSON: { "pipeline": ["agent1", "agent2"], "subtasks": ["subtask1", "subtask2"] }`,
}],
});
const plan = JSON.parse(routerResponse.choices[0].message.content!);
let context = '';
// Paso 2: Ejecutar pipeline
for (let i = 0; i < plan.pipeline.length; i++) {
const agentName = plan.pipeline[i];
const subtask = plan.subtasks[i];
const agent = this.agents.get(agentName)!;
console.log(`\n🤖 Agente: ${agentName} — Tarea: ${subtask}`);
const result = await agent.run(
`${subtask}\n\nContexto previo:\n${context}`
);
context += `\n\nResultado de ${agentName}:\n${result}`;
}
return context;
}
}
Guardrails para agentes
class SafeAgent extends Agent {
private budget: { maxTokens: number; maxCalls: number; maxTime: number };
private usage = { tokens: 0, calls: 0, startTime: 0 };
constructor(tools: Tool[], budget = {
maxTokens: 50_000,
maxCalls: 20,
maxTime: 60_000, // 1 minuto
}) {
super(tools);
this.budget = budget;
}
async run(task: string): Promise<string> {
this.usage.startTime = Date.now();
// Override del loop con checks de budget
// ...
return super.run(task);
}
private checkBudget(): void {
if (this.usage.tokens > this.budget.maxTokens) {
throw new Error(`Token budget exceeded: ${this.usage.tokens}/${this.budget.maxTokens}`);
}
if (this.usage.calls > this.budget.maxCalls) {
throw new Error(`Call budget exceeded: ${this.usage.calls}/${this.budget.maxCalls}`);
}
if (Date.now() - this.usage.startTime > this.budget.maxTime) {
throw new Error(`Time budget exceeded`);
}
}
}
Patterns y anti-patterns
| Pattern |
Descripción |
| ReAct |
Reason → Act → Observe en loop |
| Plan and Execute |
Planificar todos los pasos primero, luego ejecutar |
| Reflexion |
El agente evalúa sus propias respuestas y mejora |
| Human-in-the-loop |
Pedir confirmación en acciones destructivas |
| Anti-pattern |
Problema |
| Sin límite de iteraciones |
El agente puede loopear infinitamente |
| Sin manejo de errores |
Un fallo en una tool rompe todo |
| Demasiadas tools |
El LLM se confunde; máximo 10-15 |
| Sin observabilidad |
No puedes debuggear qué hizo el agente |