Introduzione: L'Ecosistema LangChain nel 2026
LangChain è il framework open-source più diffuso al mondo per costruire applicazioni basate su Large Language Model. Nato a fine 2022 come libreria Python per concatenare chiamate a LLM, si è evoluto in un ecosistema completo che comprende orchestrazione di agenti, gestione della memoria, integrazione con centinaia di strumenti esterni e supporto nativo per Retrieval-Augmented Generation (RAG). Nel 2026, l'ecosistema LangChain non è più una singola libreria: è una costellazione di pacchetti modulari progettati per affrontare ogni aspetto dello sviluppo di applicazioni AI, dalla prototipazione rapida alla produzione enterprise.
Il cambiamento più significativo avvenuto nel panorama LangChain è stato il passaggio da AgentExecutor, il sistema legacy per l'orchestrazione degli agenti, a LangGraph, una libreria basata su grafi che permette di costruire flussi di lavoro stateful, ciclici e altamente personalizzabili. Con il rilascio di LangGraph 1.0, l'architettura degli agenti è cambiata radicalmente: i flussi lineari e rigidi di AgentExecutor sono stati sostituiti da grafi diretti che supportano branching condizionale, stato persistente e interazione uomo-macchina nativa.
In questo articolo esploreremo in profondità l'intero stack LangChain: dall'architettura modulare ai tool custom, dal Chain Design ai pattern avanzati di gestione errori. Vedremo come AgentExecutor funzionava e perchè è stato superato, per poi concentrarci su LangGraph e su come costruire agenti moderni, robusti e pronti per la produzione.
Cosa Imparerai in Questo Articolo
- L'architettura modulare di LangChain: LLMs, Chains, Agents, Tools e Memory
- perchè AgentExecutor e stato deprecato e quali erano i suoi limiti
- Come LangGraph risolve i problemi di AgentExecutor con un'architettura graph-based
- Come definire Custom Tools con il decoratore
@toole type hints - Chain Design Patterns: sequential, parallel e conditional branching con LCEL
- Integrazione con diversi LLM provider (OpenAI, Anthropic Claude, Ollama)
- Strategie di gestione errori: retry, fallback, timeout e iteration limits
- Case study completo: un Research Assistant autonomo end-to-end
L'Architettura LangChain
L'ecosistema LangChain è organizzato in moduli separati che collaborano tra loro. Comprendere questa architettura è fondamentale per scegliere i componenti giusti per ogni progetto e per evitare di importare funzionalità non necessarie.
I Componenti Fondamentali
L'architettura si articola in cinque pilastri principali, ognuno con un ruolo ben definito nell'ecosistema complessivo:
- LLMs e Chat Models: wrapper standardizzati per i modelli di linguaggio. Forniscono un'interfaccia uniforme indipendentemente dal provider (OpenAI, Anthropic, Google, modelli locali via Ollama). La classe
ChatOpenAI,ChatAnthropicoChatOllamaespongono tutti lo stesso metodo.invoke() - Prompt Templates: sistemi di templating per costruire prompt strutturati. Supportano variabili dinamiche, few-shot examples e system messages. Il tipo più comune è
ChatPromptTemplateche genera liste di messaggi tipizzati - Chains: composizioni di componenti in sequenze di elaborazione. Una chain prende un input, lo passa attraverso una serie di trasformazioni e produce un output. Con LCEL (LangChain Expression Language), le chain si compongono con l'operatore pipe
| - Tools: funzioni che gli agenti possono invocare per interagire con il mondo esterno. Ogni tool ha un nome, una descrizione e uno schema JSON che definisce i parametri accettati. L'LLM usa la descrizione per decidere quando e come invocare il tool
- Memory: sistemi per mantenere il contesto tra le interazioni. Dalla semplice cronologia dei messaggi (
ConversationBufferMemory) a sistemi più sofisticati comeConversationSummaryMemoryche riassume le conversazioni precedenti per risparmiare token
La Struttura dei Pacchetti
Pacchetti dell'Ecosistema LangChain
| Pacchetto | Scopo | Esempio d'Uso |
|---|---|---|
langchain-core |
Interfacce base, LCEL, schema | Runnables, PromptTemplate, BaseMessage |
langchain |
Chains, agents, retrieval | AgentExecutor (legacy), RetrievalQA |
langchain-openai |
Integrazione OpenAI | ChatOpenAI, OpenAIEmbeddings |
langchain-anthropic |
Integrazione Anthropic | ChatAnthropic |
langchain-community |
Integrazioni community | ChatOllama, WikipediaLoader |
langgraph |
Agenti graph-based | StateGraph, MessageGraph |
langsmith |
Observability e tracing | Debugging, monitoring, evaluation |
AgentExecutor: Il Passato
Per comprendere il presente dell'architettura LangChain è essenziale capire da dove veniamo.
AgentExecutor è stato per anni il componente standard per orchestrare agenti LLM in LangChain.
Il suo funzionamento era basato su un loop semplice: il modello riceveva un prompt, decideva quale tool invocare,
l'executor eseguiva il tool, il risultato veniva aggiunto al contesto e il ciclo ricominciava fino a quando
il modello decideva di fornire una risposta finale.
Come Funzionava AgentExecutor
Il ciclo di esecuzione di AgentExecutor seguiva uno schema rigido in quattro fasi che si ripeteva fino al raggiungimento di una risposta finale o al superamento del limite massimo di iterazioni:
- Reasoning: il modello analizzava il prompt e il contesto corrente per decidere l'azione successiva
- Action Selection: il modello sceglieva un tool e generava i parametri di input
- Execution: l'executor invocava il tool con i parametri generati
- Observation: il risultato del tool veniva aggiunto alla cronologia dei messaggi come "osservazione" e il loop ricominciava dal punto 1
# Esempio legacy con AgentExecutor (DEPRECATO in LangChain 0.2+)
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# Setup
llm = ChatOpenAI(model="gpt-4")
prompt = ChatPromptTemplate.from_messages([
("system", "Sei un assistente utile con accesso a strumenti."),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
# Creazione dell'agente e dell'executor
agent = create_openai_tools_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# Esecuzione
result = executor.invoke({"input": "Cerca informazioni su Python 3.12"})
Perchè AgentExecutor è Stato Deprecato
Nonostante la sua semplicità, AgentExecutor presentava limitazioni strutturali che lo rendevano inadeguato per applicazioni complesse e per ambienti di produzione. Queste limitazioni hanno motivato lo sviluppo di LangGraph come suo successore:
- Flussi esclusivamente lineari: AgentExecutor supportava solo un ciclo sequenziale di reasoning-action-observation, rendendo impossibile implementare branching condizionale, esecuzione parallela di tool o subgraph annidati
- Scarsa modularita: personalizzare il comportamento dell'agente richiedeva di sovrascrivere metodi interni o creare sottoclassi, con il rischio di rompere la compatibilità con aggiornamenti futuri
- Debugging opaco: il flusso interno era difficile da tracciare. L'opzione
verbose=Trueproduceva log testuali non strutturati, inadeguati per il debugging in produzione - Stato non persistente: lo stato dell'agente esisteva solo in memoria durante l'esecuzione. Non era possibile salvare checkpoint, riprendere esecuzioni interrotte o implementare conversazioni multi-sessione senza soluzioni custom
- Nessun supporto human-in-the-loop: non c'era un meccanismo nativo per chiedere conferma all'utente prima di eseguire azioni critiche, un requisito fondamentale per applicazioni aziendali
Nota sulla Migrazione
Se hai codice esistente che usa AgentExecutor, LangChain fornisce una guida ufficiale alla
migrazione verso LangGraph. Il team di LangChain raccomanda di usare create_react_agent di
LangGraph come sostituto diretto, con la possibilità di personalizzare il flusso successivamente. La
migrazione e generalmente semplice per i casi d'uso base, ma offre opportunità significative di miglioramento
per i casi più complessi.
LangGraph: Il Presente e il Futuro
LangGraph è la libreria ufficiale di LangChain per costruire agenti basati su grafi. Rilasciata come stabile con la versione 1.0, rappresenta un cambio di paradigma fondamentale: invece di un loop lineare, l'esecuzione dell'agente viene modellata come un grafo diretto dove i nodi rappresentano operazioni (chiamate LLM, esecuzione di tool, logica custom) e gli archi definiscono il flusso tra le operazioni, inclusi branching condizionale e cicli.
Concetti Chiave di LangGraph
- StateGraph: il contenitore principale che definisce il grafo dell'agente. Accetta un tipo di stato (tipicamente un
TypedDict) che rappresenta i dati che fluiscono attraverso il grafo e vengono modificati dai nodi - Nodi (Nodes): funzioni Python che ricevono lo stato corrente, eseguono un'operazione e ritornano un aggiornamento parziale dello stato. Ogni nodo è una funzione pura con un compito specifico
- Archi (Edges): connessioni tra nodi che definiscono il flusso di esecuzione. Gli archi possono essere statici (sempre lo stesso percorso) o condizionali (il percorso dipende dallo stato corrente)
- Checkpointing: LangGraph salva automaticamente lo stato dopo ogni nodo, permettendo di riprendere l'esecuzione da qualsiasi punto, implementare rollback e supportare conversazioni persistenti multi-sessione
- Human-in-the-loop: meccanismo nativo per interrompere l'esecuzione del grafo, presentare lo stato all'utente e riprendere dopo l'approvazione. Si implementa con
interrupt()e breakpoint configurabili
Architettura di un Agente LangGraph
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
# 1. Definizione del modello con i tool
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools)
# 2. Definizione dei nodi
def call_model(state: MessagesState):
"""Nodo che chiama il modello LLM."""
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# 3. Costruzione del grafo
graph_builder = StateGraph(MessagesState)
# Aggiunta dei nodi
graph_builder.add_node("agent", call_model)
graph_builder.add_node("tools", ToolNode(tools=tools))
# Definizione degli archi
graph_builder.add_edge(START, "agent")
graph_builder.add_conditional_edges(
"agent",
tools_condition, # Se il modello chiama tool -> nodo "tools", altrimenti -> END
)
graph_builder.add_edge("tools", "agent") # Dopo i tool, torna all'agente
# 4. Compilazione del grafo
agent = graph_builder.compile()
# 5. Esecuzione
result = agent.invoke(
{"messages": [("user", "Cerca informazioni su Python 3.12")]}
)
In questo esempio, il grafo ha due nodi: agent (che chiama il modello) e tools
(che esegue i tool invocati dal modello). L'arco condizionale tools_condition controlla se
la risposta del modello contiene chiamate a tool: se si, il flusso va al nodo tools, altrimenti
termina (END). Dopo l'esecuzione dei tool, il flusso torna al nodo agent,
creando il ciclo necessario per il reasoning iterativo.
Stato Persistente e Checkpointing
Una delle caratteristiche più potenti di LangGraph è il supporto nativo per lo stato persistente.
Utilizzando un MemorySaver o un backend di persistenza come PostgreSQL, ogni esecuzione
del grafo viene salvata automaticamente e può essere ripresa in qualsiasi momento:
from langgraph.checkpoint.memory import MemorySaver
# Compilazione con memoria persistente
checkpointer = MemorySaver()
agent = graph_builder.compile(checkpointer=checkpointer)
# Prima conversazione
config = {"configurable": {"thread_id": "user-123"}}
result1 = agent.invoke(
{"messages": [("user", "Mi chiamo Federico")]},
config=config,
)
# Seconda conversazione - l'agente ricorda il contesto precedente
result2 = agent.invoke(
{"messages": [("user", "Come mi chiamo?")]},
config=config, # Stesso thread_id
)
# L'agente rispondera "Ti chiami Federico"
Human-in-the-Loop Nativo
LangGraph supporta l'interruzione del flusso per richiedere approvazione umana prima di eseguire azioni potenzialmente pericolose. Questo meccanismo è fondamentale per applicazioni aziendali dove azioni come invio di email, modifiche a database o transazioni finanziarie richiedono supervisione:
from langgraph.types import interrupt, Command
def human_approval_node(state: MessagesState):
"""Nodo che richiede approvazione umana."""
last_message = state["messages"][-1]
# Interrompe il grafo e presenta l'azione all'utente
approval = interrupt(
{"action": "send_email", "details": last_message.content}
)
if approval == "approved":
return {"messages": [("system", "Azione approvata dall'utente.")]}
else:
return {"messages": [("system", "Azione rifiutata dall'utente.")]}
Definire Custom Tools
I tool sono il ponte tra l'intelligenza dell'LLM e il mondo esterno. Un tool ben progettato permette all'agente di compiere azioni concrete: cercare informazioni sul web, interrogare un database, leggere file, inviare notifiche o interagire con API esterne. La qualità dei tool definisce direttamente le capacità dell'agente.
Il Decoratore @tool
Il modo più semplice e idiomatico per creare un tool in LangChain è il decoratore @tool.
Questo decoratore trasforma una funzione Python ordinaria in un tool utilizzabile dagli agenti,
estraendo automaticamente il nome, la descrizione e lo schema dei parametri dalla funzione stessa.
I type hints sono essenziali: LangChain li usa per generare lo schema JSON che il modello utilizza
per capire come invocare il tool.
from langchain_core.tools import tool
@tool
def web_search(query: str, max_results: int = 5) -> str:
"""Cerca informazioni sul web usando un motore di ricerca.
Args:
query: La query di ricerca da eseguire.
max_results: Numero massimo di risultati da restituire (default: 5).
Returns:
Una stringa con i risultati della ricerca formattati.
"""
# Implementazione della ricerca (es. usando Tavily, SerpAPI, etc.)
from tavily import TavilyClient
client = TavilyClient()
results = client.search(query, max_results=max_results)
formatted = []
for r in results["results"]:
formatted.append(f"- {r['title']}: {r['content'][:200]}")
return "\n".join(formatted) if formatted else "Nessun risultato trovato."
La Descrizione e Critica
Il docstring della funzione viene usato come descrizione del tool inviata all'LLM. Una descrizione chiara, concisa e precisa e fondamentale: il modello la legge per decidere quando invocare il tool e come fornire i parametri. Descrizioni vaghe portano a invocazioni errate o mancanti. Scrivi il docstring come se stessi spiegando a un collega cosa fa la funzione e quando usarla.
Tool Avanzati: Database e File System
import sqlite3
from langchain_core.tools import tool
from typing import Optional
@tool
def query_database(sql_query: str, database_path: str = "app.db") -> str:
"""Esegue una query SQL SELECT su un database SQLite.
IMPORTANTE: Esegue solo query di lettura (SELECT). Non eseguire
INSERT, UPDATE, DELETE o altre query di modifica.
Args:
sql_query: La query SQL SELECT da eseguire.
database_path: Percorso al file del database SQLite.
Returns:
I risultati della query formattati come tabella testuale.
"""
if not sql_query.strip().upper().startswith("SELECT"):
return "Errore: Solo query SELECT sono permesse per sicurezza."
try:
conn = sqlite3.connect(database_path)
cursor = conn.execute(sql_query)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
conn.close()
if not rows:
return "La query non ha restituito risultati."
# Formattazione tabellare
header = " | ".join(columns)
separator = "-" * len(header)
body = "\n".join(" | ".join(str(v) for v in row) for row in rows)
return f"{header}\n{separator}\n{body}"
except Exception as e:
return f"Errore nell'esecuzione della query: {str(e)}"
@tool
def read_file(file_path: str, max_lines: Optional[int] = None) -> str:
"""Legge il contenuto di un file di testo dal filesystem.
Args:
file_path: Percorso completo al file da leggere.
max_lines: Numero massimo di righe da leggere (tutte se non specificato).
Returns:
Il contenuto del file come stringa, oppure un messaggio di errore.
"""
try:
with open(file_path, "r", encoding="utf-8") as f:
if max_lines:
lines = [next(f) for _ in range(max_lines)]
content = "".join(lines)
else:
content = f.read()
if len(content) > 10000:
content = content[:10000] + "\n... [troncato a 10000 caratteri]"
return content
except FileNotFoundError:
return f"File non trovato: {file_path}"
except Exception as e:
return f"Errore nella lettura del file: {str(e)}"
Chain Design Patterns
Le chain sono composizioni di componenti che trasformano un input in un output attraverso una serie
di passaggi. Con l'introduzione di LCEL (LangChain Expression Language), la composizione
delle chain è diventata dichiarativa e intuitiva, utilizzando l'operatore pipe | per
collegare i componenti.
Sequential Chains
Il pattern più semplice: ogni componente riceve l'output del precedente e produce un input per il successivo. E' ideale per pipeline lineari dove ogni passo dipende dal risultato del precedente.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o")
# Chain sequenziale: prompt -> LLM -> parser
chain = (
ChatPromptTemplate.from_messages([
("system", "Sei un esperto di tecnologia. Rispondi in modo conciso."),
("human", "{question}"),
])
| llm
| StrOutputParser()
)
result = chain.invoke({"question": "Cos'è LangGraph?"})
Parallel Execution
Quando diverse operazioni sono indipendenti tra loro, è possibile eseguirle in parallelo utilizzando
RunnableParallel. Questo pattern riduce significativamente la latenza quando si devono
eseguire più chiamate LLM o tool contemporaneamente:
from langchain_core.runnables import RunnableParallel
# Esecuzione parallela: tre analisi diverse sullo stesso input
analysis_chain = RunnableParallel(
summary=ChatPromptTemplate.from_template(
"Riassumi in 2 frasi: {text}"
) | llm | StrOutputParser(),
sentiment=ChatPromptTemplate.from_template(
"Analizza il sentiment (positivo/negativo/neutro): {text}"
) | llm | StrOutputParser(),
keywords=ChatPromptTemplate.from_template(
"Estrai 5 parole chiave: {text}"
) | llm | StrOutputParser(),
)
# Tutte e tre le analisi vengono eseguite contemporaneamente
results = analysis_chain.invoke({"text": "Il nuovo framework..."})
# results = {"summary": "...", "sentiment": "...", "keywords": "..."}
Conditional Branching
Il branching condizionale permette di scegliere percorsi diversi in base al contenuto dell'input
o a risultati intermedi. Con RunnableBranch si definiscono condizioni e chain corrispondenti:
from langchain_core.runnables import RunnableBranch
# Branching basato sulla lingua dell'input
branch = RunnableBranch(
(
lambda x: "italiano" in x["language"].lower(),
ChatPromptTemplate.from_template(
"Rispondi in italiano: {question}"
) | llm | StrOutputParser(),
),
(
lambda x: "inglese" in x["language"].lower(),
ChatPromptTemplate.from_template(
"Answer in English: {question}"
) | llm | StrOutputParser(),
),
# Default branch
ChatPromptTemplate.from_template(
"Answer: {question}"
) | llm | StrOutputParser(),
)
Integrazione LLM
LangChain supporta decine di provider LLM attraverso pacchetti dedicati. La standardizzazione dell'interfaccia permette di cambiare modello con una sola riga di codice, rendendo facile testare diversi provider o implementare strategie di fallback.
Setup con Diversi Provider
# OpenAI
from langchain_openai import ChatOpenAI
llm_openai = ChatOpenAI(
model="gpt-4o",
temperature=0.7,
max_tokens=4096,
)
# Anthropic Claude
from langchain_anthropic import ChatAnthropic
llm_claude = ChatAnthropic(
model="claude-sonnet-4-20250514",
temperature=0.7,
max_tokens=4096,
)
# Ollama (modelli locali)
from langchain_community.chat_models import ChatOllama
llm_local = ChatOllama(
model="llama3.1:8b",
temperature=0.7,
base_url="http://localhost:11434",
)
Fallback Strategy
Una strategia di fallback permette di passare automaticamente a un modello alternativo quando il provider principale non è disponibile. Questo è fondamentale per la resilienza in produzione:
# Fallback: se OpenAI fallisce, usa Claude; se Claude fallisce, usa Ollama
llm_with_fallback = llm_openai.with_fallbacks(
[llm_claude, llm_local]
)
# Il chain usera automaticamente il primo modello disponibile
chain = prompt | llm_with_fallback | StrOutputParser()
Gestione Errori
La gestione degli errori è un aspetto critico nello sviluppo di agenti AI. Un agente in produzione deve gestire gracefully errori di rete, timeout, risposte malformate e limiti di iterazione. LangChain e LangGraph offrono diversi meccanismi per costruire agenti resilienti.
Retry Logic
Il metodo .with_retry() aggiunge automaticamente la logica di retry a qualsiasi
componente Runnable, con supporto per backoff esponenziale e numero massimo di tentativi:
# Retry automatico con backoff esponenziale
llm_resilient = llm_openai.with_retry(
stop_after_attempt=3,
wait_exponential_jitter=True,
retry_if_exception_type=(TimeoutError, ConnectionError),
)
# Combinazione retry + fallback per massima resilienza
llm_production = llm_openai.with_retry(
stop_after_attempt=2
).with_fallbacks(
[llm_claude.with_retry(stop_after_attempt=2)]
)
Iteration Limits e Timeout
In LangGraph, è possibile limitare il numero di passi (nodi visitati) per prevenire loop infiniti e controllare il tempo massimo di esecuzione dell'agente:
# Configurazione dei limiti di esecuzione
config = {
"configurable": {"thread_id": "user-123"},
"recursion_limit": 25, # Massimo 25 nodi visitati
}
# Esecuzione con timeout
import asyncio
async def run_with_timeout(agent, input_data, timeout_seconds=60):
"""Esegue l'agente con un timeout globale."""
try:
result = await asyncio.wait_for(
agent.ainvoke(input_data, config=config),
timeout=timeout_seconds,
)
return result
except asyncio.TimeoutError:
return {"error": "Timeout: l'agente ha impiegato troppo tempo."}
Best Practice per la Gestione Errori
| Strategia | Quando Usarla | Implementazione |
|---|---|---|
| Retry con backoff | Errori transitori (rate limit, timeout) | .with_retry() |
| Fallback provider | Downtime del provider principale | .with_fallbacks() |
| Recursion limit | Prevenzione loop infiniti | recursion_limit nel config |
| Timeout globale | Limite tempo di esecuzione | asyncio.wait_for() |
| Error handling nei tool | Errori prevedibili nei tool | ToolException con fallback message |
Case Study: Research Assistant Autonomo
Per mettere in pratica tutti i concetti trattati, costruiamo un agente completo: un Research Assistant autonomo che riceve una domanda di ricerca, cerca informazioni sul web, analizza i risultati e genera un report strutturato. Questo esempio integra tool custom, LangGraph per l'orchestrazione, stato persistente e gestione errori.
Definizione dei Tool
from langchain_core.tools import tool
from datetime import datetime
@tool
def search_web(query: str, num_results: int = 5) -> str:
"""Cerca informazioni sul web per una query di ricerca.
Args:
query: La query di ricerca.
num_results: Numero di risultati da restituire.
Returns:
Risultati della ricerca formattati.
"""
from tavily import TavilyClient
client = TavilyClient()
results = client.search(query, max_results=num_results)
formatted = []
for r in results["results"]:
formatted.append(
f"Titolo: {r['title']}\nURL: {r['url']}\n"
f"Contenuto: {r['content'][:300]}\n"
)
return "\n---\n".join(formatted)
@tool
def save_report(title: str, content: str, format: str = "markdown") -> str:
"""Salva il report di ricerca generato in un file.
Args:
title: Titolo del report.
content: Contenuto completo del report.
format: Formato del file (markdown o text).
Returns:
Conferma del salvataggio con il percorso del file.
"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
ext = "md" if format == "markdown" else "txt"
filename = f"report_{timestamp}.{ext}"
with open(filename, "w", encoding="utf-8") as f:
f.write(f"# {title}\n\n{content}")
return f"Report salvato con successo: {filename}"
tools = [search_web, save_report]
Costruzione del Grafo dell'Agente
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
# Modello con tool binding
llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
llm_with_tools = llm.bind_tools(tools)
# System prompt dettagliato per il research assistant
SYSTEM_PROMPT = """Sei un Research Assistant autonomo. Il tuo compito e:
1. Analizzare la domanda di ricerca dell'utente
2. Cercare informazioni rilevanti sul web (usa più query diverse)
3. Sintetizzare i risultati in un report strutturato
4. Salvare il report finale
Segui sempre questo processo:
- Fai almeno 2-3 ricerche con query diverse per avere prospettive multiple
- Cita le fonti nel report
- Struttura il report con sezioni chiare
- Salva il report quando hai raccolto abbastanza informazioni
"""
def call_model(state: MessagesState):
"""Nodo principale: chiama il modello con il contesto corrente."""
messages = [("system", SYSTEM_PROMPT)] + state["messages"]
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
# Costruzione del grafo
graph = StateGraph(MessagesState)
graph.add_node("agent", call_model)
graph.add_node("tools", ToolNode(tools=tools))
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", tools_condition)
graph.add_edge("tools", "agent")
# Compilazione con memoria e limiti
checkpointer = MemorySaver()
research_agent = graph.compile(
checkpointer=checkpointer,
)
# Esecuzione
config = {
"configurable": {"thread_id": "research-001"},
"recursion_limit": 30,
}
result = research_agent.invoke(
{"messages": [("user", "Analizza lo stato attuale dei framework per AI Agents nel 2026")]},
config=config,
)
L'agente iniziera cercando informazioni con query diverse, analizzera i risultati e generera un report
strutturato con fonti citate. Grazie al checkpointing, è possibile riprendere la conversazione in
qualsiasi momento con lo stesso thread_id, e l'agente ricordera tutto il contesto precedente.
Riepilogo
In questo articolo abbiamo esplorato in profondità l'ecosistema LangChain nel 2026, dalla sua architettura modulare ai pattern più avanzati per la costruzione di agenti AI. Ecco i punti chiave:
- Architettura modulare: LangChain è organizzato in pacchetti separati (core, provider-specific, community) per massima flessibilità e performance
- AgentExecutor e deprecato: il sistema legacy presentava limiti strutturali in modularita, debugging e supporto per stati persistenti
- LangGraph e lo standard: l'architettura graph-based offre branching condizionale, checkpointing nativo e human-in-the-loop integrato
- Tool design: il decoratore
@toolcon type hints e docstring dettagliati permette di creare strumenti che l'LLM sa usare efficacemente - LCEL: LangChain Expression Language rende la composizione di chain dichiarativa e leggibile con l'operatore pipe
- Resilienza: retry, fallback e timeout sono essenziali per agenti in produzione
Prossimo Articolo
Nel prossimo articolo della serie esploreremo CrewAI, un framework indipendente che adotta un paradigma completamente diverso: il role-playing. Vedremo come coordinare team di agenti specializzati dove ogni agente interpreta un ruolo specifico (ricercatore, scrittore, revisore) e collabora con gli altri per raggiungere obiettivi complessi. Scopriremo i process type (Sequential, Hierarchical, Consensual) e come CrewAI semplifica drasticamente la costruzione di sistemi multi-agente.







