Waarom ik LangChain niet gebruik (en wat ik in plaats daarvan doe) (Nederlands (Dutch))

Waarom ik LangChain niet gebruik (en wat ik in plaats daarvan doe)

Thursday, 18 December 2025

//

14 minute read

Ik ben een .NET ontwikkelaar. Toen ik begon met het bouwen van LLM-aangedreven systemen, wees iedereen me naar LangChain. "Het is de standaard," zeiden ze. "Alle voorbeelden gebruiken het." En ze hadden gelijk - als je in het Python ecosysteem, LangChain is overal.

Maar hier is het ding: Ik ontwijk LangChain niet omdat het slecht is. Ik vermijd het omdat het problemen oplost die ik al explicieter oplos, en voor mijn gebruik gevallen - C#, lokale gevolgtrekking, privacy, determinisme - kaders toevoegen wrijving in plaats van waarde.

Dit is geen anti-LangChain post. Het is een bericht over het begrijpen van wat problemen kaders op te lossen, en het beseffen dat je ze misschien niet nodig hebt.

Thesis: Als je de problemen begrijpt die LangChain oplost, heb je LangChain niet nodig.

Wat LangChain eigenlijk goed doet

LangChain blinkt uit in verschillende dingen:

Snelle prototyping De eerste voorbeelden zijn echt goed.

Integratie van het Python-ecosysteem - Als je al in de Python/Jupyter/pandas wereld bent, lijmt LangChain alles naadloos aan elkaar.

De barrière verlagen - Voor mensen die nieuw zijn bij LLM's, biedt het nuttige abstracties: prompt sjablonen, tool calling patronen, geheugenbeheer, vector DB integraties.

LangChain is een integration acceleratorHet versnelt het pad van "Ik heb een idee" naar "Ik heb een demo." Dat is waardevol.

Maar het is ook waar de problemen beginnen voor mij als een C# ontwikkelaar gebouw productie systemen.

De problemen LangChain Solves

Voordat je een kader afwijst, moet je begrijpen welke problemen het oplost. LangChain pakt deze echte problemen aan:

  1. Contextconstructie - Het bouwen van coherente aanwijzingen uit schema, monsters, geschiedenis, en beperkingen
  2. Tool orkestration - Het beheren van meerdere tool calls in volgorde met voorwaardelijke logica
  3. Staatsbeheer - Behoud van conversatiecontext in meerdere bochten
  4. Opnieuw en foutafhandeling - Generatief herstellen wanneer de LLM ongeldige uitvoer genereert
  5. Meerstapsredenering - Het breken van complexe taken in opeenvolgende stappen (het "agent"-patroon)
  6. Waarneembaarheid - Het volgen van wat er echt gebeurd is tijdens de executie

Dit zijn legitieme problemen. De vraag is: heb je een kader nodig om ze op te lossen?

Waar LangChain begint pijn te doen

Voor mijn werk - bouwproductie .NET systemen met lokale LLM's, strenge privacyvereisten en deterministisch gedrag - LangChain introduceert wrijving op verschillende gebieden.

Verborgen Staat en Impliciete Controlestroom

LangChain beheert geheugen en context voor u. Dat klinkt handig totdat u moet debuggen waarom uw prompt 10.000 tokens langer is dan verwacht, of waarom de LLM plotseling toegang heeft tot de conversatiegeschiedenis die u dacht dat u had opgeruimd.

Het framework concateert prompts, beheert geheugen, en behandelt uitvoeringsorder impliciet. Als er iets breekt, debug je het gedrag van het framework, niet het gedrag van je code.

Framework-coupled Thinking

Zodra je LangChain adopteert, begin je te ontwerpen voor LangChain. Uw architectuur wordt gekoppeld aan de abstracties van het kader: ketens, agenten, retrievers, geheugenbuffers.

Dit is niet uniek voor LangChain - alle kaders doen dit. Maar in een snel bewegend veld als LLM's, waar de juiste abstracties nog niet geregeld zijn, is koppeling aan het wereldbeeld van een framework riskant.

De Python Impedance Mismatch

LangChain gaat ervan uit:

  • Langlevende processen (workflows in notebookstijl)
  • Veranderbare mondiale toestand
  • Python's dynamische typen en eenden typen
  • Blokkeren van I/O patronen

