Back to "Perché non uso LangChain (e quello che faccio invece)"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Agents AI Architecture C# LLM Systems Design

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

Thursday, 18 December 2025

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.

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 integrazioneAccelera 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 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:

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:

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:

// 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. 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:

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 libreriaE' uno schema.

Il mio modello di agente in C#:

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 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:

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:

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.