Saltar para o conteúdo
LangChain com Node.js: Construindo Agentes de IA
IA

LangChain com Node.js: Construindo Agentes de IA

5 de outubro de 2024·Paulo de Paula

LangChain.js conecta LLMs a fontes de dados externas e ferramentas. A diferença entre um chatbot e um agente é simples: um chatbot conversa, um agente age — executa código, consulta APIs, lê documentos, toma decisões.

Chain vs Agent

Chain:  Prompt → LLM → Output Parser (fluxo fixo, previsível)
Agent:  Prompt → LLM → Decide qual ferramenta usar → Executa → Observa → Repete

Use chains quando o fluxo é determinístico. Use agents quando o modelo precisa decidir o que fazer com base nos dados.

Chain simples com output parser

import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { z } from 'zod';
import { StructuredOutputParser } from 'langchain/output_parsers';

const model = new ChatOpenAI({
  model: 'gpt-4o-mini',
  temperature: 0,
});

// Chain de resumo
const summaryChain = ChatPromptTemplate.fromMessages([
  ['system', 'Você é um especialista em resumir textos técnicos em português.'],
  ['human', 'Resuma o seguinte texto em no máximo 3 bullet points:\n\n{text}'],
])
  .pipe(model)
  .pipe(new StringOutputParser());

const summary = await summaryChain.invoke({ text: longArticle });

// Chain com structured output via Zod
const classificationSchema = z.object({
  category: z.enum(['bug', 'feature', 'question', 'docs']),
  priority: z.enum(['low', 'medium', 'high', 'critical']),
  summary: z.string().max(100),
});

const classifyChain = ChatPromptTemplate.fromMessages([
  ['system', 'Classifique o issue do GitHub fornecido.'],
  ['human', '{issue}'],
])
  .pipe(model.withStructuredOutput(classificationSchema))

const result = await classifyChain.invoke({
  issue: 'App crashes when uploading files > 10MB'
});
// { category: 'bug', priority: 'high', summary: 'Crash ao fazer upload de arquivos grandes' }

Memory: mantendo o contexto da conversa

import { ConversationSummaryBufferMemory } from 'langchain/memory';
import { ConversationChain } from 'langchain/chains';

// Mantém as últimas N mensagens + resumo do histórico antigo
const memory = new ConversationSummaryBufferMemory({
  llm: model,
  maxTokenLimit: 2000,      // resume quando passar desse limite
  returnMessages: true,
});

const conversation = new ConversationChain({ llm: model, memory });

const r1 = await conversation.call({ input: 'Qual é a diferença entre let e const?' });
const r2 = await conversation.call({ input: 'E var?' });
const r3 = await conversation.call({ input: 'Qual dos três eu deveria usar?' });
// O modelo tem contexto de toda a conversa anterior

Ferramentas personalizadas

import { DynamicStructuredTool } from '@langchain/core/tools';
import { z } from 'zod';

const checkOrderStatusTool = new DynamicStructuredTool({
  name: 'check_order_status',
  description: 'Verifica o status de um pedido pelo número do pedido. Use quando o usuário perguntar sobre o status de um pedido.',
  schema: z.object({
    orderId: z.string().describe('Número do pedido no formato ORD-XXXXX'),
  }),
  func: async ({ orderId }) => {
    const order = await orderService.findById(orderId);
    if (!order) return `Pedido ${orderId} não encontrado.`;
    return JSON.stringify({
      id: order.id,
      status: order.status,
      estimatedDelivery: order.estimatedDelivery,
      lastUpdate: order.lastUpdate,
    });
  },
});

const searchProductsTool = new DynamicStructuredTool({
  name: 'search_products',
  description: 'Busca produtos no catálogo. Use para responder perguntas sobre disponibilidade e preços.',
  schema: z.object({
    query: z.string().describe('Termo de busca'),
    maxResults: z.number().optional().default(5),
  }),
  func: async ({ query, maxResults }) => {
    const products = await productService.search(query, maxResults);
    return JSON.stringify(products);
  },
});

Agente ReAct

import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { MemorySaver } from '@langchain/langgraph';

const agent = createReactAgent({
  llm: model,
  tools: [checkOrderStatusTool, searchProductsTool],
  // Persiste estado entre chamadas com o mesmo thread_id
  checkpointSaver: new MemorySaver(),
});

// O agente decide qual ferramenta usar (ou nenhuma)
const result = await agent.invoke(
  {
    messages: [{ role: 'user', content: 'Qual o status do pedido ORD-12345?' }],
  },
  { configurable: { thread_id: 'session-user-abc' } }
);

console.log(result.messages.at(-1).content);
// "Seu pedido ORD-12345 está em separação no estoque e tem previsão de entrega para 20/11."

Streaming de respostas

import { streamText } from 'ai'; // Vercel AI SDK integra bem com LangChain

// Com LangChain nativo
const stream = await model.stream([
  { role: 'user', content: 'Explique closure em JavaScript' },
]);

// Express + Server-Sent Events
app.get('/chat/stream', async (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const stream = await summaryChain.stream({ text: req.query.text });

  for await (const chunk of stream) {
    res.write(`data: ${JSON.stringify({ text: chunk })}\n\n`);
  }
  res.write('data: [DONE]\n\n');
  res.end();
});

Tracing com LangSmith

# .env
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=ls__...
LANGCHAIN_PROJECT=meu-agente-producao

Com LangSmith habilitado, cada invocação é rastreada automaticamente. Você consegue ver:

  • Qual prompt foi enviado ao modelo
  • Tokens utilizados e custo
  • Tempo de resposta de cada step
  • Saída de cada ferramenta
  • Onde uma chain falhou
// Trace customizado
import { traceable } from 'langsmith/traceable';

const processTicket = traceable(
  async (ticket: SupportTicket) => {
    const classification = await classifyChain.invoke({ issue: ticket.text });
    const response = await generateResponseChain.invoke({ ticket, classification });
    return { classification, response };
  },
  { name: 'process-support-ticket', tags: ['support', 'production'] }
);

O padrão mais eficiente em produção: chains para fluxos determinísticos (classificação, extração, resumo) e agents para fluxos que precisam de raciocínio e múltiplas ferramentas. Não use agents onde uma chain resolve — são mais lentos, mais caros e menos previsíveis.