DEV Community

Cover image for Cómo construir un agente de IA con LangChain.js y NestJS: tutorial completo
Adrián Colom
Adrián Colom

Posted on

Cómo construir un agente de IA con LangChain.js y NestJS: tutorial completo

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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' };
  }
}
Enter fullscreen mode Exit fullscreen mode

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,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

Lo que el tutorial rápido no te dice

  1. Rate limits: OpenAI tiene límites. Usa BullMQ con rateLimit para no excederlos.
  2. Context window: Si el historial crece, truncar. Los últimos 10 mensajes suelen bastar.
  3. Coste: GPT-4o a $5/M tokens. Un agente con 3 tools gasta ~2k tokens por call. Monitoriza.
  4. 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)