Als een .NET ontwikkelaar, neem ik aan:

  • Request-scoped lifetimes (ASP.NET Core patronen)
  • Onveranderlijke of expliciet beheerde toestand
  • Sterke typing en compile-time veiligheid
  • Overal async/wacht

De LangChain .NET poorten bestaan, maar ze spelen inhaalslag met de Python versie, en de abstracties voelen nog steeds vreemd aan idiomatische C#.

Productie Reality Gaps

Wanneer je van prototype naar productie verhuist, heb je het volgende nodig:

  • Determinisme - Dezelfde input zou voorspelbaar gedrag moeten produceren
  • Validatie - Zorg ervoor dat de uitgang van de LLM veilig is voordat u deze uitvoert
  • Sandboxen - Beperk wat gegenereerde code eigenlijk kan doen
  • Kostenbeheersing - Track token gebruik en beperkingen opleggen
  • Lokale gevolgtrekking - Start modellen offline zonder cloud afhankelijkheden

LangChain optimaliseert de iteratiesnelheid, niet de productieharding. Dat is prima voor demo's; het is een probleem voor de productie.

Wat ik bouw in plaats daarvan

Hier is het mentale model dat ik gebruik: LLM's zijn redenerende motoren, geen uitvoeringsmotoren.

LLM's Do:

  • Interpretatie - Het begrijpen van de intentie van de gebruiker uit natuurlijke taal
  • Planning - Het breken van complexe taken in stappen
  • Vertaling - Converteren van intentie in gestructureerde formaten (SQL, JSON, functiegesprekken)

LLM's NIET:

  • Bereken aggregaten - Samenvatting 100.000 rijen
  • Datasets scannen - Zoeken door grote bestanden
  • Eigen staat - Behoud van geheugen op lange termijn

Het beginsel: LLM's reden, motoren berekenen.

Deze scheiding drijft alles wat ik bouw.

Expliciete context, geen magisch geheugen

In plaats van framework-managed geheugen, bouw ik de context expliciet per verzoek:

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; }
}

Elke snelle constructie is zichtbaar. Ik weet precies wat er naar de LLM wordt gestuurd omdat ik zelf de snaar heb gebouwd:

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();
}

Geen verborgen toestand, geen magische samenzwering... alleen expliciete snaaropbouw... als het fout is, weet ik waarom.

Deterministische uitvoeringslaag

In plaats van de LLM iets te laten uitvoeren, gebruik ik het om te genereren intent, voert die intentie uit door middel van deterministische motoren:

  • SQL-motoren (DuckDB) - Voor gegevensqueries
  • Zoekmachines (Luceen, Postgres full-text) - Voor het ophalen van documenten
  • Regelmotoren - Voor bedrijfslogica
  • Domeindiensten - Voor gevalideerde bewerkingen

De LLM genereert SQL. DuckDB voert het uit. De LLM ziet de gegevens nooit:

// 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);

Dit is veiliger, sneller en debugbaar. De LLM kan niet per ongeluk draaien DROP TABLE omdat ik eerst de SQL valideer. De LLM kan geen data lekken omdat het nooit de data ziet - alleen het schema.

Een concreet voorbeeld: CSV-analyse zonder kaders

Ik schreef onlangs over het analyseren van grote CSV-bestanden met lokale LLM's. De architectuur:

Gebruikersvraag → LLM → SQL → DuckDB → Resultaten

De LLM ontvangt:

  • Het CSV-schema (kolomnamen en -typen)
  • 3 steekproefrijen (om gegevensformaat te begrijpen)
  • Vraag van de gebruiker

De LLM genereert:

  • Een DuckDB SQL query

Het systeem:

  • Valideert de SQL met behulp van EXPLAIN (vangstsyntaxisfouten zonder uitvoering)
  • Voert de zoekopdracht uit met het CSV-bestand
  • Geeft resultaten terug aan de gebruiker

De LLM ziet nooit de werkelijke data. Het ziet alleen structuur.

Dit is wat LangChain een "agent" zou noemen - een systeem dat een LLM gebruikt om acties te genereren, te valideren, uit te voeren en mogelijk opnieuw op mislukking.

