langchain llm python ai tutorial

LangChain: Tutorial Completo in Italiano (con Esempi 2026)

Guida completa a LangChain in italiano: chain, memory, agent e RAG con esempi Python funzionanti. Da zero a un'applicazione LLM funzionante, con OpenAI e Ollama.

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.txt o 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_iterations sull'AgentExecutor e 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.