Si trabajas con NestJS y quieres integrar un agente de IA que use herramientas, mantenga contexto y procese jobs asíncronos, este tutorial es para ti. No es teoría — es la arquitectura que usamos en SOM-OS para sistemas en producción.
Arquitectura
Client → NestJS Controller → BullMQ Queue → Worker
↓
LangChain Agent
↓
Tools (Zod-typed)
↓
PostgreSQL (history)
Redis (memory cache)
El flujo es simple: el usuario hace una petición, se encola en BullMQ, un worker procesa el job con un agente LangChain que tiene acceso a herramientas tipadas con Zod, y el historial se persiste en PostgreSQL.
1. Instalación
npm install @nestjs/bullmq bullmq ioredis
npm install @langchain/core @langchain/openai @langchain/community
npm install zod
2. Módulo NestJS
// ia-agent.module.ts
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bullmq';
import { IaAgentController } from './ia-agent.controller';
import { IaAgentService } from './ia-agent.service';
import { IaAgentProcessor } from './ia-agent.processor';
@Module({
imports: [
BullModule.registerQueue({
name: 'ia-agent',
defaultJobOptions: {
attempts: 3,
backoff: { type: 'exponential', delay: 2000 },
removeOnComplete: 100,
},
}),
],
controllers: [IaAgentController],
providers: [IaAgentService, IaAgentProcessor],
})
export class IaAgentModule {}
3. Controller
// ia-agent.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { IaAgentService } from './ia-agent.service';
@Controller('ia-agent')
export class IaAgentController {
constructor(private readonly iaAgentService: IaAgentService) {}
@Post('chat')
async chat(@Body() body: { message: string; sessionId: string }) {
const job = await this.iaAgentService.enqueue(body);
return { jobId: job.id, status: 'queued' };
}
}
4. Service + Queue
// ia-agent.service.ts
import { Injectable } from '@nestjs/common';
import { Queue } from 'bullmq';
import { InjectQueue } from '@nestjs/bullmq';
@Injectable()
export class IaAgentService {
constructor(
@InjectQueue('ia-agent') private readonly queue: Queue,
) {}
async enqueue(data: { message: string; sessionId: string }) {
return this.queue.add('process', data, {
delay: 0,
});
}
}
5. Worker con LangChain Agent
// ia-agent.processor.ts
import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Job } from 'bullmq';
import { ChatOpenAI } from '@langchain/openai';
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
const searchTool = tool(
async (input) => {
// Tu lógica de búsqueda aquí
return `Resultados para: ${input.query}`;
},
{
name: 'search',
description: 'Busca en la base de conocimiento',
schema: z.object({
query: z.string().describe('La consulta de búsqueda'),
}),
},
);
@Processor('ia-agent')
export class IaAgentProcessor extends WorkerHost {
private llm = new ChatOpenAI({
modelName: 'gpt-4o',
temperature: 0.3,
});
async process(job: Job<{ message: string; sessionId: string }>) {
const { message, sessionId } = job.data;
const prompt = ChatPromptTemplate.fromMessages([
['system', 'Eres un asistente útil. Usa las herramientas disponibles.'],
['placeholder', '{chat_history}'],
['human', '{input}'],
['placeholder', '{agent_scratchpad}'],
]);
const agent = await createToolCallingAgent({
llm: this.llm,
tools: [searchTool],
prompt,
});
const executor = new AgentExecutor({
agent,
tools: [searchTool],
});
const result = await executor.invoke({
input: message,
chat_history: [], // Cargar desde PostgreSQL por sessionId
});
// Persistir en PostgreSQL
// await this.saveHistory(sessionId, message, result.output);
return result;
}
}
6. Deploy con Docker
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/main.js"]
Lo que el tutorial rápido no te dice
-
Rate limits: OpenAI tiene límites. Usa BullMQ con
rateLimitpara no excederlos. - Context window: Si el historial crece, truncar. Los últimos 10 mensajes suelen bastar.
- Coste: GPT-4o a $5/M tokens. Un agente con 3 tools gasta ~2k tokens por call. Monitoriza.
- Errores: LangChain no siempre falla limpiamente. Wrappa todo en try/catch y loguea el trace completo.
Por qué NestJS y no Express
Para un agente en producción necesitas: DI para Swappable tools, guards para rate limiting, interceptors para logging, y módulos para organizar. Express te da una función — NestJS te da una arquitectura.
Escribí una comparativa más detallada en NestJS vs Express: cuándo elegir cada uno en 2026.
Si quieres ver cómo aplicamos esto en sistemas reales para negocios, échale un vistazo a SOM-OS — construimos sistemas operativos de negocio con IA en Mallorca.
¿Preguntas? Déjalas en los comentarios.
Top comments (0)