Back to "Abrufen und Analysieren von Webinhalten mit LLMs 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

Abrufen und Analysieren von Webinhalten mit LLMs in C#

Friday, 19 December 2025

Anmerkung: Dieser Artikel lehrt die Grundlagen der Web-Content-Extraktion mit LLMs mit dem einfachsten möglichen Ansatz. Für Anwendungsfälle in der Produktion (Web-Zusammenfassung, Dokumentenanalyse, Agenten-Tools), siehe DocSummarizer GitHub-Freigabe - es implementiert die hier gezeigte Architektur, aber Produktionsqualität: BERT-Embeddings, Hybrid-Suche, Playwright für SPAs, SSRF-Schutz, validierte Zitate und korrekte Abdeckungsverfolgung.

Wenn Sie ChatGPT bitten, " diesen Artikel zu lesen und zusammenzufassen", was passiert eigentlich? Wenn Sie sich vorstellen, dass die KI einen Browser öffnet und liest, wie Sie es tun würden - so funktioniert es nicht.

LLMs surfen nicht im Web. Sie begründen Fragmente, die Ihr Code auswählt.

Dieser Artikel zeigt Ihnen, wie Sie dies in C# mit Ollama bauen - keine Frameworks, nur praktischer Code, den Sie debuggen können.

TL;DR: Ihr Code holt → reinigt → Stücke → wählt aus. Das LLM sieht nur die Fragmente, die Sie injizieren. Die Auswahl ist durch Design verlustig - das ist sowohl die Beschränkung als auch die Architektur.

Die eine Sache, die diese Arbeit macht

Hier ist die Einsicht, die ändert, wie Sie diese Systeme bauen:

Auswahl ist die Produktentscheidung. Es ist auch, wo die meisten Fehler entstehen.

Das LLM ist stromabwärts von Ihrer Auswahl. Es kann keine Informationen zurückgewinnen, die Sie nicht gezeigt haben. Wenn ein Agent "fehlt, die Antwort zu finden", ist das Problem fast nie das Modell - es ist, dass Ihre Auswahl Logik wählte die falschen Stücke. (Dies ist das gleiche Prinzip hinter warum ich Frameworks wie LangChain vermeide - sie abstrahieren die Auswahllogik, die Sie debuggen müssen.)

Aus diesem Grund "agent browsing" versagt still. Der Agent selbstbewusst Antworten auf der Grundlage, was es sah. Sie wissen nie, dass es den richtigen Inhalt verpasst.

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

Das Modell kann nur begründen, was Sie ihm gegeben haben. Bauen Sie entsprechend.


Design-Ziele vor dem Code

Bevor Sie etwas schreiben, sind dies die Einschränkungen:

Warum es wichtig ist |------------|----------------| | Keine JS-Rendering Nur statisches HTML (Playwright für SPAs) | Gebundene Zeichen Hartes Budget pro Anfrage (2-4K Zeichen typisch) | Quellengebundene Antworten LLM darf Web-Kenntnisse nicht halluzinieren | Deterministische Auswahl Gleiche Eingabe → gleiche Brocken (debuggingfähig) | Beobachtbar Loggen Sie, was Sie ausgewählt, warum, und was Sie verworfen

Wenn Sie nicht erklären können, warum ein Stück ausgewählt wurde, können Sie keine Fehler debuggen.


Die 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

Jeder Schritt reduziert Daten. Bis die LLM es sieht, sind Sie von 57KB HTML auf vielleicht 2KB relevanten Text gegangen. Jede Reduktion ist verlustig. Jede Reduktion kann die Antwort wegwerfen. Dies ist das gleiche Muster, das ich für Analyse großer CSV-Dateien - die LLM Gründe, Ihr Code berechnet und wählt.


Beispiel ausführen

In diesem Artikel verwenden wir eine URL:

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

Und drei Fragen der zunehmenden Spezifität:

  1. "Was gibt es Neues in .NET 10?"
  2. "Welche Leistungsverbesserungen werden erwähnt?"
  3. "Gibt es etwas über AOT?"

Dies hält die Beispiele geerdet und zeigt, wie Auswahl mehr zählt, da Fragen spezifisch werden.


Einrichtung

# 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-Anmerkung: In Version 5.x, GenerateAsync Renditen IAsyncEnumerable<GenerateResponseStream?> - es streamt Token, wie sie erzeugt werden. await foreach. Die Beispielprojektpins 5.1.5.


Schritt 1: Holen Sie sich

Standard HTTP, aber mit den Details, die wichtig sind:

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

Was das alles angeht: Redirects (capped), Komprimierung, Timeouts, User-Agent, Content-Type-Validierung.

Was es nicht tut: JavaScript Rendering, Authentifizierung, Rate Limiting, robots.txt. Für die Produktion, fügen Sie pro-host Verzögerungen und Respekt Crawl-Richtlinien.


Schritt 2: Sauber machen

Rohes HTML ist meist Rauschen. Reinigen Sie es oder Sie verschwenden Token auf Layout.

Eine typische Seite:

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

Die 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

Reinigung ist verlustig - Handle mit Sorgfalt

Ihr Cleaner kann die Antwort absolut löschen. Häufige Fehler:

  • Aggressive Selektoren entfernen legitimen Inhalt
  • Main content hat ungewöhnliche Klassennamen (.article-body, .post-text)
  • Die Antwort ist in einer Sidebar oder Callout Box

Milderungen:

  • Überschriften explizit beibehalten (h1-h6)
  • Entfernen durch Rolle und bekannter Boilerblech, nicht weit gefasste Muster
  • Protokoll: extrahierter Elementpfad + Textlänge
  • Fallback: Wenn der Hauptinhalt verdächtig kurz ist, versuchen Sie Körper
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);

