Abrufen und Analysieren von Webinhalten mit LLMs in C#
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
- 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:
- "Was gibt es Neues in .NET 10?"
- "Welche Leistungsverbesserungen werden erwähnt?"
- "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,
GenerateAsyncRenditenIAsyncEnumerable<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
- Ausgewählte Brocken-IDs und Partituren protokollieren
- Zeigt an, welche Schlüsselwörter übereinstimmen (oder Einbettung von Entfernungen)
- Abspielen der exakten Eingabeaufforderung die gesendet wurde
- 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 HandhabungHtmlCleaner- Lärmentfernung + Fallback-StrategienContentChunker- Satz und Heading-based ChunkingWebContentAnalyzer- Vollständige Pipeline mit ProtokollierungOllamaExtensions- Helfer für Streaming-Antworten
cd Mostlylucid.LlmWebFetcher
dotnet run
Aufkommen
Bibliotheken
- AngleSharp - HTML-Parsing
- OllamaSharp - Ollama Kunde
LLMs
- Ollama - Lokale LLM-Laufzeit
- llama3.2:3b - Schnelles, fähiges Modell
- nomisch-embed-text - Einbettungen (~275MB)
Verwandte Artikel
- Reihe DocSummarizer - Der FAR-BETTER-Ansatz für die Zusammenfassung von Webinhalten. Dieser Artikel zeigt den einfachsten Weg zum Abrufen und Analysieren; DocSummarizer zeigt die Produktionsqualität mit BERT-Extraktion, hybrider Suche, validierten Zitaten und korrekter Abdeckungsverfolgung.
- Analyse großer CSV-Dateien mit lokalen LLMs - Gleiches Muster: LLM-Gründe, Code berechnet
- Warum ich LangChain nicht benutze - Rahmenloses Agentendesign
- RAG Primer - Verständnis der retrieval-augmentierten Generation
- Semantische Suche mit ONNX und Qdrant - Lokale semantische Suche
- Aufbau einer Rechtsanwältin GPT - Vollständige RAG-Implementierungsreihe
Microsoft AI Stack
- Semantischer Kern - Wenn Sie einen Rahmen wollen
- Microsoft.Extensions.AI - Abstraktionen