RAG für Implementer-Hybridsuche und automatische Indexierung
Einleitung
Teil der RAG-Serie Dies ist Teil der Produktionsintegrationsmuster von :
- RAG-Ursprünge und Grundlagen Was Einbettungen sind, warum sie wichtig sind
- RAG-Architektur und Innenarchitektur Datenverarbeitungsdatenbanken
- RAG in der Praxis Gebäude komplette RAG-Systeme
- Teil Umsetzung des Qdrant-Systems Bequeme semantische Suchstiftung
- Teil - Semantische Suche in Aktion Hybride Suche nach , und UI-Komponenten
- Teil-Hybridsuche Produktionsintegrationsmuster
- Teil 6:-GraphRAG Wissensgraphen für das Verständnis auf corpus-Ebene
Im TeilWir bauten die Stiftung... ONNX Einbettungen und Qdrant-Speicher. Teil BWir deckten die Suche UI und hybride Suchimplementierung ab. automatische Indexierung Über FileSystemWatcher: Aktualisierung des Inhalts von -Touch
Hybride Suche Das Beste aus beiden Welten
Semantische Suche ist leistungsstark, aber traditionelle Voll---Textsuche zeichnet sich immer noch bei genauen Phrasen und Fachbegriffen aus Die Lösung Beides verwenden
Warum Hybride? Unterschiedliche Ansätze haben unterschiedliche Stärken
- PostgreSQL Volltext (hier abgedecktGenaue Übereinstimmungen mit den technischen Bedingungen der ,
- Semantische VektorsucheBedeutung: ,; Kontext: MS K2; Synonyme: konzeptuell verwandte Inhalte
Reziproker Rang Fusion
Wir benutzen Gegenseitigkeitsrang Fusion Ergebnisse aus mehreren Suchquellen kombinieren
flowchart TB
A[User Query: 'docker containers'] --> B[PostgreSQL Full-Text Search]
A --> C[Semantic Vector Search]
B --> D["Results:<br/>1. 'Docker Basics' (rank 1)<br/>2. 'Containerizing Apps' (rank 2)<br/>3. 'Docker Compose' (rank 3)"]
C --> E["Results:<br/>1. 'Containerizing Apps' (rank 1)<br/>2. 'Kubernetes Guide' (rank 2)<br/>3. 'Docker Basics' (rank 3)"]
D --> F[RRF Algorithm]
E --> F
F --> G["Combined Results:<br/>1. 'Containerizing Apps'<br/> (1/61 + 1/62 = 0.0328)<br/>2. 'Docker Basics'<br/> (1/61 + 1/63 = 0.0322)<br/>3. 'Docker Compose'<br/> (1/63 = 0.0159)"]
style A stroke:#10b981,stroke-width:2px
style B stroke:#3b82f6,stroke-width:2px
style C stroke:#6366f1,stroke-width:2px
style D stroke:#3b82f6,stroke-width:2px
style E stroke:#6366f1,stroke-width:2px
style F stroke:#ec4899,stroke-width:4px
style G stroke:#8b5cf6,stroke-width:2px
Die RRF-Formel score = Σ(1 / (k + rank))
k,= , um zu verhindern, dass die frühen Ränge dominierenrankPosition in dieser Suchmethode Ergebnisse- Ergebnisse in beides Quellen erhalten Ergebnisse von jedem zusammen addiert
Warum RRF funktioniert
- DeduplikationGleiches Ergebnis in beiden Quellen punktet höher
- FairnessKeine Suchmethode dominiert unfair
- EinfachheitKeine komplexe Abstimmung erforderlich
Durchführung
public class HybridSearchService : IHybridSearchService
{
private readonly ISemanticSearchService _semanticSearchService;
private const int RrfConstant = 60;
public async Task<List<SearchResult>> SearchAsync(
string query,
string language = "en",
int limit = 10,
CancellationToken cancellationToken = default)
{
// Execute both searches in parallel
var semanticResults = await _semanticSearchService.SearchAsync(
query, limit * 2, cancellationToken);
// Filter by language and apply RRF
var filteredResults = semanticResults
.Where(r => r.Language == language)
.ToList();
return ApplyReciprocalRankFusion(filteredResults)
.Take(limit)
.ToList();
}
private List<SearchResult> ApplyReciprocalRankFusion(List<SearchResult> results)
{
var rrfScores = new Dictionary<string, RrfScore>();
for (int i = 0; i < results.Count; i++)
{
var result = results[i];
var key = $"{result.Slug}_{result.Language}";
if (!rrfScores.ContainsKey(key))
rrfScores[key] = new RrfScore { Result = result };
// RRF formula: 1 / (k + rank)
rrfScores[key].Score += 1.0 / (RrfConstant + i + 1);
}
return rrfScores.Values
.OrderByDescending(x => x.Score)
.Select(x => x.Result)
.ToList();
}
}
Anmerkung: Dies zeigt die semantische Suche nur in der Produktion.,. Ausführen der PostgreSQL-Volltextsuche parallel und schließen diese Ergebnisse in die RRF-Berechnung ein.
Integration
Wenn Sie bereits PostgreSQL Volltextsuche implementiert habenwie hier behandeltDas Hinzufügen semantischer Suche ist einfach.
// Program.cs
services.AddSemanticSearch(configuration);
services.AddSingleton<IHybridSearchService, HybridSearchService>();
[HttpGet("search/hybrid")]
public async Task<IActionResult> HybridSearch(string query, string language = "en")
{
var results = await _hybridSearchService.SearchAsync(query, language);
return PartialView("_SearchResults", results);
}
Automatische Indexierung mit File System Watcher
Die leistungsstärkste Funktion automatische IndexierungSpeichern Sie einen Blog-Post , , , es ist sofort durchsuchbar , ohne manuelle Intervention , . ,
Wie es funktioniert
flowchart TB
A[Save Markdown File] --> B[FileSystemWatcher Detects Change]
B --> C{File in Main Directory?}
C -->|Yes| D[Save to Database]
C -->|No| E[Save to Database Only]
D --> F[Create BlogPostDocument]
F --> G[Generate Embedding via ONNX]
G --> H[Store in Qdrant]
H --> I[Post Searchable Immediately]
style A stroke:#10b981,stroke-width:2px
style B stroke:#f59e0b,stroke-width:2px
style C stroke:#ec4899,stroke-width:3px
style D stroke:#3b82f6,stroke-width:2px
style E stroke:#6b7280,stroke-width:2px
style F stroke:#8b5cf6,stroke-width:2px
style G stroke:#6366f1,stroke-width:3px
style H stroke:#ef4444,stroke-width:2px
style I stroke:#10b981,stroke-width:2px
Schlüsselentwurfsentscheidung Nur Indexdateien in der Hauptmarkdown-VerzeichnisNicht Unterverzeichnissetranslated/, drafts/, comments/Dies hält den Suchindex sauber.
Integration des Dateibeobachters
Der Blog hat bereits eine MarkdownDirectoryWatcherServiceWir erweitern es, um semantische Indexierung auszulösen.
// In MarkdownDirectoryWatcherService.cs
private async Task OnChangedAsync(WaitForChangedResult e)
{
if (e.Name == null) return;
await retryPolicy.ExecuteAsync(async () =>
{
var savedModel = await blogService.SavePost(slug, language, markdown);
// Index ONLY if file is in main directory (no path separators in name)
if (!e.Name.Contains(Path.DirectorySeparatorChar) &&
!e.Name.Contains(Path.AltDirectorySeparatorChar))
{
await IndexPostForSemanticSearchAsync(scope, savedModel, language);
}
});
}
private async Task IndexPostForSemanticSearchAsync(
IServiceScope scope,
BlogPostDto post,
string language)
{
var semanticSearchService = scope.ServiceProvider.GetService<ISemanticSearchService>();
if (semanticSearchService == null) return; // Not configured
var document = new BlogPostDocument
{
Id = $"{post.Slug}_{language}",
Slug = post.Slug,
Title = post.Title,
Content = post.PlainTextContent,
Language = language,
Categories = post.Categories?.ToList() ?? new List<string>(),
PublishedDate = post.PublishedDate
};
await semanticSearchService.IndexPostAsync(document);
_logger.LogInformation("Indexed {Slug} ({Language}) in semantic search", post.Slug, language);
}
Umgang mit Delektionen
Wenn ein Beitrag gelöscht wird, entfernen Sie es aus dem semantischen Index.
private async Task OnDeletedAsync(WaitForChangedResult e)
{
await blogService.Delete(slug, language);
// Delete from semantic search ONLY if file was in main directory
if (!e.Name.Contains(Path.DirectorySeparatorChar) &&
!e.Name.Contains(Path.AltDirectorySeparatorChar))
{
var semanticSearchService = scope.ServiceProvider.GetService<ISemanticSearchService>();
await semanticSearchService?.DeletePostAsync(slug, language);
}
}
Hintergrunddienst für die erste Indexierung
Beim Start indexiert ein Hintergrunddienst vorhandene Posts noch nicht in Qdrant-:
flowchart TB
A[Application Starts] --> B[Wait 10 seconds]
B --> C[Initialize Semantic Search]
C --> D{Model Exists?}
D -->|No| E[Download from Hugging Face]
D -->|Yes| F[Load ONNX Model]
E --> F
F --> G[Scan Main Markdown Directory]
G --> H{For Each .md File}
H --> I[Compute Content Hash]
I --> J{Hash Changed?}
J -->|Yes| K[Generate Embedding]
J -->|No| L[Skip - Already Indexed]
K --> M[Store in Qdrant]
M --> H
L --> H
H -->|Done| N[Indexing Complete]
style A stroke:#10b981,stroke-width:2px
style C stroke:#6366f1,stroke-width:2px
style E stroke:#f59e0b,stroke-width:2px
style F stroke:#6366f1,stroke-width:3px
style G stroke:#8b5cf6,stroke-width:2px
style J stroke:#ec4899,stroke-width:3px
style K stroke:#6366f1,stroke-width:2px
style M stroke:#ef4444,stroke-width:2px
style N stroke:#10b981,stroke-width:2px
public class SemanticIndexingBackgroundService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Wait for app to be ready
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
// Initialize (downloads model if needed)
await _semanticSearchService.InitializeAsync(stoppingToken);
// Get all posts from main directory only
var markdownFiles = Directory.GetFiles(
_markdownConfig.MarkdownPath,
"*.md",
SearchOption.TopDirectoryOnly); // NOT subdirectories
foreach (var file in markdownFiles)
{
var needsIndexing = await _semanticSearchService.NeedsReindexingAsync(
slug, language, contentHash, stoppingToken);
if (needsIndexing)
await _semanticSearchService.IndexPostAsync(document, stoppingToken);
}
}
}
Dies stellt sicher, dass die
- Lazy Modell Laden Downloads beim ersten Einsatz nicht blockieren
- Inkrementelle Indexierung Nur neue, geänderte Beiträge
- Nur Hauptverzeichnis Dokumente und übersetzte Dateien verschmutzen den Index
Was wir gebaut haben
Über Teile hinweg haben wir jetzt :
- ✅ CPU.-.freundliche semantische Suche Keine GPU erforderlich
- ✅ Related posts Entdeckung Semantähnliche Inhalte
- ✅ Natürliche Sprachsuche Suche nach Bedeutungen nicht nur Schlüsselwörter
- ✅ Hybride Suche Das Beste aus semantischem Text
- ✅ Automatische Indexierung Aktualisierungen des Inhalts von --Zero---Touch
- ✅ Selbstbewirtet Ihre Daten bleiben auf Ihrem Server
Zukünftige Verbesserungen
- Kategorie: --Bewusste Suche Ergebnisse aus bestimmten Kategorien steigern
- Mehrsprachige Einbettungen Sprache, -Besondere Einbettungsmodelle
- OpenSearch Integration OpenSearch zum Hybrid-Mix hinzufügensiehe meinen OpenSearch Artikel)
Schlußfolgerung
Dies vervollständigt die praktische Umsetzung der semantischen Suche von RAG--, kombiniert mit Teil (-Foundation, )- und Teil B Such-UI-),e haben Sie alles, was benötigt wird, um intelligente Suche zu Ihrer Anwendung hinzuzufügen. -e läuft komplett auf CPU, ohne zusätzliche Kosten.
Lerne weiter
- RAG-Teil: Ursprung und Grundlagen Die Theorie hinter Einbettungen
- RAG-Teil Architektur und Innenarchitektur Tiefentauche in RAG-Systeme
- RAG Teil Praktische Anwendungen Komplette RAG mit LLM-Integration
- Teil Umsetzung des Qdrant-Systems Grundlage: Embedding und Vektorspeicher
- Teil - Semantische Suche in Aktion Hybride Suche nach , und UI
- Volltextsuche mit PostgreSQL Die Volltextseite der hybriden Suche
Aufkommen
Qdrant-Vektordatenbanken
- Selbstbeleuchtete Vektordatenbanken mit Qdrant Tiefes Eintauchen in Qdrant-Konzepte – die Indexierung von HNSW – die Filterung von , und C.#.
- Qdrant-Hybridsuche Qdrant®''s native Hybrid-Unterstützung
Hybride Suche
- Gegenseitige Rangfolge Fusionspapier Der RRF-Algorithmus
Dateisystem beobachten
- DateiSystemWatcher Klasse Dokumentation
- HintergrundService-Klasse Hosted services im ASP-.-NET-Core
Vollständiger Code
Alle Codes sind verfügbar unter: github-.-com./-scottgal./-mostlylucidweb
Mostlylucid.SemanticSearch/Kernsemantische SuchbibliothekMostlylucid/Blog/WatcherService/Dateibeobachter mit semantischer Indexierung