Arquitectura de Agentes de IA con Memoria Persistente y Tool Calling en Producción: LangChain, PostgreSQL y Next.js 16

"Un modelo de lenguaje sin herramientas es solo un generador probabilístico de texto; un agente con llamadas a herramientas seguras y memoria estructurada es un colaborador autónomo de negocio."
En la ingeniería de software actual, el verdadero salto productivo de la Inteligencia Artificial no radica en desplegar interfaces de chat cosméticas, sino en construir agentes capaces de razonar, consultar bases de datos corporativas, ejecutar mutaciones transaccionales y recordar el historial de interacciones de los usuarios a lo largo del tiempo.
Cuando llevamos agentes autónomos a entornos de producción empresariales (SaaS, CRMs, plataformas B2B), dos requisitos se vuelven no negociables:
- Llamadas a Herramientas Tipadas y Validadas (Structured Tool Calling): Prevenir alucinaciones de parámetros y asegurar que cada acción cumpla contratos rígidos validados con esquemas Zod.
- Memoria Conversacional y Episódica Persistente: Evitar que el contexto se pierda entre sesiones o que el consumo de tokens crezca sin límite, almacenando resúmenes y mensajes en PostgreSQL con políticas RLS.
En este artículo detallo la arquitectura de grado industrial que aplicamos combinando LangChain / LangGraph en TypeScript, PostgreSQL como almacén de memoria y Next.js 16 App Router con streaming reactivo.
1. Topología de la Arquitectura de Agentes Autónomos#
El diseño separa tajantemente la capa de presentación, el bucle cognitivo de ejecución y los adaptadores de datos:
[Usuario / Cliente Web / WhatsApp]
│
▼
[Next.js 16 App Router: API Route / Server Action]
├── Validación de Sesión y Rate Limiting
├── Carga de Historial Paginado desde PostgreSQL
└── Invocación del Agente con Streaming (AI SDK / LangChain)
│
▼
┌──────────────────────────────────────────────┐
│ Bucle Cognitivo (ReAct Agent) │
│ 1. Inferencia del LLM (Razonamiento) │
│ 2. Emisión de Tool Call con argumentos JSON │
│ 3. Validación de esquema con Zod │
│ 4. Ejecución del Adaptador de Negocio │
│ 5. Inyección de resultado al contexto │
└───────────────┬──────────────────────────────┘
│
┌─────────┴─────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ PostgreSQL Core │ │ Servicios y APIs │
│ (Memoria / RLS) │ │ (SUNAT, CRM, ERP)│
└──────────────────┘ └──────────────────┘
2. Definición de Herramientas Tipadas con Zod y LangChain#
Un riesgo grave al otorgar capacidades de acción a un LLM es permitir llamadas con tipos incompatibles o inyecciones de código. Para neutralizarlo, cada herramienta se define mediante esquemas Zod estrictos:
// src/lib/ai/tools/invoice-tool.ts
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
import { emitirComprobanteSunat } from '@/services/sunat.service';
export const createInvoiceTool = tool(
async ({ customerTaxId, customerName, items, currency }) => {
try {
const result = await emitirComprobanteSunat({
rucOCliente: customerTaxId,
razonSocial: customerName,
items,
moneda: currency,
});
return JSON.stringify({
status: 'SUCCESS',
invoiceNumber: result.serieNumero,
pdfUrl: result.enlacePdf,
totalAmount: result.montoTotal,
});
} catch (err: unknown) {
const errorMsg = err instanceof Error ? err.message : 'Error desconocido';
return JSON.stringify({ status: 'ERROR', error: errorMsg });
}
},
{
name: 'emitir_factura_electronica',
description: 'Emite una factura electrónica oficial con valor tributario en Perú según normativa de SUNAT.',
schema: z.object({
customerTaxId: z.string().length(11, 'El RUC debe tener exactamente 11 dígitos numéricos'),
customerName: z.string().min(3, 'Razón social requerida'),
items: z.array(
z.object({
description: z.string().min(1),
quantity: z.number().positive(),
unitPrice: z.number().positive(),
})
).min(1, 'Debe incluir al menos un ítem'),
currency: z.enum(['PEN', 'USD']).default('PEN'),
}),
}
);
3. Persistencia de Memoria Conversacional en PostgreSQL#
Guardar el historial íntegro de tokens en memoria RAM de un proceso Node.js es inviable en clústeres serverless o multi-instancia. Diseñamos una tabla relacional en PostgreSQL que almacena los turnos de diálogo, con una estrategia de ventana móvil y compactación de memoria:
-- Tabla de sesiones y memoria de agentes
CREATE TABLE public.ai_agent_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
agent_type TEXT NOT NULL,
session_summary TEXT DEFAULT '',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE public.ai_agent_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID REFERENCES public.ai_agent_sessions(id) ON DELETE CASCADE,
role TEXT NOT NULL CHECK (role IN ('system', 'user', 'assistant', 'tool')),
content TEXT NOT NULL,
tool_calls JSONB DEFAULT NULL,
tool_call_id TEXT DEFAULT NULL,
token_count INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Índice compuesto para recuperación cronológica ultrarrápida
CREATE INDEX idx_agent_messages_session_time
ON public.ai_agent_messages (session_id, created_at ASC);
Cuando una conversación supera los 15 mensajes, un worker en segundo plano genera un resumen semántico conciso (session_summary) y archiva los mensajes antiguos, manteniendo la latencia y la factura de tokens acotadas.
4. Orquestación y Streaming Reactivo en Next.js 16#
En Next.js 16, aprovechamos el streaming nativo de respuestas para ofrecer al usuario retroalimentación instantánea mientras el agente razona o ejecuta herramientas:
// src/app/api/agent/chat/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ChatOpenAI } from '@langchain/openai';
import { createInvoiceTool } from '@/lib/ai/tools/invoice-tool';
import { loadSessionHistory, persistMessage } from '@/services/chat-history.service';
export async function POST(req: NextRequest) {
const { sessionId, message } = await req.json();
// 1. Cargar historial persistente desde PostgreSQL
const history = await loadSessionHistory(sessionId);
await persistMessage(sessionId, 'user', message);
// 2. Instanciar LLM con Tool Calling vinculado
const model = new ChatOpenAI({
modelName: 'gpt-4o',
temperature: 0.1,
streaming: true,
}).bindTools([createInvoiceTool]);
// 3. Crear pipeline con LangGraph o bucle ejecutor
const responseStream = await model.stream([...history, { role: 'user', content: message }]);
// 4. Retornar ReadableStream con formato SSE al frontend
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of responseStream) {
if (chunk.content) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text: chunk.content })}\n\n`));
}
}
controller.close();
},
});
return new NextResponse(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
Conclusiones e Impacto en Negocio#
Adoptar un patrón riguroso de Tool Calling con tipado Zod y persistencia de memoria en PostgreSQL transforma prototipos inestables en sistemas fiables de automatización. Las empresas logran operar flujos críticos de facturación, soporte técnico y sincronización de inventarios con control presupuestario total.
Si necesitas estructurar una plataforma de software a medida o integrar agentes de IA autónomos en tu infraestructura empresarial, revisa nuestros casos de éxito o agenda una consultoría técnica directa.
¿Te gustaría profundizar en estos temas?
Aprende sobre desarrollo de software, apps a medida, automatizaciones con N8N, Next.js y Cloud con casos reales.


