# Perché non uso LangChain (e quello che faccio invece)

<!--category-- AI, Architecture, LLM, Agents, Systems Design, C# -->
<datetime class="hidden">2025-12-18T10:00</datetime>

Sono uno sviluppatore .NET. Quando ho iniziato a costruire sistemi LLM-powered, tutti mi hanno indicato verso LangChain. "E 'lo standard," hanno detto. "Tutti gli esempi lo usano." E avevano ragione - se siete nell'ecosistema Python, LangChain è ovunque.

Ma ecco il punto: non evito LangChain perché è brutto. Lo evito perché risolve i problemi che ho già risolto in modo più esplicito, e per i miei casi d'uso - C#, inferenza locale, privacy, determinismo - i framework aggiungono attrito piuttosto che valore.

Questo non è un post anti-LangChain. E 'un post sulla comprensione di quali framework problemi risolvere, e rendersi conto che non si potrebbe avere bisogno di loro.

**Tesi: Se capisci i problemi che LangChain risolve, non hai bisogno di LangChain.**

[TOC]

## Quello che LangChain in realtà fa bene

Prima di tutto siamo onesti. LangChain eccelle in diverse cose:

**Prototipazione rapida** - Si può avere una demo di lavoro in pochi minuti. Gli esempi di iniziare sono veramente buoni.

**Integrazione dell'ecosistema Python** - Se sei già nel mondo Python/Jupyter/pandas, LangChain colleziona tutto senza problemi.

**Abbassare la barriera** - Per le persone nuove a LLMs, fornisce astrazioni utili: template prompt, modelli di chiamata degli strumenti, gestione della memoria, integrazioni DB vettoriali.

LangChain è un **acceleratore di integrazione**Accelera il percorso da "Ho un'idea" a "Ho una demo."

Ma è anche dove i problemi iniziano per me come un C# sviluppatore di sistemi di produzione di costruzione.

## I problemi LangChain risolve

Prima di scartare un quadro, è necessario capire quali problemi sta risolvendo. LangChain affronta questi problemi reali:

1. **Costruzione del contesto** - Costruire prompt coerenti da schema, campioni, storia, e vincoli
2. **Orchestrazione degli strumenti** - Gestione di più chiamate tool in sequenza con logica condizionale
3. **Gestione dello Stato** - Mantenere il contesto di conversazione attraverso più giri
4. **Riprova e gestione degli errori** - Recuperare con grazia quando l'LLM genera output non valido
5. **Ragione multi-step** - Dividere le attività complesse in fasi sequenziali (il modello "agente")
6. **Osservabilità** - Rintracciare ciò che è successo durante l'esecuzione

Questi sono problemi legittimi. La domanda è: avete bisogno di un quadro per risolverli?

## Dove LangChain inizia a ferire

Per il mio lavoro - costruire sistemi .NET di produzione con LLM locali, rigorosi requisiti di privacy, e il comportamento deterministico - LangChain introduce attrito in diverse aree.

### Stato nascosto e flusso di controllo implicito

LangChain gestisce la memoria e il contesto per voi. Questo suona comodo fino a quando non è necessario debug perché il prompt è 10.000 gettoni più lungo del previsto, o perché la LLM improvvisamente ha accesso alla cronologia delle conversazioni che si pensava di aver eliminato.

Il framework concatena i suggerimenti, gestisce la memoria e gestisce implicitamente l'ordine di esecuzione. Quando qualcosa si rompe, si sta debug il comportamento del framework, non il comportamento del codice.

### Pensiero coordinato quadro

Una volta adottato LangChain, si inizia a progettare **per LangChain**. La tua architettura si accoppia alle astrazioni del framework: catene, agenti, retriever, buffer di memoria.

Questo non è unico per LangChain - tutti i framework lo fanno. Ma in un campo in rapida evoluzione come gli LLM, dove le astrazioni giuste non sono ancora sistemate, accoppiarsi alla visione del mondo di un framework è rischioso.

### The Python Impedance Mismatch

LangChain assume:

- Processi a lunga durata (flussi di lavoro in stile notebook)
- Stato mondiale mutabile
- Dattilografia dinamica e dattilografia dell'anatra di Python
- Bloccare i modelli di I/O

