Introduzione a LangChain.js: Costruire Applicazioni Pratiche con LLM
Di Jake Morrison LangChain.js è uno di questi strumenti. Fornisce un modo strutturato per costruire applicazioni alimentate da modelli di linguaggio di grandi dimensioni (LLM) utilizzando JavaScript o TypeScript. Se stai lavorando con LLM nell’ecosistema JavaScript, comprendere LangChain.js è un must. Questa guida ti guiderà attraverso passaggi pratici e concreti per iniziare e costruire applicazioni reali.
Che cos’è LangChain.js e perché usarlo?
LangChain.js è una libreria progettata per aiutare gli sviluppatori a creare applicazioni alimentate da LLM. Non sostituisce gli LLM stessi, ma piuttosto fornisce uno strato di astrazione e un set di strumenti per concatenare diverse chiamate a LLM, integrarsi con altre fonti di dati e gestire interazioni complesse.
Pensala in questo modo: chiamare direttamente un’API LLM è come usare una query di database grezza. LangChain.js fornisce un ORM (Object-Relational Mapper) per gli LLM. Gestisce schemi comuni come:
* **Catene:** Combinare più chiamate LLM o altri passaggi in una sequenza.
* **Prompt:** Gestire e formattare l’input per gli LLM.
* **Agenti:** Consentire agli LLM di prendere decisioni e utilizzare strumenti.
* **Recupero:** Integrare dati esterni (i tuoi documenti, database) con gli LLM.
* **Memoria:** Dare agli LLM un modo per ricordare interazioni passate.
Il principale vantaggio di utilizzare LangChain.js è un aumento della produttività e della manutenibilità. Scrivi meno codice boilerplate, la logica della tua applicazione diventa più chiara e risulta più facile cambiare fornitori di LLM o aggiungere nuove funzionalità. Per chiunque stia costruendo con LLM in JavaScript, LangChain.js offre vantaggi significativi.
Impostare il tuo progetto LangChain.js
Prima di scrivere codice, prepariamo il nostro ambiente. Avrai bisogno di Node.js installato.
1. **Crea una nuova directory di progetto:**
“`bash
mkdir langchain-js-project
cd langchain-js-project
“`
2. **Inizializza un progetto Node.js:**
“`bash
npm init -y
“`
3. **Installa LangChain.js e un fornitore di LLM:**
Inizieremo con OpenAI per i nostri esempi, ma LangChain.js supporta molti altri (Hugging Face, Google, ecc.).
“`bash
npm install langchain @openai/openai
“`
Se preferisci TypeScript:
“`bash
npm install langchain @openai/openai typescript @types/node ts-node
npx tsc –init
“`
Poi, aggiorna `tsconfig.json` per includere `”moduleResolution”: “node”` e `”esModuleInterop”: true`.
4. **Configura la tua chiave API:**
Avrai bisogno di una chiave API di OpenAI. Non hardcodare mai le chiavi API nel tuo codice. Usa variabili d’ambiente.
Crea un file `.env` nella radice del tuo progetto:
“`
OPENAI_API_KEY=YOUR_OPENAI_API_KEY
“`
Per caricare le variabili d’ambiente, installa `dotenv`:
“`bash
npm install dotenv
“`
Poi, in cima al tuo file di script principale (es., `index.js` o `src/index.ts`):
“`javascript
require(‘dotenv’).config();
“`
oppure per TypeScript/ESM:
“`typescript
import ‘dotenv/config’;
“`
Ora sei pronto per iniziare a costruire con LangChain.js.
La tua prima chiamata LLM con LangChain.js
Facciamo una chiamata semplice a un LLM.
“`javascript
// index.js
require(‘dotenv’).config();
const { OpenAI } = require(‘langchain/llms/openai’);
async function simpleLLMCall() {
const model = new OpenAI({ temperature: 0.7 }); // la temperatura controlla il grado di casualità
const prompt = “Qual è la capitale della Francia?”;
const result = await model.call(prompt);
console.log(result);
}
simpleLLMCall();
“`
Per eseguire questo: `node index.js`.
Vedrai “Parigi.” o output simile. Questa è l’interazione più basilare. LangChain.js avvolge la chiamata all’API OpenAI, rendendola coerente con altri fornitori di LLM.
Lavorare con i Prompt: Modelli e Variabili
Incorporare direttamente i prompt nel tuo codice può diventare disordinato. LangChain.js fornisce `PromptTemplate` per gestire i prompt in modo efficace.
“`javascript
// 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 è un buon nome per un’azienda che produce {product}?”;
const prompt = new PromptTemplate({
template: template,
inputVariables: [“product”],
});
const formattedPrompt = await prompt.format({ product: “calze colorate” });
console.log(“Prompt Formattato:”, formattedPrompt);
const result = await model.call(formattedPrompt);
console.log(“Risultato LLM:”, result);
}
templatedPrompt();
“`
Esegui questo con `node promptExample.js`.
Questo dimostra come `PromptTemplate` ti consente di definire prompt con segnaposto (`{product}`) che puoi riempire dinamicamente. Questo è cruciale per costruire applicazioni flessibili.
Catene: Collegare Chiamate e Logica LLM
Le catene sono dove LangChain.js brilla veramente. Ti permettono di combinare più passaggi, comprese le chiamate a LLM, in un flusso di lavoro coerente.
Catena LLM Semplice
La `LLMChain` è la catena più semplice. Prende un `PromptTemplate` e un LLM, poi formatta il prompt e lo passa all’LLM.
“`javascript
// 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 è la migliore {activity} da fare a {city}?”;
const prompt = new PromptTemplate({
template: template,
inputVariables: [“activity”, “city”],
});
const chain = new LLMChain({ llm: model, prompt: prompt });
const result = await chain.call({ activity: “cibo”, city: “Roma” });
console.log(“Risultato della Catena:”, result);
// L’oggetto risultato conterrà una proprietà ‘text’ con l’output dell’LLM.
}
simpleLLMChain();
“`
Esegui con `node llmChainExample.js`.
Nota come `chain.call()` prende un oggetto con le variabili in input e gestisce la formattazione del prompt e la chiamata all’LLM. Questo è un modo più pulito per interagire con il tuo LLM.
Catene Sequenziali: Flussi di Lavoro a Più Passi
A volte è necessario eseguire più chiamate a LLM in sequenza, dove l’output di una chiamata diventa l’input per la successiva. Qui entra in gioco `SimpleSequentialChain`.
“`javascript
// 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 });
// Catena 1: Genera un nome per l’azienda
const nameTemplate = “Qual è un buon nome per un’azienda che produce {product}?”;
const namePrompt = new PromptTemplate({
template: nameTemplate,
inputVariables: [“product”],
});
const nameChain = new LLMChain({ llm: model, prompt: namePrompt });
// Catena 2: Descrivi l’azienda
const descriptionTemplate = “Scrivi uno slogan di marketing breve e coinvolgente per un’azienda chiamata {companyName}.”;
const descriptionPrompt = new PromptTemplate({
template: descriptionTemplate,
inputVariables: [“companyName”],
});
const descriptionChain = new LLMChain({ llm: model, prompt: descriptionPrompt });
// Combinali in una catena sequenziale
const overallChain = new SimpleSequentialChain({
chains: [nameChain, descriptionChain],
verbose: true, // Imposta su true per vedere i passaggi intermedi
});
const result = await overallChain.run(“scarpe ecologiche”);
console.log(“Risultato Complessivo:”, result);
}
multiStepChain();
“`
Esegui con `node sequentialChainExample.js`.
La `SimpleSequentialChain` prende l’output di `nameChain` e lo usa automaticamente come input (`companyName`) per `descriptionChain`. Questo schema è estremamente potente per suddividere compiti complessi. LangChain.js rende tutto questo facile.
Generazione Aumentata da Recupero (RAG): Utilizzare i Propri Dati
Gli LLM sono potenti, ma la loro conoscenza è limitata ai dati di addestramento. Per applicazioni che richiedono informazioni aggiornate o conoscenze specifiche di un determinato dominio (documenti della tua azienda, per esempio), è necessario aumentare la conoscenza dell’LLM con dati esterni. Questo è chiamato Generazione Aumentata da Recupero (RAG).
La riga fondamentale di RAG è:
1. **Carica i dati:** Ottieni i tuoi documenti (PDF, file di testo, pagine web).
2. **Dividi i dati:** Suddividi documenti grandi in parti più piccole e gestibili.
3. **Incorpora i dati:** Converti queste parti in rappresentazioni numeriche (embeddings) utilizzando un modello di embedding.
4. **Memorizza i dati:** Memorizza questi embeddings in un database vettoriale.
5. **Recupera:** Quando un utente fa una domanda, incorpora la domanda, cerca nel database vettoriale parti di documenti simili.
6. **Aggiungi il prompt:** Aggiungi queste parti recuperate al prompt LLM.
7. **Genera:** L’LLM utilizza questo prompt arricchito per generare una risposta più informata.
LangChain.js fornisce componenti per ciascuno di questi passaggi.
Passo 1: Caricamento dei Documenti
LangChain.js ha vari caricatori di documenti. Utilizziamo un `TextLoader`.
“`javascript
// ragExample.js – Parte 1: Caricamento Documenti
require(‘dotenv’).config();
const { TextLoader } = require(‘langchain/document_loaders/fs/text’);
async function loadDocuments() {
// Crea un file di testo di esempio per la dimostrazione
const fs = require(‘fs’);
fs.writeFileSync(‘my_document.txt’, `
La veloce volpe marrone salta sopra il cane pigro.
Questo documento parla di vari animali e dei loro comportamenti.
I cani sono noti per la loro lealtà e gioia.
Le volpi sono creature astute e solitarie.
I gatti amano sonnecchiare e inseguire i topi.
`);
const loader = new TextLoader(“my_document.txt”);
const docs = await loader.load();
console.log(“Documenti Caricati:”, docs);
// Ogni documento avrà pageContent e metadata
}
loadDocuments();
“`
Passo 2: Suddividere i Documenti
I documenti grandi devono essere suddivisi in parti più piccole affinché rientrino nella finestra di contesto dell’LLM e per migliorare la rilevanza del recupero.
“`javascript
// ragExample.js – Parte 2: Suddivisione
require(‘dotenv’).config();
const { TextLoader } = require(‘langchain/document_loaders/fs/text’);
const { RecursiveCharacterTextSplitter } = require(‘langchain/text_splitter’);
async function splitDocuments() {
// (Assumi che my_document.txt sia già stato creato dalla Parte 1)
const loader = new TextLoader(“my_document.txt”);
const docs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 100, // Max caratteri per parte
chunkOverlap: 20, // Sovrapposizione tra le parti per mantenere il contesto
});
const splitDocs = await splitter.splitDocuments(docs);
console.log(“Documenti Suddivisi:”, splitDocs);
}
splitDocuments();
“`
Passi 3 & 4: Integrazione e Memorizzazione dei Documenti (Vector Store)
Qui è dove entrano in gioco gli embeddings e i database vettoriali. Utilizzeremo gli embeddings di OpenAI e `HNSWLib` (un database vettoriale in memoria locale) per semplicità. In produzione, useresti un database vettoriale dedicato come Pinecone, Chroma, Weaviate, ecc.
“`javascript
// ragExample.js – Parte 3: Embeddings e Vector Store
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. Carica e Suddividi Documenti
const fs = require(‘fs’);
fs.writeFileSync(‘my_document.txt’, `
La veloce volpe marrone salta sopra il cane pigro.
Questo documento parla di vari animali e dei loro comportamenti.
I cani sono noti per la loro lealtà e gioia.
Le volpi sono creature astute e solitarie.
I gatti amano sonnecchiare e inseguire i topi.
Un gruppo di gatti si chiama un 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. Crea Embeddings e Memorizzali nel Vector Store
const embeddings = new OpenAIEmbeddings();
const vectorStore = await HNSWLib.fromDocuments(docs, embeddings);
// 3. Crea un Recuperatore
const retriever = vectorStore.asRetriever();
// 4. Crea la Retrieval QA Chain
const model = new OpenAI({ temperature: 0 });
const chain = RetrievalQAChain.fromLLM(model, retriever);
// 5. Fai una domanda
const question = “Come si chiama un gruppo di gatti?”;
const result = await chain.call({ query: question });
console.log(“Risposta RAG:”, result.text);
const anotherQuestion = “Parlami delle volpi.”;
const anotherResult = await chain.call({ query: anotherQuestion });
console.log(“Un’altra Risposta RAG:”, anotherResult.text);
}
ragApplication();
“`
Esegui con `node ragExample.js`.
Questo esempio mette insieme tutti i pezzi di RAG. La `RetrievalQAChain` astrae il processo di recupero di documenti rilevanti e di utilizzo per rispondere a una domanda. Questo è un modo pratico per costruire chatbot o sistemi di domanda e risposta sui tuoi dati specifici utilizzando LangChain.js.
Agenti: LLM che Usano Strumenti
Gli agenti consentono agli LLM di prendere decisioni su quali strumenti utilizzare e in quale ordine, in base all’input dell’utente. Questo consente interazioni più dinamiche e complesse. Gli strumenti possono essere qualsiasi cosa: un motore di ricerca, una calcolatrice, una chiamata API ai tuoi sistemi interni, o anche un’altra catena LLM.
Creiamo un agente semplice che può eseguire calcoli usando uno strumento `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()];
// Inizializza l’esecutore dell’agente
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: “openai-functions”, // Usa la funzione di chiamata di OpenAI
verbose: true, // Vedi il processo di pensiero dell’agente
});
console.log(“Chiamando l’agente con una semplice domanda matematica…”);
const result1 = await executor.call({ input: “Qual è 123 più 456?” });
console.log(“Risultato Agente 1:”, result1.output);
console.log(“\nChiamando l’agente con una domanda più complessa…”);
const result2 = await executor.call({ input: “Qual è la radice quadrata di 625 moltiplicata per 3?” });
console.log(“Risultato Agente 2:”, result2.output);
}
runAgent();
“`
Esegui con `node agentExample.js`.
Con `verbose: true`, vedrai i “pensieri” dell’agente: identifica che è necessario un calcolo, utilizza lo strumento `Calculator`, e poi fornisce la risposta. Questo è un modello potente per costruire applicazioni che vanno oltre la semplice generazione di testi. LangChain.js semplifica la configurazione di questi agenti.
Memoria: Mantenere il Contesto della Conversazione
Per impostazione predefinita, gli LLM sono privi di stato. Ogni chiamata API è indipendente. Per applicazioni conversazionali, l’LLM deve ricordare le interazioni passate. LangChain.js fornisce vari tipi di memoria per raggiungere questo obiettivo.
`ConversationBufferMemory` è una scelta comune, memorizzando la cronologia della conversazione grezza.
“`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(“Inizio della conversazione…”);
let result1 = await chain.call({ input: “Ciao, mi chiamo Jake.” });
console.log(“Utente: Ciao, mi chiamo Jake.”);
console.log(“AI:”, result1.response);
let result2 = await chain.call({ input: “Qual è il mio nome?” });
console.log(“Utente: Qual è il mio nome?”);
console.log(“AI:”, result2.response);
let result3 = await chain.call({ input: “Dimmi un fatto divertente su JavaScript.” });
console.log(“Utente: Dimmi un fatto divertente su JavaScript.”);
console.log(“AI:”, result3.response);
}
conversationalApp();
“`
Esegui con `node memoryExample.js`.
Il `ConversationBufferMemory` mantiene la cronologia della chat, consentendo al LLM di rispondere correttamente a “Qual è il mio nome?” sulla base del turno precedente. LangChain.js offre altri tipi di memoria per casi d’uso più avanzati, come riassumere conversazioni o memorizzare solo parti specifiche.
Consigli Pratici per lo Sviluppo con LangChain.js
1. **Inizia Semplice:** Non cercare di costruire un agente complesso con più strumenti e memoria subito. Inizia con chiamate LLM semplici e `LLMChain`.
2. **Usa `verbose: true`:** Durante il debug di catene o agenti, impostare `verbose: true` è fondamentale. Ti mostra i passi intermedi, i prompt inviati e le risposte ricevute, aiutandoti a capire perché la tua catena potrebbe non comportarsi come previsto.
3. **Gestisci le Chiavi API in Sicurezza:** Usa sempre variabili d’ambiente per le tue chiavi API. Non committarle mai nel controllo di versione.
4. **Comprendi le Finestra di Contesto:** Fai attenzione ai limiti di token del tuo LLM scelto. Prompt lunghi, una cronologia di conversazioni estesa o molti documenti recuperati possono rapidamente superare questi limiti. LangChain.js ha strumenti per aiutare a gestire questo, ma è importante esserne consapevoli.
5. **Sperimenta con `temperature`:** Il parametro `temperature` controlla la casualità dell’output del LLM. Valori più alti (es. 0.7-1.0) portano a risposte più creative e meno deterministiche. Valori più bassi (es. 0.0-0.3) sono più fattuali e coerenti. Regola questo in base alle esigenze della tua applicazione.
6. **Esplora la Documentazione:** La documentazione di LangChain.js è approfondita. Se stai cercando un caricatore, una catena o uno strumento specifico, è probabile che sia coperto lì.
7. **Considera i Database di Vettori in Produzione:** Per applicazioni RAG nel mondo reale, sostituisci `HNSWLib` con un database di vettori persistente come Pinecone, Chroma o Weaviate. Questo ti permetterà di scalare e memorizzare grandi quantità di dati.
8. **Gestione degli Errori:** Implementa una solida gestione degli errori per le chiamate API, specialmente in ambienti di produzione. Problemi di rete, limiti di frequenza o input non validi possono causare fallimenti.
Domande Frequenti su LangChain.js
Q1: LangChain.js è solo per i modelli OpenAI?
No, LangChain.js supporta un’ampia gamma di fornitori di LLM, tra cui OpenAI, modelli Hugging Face (tramite `HuggingFaceHub` o `HuggingFaceInference`), Google (es. `GoogleGenerativeAI`), Anthropic e molti altri. Le astrazioni principali di LangChain.js ti consentono di cambiare fornitori di LLM con minimi cambiamenti nel codice.
Q2: Qual è la differenza tra `model.call()` e `chain.call()` o `chain.run()`?
`model.call()` è il modo più diretto per interagire con un’istanza LLM singola, passando un prompt di stringa grezza. `chain.call()` è utilizzato per le catene e richiede un oggetto contenente variabili di input (che vengono poi formattate da un `PromptTemplate` se usate all’interno della catena). `chain.run()` è un metodo di convenienza per catene che hanno solo una singola variabile di input e restituisce direttamente il testo di output. Per catene più complesse con più input/output, è preferibile `chain.call()`.
Q3: Come posso integrare LangChain.js con il mio database o API esistenti?
Puoi integrare LangChain.js con i tuoi sistemi esistenti creando strumenti personalizzati per gli agenti. Uno strumento è essenzialmente una funzione che prende una stringa di input e restituisce una stringa di output. Puoi definire uno strumento che interroga il tuo database, chiama un’API interna, o esegue qualunque altra logica personalizzata, e poi rendere disponibile questo strumento al tuo agente LangChain.js. Per RAG, puoi creare implementazioni personalizzate di `DocumentLoader` per caricare dati dalle tue fonti dati specifiche.
Q4: Quando dovrei usare LangChain.js invece di chiamare direttamente le API LLM?
Usa LangChain.js quando hai bisogno di costruire applicazioni che coinvolgono più di una singola chiamata LLM isolata. Se la tua applicazione richiede:
* Ragionamento multilivello (catene).
* Integrazione di dati esterni (RAG).
* Fornire al LLM accesso a strumenti esterni (agenti).
* Gestire la cronologia delle conversazioni (memoria).
* Scambiare facilmente fornitori di LLM.
* Codice più strutturato e mantenibile.
Se stai solo facendo un prompt una tantum per testare, le chiamate API dirette potrebbero essere più semplici, ma per qualsiasi applicazione pratica, LangChain.js diventa rapidamente indispensabile.
Conclusione
LangChain.js è un framework potente per costruire applicazioni LLM sofisticate in JavaScript e TypeScript. Comprendendo i suoi componenti principali—LLM, Prompt, Catene, Recupero, Agenti e Memoria—puoi costruire sistemi intelligenti che vanno oltre la semplice generazione di testo. Questa guida ha fornito un punto di partenza pratico e azionabile. Dalle semplici chiamate LLM a pipeline e agenti RAG complessi, LangChain.js offre gli strumenti per dare vita alle tue idee di automazione AI. Inizia a sperimentare, costruisci piccoli progetti, e scoprirai rapidamente il potenziale di LangChain.js nel tuo flusso di lavoro di sviluppo.
🕒 Published: