Context rot: waarom een groter context window je LLM-feature niet redt

Vendors adverteren met 1M-token context windows, maar de bruikbare ruimte groeit niet mee. Wat context rot betekent voor je .NET-LLM-features en hoe je eromheen bouwt.

Jean-Pierre Broeders

Freelance .NET Developer

15 juni 202612 min. leestijd
Context rot: waarom een groter context window je LLM-feature niet redt

Context rot: waarom een groter context window je LLM-feature niet redt

Er staat deze week een kort stukje op de Hacker News-voorpagina dat een naam geeft aan een gevoel dat ik al maanden had. Garrit Franke verdeelt het context window van een LLM in twee zones: een smart zone, waar het model scherp is, en een dumb zone, waar de aandacht wegzakt en het model vergeet wat je vijf minuten geleden hebt verteld. De grens ligt ergens rond de 100.000 tokens — ongeacht of de doos "200k", "1M" of "2M" claimt.

Als je LLM-features bouwt voor productie, en niet alleen in ChatGPT plakt, is dit geen filosofisch praatje. Het raakt de architectuur van je RAG-pipeline, je agent-loops en je promptbudget direct. Ik werk vooral in .NET, dus laat ik laten zien wat context rot concreet betekent voor een C#-codebase en hoe je er pragmatisch omheen bouwt.

De bron, voor wie meeleest: het stuk van Garrit (garrit.xyz) en de bijbehorende HN-discussie.

Het getal op de doos is marketing

Laten we eerst het misverstand opruimen. Een context window van 1 miljoen tokens betekent niet dat je betrouwbaar met 1 miljoen tokens kunt werken. Het betekent dat het model technisch niet crasht als je zoveel tokens aanlevert. Dat is iets heel anders dan "het model gebruikt al die informatie even goed."

Twee onderzoeken zijn hier het noemen waard. De RULER-benchmark liet al in 2024 zien dat de effectieve contextlengte van een model vaak een fractie is van het geadverteerde getal. En het context rot-rapport van Chroma (juli 2025) testte achttien frontier-modellen — GPT-4.1, Claude 4, Gemini 2.5, Qwen3 — en vond bij élk model dat de kwaliteit daalt naarmate de input groeit. Niet alleen wanneer het window vol raakt: het verval is geleidelijk en begint vroeg. Voor modellen met een 1M-window zag Chroma het duidelijk meetbaar worden rond de 300.000 à 400.000 tokens, met de steilste degradatie tussen 100k en 500k.

De kern: attention is geen gratis lunch. Hoe meer tokens er strijden om de aandacht van het model, hoe minder scherp het op elke individuele token let. De architectuur achter lange context windows werkt, maar het plakt over een probleem heen dat het onderliggende attention-mechanisme niet echt oplost. Het nummer op de doos wordt elke release groter. Het bruikbare deel houdt geen gelijke tred.

Wat dit betekent voor je code

Als je een chatbot bouwt die één vraag beantwoordt op basis van een korte prompt, merk je hier weinig van. Het wordt pas pijnlijk in precies de scenario's waar wij als bouwers in terechtkomen:

Een RAG-pipeline die "voor de zekerheid" de top-50 chunks meestuurt in plaats van de top-5. Een agent-loop die file reads, tool-output en een lange debugsessie opstapelt tot je voor de lunch al op 100k zit. Een samenvattingsfeature die een heel rapport van 80 pagina's in één keer in de prompt propt. In alle drie de gevallen voelt het alsof je het model meer geeft om mee te werken. In werkelijkheid duw je het de dumb zone in.

De verleiding is groot, want het is makkelijker om alles maar mee te sturen dan om te kiezen. Maar "meer context" en "betere context" zijn niet hetzelfde, en vanaf een bepaald punt zijn ze elkaars vijand.

Behandel je context window als een budget

De praktische mindset die ik heb overgenomen: het context window is een budget, geen opslagruimte. Je hebt een beperkt aantal "scherpe" tokens, en alles wat je niet strikt nodig hebt, verdringt iets wat je wél nodig hebt.

In .NET begin ik daarom met expliciet meten. Sinds Microsoft.Extensions.AI de gemeenschappelijke abstractie is geworden, werk ik tegen IChatClient en houd ik mijn eigen budget bij. Een ruwe tokenschatter is voldoende om beslissingen te nemen — je hoeft niet de exacte tokenizer van het model na te bouwen om te weten dat 50 chunks te veel zijn.

using Microsoft.Extensions.AI;

public sealed class ContextBudget
{
    private readonly int _maxTokens;
    private int _used;

    public ContextBudget(int maxTokens) => _maxTokens = maxTokens;

    public int Remaining => _maxTokens - _used;

