# Miksi en käytä LangChainia (ja mitä teen sen sijaan)

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

Olen .NET-kehittäjä. Kun aloin rakentaa LLM-käyttöisiä järjestelmiä, kaikki osoittivat minua kohti LangChainia. "Se on standardi", he sanoivat. "Kaikki esimerkit käyttävät sitä." Ja he olivat oikeassa - jos olet Python-ekosysteemissä, LangChain on kaikkialla.

Mutta asia on näin: En välttele LangChainia, koska se on huono asia. Vältän sitä, koska se ratkaisee ongelmia, jotka jo ratkaisen selkeämmin, ja käyttötapani osalta - C#, paikallinen päättely, yksityisyys, determinismi - kehykset tuovat enemmän kitkaa kuin arvoa.

Tämä ei ole mikään LangChain-vastainen viesti, vaan viesti siitä, miten ymmärretään, mitä ongelmia kehykset ratkaisevat, ja tajutaan, että niitä ei välttämättä tarvita.

**Teesi: Jos ymmärrät LangChainin ratkaisemia ongelmia, et tarvitse LangChainia.**

[TOC]

## Mitä LangChain todella tekee hyvin

LangChain on hyvä monessa asiassa:

**Nopea prototyypitys** Työskentelydemarin voi tehdä minuuteissa. Alkuesimerkit ovat aidosti hyviä.

**Python-ekosysteemin integraatio** - Jos olet jo Python/Jupyter/panda-maailmassa, LangChain liimaa kaiken yhteen saumattomasti.

**Esteen alentaminen** - LLM:lle uusille ihmisille se tarjoaa käyttökelpoisia abstraktioita: nopeita malleja, työkalunkäyttömalleja, muistinhallintaa, vektorin DB-integraatioita.

LangChain on **Kotouttamiskiihdytin**Se ei ole tekoälyn vaatimus, vaan nopeuttaa polkua "Minulla on idea" ja "Minulla on demo".

Mutta se on myös se, mistä ongelmat alkavat minulle C#-kehittäjänä, joka rakentaa tuotantojärjestelmiä.

## LangChainin ongelmat ratkeavat

Ennen kuin hylkäät kehyksen, sinun on ymmärrettävä, mitä ongelmia se ratkaisee. LangChain käsittelee näitä todellisia kysymyksiä:

1. **Kontekstirakenne** - Johdonmukaisten vihjeiden rakentaminen skeemasta, näytteistä, historiasta ja rajoitteista
2. **Työkalun orkestrointi** - Useiden työkalupuheluiden hallinta peräkkäin ehdollisella logiikalla
3. **Valtionhallinto** - Keskustelun kontekstin säilyttäminen monin eri kääntein
4. **Uudelleenyrittäminen ja virheiden käsittely** - Toipuminen viehkeästi, kun LLM tuottaa epäkelpoa ulostuloa
5. **Monivaiheinen päättely** - Monimutkaisten tehtävien jakaminen vaiheittain (agenttikuvio)
6. **Havainnointikelpoisuus** - Seurataan, mitä oikeasti tapahtui teloituksen aikana

Nämä ovat laillisia ongelmia, ja kysymys kuuluu: tarvitseeko niiden ratkaisemiseksi kehystä?

## Missä LangChain alkaa satuttaa

Työlleni - rakennustuotannolle .NET-järjestelmät paikallisilla LLM-järjestelmillä, tiukat yksityisyysvaatimukset ja deterministinen käyttäytyminen - LangChain tuo kitkaa useille alueille.

### Piilotettu tila ja ehdoton valvontavirta

LangChain hoitaa muistia ja kontekstia sinulle. Se kuulostaa kätevältä, kunnes sinun täytyy selvittää, miksi pikavippisi on odotettua pidempi, tai miksi LLM:llä on yhtäkkiä pääsy keskusteluhistoriaan, jonka luulit selvinneesi.

Kehys konkretisoidaan, hallitaan muistia ja käsitellään täytäntöönpanomääräystä epäsuorasti. Kun jokin menee rikki, rikot kehyksen käyttäytymisen, et koodisi käytöstä.

### Kehysajatus kytköksissä

