# Extraire et analyser les contenus Web avec des LLM dans C#

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

> **Note :**: Cet article enseigne les principes fondamentaux de l'extraction du contenu Web avec des LLM en utilisant la méthode la plus simple possibleM SK1 Pour les cas d'utilisation pour la production ( récapitulation du web , analyse de documents M SK4 outils d'agents MSC5 voir **[DocSummarizer](/blog/building-a-document-summarizer-with-rag)** [![Mise à jour de GitHub](https://img.shields.io/github/v/release/scottgal/mostlylucidweb?filter=docsummarizer*&label=docsummarizer)](https://github.com/scottgal/mostlylucidweb/releases?q=docsummarizer) - il met en œuvre l’architecture présentée ci-dessus mais la production-gradeM SK2 Emplacements BERTMSC3 recherche hybride , Playwright pour SPAsMska5 protection SSRFM Ska6 citations validéesMSKA7 et suivi de couverture appropriéeMske8

Lorsque vous demandez à ChatGPT " de lire cet article et d’en résumer le texte ," ce qui se passe en fait

**Les LLM ne naviguent pas sur le Web.**

Cet article vous montre comment construire cela en C# avec Ollama - sans cadres, simplement un code pratique que vous pouvez débugerM SK3

> **TL;DR**: Votre code reçoit → nettoie

[TOC]

## La seule chose qui fait que cela fonctionne

Voici le point de vue qui modifie la façon dont vous construisez ces systèmes.

**La sélection est la décision du produit. Celle-ci ' c'est aussi là où la plupart des défaillances se produisent**

Le LLM est en aval de votre sélection. [pourquoi je évite les cadres comme LangChain](/blog/why-i-dont-use-langchain) - ils abstraient la logique de sélection dont vous avez besoin pour débuger

C'est la raison pour laquelle "l'agent navigue sur le site " faille silencieusement. L'agent répond avec confiance en fonction de ce qu'il a vuM SK3 Vous ne savez jamais s'il n'a pas manqué au contenu appropriéMSC4

```mermaid
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
```

Le modèle ne peut qu’expliquer ce que vous lui avez donné.

---


## Objectifs avant le code

Avant de écrire quelque chose, il s’agit des contraintes:

| Constraint | Pourquoi cela importe |
|------------|----------------|
| **Aucune traduction JS** | HTML statique seulement (Droit de jeu pour SPAs) MSC3
| **Tokens limités** | Budget forfaitaire par demande
| **Source-réponses limitées** | LLM ne doit pas halluciner les connaissances du Web |
| **Choix déterministe** | Les mêmes intrants → les mêmes fragments
| **Observables** | Inscrivez ce que vous avez sélectionné, pourquoiM SK2 et ce que nous avons discardé |

Si vous pouvez't expliquer pourquoi un chunk a été sélectionné, vous pouvez

---


## Le pipeline

```mermaid
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
```

Chaque étape réduit les données. [Analyse de grands fichiers CSV](/blog/analysing-large-csv-files-with-local-llms) - les raisons de la LLM , votre code calcule et sélectionne.

---


## Exemple en cours

Tout au long de cet article, nous utilisons un URL.

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

Et trois questions de plus en plus précises:

1. "Qu'est-ce qu'il y a de nouveau dans '?
2. " Quelles améliorations du rendement sont mentionnées?
3. " Y a-t-il quelque chose à propos de l’OT?

Cela permet de garder les exemples à fond et montre comment la sélection est plus importante lorsque les questions sont précises.

---


## Mise en place

```bash
# 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)
```

> **Note OllamaSharp**: Dans la version `GenerateAsync` Retours `IAsyncEnumerable<GenerateResponseStream?>` - il envoie des tokens à mesure qu’ils sont générés. `await foreach`. Les pins de projet échantillonnés 5.1.5.

---


## Étape 1: Extraire

HTTP standard, mais avec les détails qui importent:

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

**Qu'est-ce que cela fait?**: Redirections ( captée), compressionM SK3 éloignements de tempsMSC4 UtilisateurMska5 agentMka6 contenuM Ska7 validation du typeM ska8

**Ce qu'il ne fait't**: Rendement JavaScript, AuthentificationM SK2 Limitation des vitessesMST3 Robots MST4txtMst5 Pour la productionM ST6 Ajouter parM st7 Délais d'accueil et respecter les politiques de crawlingMstr8

---


## Étape 2: Nettoyage

HTML brut est surtout bruit. La nettoyer ou vous jetez des tokens sur le plan de layout.

Une page typique:

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

### La Stratégie

```mermaid
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
```

### Nettoyage est perdu - Manipuler avec soin

Votre nettoyeur peut absolument supprimer la réponse. Irrégularités courantes:

- Les sélecteurs agressifs éliminent le contenu légitime
- Le contenu principal comporte des noms de classe inhabituels (`.article-body`, `.post-text`)
- La réponse se trouve dans une barre de côté ou une boîte d'appel.

**Améliorations**:

- Garder explicitement les titres (`h1`-`h6`)
- Supprimer par *rôle* et *plate-boiler connue*, ne sont pas des modèles larges
- Log: chemin d'éléments extraits + longueur du texte
- Fallback: si le contenu principal est soupçonnellement courtM SK1 essayer corps

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

> **Extraction de la lecture** (paragraphes de cotationM SK1densité du texte ) est un trou de lapin tout entier. Pour l'instantMSC4 sélecteurMNK5 travaux d'extraction basés sur la documentation et les blogsMRK6 Le projet-échantillon comprend un extracteur de cote si vous en avez besoin

---


## Étape 3: Contenant

Vous avez 6KB de texte propre. Pourquoi ne pas en envoyer tousM SK2

- **Aiguille dans le filet d'herbe**: Les LLM fonctionnent moins bien lorsque l'information pertinente est enfouie dans un grand contexte
- **Coût**: Plus de tokens = plus d'argent et de latence
- **Concentration**: Les chunks sélectionnés produisent un raisonnement meilleur que tout en même temps

La stratégie d’atténuation est plus importante que vous ne le croyez. [Article sur l'architecture RAG](/blog/rag-architecture) - les mêmes principes s’appliquent que vous ' tirez parti des pages Web ou des documents

### Sentence naïve

Il s’agit d’un point de départ, non du code de production:

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

**Pourquoi cela est naïf?**:

- `". "` interruptions sur les adresses URL "DrM SK1 Smith", "vMSC4
- L'estimation du symbole par nombre de mots est ±20% hors
- Aucun chevauchement entre les chunks ( perd le contexte aux limites)

### Amélioration de la qualité

Pour la documentation, chunk by section:

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

Cela préserve la structure du document et rend la sélection plus significative.

---


## Étape 4: Sélectionner

C’est là que se produisent la plupart des défaillances.

Vous avez 5 chunks. L'utilisateur a demandé " Quelles sont les améliorations de performance dans le NET ?

```mermaid
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
```

### Correspondance des mots clés (Simple, DébugableM SK2

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

**Principales améliorations par rapport au comptage naïf**:

- Remplacement des mots d'arrêt (d'autrefois "qu'est-ce que" et MSC3leMSC4compare toujoursM SK5
- Augmentation de la position (signal structurelM SK1
- **Enregistrement de mots-clés correspondants** - c'est votre ligne de sauvetage de débugging

### Embedding-Based (SemanticM SK2

Mots clés manquants sur les synonymes. M SK1perf" wonMSC3t match "performance improvementsMST5

Les embeddings trouvent une similarité sémantique. Si vous voulez aller plus loin sur les embeddeds et la recherche vectorielle, Je vais vous en exposer de manière approfondie dans le [Série d'amorçage RAG](/blog/rag-primer) et [Recherche sémantique avec ONNX](/blog/semantic-search-with-onnx-and-qdrant).

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

**Commerce-off**:

- Mots clés: FastM SK1 débuggable , correspondance littéraire
- Embeddings: Semantic, mais plus lente et nécessite le caching

Pour la production de , cache embeddings par (URLM SK2 chunk hash) dans SQLite ou une base de données vectorielle comme [Qdrant](/blog/building-a-lawyer-gpt-for-your-blog-part4).

---


## Envoyer au LLM

Structure du prompt pour forcer la source-réponses limitées avec citationM SK1

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

Cela passe de "résumé des discussions" à "analyse avec provenanceM SK3

### En appelant Ollama

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

---


## Utilisation de l'outil

Ce que nous avons construit est un modèle d’agent sans cadre. [pourquoi je préfère cette approche plutôt que LangChain](/blog/why-i-dont-use-langchain) - l'orchestre explicite bat les abstractions magiques lorsqu'il s'agit de débuquer des problèmes

```mermaid
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
```

La boucle: si la confiance est faible , essayer avec plus de morceaux ou des mots clés différents.

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

---


## Qu'est-ce qui va mal?

```mermaid
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
```

**La règle de débugging**:

> Si la réponse est erronée, c’est presque toujours parce que **la sélection était erronée**, non parce que le modèle a échoué

### Liste de vérification des débuggings

1. **Enregistrez les IDs et les notes du tronçon sélectionné**
2. **Indiquer les mots-clés correspondants** (ou distances d'implantationM SK1
3. **Jouer à nouveau le prompt exact** qui a été envoyée
4. **Comparaison**: A-t-il été le contenu approprié dans l'HTML téléchargé?

Le défaut est habituellement le suivant :

---


## Mettre les choses ensemble

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

Usage avec notre exemple en cours:

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

Les résultats comprennent les citations et la confiance:

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

---


## Quand cela fonctionne (et ne le fait pas'tM SK2

| Fonctionne bien | Ne fonctionne pas
|------------|--------------|
| Documentation
| Postes sur le blogM SK1 articles | Dynamiques/ contenu interactif |
| Références techniques | MultiM SK2 recherche sur les pages ≥|
| HTML statique | Contenu authentifié S|

Pour JS-sites lourds, vous avez besoin [Playwright pour .NET](https://playwright.dev/dotnet/).

---


## Le principal principe

```mermaid
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
```

Le LLM ne peut qu’expliquer ce que vous lui avez donné.

Ne demandez pas au LLM de naviguer. Demandez-le à raisonner.

---


## Projet d'échantillonnage complet

Mise en œuvre opérationnelle complète: [Mostlylucid.LlmWebFetcher](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid.LlmWebFetcher)

Comprend:

- `WebFetcher` - HTTP avec manipulation appropriée
- `HtmlCleaner` - Élimination du bruit + stratégies de régression
- `ContentChunker` - Répartition fondée sur une phrase et un titre
- `WebContentAnalyzer` - Pipeline complet avec signalisation
- `OllamaExtensions` - Aideur pour les réponses en streaming

```bash
cd Mostlylucid.LlmWebFetcher
dotnet run
```

---


## Ressources

**Bibliothèques**

- [Écarpe d'angle](https://anglesharp.github.io/) - Partage HTML
- [OllamaSharp](https://github.com/awaescher/OllamaSharp) - Client Ollama

**LLMs**

- [Ollama](https://ollama.ai/) - Durée de fonctionnement local du LLM
- [3.2:3b](https://ollama.ai/library/llama3.2) Modèle capable - Fast,
- [nomic-embed-texte](https://ollama.ai/library/nomic-embed-text) - Embeddings ~275MBM SK2

**Articles connexes**

- **[La série DocSummarizer](/blog/building-a-document-summarizer-with-rag)** - **L'approche de plus en plus efficace** pour la résumation du contenu Web. Cet article montre la façon la plus simple de récupérer et d’analyser les données; DocSummarizer montre la productionM SK2 le mode de classement avec l’extraction BERT , la recherche hybride M SK4 des citations validées , et un suivi approprié de la couverture MSC6
- [Analyse des grands fichiers CSV avec les LLM locaux](/blog/analysing-large-csv-files-with-local-llms) - Le même modèle : Les raisons de LLMM SK2 Computes de code
- [Pourquoi je n’utilise pas LangChain](/blog/why-i-dont-use-langchain) - Cadre
- [Première étape du RAG](/blog/rag-primer) - Comprendre la récupération-génération améliorée
- [Recherche sémantique avec ONNX et Qdrant](/blog/semantic-search-with-onnx-and-qdrant) - Recherche sémantique locale
- [Établissement d'un avocat GPT](/blog/building-a-lawyer-gpt-for-your-blog-part1) - série complète de mise en œuvre du RAG

**Stack d'AI de Microsoft**

- [Kernel sémantique](https://github.com/microsoft/semantic-kernel) - Si vous voulez un cadre
- [Microsoft.Extensions.AI](https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/) - Résumés