Ablesbarkeitsextraktion (Scoring Absätze, Textdichte) ist ein ganzes Kaninchenloch. Zur Zeit arbeitet selektorbasierte Extraktion für Dokumentation und Blogs. Das Beispielprojekt enthält einen Scoring-Extraktor, wenn Sie es brauchen.


Schritt 3: Chunk

Sie haben 6KB sauberen Text. Warum nicht senden Sie alles?

  • Nadel im Heuhaufen: LLMs führen schlechter aus, wenn relevante Informationen im großen Kontext vergraben sind
  • Kosten: Mehr Token = mehr Geld und Latenz
  • Schwerpunkt: Ausgewählte Stücke produzieren bessere Argumentation als alles auf einmal

Ich beziehe mich eingehend auf die Strategie, die mehr zählt, als Sie erwarten würden. RAG-Architekturartikel - die gleichen Grundsätze gelten, ob Sie Web-Seiten oder Dokumente spalten.

Naive Sentence Chunking

Dies ist ein Ausgangspunkt, nicht Produktionscode:

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

Warum das naiv ist:

  • ". " bricht auf "Dr. Smith", "v1.0", URLs
  • Token-Schätzung nach Wortzahl ist ±20% off
  • Keine Überlappung zwischen Brocken (verliert den Kontext an Grenzen)

Besser: Heading-Based-Chunking

Zur Dokumentation nach Abschnitten:

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

Dies bewahrt die Dokumentenstruktur und macht die Auswahl sinnvoller.


Schritt 4: Wählen

Hier passieren die meisten Fehler. Und wo Debugging beginnen sollte.

Sie haben 5 Stücke. Der Benutzer fragte: "Welche Leistungsverbesserungen sind in .NET 10?" Nur 1-2 Stücke erwähnen die Leistung. Senden Sie diese.

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

Stichwort Passend (einfach, debuggierbar)

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

Wesentliche Verbesserungen gegenüber naiver Zählung:

  • Stoppwortentfernung (sonst "was" und "das" immer übereinstimmen)
  • Fahrtrichtungsanzeiger (strukturelles Signal)
  • Protokollierung abgestimmter Schlüsselwörter - das ist deine Debugging-Lifeline

Einbetten-basiert (semantisch)

Stichwörter scheitern auf Synonymen. "perf" wird nicht mit "Performance-Verbesserungen" übereinstimmen.

Einbettungen finden semantische Ähnlichkeit. Wenn Sie tiefer auf Einbettungen und Vektorsuche gehen wollen, decke ich dies ausführlich in der Serie RAG-Primer und semantische Suche mit 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();
}

Handelshemmnisse:

  • Schlagwörter: Schnell, debuggbar, wörtlich passend
  • Einbettungen: Semantisch, aber langsamer und braucht Caching

Für die Produktion, Cache-Embeddings pro (URL, Chunk Hash) in SQLite oder einer Vektordatenbank wie Qdrant.


Senden an die LLM

Strukturieren Sie die Aufforderung, quellengebundene Antworten mit Zitaten zu erzwingen:

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

Das bewegt sich von "Chatty Summary" zu "Analyse mit Provenienz".

Ollama anrufen

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

Dies ist Tool-Nutzung

Was wir aufgebaut haben, ist ein Agentenmuster ohne Rahmen. warum ich diesen Ansatz vor LangChain bevorzuge - explizite Orchestrierung schlägt magische Abstraktionen beim 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

Die Schleife: Wenn das Vertrauen niedrig ist, versuchen Sie es mit mehr Brocken oder anderen Schlüsselwörtern.

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

Was falsch läuft

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

Die Debugging-Regel:

Wenn die Antwort falsch ist, dann fast immer, weil Auswahl war falsch, nicht weil das Modell fehlgeschlagen ist.

Checkliste deaktivieren

  1. Ausgewählte Brocken-IDs und Partituren protokollieren
  2. Zeigt an, welche Schlüsselwörter übereinstimmen (oder Einbettung von Entfernungen)
  3. Abspielen der exakten Eingabeaufforderung die gesendet wurde
  4. Vergleich: War der richtige Inhalt im heruntergeladenen HTML? Sauberer Text? Irgendwelche Brocken?

Der Ausfall ist normalerweise nicht die LLM, sondern stromaufwärts.


Zusammenstellen

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

Verwendung mit unserem Beispiel:

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

Die Produktion umfasst Zitate und Vertrauen:

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

Wenn das funktioniert (und nicht)

Arbeitet gut, funktioniert nicht |------------|--------------| Dokumentation: JavaScript SPAS Blog-Posts, Artikel Dynamische/interaktive Inhalte Technical references mehrseitige Forschung Statische HTML-Authentifizierte Inhalte

Für JS-schwere Websites, benötigen Sie Playwright für .NET.


Das Schlüsselprinzip

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

Die LLM kann nur darüber nachdenken, was Sie ihr gegeben haben. Auswahl ist Ihre Verantwortung.

Bitten Sie das LLM nicht zu durchsuchen. Bitten Sie es zur Vernunft.


Vollständiges Beispielprojekt

Vollständige Arbeitsumsetzung: Meistlucid.LlmWebFetcher

Beinhaltet:

  • WebFetcher - HTTP mit korrekter Handhabung
  • HtmlCleaner - Lärmentfernung + Fallback-Strategien
  • ContentChunker - Satz und Heading-based Chunking
  • WebContentAnalyzer - Vollständige Pipeline mit Protokollierung
  • OllamaExtensions - Helfer für Streaming-Antworten
cd Mostlylucid.LlmWebFetcher
dotnet run

Aufkommen

Bibliotheken

LLMs

Verwandte Artikel

Microsoft AI Stack

logo

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