    // Ruwe schatting: ~4 tekens per token voor Engels/NL.
    // Goed genoeg om beslissingen op te baseren, niet om te factureren.
    public static int Estimate(string text) =>
        (int)Math.Ceiling(text.Length / 4.0);

    public bool TryAdd(string text)
    {
        var cost = Estimate(text);
        if (cost > Remaining) return false;
        _used += cost;
        return true;
    }
}

Let op het belangrijke detail: ik zet _maxTokens niet op het geadverteerde window. Ik zet het op de zone waarvan ik geloof dat het model er scherp blijft — vaak iets in de orde van 30k tot 60k voor een retrieval-taak, ruim onder de 100k-grens waar het verval begint. Dat is een bewuste, conservatieve keuze. Liever een krappe, scherpe context dan een ruime, vage.

Retrieval: kies, niet "voor de zekerheid"

De grootste winst zit in je retrieval-stap. De anti-pattern die ik in het wild zie, is een vector search die de top-N teruggeeft op puur cosinus-similarity, waarna iemand N op 40 zet "omdat het dan completer is." Dat is precies de verkeerde reflex.

Beter is een budget-gedreven selectie: sorteer op relevantie, en stop met toevoegen zodra het budget op is. Zo laat je de matige chunks weg in plaats van je scherpe tokens eraan te verspillen.

public sealed record Chunk(string Text, float Score);

public static IReadOnlyList<Chunk> SelectWithinBudget(
    IEnumerable<Chunk> ranked,
    int budgetTokens)
{
    var selected = new List<Chunk>();
    var used = 0;

    foreach (var chunk in ranked.OrderByDescending(c => c.Score))
    {
        var cost = ContextBudget.Estimate(chunk.Text);
        if (used + cost > budgetTokens)
            continue; // sla over, probeer of een kleinere chunk nog past

        selected.Add(chunk);
        used += cost;
    }

    return selected;
}

Een subtiliteit die in het Chroma-rapport terugkomt: distractors — chunks die er relevant uitzien maar het niet zijn — schaden de prestaties meer naarmate er meer context is. Met andere woorden, een grote berg "bijna goede" chunks is erger dan een klein setje echt relevante. Dat is een extra argument om streng te zijn bij het selecteren in plaats van royaal.

Chunking en positie

Naast hoeveel je meestuurt, telt ook wáár het staat. Modellen hebben een bekende voorkeur voor het begin en het einde van de context (de "lost in the middle"-bevinding). Als je een handvol chunks plus een systeemprompt en een vraag combineert, plaats het belangrijkste materiaal dan niet diep in het midden.

In de praktijk bouw ik de prompt in een vaste volgorde op: instructies bovenaan, de meest relevante context daar direct onder, minder relevante context daarna, en de concrete vraag onderaan vlak voor de generatie. Het kost niets en het scheelt meetbaar.

public static string BuildPrompt(
    string systemInstructions,
    IReadOnlyList<Chunk> context,
    string question)
{
    var sb = new System.Text.StringBuilder();
    sb.AppendLine(systemInstructions);
    sb.AppendLine();
    sb.AppendLine("## Relevante context");

    // Meest relevante eerst; het model leest het begin het scherpst.
    foreach (var chunk in context.OrderByDescending(c => c.Score))
    {
        sb.AppendLine(chunk.Text);
        sb.AppendLine("---");
    }

    sb.AppendLine();
    sb.AppendLine("## Vraag");
    sb.AppendLine(question);
    return sb.ToString();
}

Agents: verplaats informatie de sessie uit

Voor agent-workflows is het advies van Garrit raak: laat een breadcrumb achter. In plaats van te vertrouwen op auto-compaction — waarbij de samenvatting wordt gemaakt door een model dat al in de dumb zone zit — schrijf je zelf een spec en start je een verse sessie. Dat is een hoger-signaal overdracht dan welke geautomatiseerde samenvatting ook, omdat jij bepaalt wat ertoe doet.

In .NET-termen: behandel de tussenresultaten van je agent als artefacten die je expliciet de live-sessie uit haalt. Een plan, een PRD, een takenlijst — opgeslagen buiten het context window, en weer ingeladen wanneer ze nodig zijn. Het patroon dat projecten als obra/superpowers en mattpocock/skills formaliseren, komt hierop neer: hou de werksessie in de smart zone door bewust informatie naar kleine, benoemde artefacten te verplaatsen.

Een minimale versie in code: laat elke stap zijn output naar een store schrijven, en geef de volgende stap alleen de samenvatting plus de relevante artefacten mee — niet de volledige historie.

public interface IArtifactStore
{
    Task SaveAsync(string key, string content, CancellationToken ct = default);
    Task<string?> LoadAsync(string key, CancellationToken ct = default);
}

