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 → RepeteUse 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-producaoCom 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.