Back to "Webcontent ophalen en analyseren met LLM's in C#"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Agents AI C# LLM Systems Design

Webcontent ophalen en analyseren met LLM's in C#

Friday, 19 December 2025

Notitie: Dit artikel leert de basisprincipes van webcontent extractie met LLM's met behulp van de eenvoudigst mogelijke aanpak. Voor productie use cases (websamenvatting, document analyse, agent tools), zie DocSummarizer GitHub release - het implementeert de hier getoonde architectuur maar productie-kwaliteit: BERT inbeddingen, hybride zoekopdracht, Playwright voor SPA's, SSRF bescherming, gevalideerde citaten, en de juiste dekking tracking.

Als je ChatGPT vraagt om "lees dit artikel en vat het samen," wat gebeurt er dan eigenlijk? Als je je voorstelt dat de AI een browser opent en leest zoals je zou doen - zo werkt het niet.

LLM's surfen niet op het web. Ze redeneren over fragmenten die uw code selecteert.

Dit artikel laat zien hoe je dit kunt bouwen in C# met Ollama - geen kaders, alleen praktische code die je kunt debuggen.

TL;DR: Uw code haalt → reinigt → brokken → selecteert. De LLM ziet alleen de fragmenten die u injecteert. Selectie is verlies door ontwerp - dat is zowel de beperking als de architectuur.

Het ene ding dat dit werk maakt

Hier is het inzicht dat verandert hoe je deze systemen bouwt:

Selectie is de productbeslissing. Het is ook waar de meeste mislukkingen ontstaan.

De LLM is stroomafwaarts van uw selectie. Het kan informatie die u niet hebt getoond niet herstellen. Wanneer een agent "fails om het antwoord te vinden," het probleem is bijna nooit het model - het is dat uw selectie logica koos de verkeerde brokken. (Dit is hetzelfde principe achter waarom ik kaders als LangChain vermijd - ze abstracteren de selectie logica die je nodig hebt om te debuggen.)

Dit is de reden waarom "agent surfen" faalt stilletjes. De agent antwoordt zelfverzekerd op basis van wat het zag. Je weet nooit dat het miste de juiste inhoud.

flowchart LR
    URL[Full Page] --> Select[Your Selection]
    Select --> LLM[LLM Sees This]
    LLM --> Answer[Answer]
    
    URL -.->|"50KB"| Select
    Select -.->|"2KB"| LLM
    
    Miss[Missed Content] -.->|"Never seen"| X[❌]
    
    style Select stroke:#e74c3c,stroke-width:3px
    style Miss stroke:#95a5a6,stroke-width:2px,stroke-dasharray: 5 5

Het model kan alleen redeneren over wat je het gaf. Bouw dienovereenkomstig.


Ontwerpdoelen vóór code

Alvorens iets te schrijven, dit zijn de beperkingen:

