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.
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.
Voordat je een kader afwijst, moet je begrijpen welke problemen het oplost. LangChain pakt deze echte problemen aan:
Dit zijn legitieme problemen. De vraag is: heb je een kader nodig om ze op te lossen?
Voor mijn werk - bouwproductie .NET systemen met lokale LLM's, strenge privacyvereisten en deterministisch gedrag - LangChain introduceert wrijving op verschillende gebieden.
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.
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.
LangChain gaat ervan uit:
Als een .NET ontwikkelaar, neem ik aan:
De LangChain .NET poorten bestaan, maar ze spelen inhaalslag met de Python versie, en de abstracties voelen nog steeds vreemd aan idiomatische C#.
Wanneer je van prototype naar productie verhuist, heb je het volgende nodig:
LangChain optimaliseert de iteratiesnelheid, niet de productieharding. Dat is prima voor demo's; het is een probleem voor de productie.
Hier is het mentale model dat ik gebruik: LLM's zijn redenerende motoren, geen uitvoeringsmotoren.
Het beginsel: LLM's reden, motoren berekenen.
Deze scheiding drijft alles wat ik bouw.
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.
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:
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.
Ik schreef onlangs over het analyseren van grote CSV-bestanden met lokale LLM's. De architectuur:
Gebruikersvraag → LLM → SQL → DuckDB → Resultaten
De LLM ontvangt:
De LLM genereert:
Het systeem:
EXPLAIN (vangstsyntaxisfouten zonder uitvoering)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.
De term "agent" wordt constant rondgegooid, meestal om te betekenen "alles met een LLM." Laten we precies zijn.
Een agent is:
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.
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.
Het kader (voorheen bekend als Microsoft.Extensions.AI) biedt:
Belangrijkste onderdelen:
IChatClient - Unified interface voor chatvoltooidsIEmbeddingGenerator - Vector inbeddingen over de providersAIFunction - Type-veilige functie bellenVoorbeeld:
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;
});
Zelfs met Microsoft's framework, geef ik er de voorkeur aan om core orkestratie expliciet te houden:
Ik wil niet:
Ik wil:
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:
Wanneer moet je framework-less gaan:
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.
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.
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 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:
OllamaSharp, OpenAI SDK)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.
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:
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.