Começando com LangChain.js: Construindo Aplicações Práticas de LLM
Por Jake Morrison LangChain.js é uma dessas ferramentas. Ele oferece uma maneira estruturada de criar aplicações alimentadas por grandes modelos de linguagem (LLMs) usando JavaScript ou TypeScript. Se você está trabalhando com LLMs no ecossistema JavaScript, entender LangChain.js é essencial. Este guia irá levá-lo através de etapas práticas e concretas para começar e construir aplicações reais.
O que é LangChain.js e por que usá-lo?
LangChain.js é uma biblioteca projetada para ajudar desenvolvedores a criar aplicações alimentadas por LLMs. Ele não substitui os próprios LLMs, mas oferece uma camada de abstração e um conjunto de ferramentas para encadear diferentes chamadas LLM, integrar outras fontes de dados e gerenciar interações complexas.
Pense da seguinte maneira: chamar diretamente uma API LLM é como usar uma consulta de banco de dados bruta. LangChain.js fornece um ORM (Object-Relational Mapper) para LLMs. Ele gerencia modelos comuns como:
* **Cadeias:** Combinação de várias chamadas LLM ou outras etapas em uma sequência.
* **Prompts:** Gerenciamento e formatação de entradas para os LLMs.
* **Agentes:** Permitir que os LLMs tomem decisões e usem ferramentas.
* **Recuperação:** Integração de dados externos (seus documentos, bancos de dados) com os LLMs.
* **Memória:** Dar aos LLMs um meio de se lembrar de interações passadas.
A principal vantagem de usar LangChain.js é o aumento da produtividade e da manutenibilidade. Você escreve menos código repetitivo, a lógica da sua aplicação se torna mais clara e é mais fácil mudar de fornecedor LLM ou adicionar novas funcionalidades. Para quem constrói com LLMs em JavaScript, LangChain.js oferece vantagens significativas.
Configurando seu projeto LangChain.js
Antes de escrever código, vamos preparar nosso ambiente. Você precisará ter o Node.js instalado.
1. **Crie um novo diretório de projeto:**
`
mkdir langchain-js-project
cd langchain-js-project
`
2. **Inicialize um projeto Node.js:**
`
npm init -y
`
3. **Instale LangChain.js e um fornecedor LLM:**
Começaremos com OpenAI para nossos exemplos, mas LangChain.js suporta muitos outros (Hugging Face, Google, etc.).
`
npm install langchain @openai/openai
`
Se você preferir TypeScript:
`
npm install langchain @openai/openai typescript @types/node ts-node
npx tsc –init
`
Em seguida, atualize `tsconfig.json` para incluir `“moduleResolution”: “node”` e `“esModuleInterop”: true`.
4. **Configure sua chave API:**
Você precisará de uma chave API OpenAI. Nunca codifique as chaves API diretamente em seu código. Use variáveis de ambiente.
Crie um arquivo `.env` na raiz do seu projeto:
`
OPENAI_API_KEY=YOUR_OPENAI_API_KEY
`
Para carregar as variáveis de ambiente, instale `dotenv`:
`
npm install dotenv
`
Em seguida, no topo do seu arquivo de script principal (por exemplo, `index.js` ou `src/index.ts`):
`
require(‘dotenv’).config();
`
ou para TypeScript/ESM:
`
import ‘dotenv/config’;
`
Agora, você está pronto para começar a construir com LangChain.js.
Seu primeiro chamado LLM com LangChain.js
Vamos fazer uma chamada simples a um LLM.
`
// index.js
require(‘dotenv’).config();
const { OpenAI } = require(‘langchain/llms/openai’);
async function simpleLLMCall() {
const model = new OpenAI({ temperature: 0.7 }); // a temperatura controla o caráter aleatório
const prompt = “Qual é a capital da França?”;
const result = await model.call(prompt);
console.log(result);
}
simpleLLMCall();
`
Para executar isso: `node index.js`.
Você deve ver “Paris.” ou uma saída similar. Esta é a interação mais básica. LangChain.js encapsula a chamada API OpenAI, tornando-a consistente com outros fornecedores LLM.
Trabalhando com prompts: Modelos e variáveis
Incorporar prompts diretamente no seu código pode se tornar confuso. LangChain.js fornece `PromptTemplate` para gerenciar os prompts de maneira eficaz.
`
// promptExample.js
require(‘dotenv’).config();
const { OpenAI } = require(‘langchain/llms/openai’);
const { PromptTemplate } = require(‘langchain/prompts’);
async function templatedPrompt() {
const model = new OpenAI({ temperature: 0.7 });
const template = “Qual é um bom nome para uma empresa que fabrica {product}?”;
const prompt = new PromptTemplate({
template: template,
inputVariables: [“product”],
});
const formattedPrompt = await prompt.format({ product: “meias coloridas” });
console.log(“Prompt formatado:”, formattedPrompt);
const result = await model.call(formattedPrompt);
console.log(“Resultado LLM:”, result);
}
templatedPrompt();
`
Execute isso com `node promptExample.js`.
Isso demonstra como `PromptTemplate` permite que você defina prompts com espaços reservados (`{product}`) que você pode preencher dinamicamente. Isso é essencial para construir aplicações flexíveis.
Cadeias: Conectando chamadas LLM e lógica
As cadeias são onde LangChain.js realmente brilha. Elas permitem que você combine várias etapas, incluindo chamadas LLM, em um único fluxo de trabalho coerente.
Cadeia LLM simples
O `LLMChain` é a cadeia mais básica. Ele aceita um `PromptTemplate` e um LLM, formata o prompt e o passa para o LLM.
`
// llmChainExample.js
require(‘dotenv’).config();
const { OpenAI } = require(‘langchain/llms/openai’);
const { PromptTemplate } = require(‘langchain/prompts’);
const { LLMChain } = require(‘langchain/chains’);
async function simpleLLMChain() {
const model = new OpenAI({ temperature: 0.7 });
const template = “Qual é a melhor {activity} para fazer em {city}?”;
const prompt = new PromptTemplate({
template: template,
inputVariables: [“activity”, “city”],
});
const chain = new LLMChain({ llm: model, prompt: prompt });
const result = await chain.call({ activity: “comida”, city: “Roma” });
console.log(“Resultado da Cadeia:”, result);
// O objeto resultado conterá uma propriedade ‘text’ com a saída do LLM.
}
simpleLLMChain();
`
Execute com `node llmChainExample.js`.
Note como `chain.call()` aceita um objeto com as variáveis de entrada e ele gerencia a formatação do prompt e a chamada ao LLM. É uma maneira mais limpa de interagir com seu LLM.
Cadeias sequenciais: Fluxo de trabalho em várias etapas
Às vezes, você precisa realizar várias chamadas LLM em sequência, onde a saída de uma chamada se torna a entrada para a próxima. É aí que `SimpleSequentialChain` é útil.
`
// sequentialChainExample.js
require(‘dotenv’).config();
const { OpenAI } = require(‘langchain/llms/openai’);
const { PromptTemplate } = require(‘langchain/prompts’);
const { LLMChain, SimpleSequentialChain } = require(‘langchain/chains’);
async function multiStepChain() {
const model = new OpenAI({ temperature: 0.7 });
// Cadeia 1: Gerar um nome de empresa
const nameTemplate = “Qual é um bom nome para uma empresa que fabrica {product}?”;
const namePrompt = new PromptTemplate({
template: nameTemplate,
inputVariables: [“product”],
});
const nameChain = new LLMChain({ llm: model, prompt: namePrompt });
// Cadeia 2: Descrever a empresa
const descriptionTemplate = “Escreva um slogan marketing curto e envolvente para uma empresa chamada {companyName}.”;
const descriptionPrompt = new PromptTemplate({
template: descriptionTemplate,
inputVariables: [“companyName”],
});
const descriptionChain = new LLMChain({ llm: model, prompt: descriptionPrompt });
// Combinar os dois em uma cadeia sequencial
const overallChain = new SimpleSequentialChain({
chains: [nameChain, descriptionChain],
verbose: true, // Definido como true para ver as etapas intermediárias
});
const result = await overallChain.run(“sapatos ecológicos”);
console.log(“Resultado Global:”, result);
}
multiStepChain();
`
Execute com `node sequentialChainExample.js`.
O `SimpleSequentialChain` pega a saída da `nameChain` e a utiliza automaticamente como entrada (`companyName`) para a `descriptionChain`. Esse modelo é extremamente poderoso para decompor tarefas complexas. LangChain.js facilita isso.
Recuperação Aumentada por Geração (RAG): Usando seus próprios dados
Os LLMs são poderosos, mas seu conhecimento é limitado aos dados de treinamento. Para aplicações que necessitam de informações atuais ou conhecimentos específicos de um domínio (os documentos da sua empresa, por exemplo), você deve aumentar o conhecimento do LLM com dados externos. Isso é chamado de Recuperação Aumentada por Geração (RAG).
A ideia principal do RAG é:
1. **Carregar os dados:** Obtenha seus documentos (PDF, arquivos de texto, páginas da web).
2. **Dividir os dados:** Divida grandes documentos em partes menores e gerenciáveis.
3. **Integrar os dados:** Converta essas partes em representações numéricas (integrações) usando um modelo de integração.
4. **Armazenar os dados:** Armazene essas integrações em uma base de dados vetorial.
5. **Recuperar:** Quando um usuário faz uma pergunta, integre a pergunta, busque na base de dados vetorial partes de documentos semelhantes.
6. **Aumentar o prompt:** Adicione essas partes recuperadas ao prompt do LLM.
7. **Gerar:** O LLM usa esse prompt aumentado para gerar uma resposta mais informada.
LangChain.js fornece componentes para cada uma dessas etapas.
Etapa 1: Carregamento dos documentos
LangChain.js possui vários carregadores de documentos. Vamos usar um `TextLoader`.
“`javascript
// ragExample.js – Parte 1: Carregamento dos documentos
require(‘dotenv’).config();
const { TextLoader } = require(‘langchain/document_loaders/fs/text’);
async function loadDocuments() {
// Crie um arquivo de texto fictício para demonstração
const fs = require(‘fs’);
fs.writeFileSync(‘my_document.txt’, `
O rápido lobo marrom salta sobre o cão preguiçoso.
Este documento fala sobre vários animais e seus comportamentos.
Os cães são conhecidos por sua lealdade e ludicidade.
Os lobos são criaturas astutas e solitárias.
Os gatos adoram dormir e caçar ratos.
`);
const loader = new TextLoader(“my_document.txt”);
const docs = await loader.load();
console.log(“Documentos carregados:”, docs);
// Cada doc terá pageContent e metadata
}
loadDocuments();
“`
Etapa 2: Divisão dos documentos
Grandes documentos devem ser divididos em partes menores para que se encaixem na janela de contexto do LLM e para melhorar a relevância das recuperações.
“`javascript
// ragExample.js – Parte 2: Divisão
require(‘dotenv’).config();
const { TextLoader } = require(‘langchain/document_loaders/fs/text’);
const { RecursiveCharacterTextSplitter } = require(‘langchain/text_splitter’);
async function splitDocuments() {
// (Suponha que my_document.txt já tenha sido criado a partir da Parte 1)
const loader = new TextLoader(“my_document.txt”);
const docs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 100, // Máx caracteres por parte
chunkOverlap: 20, // Sobreposição entre as partes para manter o contexto
});
const splitDocs = await splitter.splitDocuments(docs);
console.log(“Documentos divididos:”, splitDocs);
}
splitDocuments();
“`
Etapa 3 & 4: Integração e armazenamento dos documentos (armazenamento vetorial)
É aqui que as integrações e as bases de dados vetoriais entram em cena. Vamos usar integrações da OpenAI e `HNSWLib` (um armazenamento vetorial local em memória) para simplicidade. Em produção, você deve usar um banco de dados vetorial dedicado como Pinecone, Chroma, Weaviate, etc.
“`javascript
// ragExample.js – Parte 3: Integrações e armazenamento vetorial
require(‘dotenv’).config();
const { OpenAIEmbeddings } = require(‘langchain/embeddings/openai’);
const { HNSWLib } = require(‘langchain/vectorstores/hnswlib’);
const { RecursiveCharacterTextSplitter } = require(‘langchain/text_splitter’);
const { TextLoader } = require(‘langchain/document_loaders/fs/text’);
const { OpenAI } = require(‘langchain/llms/openai’);
const { RetrievalQAChain } = require(‘langchain/chains’);
async function ragApplication() {
// 1. Carregar e dividir os documentos
const fs = require(‘fs’);
fs.writeFileSync(‘my_document.txt’, `
O rápido lobo marrom salta sobre o cão preguiçoso.
Este documento fala sobre vários animais e seus comportamentos.
Os cães são conhecidos por sua lealdade e ludicidade.
Os lobos são criaturas astutas e solitárias.
Os gatos adoram dormir e caçar ratos.
Um grupo de gatos é chamado de clowder.
`);
const loader = new TextLoader(“my_document.txt”);
const rawDocs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 100,
chunkOverlap: 20,
});
const docs = await splitter.splitDocuments(rawDocs);
// 2. Criar integrações e armazenar no armazenamento vetorial
const embeddings = new OpenAIEmbeddings();
const vectorStore = await HNSWLib.fromDocuments(docs, embeddings);
// 3. Criar um recuperador
const retriever = vectorStore.asRetriever();
// 4. Criar a cadeia QA de recuperação
const model = new OpenAI({ temperature: 0 });
const chain = RetrievalQAChain.fromLLM(model, retriever);
// 5. Fazer uma pergunta
const question = “Como se chama um grupo de gatos?”;
const result = await chain.call({ query: question });
console.log(“Resposta RAG:”, result.text);
const anotherQuestion = “Fale-me sobre lobos.”;
const anotherResult = await chain.call({ query: anotherQuestion });
console.log(“Outra resposta RAG:”, anotherResult.text);
}
ragApplication();
“`
Execute com `node ragExample.js`.
Este exemplo reúne todas as peças do RAG. A `RetrievalQAChain` abstrai o processo de recuperação dos documentos relevantes e de sua utilização para responder a uma pergunta. É uma maneira conveniente de criar chatbots ou sistemas de perguntas e respostas sobre seus dados específicos usando LangChain.js.
Agentes: LLM usando ferramentas
Os agentes permitem que os LLMs tomem decisões sobre quais ferramentas usar e em que ordem, com base nas entradas do usuário. Isso possibilita interações mais dinâmicas e complexas. As ferramentas podem ser qualquer coisa: um motor de busca, uma calculadora, uma chamada API para seus sistemas internos, ou até mesmo outra cadeia LLM.
Vamos criar um agente simples que pode realizar cálculos usando uma ferramenta `Calculator`.
“`javascript
// agentExample.js
require(‘dotenv’).config();
const { OpenAI } = require(‘langchain/llms/openai’);
const { initializeAgentExecutorWithOptions } = require(‘langchain/agents’);
const { Calculator } = require(‘langchain/tools/calculator’);
async function runAgent() {
const model = new OpenAI({ temperature: 0 });
const tools = [new Calculator()];
// Inicializar o executor de agente
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: “openai-functions”, // Usar a função de chamada de funcionalidades da OpenAI
verbose: true, // Ver o processo de pensamento do agente
});
console.log(“Chamando o agente com uma pergunta matemática simples…”);
const result1 = await executor.call({ input: “Qual é 123 mais 456?” });
console.log(“Resultado do agente 1:”, result1.output);
console.log(“\nChamando o agente com uma pergunta mais complexa…”);
const result2 = await executor.call({ input: “Qual é a raiz quadrada de 625 multiplicada por 3?” });
console.log(“Resultado do agente 2:”, result2.output);
}
runAgent();
“`
Execute com `node agentExample.js`.
Com `verbose: true`, você verá os “pensamentos” do agente: ele identifica que um cálculo é necessário, usa a ferramenta `Calculator`, e depois fornece a resposta. É um modelo poderoso para construir aplicações que vão além da simples geração de texto. LangChain.js simplifica a configuração desses agentes.
Memória: Manter o contexto da conversa
Por padrão, os LLMs são sem estado. Cada chamada API é independente. Para aplicações de conversa, o LLM deve se lembrar das interações passadas. LangChain.js oferece diferentes tipos de memória para isso.
`ConversationBufferMemory` é uma escolha comum, armazenando o histórico bruto das conversas.
“`javascript
// memoryExample.js
require(‘dotenv’).config();
const { OpenAI } = require(‘langchain/llms/openai’);
const { ConversationChain } = require(‘langchain/chains’);
const { ConversationBufferMemory } = require(‘langchain/memory’);
async function conversationalApp() {
const model = new OpenAI({ temperature: 0.7 });
const memory = new ConversationBufferMemory();
const chain = new ConversationChain({ llm: model, memory: memory });
console.log(“Iniciando a conversa…”);
let result1 = await chain.call({ input: “Oi, meu nome é Jake.” });
console.log(“Usuário : Oi, meu nome é Jake.”);
console.log(“IA :”, result1.response);
let result2 = await chain.call({ input: “Qual é o meu nome?” });
console.log(“Usuário : Qual é o meu nome?”);
console.log(“IA :”, result2.response);
let result3 = await chain.call({ input: “Conte-me um fato interessante sobre JavaScript.” });
console.log(“Usuário : Conte-me um fato interessante sobre JavaScript.”);
console.log(“IA :”, result3.response);
}
conversationalApp();
“`
Execute com `node memoryExample.js`.
`ConversationBufferMemory` mantém o histórico das conversas, permitindo que o LLM responda corretamente à pergunta “Qual é o meu nome?” com base na rodada anterior. O LangChain.js oferece outros tipos de memória para casos de uso mais avançados, como resumo de conversas ou armazenamento apenas de partes específicas.
Dicas Práticas para o Desenvolvimento em LangChain.js
1. **Comece Simples:** Não tente construir um agente complexo com várias ferramentas e memórias imediatamente. Comece com chamadas LLM simples e `LLMChain`.
2. **Use `verbose: true`:** Ao depurar cadeias ou agentes, definir `verbose: true` é valioso. Isso mostra as etapas intermediárias, os prompts enviados e as respostas recebidas, ajudando você a entender por que sua cadeia pode não estar se comportando como esperado.
3. **Gerencie as Chaves API de Forma Segura:** Sempre use variáveis de ambiente para suas chaves API. Nunca as coloque no controle de versão.
4. **Entenda as Janelas de Contexto:** Preste atenção aos limites de tokens do seu LLM escolhido. Prompts longos, um histórico de conversa extenso ou muitos documentos recuperados podem rapidamente ultrapassar esses limites. O LangChain.js tem ferramentas para gerenciar isso, mas é importante estar ciente.
5. **Experimente com `temperature`:** O parâmetro `temperature` controla o caráter aleatório da saída do LLM. Valores mais altos (por exemplo, 0.7-1.0) resultam em respostas mais criativas e menos deterministas. Valores mais baixos (por exemplo, 0.0-0.3) são mais factuais e consistentes. Ajuste conforme as necessidades da sua aplicação.
6. **Explore a Documentação:** A documentação do LangChain.js é completa. Se você está procurando um carregador, uma cadeia ou uma ferramenta específica, é bem provável que esteja coberta.
7. **Considere Armazenamento de Vetores em Produção:** Para aplicações RAG em condições reais, substitua `HNSWLib` por um banco de dados vetorial persistente como Pinecone, Chroma ou Weaviate. Isso permitirá que você escale e armazene grandes quantidades de dados.
8. **Gerenciamento de Erros:** Implemente um gerenciamento de erros sólido para chamadas API, especialmente em ambientes de produção. Problemas de rede, limites de taxa ou entradas inválidas podem causar falhas.
Perguntas Frequentes sobre LangChain.js
Q1: LangChain.js é apenas para modelos OpenAI?
Não, o LangChain.js suporta uma ampla gama de fornecedores de LLM, incluindo OpenAI, modelos da Hugging Face (via `HuggingFaceHub` ou `HuggingFaceInference`), Google (por exemplo, `GoogleGenerativeAI`), Anthropic e muitos outros. As abstrações básicas do LangChain.js permitem que você troque fornecedores de LLM com o mínimo de alterações de código.
Q2: Qual é a diferença entre `model.call()` e `chain.call()` ou `chain.run()`?
`model.call()` é a maneira mais direta de interagir com uma instância única de LLM, passando um prompt da cadeia bruta. `chain.call()` é usado para cadeias e aceita um objeto contendo variáveis de entrada (que são então formatadas por um `PromptTemplate` se usado na cadeia). `chain.run()` é um método de conveniência para cadeias que possuem apenas uma variável de entrada e retorna diretamente o texto de saída. Para cadeias mais complexas com várias entradas/saídas, `chain.call()` é preferido.
Q3: Como posso integrar LangChain.js com meu banco de dados ou API existente?
Você pode integrar o LangChain.js com seus sistemas existentes criando ferramentas personalizadas para os agentes. Uma ferramenta é essencialmente uma função que recebe uma string de entrada e retorna uma string de saída. Você pode definir uma ferramenta que consulta seu banco de dados, chama uma API interna ou realiza qualquer outra lógica personalizada, e depois disponibilizar essa ferramenta para seu agente LangChain.js. Para RAG, você pode criar implementações personalizadas de `DocumentLoader` para carregar dados de suas fontes específicas.
Q4: Quando devo usar LangChain.js em vez de chamar diretamente as APIs LLM?
Use LangChain.js quando precisar construir aplicações que envolvem mais do que um simples chamado isolado de LLM. Se sua aplicação requer:
* Raciocínio em várias etapas (cadeias).
* Integração de dados externos (RAG).
* Dar ao LLM acesso a ferramentas externas (agentes).
* Gerenciamento do histórico das conversas (memória).
* Troca fácil de fornecedores de LLM.
* Código mais estruturado e manutenível.
Se você está apenas fazendo um prompt único para testes, chamadas API diretas podem ser mais simples, mas para qualquer aplicação prática, o LangChain.js torna-se rapidamente indispensável.
Conclusão
LangChain.js é um framework poderoso para construir aplicações LLM sofisticadas em JavaScript e TypeScript. Ao entender seus componentes-chave—LLMs, Prompts, Chains, Retrieval, Agents e Memory—você pode construir sistemas inteligentes que vão além da simples geração de texto. Este guia forneceu um ponto de partida prático e acionável. Desde chamadas LLM simples até pipelines RAG complexos e agentes, o LangChain.js oferece as ferramentas para dar vida às suas ideias de automação de IA. Comece a experimentar, construa pequenos projetos e você descobrirá rapidamente o potencial do LangChain.js no seu fluxo de desenvolvimento.
🕒 Published: