# Varför jag inte använder LangChain (och vad jag gör istället)

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

Jag är en .NET-utvecklare. När jag började bygga LLM-drivna system, alla pekade mig mot LangChain. "Det är standarden", sade de. "Alla exempel använder det." Och de hade rätt - om du är i Python ekosystem, LangChain är överallt.

Men så här är det: Jag undviker inte LangChain eftersom det är dåligt. Jag undviker det eftersom det löser problem jag redan löser mer explicit, och för mitt bruk fall - C#, lokala slutsatser, integritet, determinism - ramar lägga friktion snarare än värde.

Detta är inte ett anti-LangChain inlägg. Det är ett inlägg om att förstå vilka problem ramar lösa, och inse att du kanske inte behöver dem.

**Uppsats: Om du förstår problemen LangChain löser behöver du inte LangChain.**

[TOC]

## Vad LangChain faktiskt gör bra

Låt oss vara rättvisa först. LangChain utmärker sig på flera saker:

**Snabb prototypering** - Du kan få en fungerande demo på några minuter.

**Integrering av Python-ekosystem** - Om du redan är i Python/Jupyter/pandas-världen limmar LangChain ihop allt sömlöst.

**Sänkning av barriären** - För människor nya till LLMs, ger det användbara abstraktioner: snabba mallar, verktyg anropsmönster, minneshantering, vektor DB integrationer.

LangChain är en **integrationsaccelerator**, inte ett AI krav. Det påskyndar vägen från "Jag har en idé" till "Jag har en demo." Det är värdefullt.

Men det är också där problemen börjar för mig som en C# utvecklare bygga produktionssystem.

## Problemen LangChain Solves

Innan du avfärdar ett ramverk, måste du förstå vilka problem det löser. LangChain tar upp dessa verkliga frågor:

1. **Sammanhangsbyggnad** - Bygga sammanhängande uppmaningar från schema, prover, historia, och begränsningar
2. **Verktygsorkestrering** - Hantera flera verktyg samtal i följd med villkorlig logik
3. **Statlig förvaltning** - Bibehålla konversation sammanhang över flera varv
4. **Hantering av försök och fel** - Återvinning graciöst när LLM genererar ogiltig utgång
5. **Flerstegsresonemang** - Bryta komplexa uppgifter i sekventiella steg (agensmönstret)
6. **Observationsförmåga** - Spåra vad som hände under avrättningen.

Detta är legitima problem: Behöver ni en ram för att lösa dem?

## Där LangChain börjar göra ont

För mitt arbete - byggproduktion .NET system med lokala LLMs, strikta sekretesskrav, och deterministiskt beteende - LangChain introducerar friktion i flera områden.

### Dolda tillstånd och implicit kontrollflöde

LangChain hanterar minne och sammanhang för dig. Det låter bekvämt tills du behöver felsöka varför din prompt är 10 000 tokens längre än förväntat, eller varför LLM plötsligt har tillgång till konversationshistorik du trodde att du hade rensat.

Ramverket konkateterar, hanterar minne, och hanterar exekveringsorder implicit. När något går sönder, du debuggar ramverkets beteende, inte din kod beteende.

### Ramuppbyggt tänkande

När du har adopterat LangChain, börjar du designa **för LangChain**. Din arkitektur kopplas till ramverkets abstraktioner: kedjor, agenter, retrievers, minnesbuffertar.

Detta är inte unikt för LangChain - alla ramar gör detta. Men i ett snabbrörligt fält som LLMs, där rätt abstraktioner inte är lösta ännu, koppling till ett ramverks världsbild är riskabelt.

### Pythonimpedansmismatchen

LangChain antar:

- Långlivade processer (arbetsflöden i notebook-stil)
- Mutabel global stat
- Pythons dynamiska maskinskrivning och andtypning
- Blockering av I/O-mönster

Som .NET-utvecklare, antar jag:

- Förfrågan-scoped livstider (ASP.NET Core mönster)
- Oförgängligt eller explicit förvaltat tillstånd
- Stark maskinskrivning och kompileringstidsäkerhet
- Async/avänta överallt

och [LangChain .NET-portar](https://github.com/tryAGI/LangChain) Existera, men de spelar ifatt med Python-versionen, och abstraktionerna känns fortfarande främmande för idiomatiska C#.

### Verkligheten i produktionen glappar

När du går från prototyp till produktion behöver du:

- **Determinism** - Samma indata bör ge förutsägbart beteende
- **Validering** - Se till att LLM: s utgång är säker innan den körs.
- **Sandboxning** - Begränsa vad genererad kod faktiskt kan göra
- **Kostnadskontroll** - Spåra token användning och införa gränser
- **Lokala slutsatser** - Kör modeller offline utan molnberoenden

LangChain optimerar för iterationshastighet, inte för härdning av produktionen. Det är bra för demos; det är ett problem för produktionen.

## Vad jag bygger i stället

Här är den mentala modellen jag använder: **LLM är resonemangsmotorer, inte exekveringsmotorer.**

### LLMs gör:

- **Tolkning** - Förstå användarens avsikt från naturligt språk
- **Planering** - Bryta komplexa uppgifter i steg
- **Översättning** - Konvertera avsikt till strukturerade format (SQL, JSON, funktion samtal)

### LLMS Gör INTE:

- **Beräkna aggregat** - Här är 100 000 rader.
- **Skanna datauppsättningar** - Söker igenom stora filer
- **Egen stat** - Bibehålla långtidsminnet

Principen: **LLM:s skäl. Motorerna beräknar.**

Den här separationen driver allt jag bygger.

### Explicit sammanhang, inte magiskt minne

Istället för ramstyrt minne bygger jag sammanhang uttryckligen per begäran:

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

Varje snabb konstruktion är synlig. Jag vet exakt vad som skickas till LLM eftersom jag byggde strängen själv:

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

Inget gömt tillstånd, ingen magisk konkatetering, bara explicit strängbyggnad.

### Deterministiska genomförandeskikt

Istället för att låta LLM köra något, använder jag det för att generera **avsikt**, sedan utföra denna avsikt genom deterministiska motorer:

- **SQL-motorer** (DuckDB) - För datafrågor
- **Sökmotorer** (Lucene, Postgres fulltext) - För dokumenthämtning
- **Regelmotorer** - För affärslogik
- **Domäntjänster** - För validerade verksamheter

LLM genererar SQL. DuckDB kör den. LLM ser aldrig data:

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

Detta är säkrare, snabbare och avlusningsbart. LLM kan inte av misstag köra `DROP TABLE` LLM kan inte läcka data för den ser aldrig datan - bara schemat.

## Ett konkret exempel: CSV-analys utan ramar

Jag skrev nyligen om [analysera stora CSV-filer med lokala LLMs](https://mostlylucid.net/blog/analysing-large-csv-files-with-local-llms). Arkitekturen:

**Användarfråga → LLM → SQL → DuckDB → Resultat**

LLM tar emot följande:

- CSV-schemat (kolumnnamn och -typer)
- 3 exempelrader (för att förstå dataformat)
- Användarens fråga

LLM genererar följande:

- En sökfråga för DuckDB SQL

Systemet därefter:

- Validerar SQL med hjälp av `EXPLAIN` (fångster av syntaxfel utan att verkställas)
- Kör frågan mot CSV- filen
- Returnerar resultat till användaren

LLM ser aldrig den faktiska datan, den ser bara strukturen.

Detta är vad LangChain skulle kalla en "agent" - ett system som använder en LLM för att generera åtgärder, validerar dem, kör dem, och potentiellt retries på fel.

Förutom att jag byggde den i ca 200 rader C# utan ram:

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

Inga kedjor, inga agenter ramar, ingen magi. Bara explicit orkestrering av LLM → validering → avrättning.

## Vad är en agent?

Termen "agent" kastas ständigt runt, vanligtvis för att betyda "allt som involverar en LLM". Låt oss vara exakta.

En agent är:

- **En slinga** - Den kör flera iterationer.
- **Med tillstånd** - Den minns vad den har försökt.
- **Med inbyggd utrustning för inspelning eller återgivning av ljud eller videosignaler** - Den kan vidta åtgärder i världen
- **Med återkoppling** - Det observerar resultat och justerar

En agent är **inte ett bibliotek**Det är ett mönster.

Mitt agentmönster i 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);
    }
}
```

Det här är en slinga med tillstånd, verktyg och feedback.

## Där Microsofts Agent Framework passar

För att vara rättvis mot .NET ekosystem, har Microsoft släppt [Microsoft Agent-ramverk](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) som är avsedd för .NET utvecklare bygga produktion AI-system.

### Vad Microsoft Agent Framework blir rätt

Ramverket (tidigare känt som Microsoft.Extensions.AI) ger:

- **Explicit orkestrering** - Du styr agentloopen, inte ramen.
- **Starkt skrivande** - Compile-time säkerhet för verktygsdefinitioner och funktion anrop
- **Första klassens observerbarhet** - Inbyggd telemetri, loggning och distribuerad spårning via OpenTelemetri
- **Företagsgränser** - Designad för produktion .NET-system med korrekt DI, konfiguration och livscykelhantering
- **Stöd för flera modeller** - Abstraktioner över OpenAI, Azure OpenAI, Ollama och andra leverantörer
- **Integrering av semantisk kernel** - Fungerar med Microsofts bredare AI stack

Nyckelkomponenter:

- `IChatClient` - Enhetligt gränssnitt för chattkompletteringar
- `IEmbeddingGenerator` - Vector inbäddar mellan leverantörer
- `AIFunction` - Typsäker funktion anrop
- Middleware pipeline - För loggning, försök igen, caching, telemetri

**Exempel:**

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

### Där jag fortfarande håller mig på en lägre nivå

Även med Microsofts ramverk, Jag föredrar att hålla kärnan orkestrering explicit:

**Jag vill inte:**

- **Ogenomskinliga planerare** - Ramverket bestämmer självständigt vilket verktyg som ska användas
- **Implicit verktygsval** - Magisk routing baserad på naturliga språkbeskrivningar
- **Gömd försökslogik** - Ramverksstyrt felåterhämtning Jag kan inte inspektera

**Jag vill:**

- **Synliga slingor** - Jag ser varje iteration i min kod.
- **Testbara steg** - Jag kan testa beslutslogiken.
- **Utbytbara komponenter** - Jag kan byta LLM, verktygen, valideringslagret.
- **Explicit tillstånd** - Jag vet precis vad som finns i sammanhanget.

Microsofts Agent Framework är närmare hur jag tänker än LangChain. Det respekterar .NET mönster, använder beroende injektion korrekt, och inte bekämpa ekosystemet. Men jag föredrar fortfarande skriva orkestrering själv.

**När du ska använda Microsoft Agent Framework:**

- Bygga chattprogram med funktionssamtal
- Behöver stöd med flera modeller (växling mellan OpenAI, Azure, Ollama)
- Vill företag funktioner (telemetri, loggning, distribuerad spårning)
- Arbeta i ett team som föredrar ramkonsistens
- Byggande ovanpå semantisk kernel

**När du ska gå ramlös:**

- Du behöver full kontroll över agentloopen.
- Du bygger egna resonemang mönster
- Du vill ha noll abstraction overhead
- Du optimerar för specifika användningsfall (som CSV-analys eller webbskrapning)
- Du vill förstå exakt hur det fungerar

Ramen eliminerar inte arkitektoniska beslut. Du väljer fortfarande vad du ska sätta i sammanhang, hur man delar data, och när man ska försöka igen. Det gör bara rörsystemet lättare.

## Varför detta skalar bättre långsiktig

Ramlösa system åldras bättre av flera skäl:

**Prestanda** - Ingen abstraction overhead. Min CSV frågetjänst körs under 100 ms eftersom det inte finns något ramverk mellan LLM och DuckDB.

**Kostnadsförutsägbarhet** Jag kontrollerar vad som går till LLM, ingen dold snabb inflation från ramstyrt minne.

**Avlusningsförmåga** - När nåt går sönder, så förstör jag min kod, inte omvänt konstruerar ett ramverks magi.

**Sekretess** - För system med strikta dataresidens krav, veta exakt vad som lämnar maskinen spelar roll.

**Avstängningsscenarier** - Edge-enheter, luftstyrda nätverk, reglerade miljöer. Ramverk antar tillgång till internet och molntjänster.

**Regelefterlevnad** - Inom ekonomi, hälso- och sjukvård och regering behöver du ofta förklara och granska varje beslut. "ramverket gjorde det" är inte ett acceptabelt svar.

Ju mer begränsad din miljö är, desto mer vill du ha explicit kontroll.

## När jag skulle använda LangChain

För att avväpna kritiker: det finns legitima fall där jag skulle nå LangChain.

**Hackathoner** - Snabbhet till demo betyder mer än arkitektur.

**Slänga bort POC:er** - Om du bekräftar en idé och planerar att skriva om för produktion ändå.

**Python-tunga team** - Om ditt team redan är flytande i Python, är ekosystemet passform stark.

**Undervisningskoncept** LangChains abstraktioner kan hjälpa nybörjare att förstå agentmönstret innan de bygger egna.

Att veta när **inte** Att använda något är lika värdefullt som att veta när man ska använda det.

## Det bredare mönstret: Ramverk vs. Första Principerna

Det handlar inte om LangChain, utan om avvägningen mellan ramverk och första principer.

Ramverk påskynda bekanta problem. Om du bygger 100: e CRUD API, nå för Entity Framework eller Dapper. Mönstren är avgjorda.

Men LLM-drivna system? Rätt abstraktioner är inte klara ännu. Vi vet inte om "kedjor" eller "agenter" eller "retrievers" är rätt mentala modeller. Vi fortfarande räkna ut det.

I den miljön föredrar jag att bygga nära metallen:

- LLMs via direkta API-samtal (`OllamaSharp`, OpenAI SDK)
- Snabb konstruktion via explicit strängbyggnad
- Validering via domänspecifik logik
- Körning via specialbyggda motorer (SQL, sökning, etc.)

Som .NET-utvecklare har jag starka åsikter om hur system ska byggas: explicita livstider, stark maskinskrivning, async hela vägen ner, beroendeinjektion för testbarhet.

LangChains abstraktioner stämmer inte överens med åsikterna, så jag använder dem inte.

## Borttagningen

Om du är en .NET utvecklare tittar på LangChain och undrar "Behöver jag detta?", här är mitt svar:

**Du måste lösa problemen LangChain löser** - sammanhangshantering, verktyg orkestrering, försök logik, observerbarhet.

**Du behöver inte LangChain för att lösa dem.** - Speciellt om du värdesätter explicithet, stark maskinskrivning, och produktion härdning över snabba prototyper.

Principen jag bygger vidare på:

**"Llms resonera. Motorer beräkna. Orkestrering är din att äga."**

Eller mer enkelt:

**"Om du förstår de problem ett ramverk löser behöver du ofta inte ramverket."**

Bygg system som gör vettigt i ditt ekosystem, med dina begränsningar, med hjälp av ditt språks idiomer. För mig, det är C#, stark skrivande, explicit kontrollflöde, och deterministiska utförande lager.

För dig kan det vara annorlunda.

Målet är inte att undvika ramar. Målet är att välja dem medvetet, förstå både vad de ger och vad de kostar.

---


**Läs vidare:**

- [Analysera stora CSV-filer med lokala LLM i C#](/blog/analysing-large-csv-files-with-local-llms) - Ett konkret exempel på LLM + SQL utan ramar
- [Hämta och analysera webbinnehåll med LLMs](/blog/fetching-and-analysing-web-content-with-llms) - Webbskrapning och analys utan ramverk
- [Dokumentation för Microsoft Agent Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) - Officiell Microsoft agent ram för .NET
- [Microsoft.Extensions.AI](https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/) - Grundal AI abstractions för .NET
- [Semantisk kernel](https://github.com/microsoft/semantic-kernel) - Microsofts LLM orkestrering SDK
- [Dokumentation av LangChain](https://python.langchain.com/) - För att förstå vad du väljer att inte använda.
- [OllamaSharp Ordförande](https://github.com/awaescher/OllamaSharp) - C# klient för lokal LLM inference