public sealed class AgentStep
{
    private readonly IChatClient _client;
    private readonly IArtifactStore _store;

    public AgentStep(IChatClient client, IArtifactStore store)
    {
        _client = client;
        _store = store;
    }

    public async Task<string> RunAsync(
        string instruction,
        IReadOnlyList<string> artifactKeys,
        CancellationToken ct = default)
    {
        var messages = new List<ChatMessage>
        {
            new(ChatRole.System, "Je bent een focused sub-agent. Werk alleen aan de opdracht.")
        };

        // Laad alleen wat deze stap nodig heeft — niet de hele geschiedenis.
        foreach (var key in artifactKeys)
        {
            var artifact = await _store.LoadAsync(key, ct);
            if (artifact is not null)
                messages.Add(new(ChatRole.User, $"Artefact {key}:\n{artifact}"));
        }

        messages.Add(new(ChatRole.User, instruction));

        var response = await _client.GetResponseAsync(messages, cancellationToken: ct);
        var output = response.Text ?? string.Empty;

        await _store.SaveAsync($"step-output-{Guid.NewGuid():N}", output, ct);
        return output;
    }
}

De winst zit niet in de elegantie van deze code, maar in de discipline die hij afdwingt: elke stap krijgt een krappe, doelgerichte context in plaats van een groeiende sneeuwbal.

Test het, vertrouw het niet op gevoel

Het lastige aan context rot is dat het geleidelijk is. Je feature breekt niet; hij wordt langzaam vager. Daarom is de belangrijkste maatregel een evaluatieset die je context-grootte meeneemt als variabele. Bouw een handvol vragen met bekende antwoorden, en draai dezelfde vragen met verschillende hoeveelheden context: alleen de top-3 chunks, de top-10, de top-40. Als de top-40-variant slechter scoort dan de top-5 — en dat gebeurt vaker dan je denkt — heb je je antwoord.

public sealed record EvalCase(string Question, string ExpectedSubstring);

public static async Task<double> ScoreAsync(
    IChatClient client,
    IReadOnlyList<EvalCase> cases,
    Func<string, IReadOnlyList<Chunk>> retrieve,
    int budgetTokens)
{
    var correct = 0;

    foreach (var c in cases)
    {
        var ranked = retrieve(c.Question);
        var context = SelectWithinBudget(ranked, budgetTokens);
        var prompt = BuildPrompt("Beantwoord op basis van de context.", context, c.Question);

        var response = await client.GetResponseAsync(prompt);
        if ((response.Text ?? "").Contains(c.ExpectedSubstring, StringComparison.OrdinalIgnoreCase))
            correct++;
    }

    return (double)correct / cases.Count;
}

Draai dit met verschillende budgetTokens en je hebt een curve in plaats van een onderbuikgevoel. Mijn ervaring is dat die curve vaak vroeg piekt en daarna afvlakt of zelfs daalt — precies wat de literatuur voorspelt.

De afweging eerlijk benoemd

Er is een tegengeluid, en dat verdient een plek. Lange context windows zijn niet nutteloos. Voor taken waar je echt veel materiaal in één keer nodig hebt — een groot codebestand begrijpen, een lang document samenvatten waar je geen goede chunking voor hebt — is een ruim window een reële verbetering ten opzichte van het alternatief, namelijk het materiaal helemaal niet kunnen meenemen. En modellen worden beter: de grens van de smart zone schuift met elke generatie op. Wie vandaag alles dichttimmert rond een harde 100k-grens, bouwt mogelijk volgend jaar een onnodige beperking in.

De nuance is dus: gebruik lange context waar hij echt waarde toevoegt, maar leun er niet standaard op als vervanging voor goede retrieval en contextcuratie. "Stop er gewoon alles in" is geen architectuur; het is uitstel van de keuze die je toch moet maken.

Conclusie

De boodschap van het trending HN-stuk is voor mij als bouwer geen pessimisme, maar een ontwerprichtlijn. Behandel het context window als een budget, niet als een opslagbak. Kies je context bewust in plaats van royaal. Plaats het belangrijkste materiaal waar het model het scherpst leest. Verplaats informatie de live-sessie uit naar artefacten. En meet het effect met een evalset die context-grootte als variabele meeneemt, zodat je beslissingen op data baseert in plaats van op het getal op de doos.

Het getal op de doos wordt elke release groter. Het bruikbare deel groeit langzamer. Bouw alsof dat zo is, dan word je niet verrast.

Bron: "Don't trust large context windows" — Garrit's Notes en de Hacker News-discussie. Onderzoek: RULER en het context rot-rapport van Chroma.

Wil je op de hoogte blijven?

Schrijf je in voor mijn nieuwsbrief of neem contact op voor freelance projecten.

Neem Contact Op