Alleen bouwde ik het in ~200 lijnen van C# zonder kader:

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);
    }
}

Geen ketens, geen agenten kader, geen magie. Gewoon expliciete orkestratie van LLM → validatie → uitvoering.

Wat is een agent, echt?

De term "agent" wordt constant rondgegooid, meestal om te betekenen "alles met een LLM." Laten we precies zijn.

Een agent is:

  • Een lus - Het heeft meerdere iteraties.
  • Met status - Het herinnert zich wat het geprobeerd heeft.
  • met gereedschap - Het kan actie ondernemen in de wereld
  • Met feedback - Het observeert resultaten en past zich aan

Een agent is geen bibliotheekHet is een patroon.

Mijn agent patroon 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);
    }
}

Dit is een agent. Het is een lus met staat, gereedschap en feedback. Ik schreef het in 30 regels. Ik had geen kader nodig.

Waar Microsoft's Agent Framework past

Om eerlijk te zijn tegen het .NET ecosysteem, Microsoft heeft de Microsoft Agent Framework dat is speciaal gebouwd voor .NET ontwikkelaars bouwen productie AI systemen.

Wat het Microsoft Agent Framework krijgt goed

Het kader (voorheen bekend als Microsoft.Extensions.AI) biedt:

  • Expliciete orkestratie - Jij controleert de agentlus, niet het kader.
  • Sterk typen - Compile-time veiligheid voor instrumentdefinities en functieaanroepen
  • Observeerbaarheid van eerste klas - Ingebouwde telemetrie, logging en gedistribueerd traceren via OpenTelemetrie
  • Bedrijfsgrenzen - Ontworpen voor productie .NET systemen met de juiste DI, configuratie, en lifecycle management
  • Ondersteuning met meerdere modellen - Abstracties over OpenAI, Azure OpenAI, Ollama en andere aanbieders
  • Integratie van semantische kernel - Werkt met Microsoft's bredere AI stack

Belangrijkste onderdelen:

  • IChatClient - Unified interface voor chatvoltooids
  • IEmbeddingGenerator - Vector inbeddingen over de providers
  • AIFunction - Type-veilige functie bellen
  • Middleware pijpleiding - Voor logging, retry, caching, telemetrie

Voorbeeld:

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;
});

Waar ik nog steeds beneden blijf

Zelfs met Microsoft's framework, geef ik er de voorkeur aan om core orkestratie expliciet te houden:

Ik wil niet:

  • Ondoorzichtige planners - Het kader autonoom beslissen welk instrument te bellen
  • Impliciete gereedschapsselectie - Magische routering op basis van natuurlijke taalbeschrijvingen
  • Verborgen retry logica - Framework-beheerde fout herstel Ik kan niet inspecteren

Ik wil:

  • Zichtbare lussen - Ik zie elke iteratie in mijn code.
  • Te testen stappen - Ik kan de beslissingslogica testen.
  • Vervangbare onderdelen - Ik kan de LLM ruilen, de tools, de validatielaag
  • Expliciete staat - Ik weet precies wat er in de context staat.

Microsoft's Agent Framework is dichter bij hoe ik denk dan LangChain. Het respecteert .NET patronen, gebruikt afhankelijkheid injectie goed, en vecht niet tegen het ecosysteem. Maar ik nog steeds liever het schrijven van de orkestratie zelf.

Wanneer het Microsoft Agent Framework te gebruiken:

  • Bouwen van chattoepassingen met functieaanroepen
  • Noodzaak multi-model ondersteuning (schakelaar tussen OpenAI, Azure, Ollama)
  • Wil enterprise features (telemetrie, logging, gedistribueerd traceren)
  • Werken in een team dat de voorkeur geeft aan consistentie van het kader
  • Bouwen op de top van Semantic Kernel

Wanneer moet je framework-less gaan:

  • Je hebt volledige controle over de agent loop nodig.
  • Je bouwt aangepaste redeneerpatronen.
  • Je wilt nul abstractie overhead
  • U optimaliseert voor specifieke use cases (zoals CSV analyse of web scraping)
  • U wilt precies begrijpen hoe het werkt