Waarom het er toe doet |------------|----------------| | Geen JS-weergave Alleen HTML (Playwright voor SPA's) | Gebonden tokens Hard budget per aanvraag (met 2-4K tokens typisch) | Brongebonden antwoorden LLM mag webkennis niet hallucineren | Deterministische selectie Zelfde invoer → zelfde brokken (debugable) | Waarneembaar Log in wat je hebt geselecteerd, waarom en wat je hebt weggegooid

Als je niet kunt uitleggen waarom een brok is geselecteerd, kun je geen fouten debuggen.


De Pipeline

flowchart TB
    URL[URL] --> Fetch[1. Fetch]
    Fetch --> Clean[2. Clean]
    Clean --> Chunk[3. Chunk]
    Chunk --> Select[4. Select]
    Select --> LLM[LLM]
    LLM --> Answer[Answer]
    
    Fetch -.->|"57KB HTML"| Clean
    Clean -.->|"6KB text"| Chunk
    Chunk -.->|"5 chunks"| Select
    Select -.->|"2 chunks"| LLM
    
    style Select stroke:#e74c3c,stroke-width:3px
    style Clean stroke:#f39c12,stroke-width:3px

Elke stap vermindert de data. Tegen de tijd dat de LLM het ziet, ben je van 57KB van HTML naar misschien 2KB van relevante tekst gegaan. Elke reductie is verliesloos. Elke reductie kan het antwoord weggooien. Dit is hetzelfde patroon dat ik gebruik voor het analyseren van grote CSV-bestanden - de LLM redenen, uw code berekent en selecteert.


Uitvoerend voorbeeld

In dit artikel gebruiken we één URL:

https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10/overview

En drie vragen van toenemende specificiteit:

  1. "Wat is er nieuw in .NET 10?"
  2. "Welke prestatieverbeteringen worden genoemd?"
  3. "Is er iets over AOT?"

Dit houdt de voorbeelden gegrond en laat zien hoe selectie belangrijker is naarmate vragen specifiek worden.


Instellen

# Install Ollama from https://ollama.ai
ollama pull llama3.2:3b

# NuGet packages
dotnet add package AngleSharp      # HTML parsing
dotnet add package OllamaSharp     # Ollama client (5.1.x)

OllamaSharp-notitie: In versie 5.x, GenerateAsync geeft IAsyncEnumerable<GenerateResponseStream?> Het stroomt tokens als ze worden gegenereerd. await foreach. De steekproef project pinnen 5.1.5.


Stap 1: Ophalen

Standaard HTTP, maar met de details die er toe doen:

public class WebFetcher : IDisposable
{
    private readonly HttpClient _http;
    
    public WebFetcher()
    {
        var handler = new HttpClientHandler
        {
            AllowAutoRedirect = true,
            MaxAutomaticRedirections = 5,  // Cap redirects
            AutomaticDecompression = DecompressionMethods.All
        };
        
        _http = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(30) };
        _http.DefaultRequestHeaders.Add("User-Agent", 
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
    }
    
    public async Task<string> FetchAsync(string url)
    {
        var response = await _http.GetAsync(url);
        
        // Bail if not HTML
        var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
        if (!contentType.Contains("html") && !contentType.Contains("text"))
            throw new InvalidOperationException($"Not HTML: {contentType}");
        
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
    
    public void Dispose() => _http.Dispose();
}

Waar dit mee omgaat.: Redirects (capped), compressie, time-outs, User-Agent, content-type validatie.

Wat het niet doet.: JavaScript rendering, authentication, rate limiting, robots.txt. Voor de productie, voeg per-host vertragingen en respect crawl beleid.


Stap 2: Schoonmaken

Raw HTML is meestal lawaai. Maak het schoon of verspil tokens op de lay-out.

Een typische pagina:

57KB HTML → 6KB useful text (90% reduction)

De strategie

flowchart LR
    HTML[Raw HTML] --> Remove[Remove Noise]
    Remove --> Find[Find Main Content]
    Find --> Extract[Extract Text + Headings]
    Extract --> Normalize[Normalize]
    
    Remove -.->|"script, style, nav, ads"| Find
    Find -.->|"main, article, .content"| Extract
    
    style Remove stroke:#e74c3c,stroke-width:3px
    style Find stroke:#f39c12,stroke-width:3px

Reiniging is verlies - Handvat met zorg

Uw schoonmaker kan absoluut verwijderen het antwoord. Veel voorkomende fouten:

  • Agressieve selecties verwijderen legitieme inhoud
  • De hoofdinhoud heeft ongebruikelijke klassenamen (.article-body, .post-text)
  • Antwoord bevindt zich in een zijbalk of uitroepvak

Mitigaties:

  • Rubrieken expliciet bewaren (h1-h6)
  • Verwijderen door rol en bekende ketelplaat, geen brede patronen
  • Log: uitgepakt elementpad + tekstlengte
  • Terugval: als de belangrijkste inhoud is verdacht kort, probeer lichaam
public class HtmlCleaner
{
    private readonly HtmlParser _parser = new();
    
    // Known noise - remove these entirely
    private static readonly string[] NoiseElements = 
        { "script", "style", "nav", "footer", "aside", "iframe", "noscript" };
    
    // Boilerplate patterns - remove by role, not aggressive wildcards
    private static readonly string[] NoiseSelectors = 
    {
        "[role='navigation']", "[role='banner']", "[role='complementary']",
        "[class*='cookie']", "[class*='newsletter']", "[aria-hidden='true']"
    };
    
    // Where to find content - order matters (most specific first)
    private static readonly string[] ContentSelectors = 
        { "main", "article", "[role='main']", ".content", ".post-content" };
    
    public CleanResult Clean(string html)
    {
        var doc = _parser.ParseDocument(html);
        
        // Remove noise
        foreach (var tag in NoiseElements)
            foreach (var el in doc.QuerySelectorAll(tag).ToList())
                el.Remove();
        
        foreach (var selector in NoiseSelectors)
            foreach (var el in doc.QuerySelectorAll(selector).ToList())
                el.Remove();
        
        // Find main content
        IElement? main = null;
        string? matchedSelector = null;
        
        foreach (var selector in ContentSelectors)
        {
            main = doc.QuerySelector(selector);
            if (main != null) { matchedSelector = selector; break; }
        }
        
        // Fallback to body if main content is suspiciously short
        var text = main?.TextContent ?? "";
        if (text.Length < 500 && doc.Body != null)
        {
            main = doc.Body;
            matchedSelector = "body (fallback)";
            text = main.TextContent;
        }
        
        return new CleanResult
        {
            Text = NormalizeWhitespace(text),
            MatchedSelector = matchedSelector ?? "none",
            OriginalLength = html.Length
        };
    }
    
    private string NormalizeWhitespace(string text)
    {
        text = Regex.Replace(text, @"[ \t]+", " ");
        text = Regex.Replace(text, @"\n\s*\n+", "\n\n");
        return text.Trim();
    }
}

public record CleanResult(string Text, string MatchedSelector, int OriginalLength);

Extractie van leesbaarheid (Scoring paragraphs, text density) is een heel konijnenhol. Voor nu, selecte-based extractie werkt voor documentatie en blogs. Het voorbeeld project bevat een scoren extractor als je het nodig hebt.


Stap 3: Chunk

Je hebt 6KB aan schone sms'jes.

  • Naald in hooiberg: LLM's presteren slechter wanneer relevante informatie wordt begraven in grote context
  • Kosten: Meer tokens = meer geld en latentie
  • Focus: Geselecteerde stukken produceren beter redeneren dan alles tegelijk

Chunking strategie is belangrijker dan je zou verwachten. Ik bedek dit in de diepte in de RAG architectuur artikel - dezelfde principes gelden of je nu webpagina's of documenten aan het doorknippen bent.

Native Sentence Chunking

Dit is een beginpunt, geen productiecode:

public List<string> ChunkBySentence(string text, int maxTokens = 2000)
{
    var chunks = new List<string>();
    
    // WARNING: This breaks on abbreviations, decimals, URLs, code samples
    var sentences = text.Split(new[] { ". ", ".\n", "! ", "? " }, 
        StringSplitOptions.RemoveEmptyEntries);
    
    var current = new StringBuilder();
    var tokens = 0;
    
    foreach (var sentence in sentences)
    {
        var sentenceTokens = EstimateTokens(sentence);
        
        if (tokens + sentenceTokens > maxTokens && current.Length > 0)
        {
            chunks.Add(current.ToString().Trim());
            current.Clear();
            tokens = 0;
        }
        
        current.Append(sentence).Append(". ");
        tokens += sentenceTokens;
    }
    
    if (current.Length > 0)
        chunks.Add(current.ToString().Trim());
    
    return chunks;
}

// Rough estimate - OK for demos, not for billing
private int EstimateTokens(string text) 
    => (int)(text.Split(' ').Length * 1.3);

Waarom dit naïef is:

  • ". " pauzes op "Dr. Smith," "v1.0," URL's
  • Token schatting door woordtelling is ±20% korting
  • Geen overlapping tussen brokken (verlies context aan grenzen)

Beter: Op koers gerichte Chunking

Voor documentatie, brok per sectie:

public List<ContentChunk> ChunkByHeadings(string html)
{
    var doc = new HtmlParser().ParseDocument(html);
    var chunks = new List<ContentChunk>();
    
    var headings = doc.QuerySelectorAll("h1, h2, h3");
    
    foreach (var heading in headings)
    {
        var content = new StringBuilder();
        content.AppendLine(heading.TextContent);
        
        var sibling = heading.NextElementSibling;
        while (sibling != null && !sibling.TagName.StartsWith("H"))
        {
            content.AppendLine(sibling.TextContent);
            sibling = sibling.NextElementSibling;
        }
        
        chunks.Add(new ContentChunk
        {
            Heading = heading.TextContent.Trim(),
            Content = content.ToString().Trim(),
            HeadingLevel = int.Parse(heading.TagName[1..])
        });
    }
    
    return chunks;
}

Dit behoudt de documentstructuur en maakt selectie zinvoller.


Stap 4: Selecteren

Dit is waar de meeste mislukkingen gebeuren. En waar debuggen zou moeten beginnen.

Je hebt 5 brokken. De gebruiker vroeg "Welke prestatieverbeteringen zijn in .NET 10?" Slechts 1-2 brokken noemen prestaties. Stuur deze.

flowchart TB
    Q["Question: What perf improvements?"] --> Score[Score Each Chunk]
    
    subgraph Chunks
        C1["Chunk 1: Overview..."] 
        C2["Chunk 2: Runtime perf..."]
        C3["Chunk 3: Libraries..."]
        C4["Chunk 4: SDK changes..."]
    end
    
    Score --> C1
    Score --> C2
    Score --> C3
    Score --> C4
    
    C2 -->|"score: 3"| Top[Selected]
    C3 -->|"score: 1"| Top
    
    style C2 stroke:#27ae60,stroke-width:3px
    style C1 stroke:#95a5a6,stroke-width:2px,stroke-dasharray: 5 5
    style C4 stroke:#95a5a6,stroke-width:2px,stroke-dasharray: 5 5

Keyword Matching (Simple, Debuggable)

public record ScoredChunk(string Content, string? Heading, int Score, List<string> MatchedKeywords);

public List<ScoredChunk> SelectByKeywords(
    List<ContentChunk> chunks, 
    string question, 
    int topK = 3)
{
    // Normalize and filter stopwords
    var keywords = question.ToLower()
        .Split(' ', StringSplitOptions.RemoveEmptyEntries)
        .Where(w => w.Length > 3)
        .Where(w => !Stopwords.Contains(w))
        .Select(w => w.Trim(',', '.', '?', '!'))
        .Distinct()
        .ToList();
    
    var scored = chunks.Select(chunk =>
    {
        var text = (chunk.Heading + " " + chunk.Content).ToLower();
        var matched = keywords.Where(kw => text.Contains(kw)).ToList();
        
        // Boost if keyword appears in heading
        var headingBoost = chunk.Heading != null && 
            keywords.Any(kw => chunk.Heading.ToLower().Contains(kw)) ? 2 : 0;
        
        return new ScoredChunk(
            chunk.Content, 
            chunk.Heading, 
            matched.Count + headingBoost,
            matched
        );
    })
    .OrderByDescending(x => x.Score)
    .Take(topK)
    .ToList();
    
    // LOG THIS - it's your debugging lifeline
    foreach (var s in scored)
        Console.WriteLine($"  [{s.Score}] {s.Heading ?? "(no heading)"}: {string.Join(", ", s.MatchedKeywords)}");
    
    return scored;
}

private static readonly HashSet<string> Stopwords = new()
    { "what", "how", "does", "the", "are", "is", "in", "for", "of", "to", "and" };

Belangrijkste verbeteringen boven naïef tellen:

  • Stopwoord verwijderen (anders "wat" en "de" altijd overeenkomen)
  • Rubriek boost (structurele signaal)
  • Gematchte zoekwoorden loggen - Dit is je debuggen reddingslijn

Inbeddingsbasis (Semantisch)

Trefwoorden falen op synoniemen. "perf" komt niet overeen met "prestatieverbeteringen."

Inbeddingen vinden semantische gelijkenis. Als je dieper wilt gaan op inbeddingen en vector zoeken, ik bedek dit uitgebreid in de RAG primer series en semantisch zoeken met ONNX.

public async Task<List<ScoredChunk>> SelectByEmbedding(
    List<ContentChunk> chunks, 
    string question, 
    int topK = 3)
{
    var questionEmbed = await EmbedAsync(question);
    
    // Cache these per URL in production
    var scored = new List<(ContentChunk Chunk, double Score)>();
    
    foreach (var chunk in chunks)
    {
        var chunkEmbed = await EmbedAsync(chunk.Content);
        var similarity = CosineSimilarity(questionEmbed, chunkEmbed);
        scored.Add((chunk, similarity));
    }
    
    return scored
        .OrderByDescending(x => x.Score)
        .Take(topK)
        .Select(x => new ScoredChunk(x.Chunk.Content, x.Chunk.Heading, (int)(x.Score * 100), new()))
        .ToList();
}

private async Task<double[]> EmbedAsync(string text)
{
    var request = new EmbedRequest { Model = "nomic-embed-text", Input = [text] };
    var response = await _ollama.EmbedAsync(request);
    return response.Embeddings.First().ToArray();
}

Afhandelen:

  • Trefwoorden: Snel, debugbaar, letterlijk bijpassend
  • Inbeddingen: Semantisch, maar langzamer en heeft caching nodig

Voor productie, cache inbeddingen per (URL, chunk hash) in SQLite of een vector database zoals Qdrant.


Verzenden naar de LLM

Structuur van de prompt om brongebonden antwoorden te forceren met aanhaling:

public string BuildPrompt(string url, List<ScoredChunk> chunks, string question)
{
    var sb = new StringBuilder();
    
    sb.AppendLine("You are answering a question using ONLY the content below.");
    sb.AppendLine("Rules:");
    sb.AppendLine("- Answer ONLY from the provided sources");
    sb.AppendLine("- Cite which SOURCE number supports each claim");
    sb.AppendLine("- Include 1-2 brief quotes as evidence");
    sb.AppendLine("- If the answer isn't in the sources, say 'Not enough information'");
    sb.AppendLine("- End with Confidence: High/Medium/Low");
    sb.AppendLine();
    
    for (int i = 0; i < chunks.Count; i++)
    {
        sb.AppendLine($"=== SOURCE {i + 1} ===");
        if (chunks[i].Heading != null)
            sb.AppendLine($"Section: {chunks[i].Heading}");
        sb.AppendLine($"From: {url}");
        sb.AppendLine(chunks[i].Content);
        sb.AppendLine();
    }
    
    sb.AppendLine($"Question: {question}");
    sb.AppendLine();
    sb.AppendLine("Answer (with citations and confidence):");
    
    return sb.ToString();
}

Dit gaat van "chatty summary" naar "analyse met herkomst."

Ollama bellen

public async Task<string> AskAsync(string prompt)
{
    var request = new GenerateRequest { Model = "llama3.2:3b", Prompt = prompt };
    
    var response = new StringBuilder();
    await foreach (var chunk in _ollama.GenerateAsync(request))
    {
        if (chunk?.Response != null)
            response.Append(chunk.Response);
    }
    
    return response.ToString().Trim();
}

Dit is gereedschap gebruiken

Wat we hebben gebouwd is een agent patroon zonder het kader. Waarom heb ik liever deze aanpak dan LangChain - expliciete orkestratie is beter dan magische abstracties bij debuggen.

flowchart LR
    subgraph Tools["Tools (Deterministic)"]
        T1[fetch_url]
        T2[clean_html]
        T3[chunk_text]
        T4[select_relevant]
    end
    
    subgraph LLM["LLM (Reasoning)"]
        R[Interpret + Answer]
    end
    
    T1 --> T2 --> T3 --> T4 --> R
    
    R -->|"Low confidence"| Retry[Retry with different selection]
    Retry --> T4
    
    style T4 stroke:#e74c3c,stroke-width:3px
    style R stroke:#3498db,stroke-width:3px

De lus: als het vertrouwen laag is, probeer het opnieuw met meer brokken of verschillende trefwoorden.

var answer = await AskAsync(prompt);

if (answer.Contains("Not enough information") || answer.Contains("Confidence: Low"))
{
    // Retry with more chunks
    var moreChunks = SelectByKeywords(allChunks, question, topK: 5);
    answer = await AskAsync(BuildPrompt(url, moreChunks, question));
}

Wat er misgaat

flowchart TB
    subgraph Failures["Failure Modes"]
        F1[Cleaner removes content]
        F2[Chunking breaks mid-thought]
        F3[Selection picks wrong chunks]
        F4[LLM hallucinates connections]
    end
    
    F1 --> R1["'Not enough info' - answer existed"]
    F2 --> R2["Partial answer - context lost"]
    F3 --> R3["Wrong answer - right content skipped"]
    F4 --> R4["Confident but wrong"]
    
    style F3 stroke:#e74c3c,stroke-width:3px

De debugregel:

Als het antwoord verkeerd is, is het bijna altijd omdat selectie was fout, niet omdat het model faalde.

Checklist debuggen

  1. Geselecteerde brok-ID's en -scores loggen
  2. Toon welke trefwoorden overeenkomen (of inbeddingsafstanden)
  3. De exacte prompt opnieuw afspelen dat werd verzonden
  4. Vergelijken: Was de juiste inhoud in de opgehaalde HTML? Geschoonde tekst? Brok?

De storing is meestal niet de LLM, maar stroomopwaarts.


Het samenbrengen

public class WebAnalyzer : IDisposable
{
    private readonly WebFetcher _fetcher = new();
    private readonly HtmlCleaner _cleaner = new();
    private readonly OllamaApiClient _ollama = new(new Uri("http://localhost:11434"));
    
    public async Task<AnalysisResult> AnalyzeAsync(string url, string question)
    {
        // 1. Fetch
        var html = await _fetcher.FetchAsync(url);
        
        // 2. Clean (with observability)
        var cleaned = _cleaner.Clean(html);
        Console.WriteLine($"Cleaned: {cleaned.OriginalLength} → {cleaned.Text.Length} bytes ({cleaned.MatchedSelector})");
        
        // 3. Chunk
        var chunks = ChunkByHeadings(html);
        Console.WriteLine($"Chunks: {chunks.Count}");
        
        // 4. Select (with logging)
        Console.WriteLine("Selection scores:");
        var selected = SelectByKeywords(chunks, question, topK: 3);
        
        // 5. Prompt + LLM
        var prompt = BuildPrompt(url, selected, question);
        var answer = await AskAsync(prompt);
        
        return new AnalysisResult
        {
            Answer = answer,
            ChunksUsed = selected.Count,
            SelectionScores = selected.Select(s => s.Score).ToList()
        };
    }
    
    public void Dispose() => _fetcher.Dispose();
}

Gebruik met ons draaiende voorbeeld:

using var analyzer = new WebAnalyzer();

var result = await analyzer.AnalyzeAsync(
    "https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10/overview",
    "What performance improvements are in .NET 10?"
);

Console.WriteLine(result.Answer);

Output omvat aanhalingstekens en vertrouwen:

Based on SOURCE 1 and SOURCE 2:

.NET 10 includes several performance improvements:
- JIT improvements including better inlining and method devirtualization (SOURCE 1)
- "Enhanced loop inversion for better optimization" (SOURCE 1)
- NativeAOT enhancements for improved code generation (SOURCE 2)

Confidence: High

Wanneer dit werkt (en niet doet)

Werkt goed Werkt niet |------------|--------------| Documentatie JavaScript STA's Blogposts, artikelen en dynamische/interactieve inhoud Technische referenties Multi-page onderzoek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Geauthentiseerde HTML-inhoud

Voor JS-zware sites, moet je Playwright voor .NET.


Het belangrijkste beginsel

flowchart LR
    subgraph Your["Your Code's Job"]
        direction TB
        F[Fetch reliably]
        C[Clean carefully]
        S[Select correctly]
        O[Observe everything]
    end
    
    subgraph LLM["LLM's Job"]
        direction TB
        R[Reason over what you gave it]
        A[Admit when it doesn't know]
    end
    
    Your --> LLM
    
    style S stroke:#e74c3c,stroke-width:3px
    style R stroke:#3498db,stroke-width:3px

De LLM kan alleen redeneren over wat je hem hebt gegeven. Selectie is jouw verantwoordelijkheid.

Vraag de LLM niet om te bladeren, vraag het om te redeneren.


Volledige steekproefproject

Complete uitvoering van het werk: Meestal lucid.LlmWebFetcher

Omvat:

  • WebFetcher - HTTP met de juiste handling
  • HtmlCleaner - Geluidsverwijdering + terugvalstrategieën
  • ContentChunker - Verdachtheid en op koers gebaseerde brokstukken
  • WebContentAnalyzer - Volledige pijpleiding met houtkap
  • OllamaExtensions - Helper voor het streamen van reacties
cd Mostlylucid.LlmWebFetcher
dotnet run

Middelen

Bibliotheken

LLM's

Gerelateerde artikelen

Microsoft AI Stack

logo

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