Come sviluppatore .NET, presumo:

- Durata delle richieste (modelli ASP.NET Core)
- Stato immutabile o esplicitamente gestito
- Forte dattilografia e sicurezza dei tempi di compilazione
- Async/Await ovunque

La [Porte .NET LangChain](https://github.com/tryAGI/LangChain) esistono, ma stanno giocando a recupero con la versione Python, e le astrazioni si sentono ancora estranei a C# idiomatico.

### Production Reality Gaps

Quando si passa dal prototipo alla produzione, è necessario:

- **Determinismo** - Stesso input dovrebbe produrre un comportamento prevedibile
- **Convalida** - Assicurarsi che l'uscita del LLM sia sicura prima di eseguirlo
- **SandboxingCity name (optional, probably does not need a translation)** - Limitare ciò che il codice generato può effettivamente fare
- **Controllo dei costi** - Tracciare l'uso token e imporre limiti
- **Inferenza locale** - Eseguire i modelli offline senza dipendenze cloud

LangChain ottimizza la velocità di iterazione, non l'indurimento di produzione. Va bene per le demo; è un problema per la produzione.

## Cosa costruisco invece

Ecco il modello mentale che uso: **I LLM sono motori di ragionamento, non motori di esecuzione.**

### LLMs Do:

- **Interpretazione** - Comprendere l'intento dell'utente dal linguaggio naturale
- **Pianificazione** - Rottura di compiti complessi in fasi
- **Traduzione** - Conversione dell'intento in formati strutturati (SQL, JSON, chiamate di funzione)

### I LLM NON:

- **Aggregati di calcolo** - Sommando 100.000 righe
- **Scansiona set di dati** - Ricerca attraverso file di grandi dimensioni
- **Stato proprio** - Mantenere la memoria a lungo termine

Il principio: **LLMs ragione. motori calcolare.**

Questa separazione guida tutto quello che costruisco.

### Contesto esplicito, non memoria magica

Invece della memoria gestita da framework, costruisco esplicitamente il contesto per richiesta:

```csharp
public class QueryContext
{
    public List<ColumnInfo> Schema { get; set; }
    public List<Dictionary<string, string>> SampleRows { get; set; }
    public List<ConversationTurn> History { get; set; }
    public string UserQuestion { get; set; }
}
```

Ogni costruzione rapida è visibile. So esattamente cosa viene inviato alla LLM perché ho costruito io stesso la corda:

```csharp
private string BuildPrompt(QueryContext context)
{
    var sb = new StringBuilder();
    sb.AppendLine("You are a SQL expert. Generate a query based on:");
    sb.AppendLine();
    
    // Schema
    sb.AppendLine("Schema:");
    foreach (var col in context.Schema)
        sb.AppendLine($"  - {col.Name}: {col.Type}");
    
    // History (if any)
    if (context.History.Any())
    {
        sb.AppendLine("\nPrevious conversation:");
        foreach (var turn in context.History.TakeLast(3))
            sb.AppendLine($"  Q: {turn.Question} → SQL: {turn.Sql}");
    }
    
    // Current question
    sb.AppendLine($"\nQuestion: {context.UserQuestion}");
    sb.AppendLine("Generate SQL (no explanation, just the query):");
    
    return sb.ToString();
}
```

Niente stato nascosto, nessuna concatenazione magica, solo un'esplicita costruzione di stringhe, quando e' sbagliato, so perche'.

### Livelli di esecuzione deterministici

Invece di lasciare che l'LLM esegua qualsiasi cosa, la uso per generare **intento**, quindi eseguire tale intento attraverso motori deterministici:

- **Motori SQL** (DuckDB) - Per richieste di dati
- **Motori di ricerca** (Lucene, Postgres full-text) - Per il recupero di documenti
- **Motori a regole** - Per la logica degli affari
- **Servizi di dominio** - Per operazioni convalidate

LLM genera SQL. DuckDB lo esegue. LLM non vede mai i dati:

```csharp
// LLM generates intent
var sql = await GenerateSqlAsync(context);

// Validate before execution
var error = ValidateSql(connection, sql);
if (error != null)
{
    // Retry with error feedback
    sql = await GenerateSqlAsync(context, previousError: error);
}

// Execute in sandboxed engine
var results = ExecuteQuery(connection, sql);
```

Questo è più sicuro, veloce e debuggabile. L'LLM non può eseguire accidentalmente `DROP TABLE` perché convalido prima l'SQL. L'LLM non può trapelare dati perché non vede mai i dati - solo lo schema.

## Un esempio concreto: Analisi CSV Senza Framework

Recentemente ho scritto circa [analisi di grandi file CSV con LLM locali](https://mostlylucid.net/blog/analysing-large-csv-files-with-local-llms). L'architettura:

**Domanda dell'utente → LLM → SQL → DuckDB → Risultati**

L'LLM riceve:

- Lo schema CSV (nomi e tipi di colonne)
- 3 righe campione (per comprendere il formato dei dati)
- Domanda dell'utente

L'LLM genera:

- Una query SQL di DuckDB

Il sistema quindi:

- Convalida il SQL usando `EXPLAIN` (errori di sintassi delle catture senza esecuzione)
- Esegue la query contro il file CSV
- Restituisce i risultati all'utente

L'LLM non vede mai i dati reali. Vede solo la struttura.

Questo è ciò che LangChain definirebbe un "agente" - un sistema che utilizza un LLM per generare azioni, convalidarle, eseguirle, e potenzialmente riprovare sul fallimento.

Tranne che l'ho costruito in ~200 linee di C# senza framework:

```csharp
public class CsvQueryService
{
    private readonly OllamaApiClient _ollama;
    private readonly string _model;
    
    public async Task<QueryResult> QueryAsync(string csvPath, string question)
    {
        using var connection = new DuckDBConnection("DataSource=:memory:");
        connection.Open();
        
        // 1. Build context
        var context = BuildContext(connection, csvPath, question);
        
        // 2. Generate SQL
        var sql = await GenerateSqlAsync(context);
        
        // 3. Validate
        var error = ValidateSql(connection, sql);
        if (error != null)
        {
            // Retry once with error feedback
            sql = await GenerateSqlAsync(context, error);
        }
        
        // 4. Execute
        return ExecuteQuery(connection, sql);
    }
}
```

Niente catene, niente agenti framework, niente magia. Solo un'esplicita orchestrazione di LLM → convalida → esecuzione.

## Cos'e' un agente, davvero?

Il termine "agente" viene continuamente gettato in giro, di solito per significare "qualsiasi cosa che coinvolga un LLM." Cerchiamo di essere precisi.

Un agente è:

- **Un ciclo** - Funziona più iterazioni
- **Con stato** - Ricorda cosa ha provato.
- **con utensili** - Può agire nel mondo
- **Con feedback** - Osserva i risultati e si adatta

Un agente e' **non è una libreria**E' uno schema.

Il mio modello di agente in C#:

```csharp
public class Agent
{
    private readonly List<ConversationTurn> _history = new();
    
    public async Task<string> RunAsync(string goal)
    {
        while (!IsGoalAchieved(goal))
        {
            // 1. Generate next action based on history
            var action = await GenerateActionAsync(goal, _history);
            
            // 2. Validate before executing
            if (!IsActionSafe(action))
            {
                _history.Add(new ConversationTurn 
                { 
                    Action = action, 
                    Result = "REJECTED: Unsafe action" 
                });
                continue;
            }
            
            // 3. Execute through deterministic tool
            var result = await ExecuteActionAsync(action);
            
            // 4. Record and continue
            _history.Add(new ConversationTurn { Action = action, Result = result });
        }
        
        return GenerateSummary(_history);
    }
}
```

Questo è un agente. È un ciclo con stato, strumenti e feedback. L'ho scritto in 30 righe. Non avevo bisogno di un quadro.

## Dove Microsoft Agent Framework si adatta

Per essere giusto per l'ecosistema .NET, Microsoft ha rilasciato il [Microsoft Agent Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) che è costruito appositamente per gli sviluppatori .NET che costruiscono sistemi di produzione AI.

### Che cosa il Microsoft Agent Framework ottiene correttamente

Il framework (precedentemente noto come Microsoft.Extensions.AI) fornisce:

- **Orchestrazione esplicita** - Tu controlli il loop degli agenti, non il framework.
- **Forte dattilografia** - Compile-time sicurezza per le definizioni degli strumenti e la funzione di chiamata
- **Osservabilità di prima classe** - Telemetria integrata, registrazione e tracciamento distribuito tramite OpenTelemetry
- **Confini delle imprese** - Progettato per la produzione di sistemi .NET con corretta gestione del ciclo di vita, configurazione e DI
- **Supporto multimodello** - Abstractions over OpenAI, Azure OpenAI, Ollama, e altri fornitori
- **Integrazione del kernel semantico** - Funziona con lo stack AI più ampio di Microsoft

Componenti chiave:

- `IChatClient` - Interfaccia unificata per il completamento della chat
- `IEmbeddingGenerator` - Inserimento vettoriale tra i fornitori
- `AIFunction` - Funzione Type-Safe chiamante
- Middleware pipeline - Per la registrazione, riprovare, cache, telemetria

**Esempio:**

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddChatClient(builder => 
    builder.UseOllama("llama3.2")
           .UseOpenTelemetry()
           .UseLogging());

var app = builder.Build();

app.MapPost("/chat", async (IChatClient client, string message) =>
{
    var response = await client.CompleteAsync(message);
    return response.Content;
});
```

### Dove continuo a stare a basso livello

Anche con il framework di Microsoft, preferisco mantenere l'orchestrazione di base esplicita:

**Non voglio:**

- **Progettisti opachi** - Il quadro che decide autonomamente quale strumento chiamare
- **Selezione implicita degli strumenti** - Instradamento magico basato su descrizioni linguistiche naturali
- **Logica di riprova nascosta** - Framework-managed errore di recupero Non posso ispezionare

**Voglio:**

- **Loop visibili** - Vedo ogni iterazione nel mio codice.
- **Passi provabili** - Posso testare la logica decisionale.
- **Componenti sostituibili** - Posso scambiare l'LLM, gli strumenti, il livello di validazione
- **Stato esplicito** - So esattamente cosa c'e' nel contesto.

Microsoft Agent Framework è più vicino a come penso di LangChain. Rispetta i modelli .NET, utilizza l'iniezione di dipendenza correttamente, e non combatte l'ecosistema. Ma io stesso preferisco ancora scrivere l'orchestrazione.

**Quando usare Microsoft Agent Framework:**

- Costruire applicazioni di chat con funzione di chiamata
- Bisogno di supporto multi-modello (interruttore tra OpenAI, Azure, Ollama)
- Vuoi funzionalità aziendali (telemetria, registrazione, tracciamento distribuito)
- Lavorare in un team che preferisce la coerenza del quadro
- Edificio in cima al Kernel Semantico

**Quando andare framework-less:**

- Hai bisogno di controllo completo sul loop agente
- Stai costruendo schemi di ragionamento personalizzati
- Vuoi zero astrazione in alto
- Si sta ottimizzando per casi d'uso specifici (come l'analisi CSV o raschiamento del web)
- Vuoi capire esattamente come funziona

Il framework non elimina le decisioni architettoniche. Scegli ancora cosa mettere in contesto, come tagliare i dati, e quando riprovare. Rende solo l'impianto idraulico più facile.

## Perché questo scala meglio a lungo termine

I sistemi privi di quadro invecchiano meglio per diversi motivi:

**Prestazioni** Il mio servizio di query CSV funziona sotto i 100 metri perché non c'è un framework tra l'LLM e DuckDB.

**Prevedibilità dei costi** - Io controllo esattamente quello che va alla LLM. Nessuna inflazione improvvisa nascosta dalla memoria gestita da un framework.

**Debugability** Quando qualcosa si rompe, sto debugando il mio codice, non reverse-engineering la magia di un framework.

**Privacy** - Per sistemi con rigorosi requisiti di residenza dei dati, sapendo esattamente cosa rimane della macchina.

**Scenari fuori rete** - Dispositivi di bordo, reti pneumatiche, ambienti regolamentati. I framework assumono l'accesso a Internet e i servizi cloud.

**Conformità normativa** - In finanza, sanità e governo, spesso è necessario spiegare e controllare ogni decisione. "Il quadro è stato fatto" non è una risposta accettabile.

Più il vostro ambiente è limitato, più volete un controllo esplicito.

## Quando userei LangChain

Per disarmare i critici: ci sono casi legittimi in cui io raggiungerei LangChain.

**HackathonsCity name (optional, probably does not need a translation)** - La velocita' verso la demo conta piu' dell'architettura.

**POC di lancio** - Se stai convalidando un'idea e hai comunque intenzione di riscrivere per la produzione.

**Squadre Python-heavy** - Se la tua squadra è già fluente in Python, l'ecosistema in forma è forte.

**Concetti didattici** - Le astrazioni di LangChain possono aiutare i principianti a capire il modello dell'agente prima di costruirne uno.

Sapere quando **non** usare qualcosa è prezioso quanto sapere quando usarlo.

## Il modello più ampio: Frameworks vs. Primi Principi

Non si tratta di LangChain, si tratta del compromesso tra quadri e ingegneria dei primi principi.

I Framework accelerano i problemi familiari. Se stai costruendo la 100a API CRUD, raggiungi il Framework Entity o Dapper. I modelli sono regolati.

Ma i sistemi alimentati da LLM? Le astrazioni giuste non sono ancora sistemate. Non sappiamo se "catena" o "agente" o "retriever" siano i modelli mentali giusti. Stiamo ancora cercando di capirlo.

In quell'ambiente, preferisco costruire vicino al metallo:

- LLM tramite chiamate API dirette (`OllamaSharp`, OpenAI SDK)
- Prompt construction via explicit string building
- Convalida attraverso la logica specifica del dominio
- Esecuzione tramite motori appositamente costruiti (SQL, ricerca, ecc.)

Come sviluppatore di .NET, ho forti opinioni su come i sistemi dovrebbero essere costruiti: vite esplicite, forte dattilografia, asincronia fino in fondo, iniezione di dipendenza per testabilità.

Le astrazioni di LangChain non corrispondono a quelle opinioni, quindi non le uso.

## The TakeawayCity name (optional, probably does not need a translation)

Se sei uno sviluppatore .NET guardando LangChain e chiedendo "Ho bisogno di questo?", ecco la mia risposta:

**Devi risolvere i problemi che LangChain risolve** - gestione del contesto, orchestrazione degli strumenti, riprova logica, osservabilità.

**Non hai bisogno di LangChain per risolverli.** - Specialmente se apprezzi l'esplicitezza, la forte dattilografia e l'indurimento della produzione sulla rapida prototipazione.

Il principio su cui mi baserò è:

**"Motivazioni dell'LLM. I motori computano. L'orchestra è vostra."**

O più semplicemente:

**"Se capisci i problemi che un framework risolve, spesso non hai bisogno del framework."**

Costruisci sistemi che abbiano senso nel tuo ecosistema, con i tuoi vincoli, usando gli idiomi del tuo linguaggio. Per me, questo è C#, forte dattilografia, flusso di controllo esplicito e livelli di esecuzione deterministici.

Per te potrebbe essere diverso.

L'obiettivo non è quello di evitare quadri. L'obiettivo è quello di sceglierli consapevolmente, capire sia quello che forniscono e quello che costano.

---


**Ulteriore lettura:**

- [Analisi di grandi file CSV con LLM locali in C#](/blog/analysing-large-csv-files-with-local-llms) - Un esempio concreto di LLM + SQL senza framework
- [Recupero e analisi dei contenuti Web con LLM](/blog/fetching-and-analysing-web-content-with-llms) - Raschiatura e analisi web senza strutture
- [Documentazione Microsoft Agent Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) - Quadro ufficiale degli agenti Microsoft per .NET
- [Microsoft.Estensioni.AI](https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/) - Fondamentali astrazioni AI per .NET
- [Kernel semantico](https://github.com/microsoft/semantic-kernel) - Microsoft LLM orchestrazione SDK
- [Documentazione LangChain](https://python.langchain.com/) - Per capire cosa stai scegliendo di non usare.
- [OllamaSharpCity name (optional, probably does not need a translation)](https://github.com/awaescher/OllamaSharp) - Client C# per l'inferenza LLM locale