Het framework elimineert geen architectonische beslissingen. Je kiest nog steeds wat je in de context moet zetten, hoe je data moet splitsen, en wanneer je het opnieuw moet proberen. Het maakt het sanitair makkelijker.

Waarom dit scales betere lange termijn

Kaderloze systemen verouderen om verschillende redenen beter:

Prestaties - Geen abstractie overhead. Mijn CSV query service draait sub-100ms omdat er geen kader is tussen de LLM en DuckDB.

Kostenvoorspelbaarheid - Ik controleer precies wat er naar de LLM gaat.

Debugability - Als er iets breekt, debug ik mijn code, niet reverse-engineer ik de magie van een framework.

Privacy - Voor systemen met strikte data residentie eisen, weten precies wat laat de machine telt.

Offlinescenario's - Rand apparaten, lucht-gapped netwerken, gereguleerde omgevingen. Kaders veronderstellen internet toegang en cloud services.

Naleving van de regelgeving - In financiën, gezondheidszorg en overheid, moet je vaak elke beslissing uitleggen en controleren. "Het kader deed het" is geen acceptabel antwoord.

Hoe meer beperkingen uw omgeving, hoe meer u wilt expliciete controle.

When I would use LangChain

Om critici te ontwapenen: er zijn legitieme gevallen waar ik zou reiken naar LangChain.

Hackathons - Snelheid naar demo is belangrijker dan architectuur.

Gooibare POC's - Als je toch een idee valideert en van plan bent om te herschrijven voor productie.

Python-zware teams - Als je team al vloeiend is in Python, is het ecosysteem sterk.

Onderwijsconcepten - LangChain's abstracties kunnen beginners helpen het agent patroon te begrijpen voordat ze zelf bouwen.

Weten wanneer niet Om iets te gebruiken is net zo waardevol als weten wanneer om het te gebruiken.

Het bredere patroon: Frameworks vs. First Principles

Het gaat niet om LangChain, maar om de afweging tussen kaders en eerste principes.

Frameworks versnellen vertrouwde problemen. Als je de 100e CRUD API bouwt, bereik dan het Entity Framework of Dapper. De patronen zijn geregeld.

Maar LLM-aangedreven systemen? De juiste abstracties zijn nog niet geregeld. We weten niet of "ketens" of "agents" of "retrievers" de juiste mentale modellen zijn. We zijn het nog steeds aan het uitzoeken.

In die omgeving bouw ik liever dicht bij het metaal:

  • LLM's via directe API-oproepen (OllamaSharp, OpenAI SDK)
  • Snelle constructie via expliciete snaarbouw
  • Validatie via domeinspecifieke logica
  • Uitvoering via speciaal gebouwde motoren (SQL, zoeken, enz.)

Als .NET-ontwikkelaar heb ik sterke meningen over hoe systemen opgebouwd moeten worden: expliciete levensduurn, sterke typen, async helemaal naar beneden, afhankelijkheidsinjectie voor testbaarheid.

LangChain's abstracties brengen die meningen niet goed in kaart, dus ik gebruik het niet.

The Takeaway

Als je een .NET ontwikkelaar bent die LangChain bekijkt en je afvraagt "Heb ik dit nodig?" dan is hier mijn antwoord:

Je moet de problemen oplossen LangChain lost - context management, instrument orkestratie, retry logica, observeerbaarheid.

Je hebt LangChain niet nodig om ze op te lossen. - Vooral als u explicietheid, sterke typen, en productie verharding over snelle prototyping waardeert.

Het principe waarop ik voortborduur:

"LLM's reden. Motoren berekenen. Orkestratie is van jou."

Of simpeler:

"Als je de problemen begrijpt die een kader oplost, heb je vaak het kader niet nodig."

Bouw systemen die zinvol zijn in uw ecosysteem, met uw beperkingen, met behulp van uw taal idiomen. Voor mij, dat is C#, sterk typen, expliciete controle flow, en deterministische uitvoering lagen.

Voor jou is het misschien anders.

Het doel is niet om kaders te vermijden. Het doel is om ze bewust te kiezen, begrijpen zowel wat ze bieden en wat ze kosten.


Meer lezen:

Finding related posts...
logo

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