Kun olet adoptoinut LangChainin, alat suunnitella **LangChainille**. Arkkitehtuuri yhdistetään kehyksen abstraktioihin: ketjuihin, agentteihin, noutajiin, muistipuskureihin.

Tämä ei ole vain LangChainille - kaikki puitteet tekevät näin, mutta LLM:n kaltaisella nopeasti liikkuvalla kentällä, jossa oikeat abstraktiot eivät ole vielä asettuneet paikoilleen, on riskialtista kytkeytyä kehyksen maailmankuvaan.

### Python Impedancen virheottelu

LangChain olettaa:

- Pitkäikäiset prosessit (noottikirjatyyppiset työnvirrat)
- Mutageeninen globaali tila
- Pythonin dynaaminen kirjoittaminen ja ankan kirjoittaminen
- I/O-mallien estäminen

.NET-kehittäjänä oletan:

- Pyynnön kohteena olevat elinajat (ASP.NET Core -mallit)
- Muuttumaton tai nimenomaisesti hoidettu tila
- Vahva kirjoitus- ja kokoamisturvallisuus
- Async/odota kaikkialla

Erytropoietiini [LangChain .NET-portit](https://github.com/tryAGI/LangChain) On olemassa, mutta he pelaavat Python-version kanssa, ja abstraktiot tuntuvat yhä vieraalta idiomaattiselle C#:lle.

### Tuotantotodellisuuden aukot

Kun siirryt prototyypistä tuotantoon, tarvitset:

- **Määritteleminen** - Saman syötteen pitäisi tuottaa ennakoitavaa käyttäytymistä
- **Validointi** - Varmista, että LLM:n ulostulo on turvallinen ennen sen toimeenpanoa
- **Hiekkanyrkkeily** - Rajoita sitä, mitä luotu koodi todella voi tehdä
- **Kustannusten hallinta** - Raiteen nimikyltin käyttö ja rajojen asettaminen
- **Paikallinen päätelmä** - Suorita mallit pois päältä ilman pilvistä riippuvuutta

LangChain optimoi iterointinopeuden, ei tuotannon kovettumisen. Se sopii demoille, se on ongelma tuotannolle.

## Mitä minä sen sijaan rakennan

Tässä on mielimalli, jota käytän: **LLM:t ovat päättelymoottoreita, eivät toteutusmoottoreita.**

### LLM:t tekevät:

- **Tulkinta** - Käyttäjätarkoitusten ymmärtäminen luonnolliselta kieleltä
- **Suunnittelu** - Monimutkaiset tehtävät porrastetusti
- **Kääntäminen** - Aikeiden muuttaminen jäsennellyiksi muodoiksi (SQL, JSON, Function Calls)

### LLM:t EIVÄT:

- **Laske aggregaatit** - Satatuhatta riviä yhteensä
- **Skannaa tietoaineistot** - Tutkitaan suuria tiedostoja
- **Oma tila** - Pitkän aikavälin muistin ylläpitäminen

Periaate: **LLM:n järki, moottorit laskevat.**

Tämä asumusero vie kaiken, mitä rakennan.

### Selkeä konteksti, ei taikamuisti

Kehysmuistin sijaan rakennan kontekstin erikseen pyydettäessä:

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

Jokainen nopea rakentaminen on näkyvissä. Tiedän tarkalleen, mitä LLM:ään lähetetään, koska rakensin narun itse:

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

Ei salattua tilaa, ei maagista konkretisaatiota, vain selkeä merkkijonorakennus, kun se on väärin, tiedän miksi.

### Deterministiset teloitustasot

Sen sijaan, että antaisin LLM:n tehdä mitä tahansa, käytän sitä luodakseni **aiesopimus**, sitten toteuttaa tämä aikomus deterministisillä moottoreilla:

- **SQL-moottorit** (DuckDB) - Tietopyyntöjä varten
- **Hakukoneet** (Lucene, Postgres kokoteksti) - Asiakirjojen hakua varten
- **Sääntömoottorit** - Liikelogiikan puolesta
- **Verkkoaluepalvelut** - Validoidut toimet

LLM tuottaa SQL. DuckDB suorittaa sen. LLM ei koskaan näe dataa:

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

Tämä on turvallisempaa, nopeampaa ja luotettavampaa. LLM ei voi vahingossa juosta `DROP TABLE` Koska vahvistan ensin SQL:n. LLM ei voi vuotaa dataa, koska se ei koskaan näe dataa - vain skeemaa.

## Konkreettinen esimerkki: CSV-analyysi ilman kehystä

Kirjoitin äskettäin aiheesta [suurten CSV-tiedostojen analysointi paikallisilla LLM-tunnuksilla](https://mostlylucid.net/blog/analysing-large-csv-files-with-local-llms)Arkkitehtuuri:

**Käyttäjäkysymys → LLM → SQL → DuckDB → Tulokset**

LLM saa seuraavat tiedot:

- CSV-skeema (sarakennuksen nimet ja tyypit)
- 3 näyteriviä (tietomuodon ymmärtämiseksi)
- Käyttäjän kysymys

LLM tuottaa:

- DuckDB:n SQL-kysely

Tämän jälkeen järjestelmä:

- Vahvistaa SQL:n `EXPLAIN` (saalis syntaksivirheitä tekemättä)
- Suorittaa kyselyn CSV-tiedostoa vastaan
- Palauttaa tulokset käyttäjälle

LLM ei koskaan näe todellista dataa, vaan vain rakenteen.

LangChain kutsuisi tätä "agentiksi" - järjestelmäksi, joka käyttää LLM:tä toimien tuottamiseen, validoi niitä, toteuttaa ne ja mahdollisesti kostautuu epäonnistumiselle.

Paitsi että rakensin sen 200 riviin C#:ta ilman kehystä:

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

Siinä kaikki. Ei ketjuja, ei agentteja, ei magiaa. Vain LLM:n selkeä orkestrointi → varmentaminen → teloitus.

## Mikä on oikeasti agentti?

Termiä "agentti" heitellään jatkuvasti, yleensä tarkoittaen "mitä tahansa, mikä liittyy LLM:ään". Tarkastetaan tarkemmin.

Agentti on:

- **Kierre** - Se pyörittää useita iterointijaksoja
- **Osavaltion kanssa** - Se muistaa, mitä se on yrittänyt.
- **Työkalut** - Se voi vaatia tekoja maailmalla
- **Palautteella** - Se tarkkailee tuloksia ja sopeuttaa

Agentilla on **ei kirjastoa**Se on kaava.

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

Tämä on agentti. Se on silmukka, jossa on tilaa, työkaluja ja palautetta. Kirjoitin sen 30 rivillä. En tarvinnut kehystä.

## Missä Microsoftin agenttikehys sopii

Ollakseen reilu .NET-ekosysteemille Microsoft on julkaissut [Microsoft Agent Framework](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) Se on suunniteltu .NET-kehittäjille, jotka rakentavat tekoälyjärjestelmiä.

### Mitä Microsoft Agent Framework oikein tekee

Kehyksessä (aiemmin Microsoft.Extensions.AI) määrätään seuraavaa:

- **Eksplisiittinen orkestrointi** - Hallitset agenttisilmukkaa, et kehystä.
- **Vahva kirjoitustapa** - Työkalumääritelmien ja toimintakutsujen kokoamisaika
- **Ensiluokkainen havainnointi** - Sisäänrakennettu telemetria, kirjautuminen ja jaettu jäljitys OpenTelemetrian kautta
- **Yritysrajat** - Suunniteltu tuottamaan .NET-järjestelmiä, joissa on asianmukainen DI-, konfiguraatio- ja elinkaarihallinta
- **Monimallinen tuki** - Esittelyt OpenAI:sta, Azure OpenAI:sta, Ollamasta ja muista toimittajista
- **Semanttinen Kernel-integraatio** - Toimii Microsoftin laajemmassa tekoälypinossa

Avainosat:

- `IChatClient` - Yhdistetty rajapinta chatin loppuunsaattamiselle
- `IEmbeddingGenerator` - Vektori upottaa eri palveluntarjoajiin
- `AIFunction` - Tyyppiturvallinen toimintokutsu
- Middleware-putki - hakkuisiin, uudelleenyrittämiseen, välimuistiin, telemetriaan

**Esimerkki:**

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

### Missä pysyn yhä matalammalla tasolla

Microsoftin kehyksistä huolimatta pidän mieluummin ydinorkesterin selkeänä:

**En halua:**

- **Läpinäkymättömät suunnittelijat** - Kehys päättää itsenäisesti, mihin välineeseen soittaa
- **Välitön työkaluvalinta** - Luonnollisiin kielikuvauksiin perustuva taikareitti
- **Piilotettu uudelleenyritä logiikkaa** - Kehyshallittu virheiden toipuminen, jota en voi tarkastaa

**Haluan:**

- **Näkyvät silmukat** Näen jokaisen iterointini koodissani
- **Testattavat vaiheet** - Pystyn testaamaan ratkaisulogiikkaa.
- **Vaihdettavat osat** - Voin vaihtaa LLM:n, työkalut, varmistuskerroksen
- **Eksplisiittinen tila** - Tiedän tarkalleen, mitä asiayhteydessä on.

Microsoftin Agent Framework on lähempänä ajattelutapaani kuin LangChain. Se kunnioittaa .NET-malleja, käyttää riippuvuusruisketta oikein, eikä taistele ekosysteemiä vastaan, mutta kirjoitan mieluummin itse.

**Milloin Microsoft Agent Frameworkia käytetään:**

- Chat-sovellusten rakentaminen funktiopuhelulla
- Tarvitaan monimallitukea (vaihde OpenAI:n, Azuren, Ollaman välillä)
- Haluavat yritysominaisuudet (telemetria, kirjaus, hajautettu jäljitys)
- Työskentely kehysten johdonmukaisuutta suosivassa tiimissä
- Rakennus Semantic Kernelin päälle

**Milloin mennä ilman kehyksiä:**

- Tarvitset täyden hallinnan agenttisilmukkaan
- Rakennat mukautetut päättelymallit
- Ei mitään abstraktiota.
- Optimoit tiettyihin käyttötapauksiin (kuten CSV-analyysiin tai verkkokaavintaan)
- Haluat ymmärtää tarkasti, miten se toimii.

Kehys ei poista arkkitehtonisia päätöksiä, vaan päättää silti, mitä laittaa kontekstiin, miten kerätä dataa ja milloin yrittää uudelleen. Se vain helpottaa putkistoa.

## Miksi tämä on parempi pitkän aikavälin mittapuu

Puitteettomat järjestelmät ikääntyvät paremmin monesta syystä:

**Suorituskyky** CSV-palvelullani on alle 100 ms, koska LLM:n ja DuckDB:n välillä ei ole kehystä.

**Kustannusten ennakoitavuus** Hallitsen tarkkaan, mitä LLM: lle tapahtuu. Ei piilotettua nopeaa inflaatiota kehysmuistista.

**Debuggattavuus** Kun jokin menee rikki, rikon koodini, en kääntele taikoja.

**Yksityisyys** - Järjestelmille, joilla on tiukat dataresidenssivaatimukset, tieto siitä, mikä koneen jättää, on tärkeää.

**Offline-skenaariot** - Edge-laitteet, ilmatiiviit verkot, säännellyt ympäristöt. Kehykset edellyttävät internet- ja pilvipalveluita.

**Sääntelyn noudattaminen** - Rahoituksessa, terveydenhuollossa ja hallituksessa on usein selitettävä ja tarkastettava jokainen päätös. "Puite teki sen" ei ole hyväksyttävä vastaus.

Mitä rajoittavampi ympäristösi on, sitä enemmän haluat selkeää hallintaa.

## Kun käyttäisin LangChainia

Kriitikoiden riisuminen aseista: LangChainin tavoittamisessa on laillisia tapauksia.

**Hakatonit** Speed to demo merkitsee muutakin kuin arkkitehtuuria.

**Hylkäävät POC-keskukset** - Jos hyväksyt idean ja suunnittelet uudelleenkirjoittamista tuotantoa varten joka tapauksessa.

**Python-raskaat joukkueet** - Jos tiimisi on jo sujuva Pythonissa, ekosysteemi sopii hyvin.

**Opetuskäsitteet** LangChainin abstraktiot voivat auttaa aloittelijoita ymmärtämään agenttikuviota ennen omiensa rakentamista.

Tietää, milloin **ei** On yhtä arvokasta käyttää jotain kuin tietää, milloin käyttää sitä.

## Laajempi malli: Kehykset vs. First Principles

Kyse ei oikeastaan ole LangChainista, vaan kehysten ja ensimmäisten periaatteiden suunnittelun välisestä vaihdosta.

Kehykset nopeuttavat tuttuja ongelmia. Jos rakennat 100. CRUD-rajapintaa, ota yhteyttä Entity Frameworkiin tai Dapperiin.

Mutta LLM-käyttöiset järjestelmät eivät ole vielä selvillä oikeista abstraktioista. Emme tiedä, ovatko "ketjut" tai "agentit" tai "palauttajat" oikeita mielenmalleja.

Siinä ympäristössä rakennan mieluummin lähelle metallia:

- LLM:t suorien API-puheluiden kautta (`OllamaSharp`, OpenAI SDK)
- Nopea rakentaminen selkeän merkkijonorakennuksen avulla
- Validointi verkkoaluekohtaisen logiikan avulla
- Suoritus tarkoitukseen rakennettujen moottoreiden avulla (SQL, haku jne.)

.NET-kehittäjänä minulla on vahvat mielipiteet siitä, miten järjestelmiä pitäisi rakentaa: selkeät elinajat, vahva kirjoittaminen, async koko matkan alas, riippuvuusruiske testausta varten.

LangChainin abstrakteja ei kartoiteta selkeästi noille mielipiteille, joten en käytä niitä.

## Takeaway

Jos olet .net-kehittäjä ja mietit LangChainia: "Tarvitsenko tätä?", tässä on vastaukseni:

**Sinun täytyy ratkaista LangChainin ratkaisemat ongelmat** - kontekstin hallinta, työkalujen orkestrointi, uudelleenyrittäminen, havainnointi.

**Et tarvitse LangChainia ratkaisemaan niitä** - Varsinkin, jos arvostaa eksplisiittisyyttä, vahvaa kirjoittamista ja tuotannon kovettumista nopeassa prototyypityksessä.

Periaate, jonka pohjana olen, on:

**"LMM-järki. Moottorit laskevat. Orkestraation voi omistaa."**

Tai yksinkertaisesti:

**"Jos ymmärtää ongelmat, jotka kehys ratkaisee, ei useinkaan tarvitse niitä."**

Rakenna ekosysteemeissäsi järkeviä järjestelmiä, joilla on omat rajoitteensa ja jotka käyttävät kielesi idiomeja. Minulle se on C#, vahva kirjoittaminen, selkeä kontrollivirta ja deterministiset toteutuskerrokset.

Sinulle se voi olla erilaista, eikä se haittaa.

Tavoitteena ei ole vältellä kehyksiä, vaan valita ne tietoisesti ja ymmärtää, mitä ne tarjoavat ja mitä ne maksavat.

---


**Lue lisää:**

- [Suurten CSV-tiedostojen analysointi paikallisilla LLM-tiedostoilla C#](/blog/analysing-large-csv-files-with-local-llms) - Konkreettinen esimerkki LLM + SQL ilman kehyksiä
- [Web-sisällön hakeminen ja analysointi LLM:n avulla](/blog/fetching-and-analysing-web-content-with-llms) - Netin kaavinta ja analysointi ilman kehyksiä
- [Microsoft Agent Framework Documentation](https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview) - Virallinen Microsoft-agenttikehys .NETille
- [Microsoft.Extensions.AI](https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/) - Perusteellinen tekoälyn abstraktio .NETille
- [Semanttinen Kernel](https://github.com/microsoft/semantic-kernel) - Microsoftin LLM-orkesteri SDK
- [LangChain-dokumentaatio](https://python.langchain.com/) - Ymmärtääksesi, mitä päätät olla käyttämättä
- [OllamaSharp](https://github.com/awaescher/OllamaSharp) - C#-asiakas paikalliselle LLM-päätelmälle