---
title: "LangChain, LangGraph, OpenRouter e Langfuse: cosa scegliere e quando"
source: "https://www.matteovincenti.com/blog/langchain-langgraph-openrouter-langfuse-quale-scegliere"
language: it
author: "Matteo Vincenti"
published: 2026-10-20
updated: 2026-10-24
category: "AI"
tags: ["LangChain", "LangGraph", "OpenRouter", "Langfuse"]
---

# LangChain, LangGraph, OpenRouter e Langfuse: cosa scegliere e quando

LangChain, LangGraph, OpenRouter e Langfuse: ruoli, benefici da valutare e criteri di scelta per un progetto AI aziendale. Scenari e dettagli tecnici.

![Una scena teatrale mostra un binario, uno scambio e una luce di ispezione](https://cdn.sanity.io/images/xrqkwry7/production/ad5f1acecfaacff36e3a793f5cecfa109b65d373-1728x910.png?w=1400&fit=max&auto=format)

Categoria: AI · Tag: LangChain, LangGraph, OpenRouter, Langfuse

Un'applicazione che assegna una categoria a un ticket e una che prepara un acquisto da approvare possono usare lo stesso modello AI. Il lavoro attorno al modello cambia parecchio: nella prima basta ottenere un risultato valido; nella seconda bisogna ritrovare una proposta dopo ore, riconoscere chi l'ha approvata e gestire l'ordine inviato a un altro sistema.

È da questa differenza che partirei per scegliere fra LangChain, LangGraph, OpenRouter e Langfuse. Sono strumenti combinabili, con alcune sovrapposizioni. Adottarli tutti insieme significa anche mantenere tutte le loro integrazioni. Preferisco assegnare prima un compito a ogni livello e tenere fuori quello che ancora non serve.

## A cosa servono i quattro strumenti

_A cosa servono i quattro strumenti_

| Strumento | Responsabilità utile in questo confronto | Quando entra nella scelta |
| --- | --- | --- |
| LangChain | Costruire un agente componendo modello, tool, prompt e middleware, cioè regole che intervengono durante l'esecuzione. | Il modello deve scegliere strumenti e proseguire il lavoro in più turni; integrazioni e regole ricorrenti giustificano un harness condiviso. |
| LangGraph | Orchestrare un processo come grafo: i nodi svolgono operazioni, gli archi stabiliscono il passaggio successivo e lo stato conserva i dati del lavoro. | Occorre controllare esplicitamente rami, attese e riprese del processo. |
| OpenRouter | Accedere a modelli attraverso un'API e controllare il routing verso i provider che li servono. | Servono più modelli o regole di accesso e instradamento da gestire in un punto comune. |
| Langfuse | Ricostruire le esecuzioni tramite trace, gestire prompt e valutare risultati. | Bisogna capire quale passaggio ha prodotto un errore, confrontare varianti o lavorare su un insieme di casi di valutazione. |

Un *harness* è il codice attorno al modello che ne governa l'uso: esegue i tool richiesti, restituisce i risultati e applica limiti e condizioni di arresto. Gli agenti LangChain usano LangGraph come runtime; LangGraph può essere utilizzato anche senza LangChain. Scegliere LangChain e progettare direttamente un grafo sono quindi due livelli diversi di controllo. [LangChain overview](https://docs.langchain.com/oss/python/langchain/overview), [LangGraph overview](https://docs.langchain.com/oss/python/langgraph/overview).

Anche OpenRouter comprende più del gateway: il suo Agent SDK gestisce il ciclo modello-tool, approvazioni e stato persistibile. Langfuse si integra inoltre con applicazioni che non usano questi framework. La tabella orienta la scelta; nei due casi seguenti contano le responsabilità effettivamente assegnate. [OpenRouter Agent SDK](https://openrouter.ai/docs/agent-sdk/call-model/tool-approval-state), [Langfuse e OpenTelemetry](https://langfuse.com/integrations/native/opentelemetry).

## Una categoria per un ticket: partirei dall'SDK diretto

Prendo un caso circoscritto: arriva un ticket, il modello propone una categoria fra quelle ammesse e l'applicazione la salva. Il percorso può stare nel servizio esistente:

1. Il servizio legge il ticket autorizzato e prepara la richiesta.
2. L'SDK del provider invia una chiamata al modello.
3. Il codice valida la struttura ricevuta e controlla che la categoria esista.
4. Salva il risultato accettato; un output invalido va alla revisione prevista dal prodotto.

La categoria può essere formalmente valida e comunque sbagliata: riconoscere quel caso richiede ticket con classificazioni di riferimento e un criterio di accettazione. Il controllo del formato e quello sul significato hanno lavori diversi.

In questa architettura terrei inizialmente fuori i quattro strumenti.

_Una categoria per un ticket: partirei dall'SDK diretto_

| Componente escluso | Motivo | Che cosa farebbe cambiare la scelta |
| --- | --- | --- |
| LangChain | Il codice decide già l'unica chiamata; manca un ciclo agente da gestire. | Il modello deve consultare strumenti e decidere come proseguire, oppure si accumulano integrazioni e politiche comuni. |
| LangGraph | Il caso si conclude nella richiesta e non prevede una ripresa successiva. | Compaiono attese, rami e recuperi che il processo deve conservare. |
| OpenRouter | Il provider è già scelto e il suo SDK soddisfa l'accesso richiesto. | Serve confrontare più modelli o centralizzare regole di routing. |
| Langfuse | I log esistenti rispondono ancora alla domanda diagnostica iniziale. | Occorre correlare passaggi, versioni dei prompt e risultati su molti casi. |

Conserverei l'identificativo del ticket, il modello richiesto, la fase dell'operazione, la durata, l'esito e il dettaglio dell'errore. Questi record aiutano a distinguere un problema nella chiamata da uno nella validazione o nel salvataggio. Nel classificatore affido al servizio applicativo il limite di tempo e il numero massimo di tentativi. Anche i retry dell'SDK devono rientrare in quel budget; se non sono controllabili, li disabilito e li gestisco nel servizio. Quando il budget finisce, il ticket passa alla revisione prevista.

Questa partenza segue un criterio che condivido nell'analisi di Anthropic: usare la soluzione più semplice adatta al compito e aggiungere complessità quando risolve un bisogno riconoscibile. [Building effective agents](https://www.anthropic.com/research/building-effective-agents).

## Un acquisto che aspetta una decisione: rendere esplicito il processo

Ora il requisito cambia. L'applicazione raccoglie dati autorizzati, prepara una proposta d'acquisto e attende un responsabile. Una proposta incompleta torna alla preparazione; una rifiutata si chiude; una approvata può diventare un ordine. L'attesa può durare ore e il servizio può riavviarsi nel frattempo.

Qui sceglierei LangGraph se voglio rappresentare questi passaggi e i relativi rami nel codice del workflow. L'architettura proposta è questa:

1. **Preparazione.** Raccoglie i dati e produce una proposta con versione, importo e destinatario.
2. **Validazione.** Controlla i requisiti dell'acquisto. Se mancano dati, torna alla preparazione con gli errori da correggere.
3. **Approvazione.** Conserva la proposta e sospende il processo. L'interfaccia mostra al responsabile la versione precisa su cui decide; il backend autentica l'utente e verifica che possa approvarla.
4. **Ripresa.** Ricarica lo stesso caso. Un rifiuto chiude la pratica; una proposta modificata richiede una nuova approvazione.
5. **Invio dell'ordine.** Un nodo separato ricontrolla i permessi, invia l'azione approvata e registra l'identificativo restituito dal sistema esterno. Se l'esito è incerto, passa a una riconciliazione prima di tentare un nuovo ordine.

Nell'acquisto il workflow conserva nello stato la scadenza del caso e i tentativi già consumati. Ogni nodo e client lavora entro il budget rimasto; un errore non lo azzera alla ripresa. All'esaurimento il caso passa alla revisione manuale. L'esito incerto dell'ordine segue il percorso di riconciliazione, anziché autorizzare un nuovo invio.

In LangGraph il *checkpointer* salva gli snapshot dello stato del singolo processo, identificato da un `thread_id`. Per ritrovarlo dopo un riavvio serve uno storage durevole: `InMemorySaver` conserva i dati soltanto nella RAM del processo. Questo riguarda l'uso della libreria; Agent Server gestisce l'infrastruttura di persistenza. Uno *Store*, invece, serve a conservare dati applicativi fra thread: non occorre aggiungerlo per la sola attesa di questo caso. [Persistence](https://docs.langchain.com/oss/python/langgraph/persistence).

L'interruzione si esprime con `interrupt()`; la ripresa usa `Command(resume=...)` sullo stesso thread. Il nodo interrotto riparte dall'inizio, perciò tengo l'invio dell'ordine nel nodo successivo all'approvazione. L'azione esterna richiede comunque una chiave stabile di idempotenza: ripetere la stessa richiesta deve permettere di recuperare l'ordine già creato, senza crearne un secondo. Se il destinatario non supporta questo contratto, va progettata una procedura di riconciliazione. [Interrupts](https://docs.langchain.com/oss/python/langgraph/interrupts).

Resta un punto da provare: l'ordine può essere stato accettato mentre il salvataggio locale fallisce. Nel grafo lo stato risulta incompleto, nel sistema esterno l'acquisto esiste già. Separare i nodi rende leggibile il passaggio; la gestione di quell'esito incerto appartiene all'integrazione con il destinatario.

### Quando terrei fuori anche LangGraph

Una sola approvazione prima di un tool può essere gestita dall'Agent SDK di OpenRouter con `requireApproval` e uno `StateAccessor` collegato a un backend persistente. I tool `deferred` possono attendere un sistema esterno e ricevere il risultato da un altro processo. Il solo “devo aspettare una persona” non basta a scegliere un grafo. [Tool approval e stato](https://openrouter.ai/docs/agent-sdk/call-model/tool-approval-state), [Deferred tools](https://openrouter.ai/docs/agent-sdk/call-model/async-tools).

Se quel loop soddisfa già il processo, lo terrei. Per la proposta con correzioni, rifiuti e riconciliazione sceglierei il grafo quando rende più chiaro chi controlla ogni transizione. Se l'applicazione ha già una macchina a stati persistente adeguata, valuterei prima di estenderla: introdurre LangGraph significa anche mantenerne modello di stato, dipendenze e politiche di recupero.

Dentro un nodo una chiamata diretta può bastare. LangChain diventa pertinente se proprio quel passaggio richiede un agente con tool; OpenRouter può fornire l'accesso ai modelli; Langfuse può osservare il percorso. Non li aggiungerei per completare lo stack.

## LangChain: delegare il ciclo agente e le integrazioni

Il caso interessante per LangChain è un assistente che consulta più strumenti e decide quali usare in base alle risposte. L'harness esegue il ciclo, mentre i middleware permettono di intervenire su chiamate, tool, limiti e retry. [Middleware](https://docs.langchain.com/oss/python/langchain/middleware/overview).

La scelta ha senso quando sostituisce integrazioni e regole ripetute con un comportamento che il team sa leggere e modificare. Per una breve sequenza fissata dal codice, scrivere quelle funzioni direttamente può restare più comprensibile.

Controllerei soprattutto dove finiscono i retry. Se il workflow ripete un nodo, il client ritenta la chiamata e il tool ripete l'azione esterna, un solo errore può generare tentativi a più livelli. Assegnerei al task un budget complessivo e renderei visibile ogni tentativo, compresi quelli falliti. Il contratto del tool rimane decisivo: input, risultato e autorizzazioni sono approfonditi nella [guida a MCP](https://www.matteovincenti.com/blog/mcp-guida-server-tools-skills).

## OpenRouter: decidere separatamente modello, provider e fallback

Un modello può essere servito da più provider. Nel routing di OpenRouter scelgo quindi sia il modello sia le condizioni per raggiungere chi lo esegue. Il campo `provider.only` limita i provider ammessi; `order` esprime una preferenza. Con `allow_fallbacks` attivo, il servizio può cercare altri provider oltre a quelli preferiti. [Provider routing](https://openrouter.ai/docs/guides/routing/provider-selection).

Il fallback fra modelli è un'altra decisione: una lista `models` autorizza a provare modelli alternativi dopo un errore. Il modello effettivamente usato compare nella risposta. Per un servizio può essere una scelta di disponibilità; per un confronto fra modelli può cambiare proprio il modello che si intende valutare. [Model fallbacks](https://openrouter.ai/docs/guides/routing/model-fallbacks).

Per questo separerei i due permessi nella configurazione e nei dati dell'esecuzione. Se una capacità è indispensabile, `provider.require_parameters: true` esclude i provider che non supportano i parametri richiesti. Il supporto del parametro resta distinto dalla correttezza della risposta.

Prima dell'adozione seguirei anche il percorso dei dati. La policy di OpenRouter e quella del provider sono due verifiche diverse; l'assenza di logging dei contenuti non significa assenza di metadati conservati. Un gateway aggiunge un punto comune di configurazione e un servizio da includere nella diagnosi degli errori. [Data collection](https://openrouter.ai/docs/guides/privacy/data-collection).

## Langfuse: ricostruire un errore e poi valutarne l'effetto

Una trace collega le operazioni di una richiesta: chiamata al modello, ricerca di documenti, tool e passaggi applicativi. Nel caso dell'acquisto vorrei vedere quale proposta è stata approvata, quale tentativo ha raggiunto il sistema esterno e dove è andato perso l'esito. Questo è il motivo per introdurre osservabilità strutturata. [Langfuse observability](https://langfuse.com/docs/observability/overview).

La valutazione risponde a una domanda ulteriore. Nel classificatore, per esempio, una trace senza errori può contenere la categoria sbagliata. In Langfuse posso collegare valutazioni manuali o automatiche a casi ed esecuzioni, ma devo scegliere il criterio: corrispondenza con una categoria di riferimento, completezza della proposta, rispetto dell'approvazione. [Evaluation](https://langfuse.com/docs/evaluation/overview).

Prima di esportare dati sceglierei cosa raccogliere e per quanto conservarlo. Il masking rimuove o sostituisce dati prima dell'esportazione. Va controllato sul percorso effettivo: nella documentazione Python corrente `mask_otel_spans` è raccomandato per i nuovi setup, mentre il precedente `mask` ha una copertura diversa. L'hook non modifica gli altri percorsi di esportazione né tutte le proprietà di uno span, cioè di una singola operazione tracciata: dati sensibili presenti altrove richiedono controlli separati. Userei valori sintetici riconoscibili per controllare input, output, metadati ed errori prima che escano dall'applicazione. [Masking](https://langfuse.com/docs/observability/features/masking).

Anche l'hosting pesa nella scelta. Langfuse Cloud è gestito dal fornitore; con il self-hosting il team si prende in carico infrastruttura, aggiornamenti e conservazione. La configurazione Docker Compose presentata per test locali non offre da sola alta disponibilità e backup. [Self-hosting](https://langfuse.com/self-hosting).

## La prova con cui sceglierei l'architettura dell'acquisto

Prima di accettare la proposta progettuale, farei passare il candidato e l'alternativa più semplice attraverso lo stesso caso: una proposta resta in attesa, il worker viene arrestato, un nuovo processo riprende la pratica e riceve due volte la stessa approvazione. Il risultato atteso è la versione corretta, autorizzata dal responsabile, con un solo ordine identificabile.

Fisserei anche modalità di durabilità dei checkpoint e garanzie del backend: scrivere in uno storage durevole e confermare il salvataggio prima di avanzare sono decisioni collegate. Le modalità di LangGraph le distinguono esplicitamente. [Checkpointers](https://docs.langchain.com/oss/python/langgraph/checkpointers).

Aggiungerei un arresto dopo l'accettazione dell'ordine esterno e prima del salvataggio locale. È il punto che distingue una ripresa dimostrata durante l'attesa dal recupero di un'azione dall'esito incerto. In un caso separato modificherei la proposta durante l'attesa e consegnerei l'approvazione della versione precedente: nessun ordine deve partire finché quella corrente non viene approvata.

Se entrambe le architetture soddisfano questi requisiti, la ripresa non è più il motivo per aggiungere un livello. La scelta si sposta su quanto sia leggibile quel processo e sul lavoro necessario per mantenerlo quando cambiano approvazioni, rami e integrazioni.
