LangChain è uno di quei framework che ami e odi. Ti fa partire in cinque minuti, poi a volte ti chiedi perché serva un'astrazione per fare una chiamata HTTP. Dopo averci costruito sopra diversi progetti in produzione, la mia opinione è netta: è lo strumento giusto quando devi orchestrare più componenti (modello, memoria, strumenti, dati), è un peso inutile quando ti serve solo una chiamata secca a un LLM.
In questa guida parto da zero e arrivo a un'applicazione funzionante, in italiano e con codice che gira davvero. Niente teoria astratta: ogni concetto ha il suo esempio copia-incolla.
Cos'è LangChain e quando usarlo
LangChain è un framework Python (esiste anche in JavaScript) per costruire applicazioni basate su LLM. La sua utilità non è "parlare con un modello" — quello lo fai con tre righe e la libreria del provider. La sua utilità è collegare i pezzi: prendere l'output di un modello e darlo a uno strumento, ricordare la conversazione, recuperare documenti, gestire il flusso.
Usalo quando:
- Devi combinare LLM + dati esterni (è il caso del RAG).
- Vuoi un agente che usa strumenti (calcolatrice, ricerca web, API).
- Ti serve memoria conversazionale strutturata.
Evitalo quando ti serve solo una singola chiamata a un modello: stai aggiungendo un livello di astrazione che non ti ripaga.
Una nota importante: dalla versione 0.1 in poi LangChain è stato spezzato in pacchetti separati (langchain-core, langchain-openai, langchain-ollama...). Molti tutorial vecchi usano import che oggi non funzionano più. Qui uso le API attuali.
Setup e installazione
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install langchain langchain-core langchain-community \
langchain-openai langchain-ollama
Hai due strade per l'LLM. Con OpenAI (qualità alta, a pagamento):
export OPENAI_API_KEY="sk-..."
Con Ollama (locale, gratis, privato): installa Ollama e scarica un modello:
ollama pull llama3.1:8b
In tutti gli esempi che seguono puoi scambiare i due modelli cambiando una riga.
Concetti fondamentali
Model: il mattone di base
from langchain_ollama import ChatOllama
# in alternativa: from langchain_openai import ChatOpenAI
llm = ChatOllama(model="llama3.1:8b", temperature=0.7)
# llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
risposta = llm.invoke("Spiega cos'è un container Docker in una frase.")
print(risposta.content)
temperature controlla la creatività: 0 per risposte deterministiche e fattuali, valori più alti (0.7-1.0) per output più vari.
Prompt template: prompt parametrici
Hard-codare il prompt è un errore da principianti. I template separano la struttura dai dati:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "Sei un esperto di {dominio}. Rispondi in modo conciso."),
("human", "{domanda}"),
])
messaggi = prompt.invoke({"dominio": "cloud computing", "domanda": "Cos'è Kubernetes?"})
print(llm.invoke(messaggi).content)
Chain: collegare i componenti con LCEL
Il cuore di LangChain moderno è la LCEL (LangChain Expression Language): colleghi i componenti con l'operatore |, come una pipe Unix. L'output di uno diventa l'input del successivo.
from langchain_core.output_parsers import StrOutputParser
chain = prompt | llm | StrOutputParser()
risposta = chain.invoke({"dominio": "DevOps", "domanda": "Cos'è la CI/CD?"})
print(risposta) # già una stringa pulita, senza l'oggetto messaggio
StrOutputParser estrae direttamente il testo. Questa è la "prima chain": tre componenti collegati che insieme formano un'unità riutilizzabile.
Memory: conversazioni che ricordano
Un LLM è stateless: ogni chiamata è indipendente, non ricorda nulla. Per fare una chat che mantiene il contesto devi gestire tu la cronologia. L'approccio moderno usa RunnableWithMessageHistory.
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
prompt = ChatPromptTemplate.from_messages([
("system", "Sei un assistente cordiale. Rispondi in italiano."),
MessagesPlaceholder(variable_name="cronologia"),
("human", "{input}"),
])
chain = prompt | llm | StrOutputParser()
# Archivio delle sessioni (in produzione: Redis, un DB, ...)
sessioni = {}
def get_history(session_id: str):
if session_id not in sessioni:
sessioni[session_id] = InMemoryChatMessageHistory()
return sessioni[session_id]
chat = RunnableWithMessageHistory(
chain,
get_history,
input_messages_key="input",
history_messages_key="cronologia",
)
config = {"configurable": {"session_id": "utente-42"}}
print(chat.invoke({"input": "Mi chiamo Marco."}, config=config))
print(chat.invoke({"input": "Come mi chiamo?"}, config=config))
# → "Ti chiami Marco." Il contesto è stato mantenuto.
Per conversazioni lunghe la cronologia cresce e con essa i costi. Le strategie tipiche: tenere solo gli ultimi N messaggi, oppure riassumere periodicamente la conversazione con l'LLM stesso.
Agent: LLM che usano strumenti
Qui LangChain dà il meglio. Un agent è un LLM a cui dai degli strumenti e la libertà di decidere quando usarli. Il modello ragiona, sceglie lo strumento, ne legge il risultato e continua finché non ha la risposta.
from langchain_core.tools import tool
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub
@tool
def calcola(espressione: str) -> str:
"""Valuta un'espressione matematica. Esempio: '23 * 47'."""
try:
return str(eval(espressione, {"__builtins__": {}}, {}))
except Exception as e:
return f"Errore: {e}"
@tool
def lunghezza_testo(testo: str) -> str:
"""Conta i caratteri di una stringa."""
return str(len(testo))
tools = [calcola, lunghezza_testo]
prompt_react = hub.pull("hwchase17/react") # prompt ReAct standard
agent = create_react_agent(llm, tools, prompt_react)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
executor.invoke({"input": "Quanto fa 1234 per 5678? E quante cifre ha il risultato?"})
Con verbose=True vedi il ragionamento dell'agente: sceglie calcola, ottiene il prodotto, poi usa lunghezza_testo sul risultato. È il pattern ReAct (Reasoning + Acting), la base degli agenti AI moderni.
Il decoratore @tool trasforma una funzione Python normale in uno strumento: la docstring è fondamentale, perché è da lì che il modello capisce quando e come usarlo. Una docstring scritta male = un agente che sbaglia strumento.
RAG con LangChain
Mettendo insieme i pezzi ottieni un sistema di Q&A sui tuoi documenti. Ho dedicato una guida intera all'argomento — come implementare RAG in Python — ma ecco la versione minima per chiudere il cerchio:
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_chroma import Chroma
from langchain_core.runnables import RunnablePassthrough
# 1. Carica e spezza
docs = TextLoader("manuale.txt").load()
chunks = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100).split_documents(docs)
# 2. Indicizza
embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large")
retriever = Chroma.from_documents(chunks, embeddings).as_retriever()
# 3. Catena di Q&A
prompt = ChatPromptTemplate.from_template(
"Rispondi usando solo questo contesto:\n{context}\n\nDomanda: {question}"
)
rag = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt | llm | StrOutputParser()
)
print(rag.invoke("Come si configura il backup?"))
Sono gli stessi mattoni di prima — prompt, model, parser collegati con | — più retriever ed embeddings. Questa è la forza di LangChain: una volta capita la LCEL, tutto si compone allo stesso modo.
Best practice e trappole
Quello che ho imparato sul campo:
- Non usare LangChain per chiamate singole. Se il tuo codice è
prompt | llm, usa direttamente l'SDK del provider. LangChain ripaga quando orchestri. - Fissa le versioni. LangChain cambia API spesso. Blocca le versioni nel
requirements.txto un aggiornamento ti romperà il codice in produzione. - Cura le docstring degli strumenti. Per gli agent sono letteralmente le istruzioni d'uso che legge il modello.
- Gestisci costi e rate limit. Con le API a pagamento, un agente in loop può bruciare credito in fretta. Imposta
max_iterationssull'AgentExecutore abilita il caching. - Usa LangSmith per il debug. Quando una chain fa cose strane, vedere ogni passaggio intermedio è l'unico modo per capire dove si rompe.
Conclusioni
LangChain non è magia e non è obbligatorio, ma quando devi mettere insieme modello, dati, memoria e strumenti ti risparmia un sacco di codice ripetitivo. La chiave per non odiarlo è capire la LCEL: una volta che pensi in termini di componenti collegati con |, tutto il resto — chain, memory, agent, RAG — diventa lo stesso pattern applicato a problemi diversi.
Il percorso che consiglio: parti dalle chain semplici, aggiungi la memory quando ti serve una conversazione, passa agli agent solo quando il modello deve davvero agire sul mondo. Non partire dagli agent: sono la parte più potente ma anche la più difficile da rendere affidabile.
Se stai progettando un agente AI o un assistente su misura per la tua azienda e vuoi una mano a portarlo dal prototipo alla produzione, scrivici: è il nostro lavoro quotidiano.
Un framework è buono quando ti fa scrivere meno codice di quello che ti fa leggere. LangChain ci riesce — se lo usi per il motivo giusto.