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
RAG i praktiken: Bygga Real-World applikationer
Saturday, 22 November 2025
Till Häfte 1 och Häfte 2 i denna serie, vi behandlade RAG ursprung, grunder och teknisk arkitektur. Du förstår vad RAG är, varför det spelar roll, och hur det fungerar under huven. Nu är det dags att omsätta den kunskapen i praktiken. Denna artikel visar hur man bygger verkliga RAG-system med fungerande C#-kod, lösa gemensamma utmaningar, och använda avancerade tekniker från ny forskning.
Inledning
på serienavigering: Detta är del 3 i RAG-serien:
- Del 1: Ursprung och grundförutsättningar - Historia, motivation och kärnkoncept
- Del 2: Arkitektur och interner - Teknisk djupdykning i hur RAG fungerar
- Del 3: RAG i praktiken (den här artikeln) - Bygga verkliga system, utmaningar och avancerade tekniker
- Del 4a: Genomförande av ONNX och Qdrant - CPU-vänlig semantisk sökfundament
- Del 4b: Semantisk sökning i verksamhet - Typeahead, hybrid sökning, och UI
- Del 5: Hybridsökning och automatisk indexning - Produktionsintegrationsmönster
- Del 6: GraphRAG - Kunskapsgrafer för corpus-nivåförståelse
Om du inte har läst delarna 1 och 2 rekommenderar jag att du börjar där för att förstå:
- Vad RAG är och varför det betyder något (del 1)
- Historien från sökordssökning till semantisk förståelse (del 1)
- Komplett RAG-rörledning: indexering, hämtning, generering (del 2)
- LLM internals: polletter, KV-cache, sammanhangsfönster (del 2)
- RAG mot finjustering och andra metoder (del 1)
Den här artikeln förutsätter att du förstår dessa grundläggande och fokuserar på implementation, optimering och verkliga mönster.
Real-World RAG program på denna blogg
Jag har byggt flera RAG-drivna funktioner på denna blogg. Låt mig visa dig konkreta exempel.
1. Related Posts Rekommendation
Varje blogginlägg kan visa "Relaterade inlägg" med semantisk likhet.
Hur det fungerar:
- Varje blogginlägg blir inbäddat när det publiceras
- När vi tittar på ett inlägg, hämtar vi dess inbäddning från Qdrant
- Hitta 5 mest liknande inlägg inbäddningar
- Visa som "Relaterade inlägg"
Varför det är bättre än taggar:
- Taggar kräver manuell kategorisering
- Semantisk sökning hittar begreppsmässigt relaterade inlägg även utan matchande taggar
- "Docker Compose" och "Container orkestrering" inlägg är relaterade semantiskt
Kodsnuttar:
public async Task<List<SearchResult>> GetRelatedPostsAsync(
string currentPostSlug,
string language,
int limit = 5)
{
// Get the current post's embedding
var currentPost = await _vectorStore.GetByIdAsync(currentPostSlug);
if (currentPost == null)
return new List<SearchResult>();
// Find similar posts
var similarPosts = await _vectorStore.SearchAsync(
currentPost.Embedding,
limit: limit + 1, // +1 because result includes the current post
filter: new Filter
{
Must =
{
new Condition
{
Field = "language",
Match = new Match { Keyword = language }
}
},
MustNot =
{
new Condition
{
Field = "slug",
Match = new Match { Keyword = currentPostSlug }
}
}
}
);
return similarPosts.Take(limit).ToList();
}
2. Semantisk bloggsökning
Sökrutan på denna blogg använder RAG-stil semantisk sökning (men utan generation delen - det är bara hämtning).
Användarupplevelse:
- Sök efter "att skapa en databas" → hittar inlägg om PostgreSQL, Entity Framework, migreringar
- Sök efter "deployment" → hittar inlägg om Docker, hosting, CI/CD
- Inget behov av exakta sökordsmatcher
Genomförande Jag tar upp detta i en kommande artikel om vektordatabaser.
3. Den "advokat GPT" skrivande assistent
Jag bygger ett komplett RAG-system för att hjälpa mig att skriva nya blogginlägg.
Användningsfall: När jag börjar skriva om "att lägga behörighetskontroll till ASP.NET Core", systemet:
- Lägger in mitt nuvarande utkast
- Söker tidigare inlägg efter relaterat innehåll om autentisering, ASP.NET, säkerhet
- Föreslår relevanta kodsnuttar jag använt förut
- Skapar automatiskt interna länkar till relaterade inlägg
- Bibehåller överensstämmelse med min skrivstil
Fullständig RAG-ledning:
public async Task<WritingAssistanceResponse> GetSuggestionsAsync(
string currentDraft,
string topic)
{
// 1. Embed the current draft
var draftEmbedding = await _embeddingService.GenerateEmbeddingAsync(
currentDraft
);
// 2. Retrieve related past content
var relatedPosts = await _vectorStore.SearchAsync(
draftEmbedding,
limit: 5
);
// 3. Build context for LLM
var prompt = BuildWritingAssistancePrompt(
currentDraft,
topic,
relatedPosts
);
// 4. Generate suggestions using local LLM
var suggestions = await _llmService.GenerateAsync(prompt);
// 5. Extract and format citations
var response = ExtractCitations(suggestions, relatedPosts);
return response;
}
Detta är RAG i aktion - hämtning (semantisk sökning) + förstärkning (att lägga sammanhang) + generation (LLM förslag).
Vanliga utmaningar och lösningar i samband med RAG
Byggproduktion RAG system är inte triviala. Här är utmaningar jag har stött på och hur man löser dem.
Utmaning 1: Chunking-strategi
Problem: Hur delar du dokument? För liten = förlust av sammanhang. För stor = irrelevant information.
Lösning: Hybridbitning baserad på dokumentstruktur.
public class SmartChunker
{
public List<Chunk> ChunkDocument(string markdown, string sourceId)
{
var chunks = new List<Chunk>();
// Parse markdown into sections
var document = Markdown.Parse(markdown);
var sections = ExtractSections(document);
foreach (var section in sections)
{
var wordCount = CountWords(section.Content);
if (wordCount < MinChunkSize)
{
// Merge small sections
MergeWithPrevious(chunks, section);
}
else if (wordCount > MaxChunkSize)
{
// Split large sections
var subChunks = SplitSection(section);
chunks.AddRange(subChunks);
}
else
{
// Just right
chunks.Add(CreateChunk(section, sourceId));
}
}
return chunks;
}
}
Bästa praxis:
- Respektera dokumentstrukturen (rubriker, punkter)
- Lägg till överlappning mellan bitar (50–100 ord)
- Bevara kodblock intakta
- Inkludera sektionsrubriker i varje del för sammanhang
Utmaning 2: Lägga till kvalitet
Problem: Generiska inbäddade modeller får inte fånga domänspecifika semantik.
Lösningar:
Alternativ 1: Finjusterade inbäddningar (avancerat)
# Using sentence-transformers in Python
from sentence_transformers import SentenceTransformer, InputExample, losses
model = SentenceTransformer('all-MiniLM-L6-v2')
# Create training examples from your domain
train_examples = [
InputExample(texts=['Docker Compose', 'container orchestration'], label=0.9),
InputExample(texts=['Entity Framework', 'ORM database'], label=0.9),
InputExample(texts=['Docker', 'apple fruit'], label=0.1)
]
# Fine-tune
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
train_loss = losses.CosineSimilarityLoss(model)
model.fit(train_objectives=[(train_dataloader, train_loss)], epochs=1)
Alternativ 2: Hybridinbäddningar (kombination av flera modeller)
public async Task<float[]> GenerateHybridEmbeddingAsync(string text)
{
var semantic = await _semanticModel.GenerateEmbeddingAsync(text);
var keyword = await _keywordModel.GenerateEmbeddingAsync(text);
// Concatenate or weighted average
return CombineEmbeddings(semantic, keyword);
}
Alternativ 3: Lägg till metadatafiltrering
var results = await _vectorStore.SearchAsync(
queryEmbedding,
limit: 10,
filter: new Filter
{
Must =
{
new Condition { Field = "category", Match = new Match { Keyword = "ASP.NET" } },
new Condition { Field = "date", Range = new Range { Gte = "2024-01-01" } }
}
}
);
Utmaning 3: Hantering av sammanhangsfönster
Problem: LLMs har symboliska gränser. Hur passar du förfrågan + sammanhang + fråga i fönstret?
Lösning: Dynamiskt sammanhangsval och sammanfattning.
public string BuildContextAwarePrompt(
string query,
List<SearchResult> retrievedDocs,
int maxTokens = 4096)
{
var promptTemplate = GetPromptTemplate();
var queryTokens = CountTokens(query);
var templateTokens = CountTokens(promptTemplate);
// Reserve tokens for: prompt + query + response
var availableForContext = maxTokens - queryTokens - templateTokens - 500; // 500 for response
// Add context until we hit limit
var selectedContext = new List<SearchResult>();
var currentTokens = 0;
foreach (var doc in retrievedDocs.OrderByDescending(d => d.Score))
{
var docTokens = CountTokens(doc.Text);
if (currentTokens + docTokens <= availableForContext)
{
selectedContext.Add(doc);
currentTokens += docTokens;
}
else
{
// Try summarizing the doc if it's important
if (doc.Score > 0.85)
{
var summary = await SummarizeAsync(doc.Text, maxTokens: 200);
var summaryTokens = CountTokens(summary);
if (currentTokens + summaryTokens <= availableForContext)
{
selectedContext.Add(new SearchResult
{
Text = summary,
Title = doc.Title,
Score = doc.Score
});
currentTokens += summaryTokens;
}
}
}
}
return FormatPrompt(query, selectedContext);
}
Utmaning 4: Hallucination trots RAG
Problem: Även med sammanhang, LLMs ibland ignorerar det och hallucinerar.
Lösningar:
1. Snabbteknik:
var systemPrompt = @"
You are a technical assistant.
CRITICAL RULES:
1. ONLY use information from the provided CONTEXT sections
2. If the context doesn't contain the answer, say 'I don't have enough information in the provided context to answer that'
3. DO NOT use your training data to supplement answers
4. Always cite the source using [1], [2] notation
5. If you're unsure, say so
CONTEXT:
{context}
QUESTION: {query}
ANSWER (following all rules above):
";
2. Validering efter generation:
public async Task<bool> ValidateResponseAgainstContext(
string response,
List<SearchResult> context)
{
// Check if response contains claims not in context
var responseSentences = SplitIntoSentences(response);
foreach (var sentence in responseSentences)
{
var isSupported = await IsClaimSupportedByContext(sentence, context);
if (!isSupported)
{
_logger.LogWarning("Hallucination detected: {Sentence}", sentence);
return false;
}
}
return true;
}
3. Iterativ förfining:
public async Task<string> GenerateWithValidationAsync(
string query,
List<SearchResult> context,
int maxAttempts = 3)
{
for (int attempt = 0; attempt < maxAttempts; attempt++)
{
var response = await _llm.GenerateAsync(
BuildPrompt(query, context)
);
var isValid = await ValidateResponseAgainstContext(response, context);
if (isValid)
return response;
// Refine prompt for next attempt
query = $"{query}\n\nPrevious attempt hallucinated. Stick strictly to the context.";
}
return "I couldn't generate a reliable answer. Please rephrase your question.";
}
Utmaning 5: Hålla indexet uppdaterat till datum
Problem: När du lägger till nya dokument måste vektordatabasen förbli aktuell.
Lösning: Automatiserad indexeringsrörledning.
public class BlogIndexingBackgroundService : BackgroundService
{
private readonly IVectorStoreService _vectorStore;
private readonly IMarkdownService _markdownService;
private readonly ILogger<BlogIndexingBackgroundService> _logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await IndexNewPostsAsync(stoppingToken);
// Check for updates every hour
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in indexing service");
}
}
}
private async Task IndexNewPostsAsync(CancellationToken ct)
{
var allPosts = await _markdownService.GetAllPostsAsync();
foreach (var post in allPosts)
{
var existingDoc = await _vectorStore.GetByIdAsync(post.Slug);
// Check if content changed
var currentHash = ComputeHash(post.Content);
if (existingDoc == null || existingDoc.ContentHash != currentHash)
{
_logger.LogInformation("Indexing updated post: {Title}", post.Title);
var chunks = _chunker.ChunkDocument(post.Content, post.Slug);
foreach (var chunk in chunks)
{
var embedding = await _embeddingService.GenerateEmbeddingAsync(chunk.Text);
await _vectorStore.UpsertAsync(
id: $"{post.Slug}_{chunk.Index}",
embedding: embedding,
metadata: new Dictionary<string, object>
{
["slug"] = post.Slug,
["title"] = post.Title,
["chunk_index"] = chunk.Index,
["content_hash"] = currentHash
},
ct: ct
);
}
}
}
}
}
Avancerad RAG-teknik
Låt oss utforska banbrytande RAG-tekniker från ny forskning.
1. Hypotetiska dokument inbäddningar (HyDE)
Problem: Användarfrågor är ofta korta och dåligt utformade. Dokumentbitar är detaljerade och välskrivna. Denna missmatchning skadar hämtning.
Lösning: Skapa ett hypotetiskt ideal dokument som skulle svara på frågan, bädda in det, sedan söka.
public async Task<List<SearchResult>> HyDESearchAsync(string query)
{
// Generate hypothetical answer (even if hallucinated)
var hypotheticalAnswer = await _llm.GenerateAsync($@"
Write a detailed, technical paragraph that would perfectly answer this question:
Question: {query}
Paragraph:"
);
// Embed the hypothetical answer
var embedding = await _embeddingService.GenerateEmbeddingAsync(
hypotheticalAnswer
);
// Search using this embedding
return await _vectorStore.SearchAsync(embedding);
}
Varför det fungerar: Det hypotetiska svaret använder samma språk och struktur som faktiska dokument, vilket förbättrar hämtningen.
2. Självbelåtenhet
Problem: Användarfrågor blandar ofta semantisk sökning med metadatafilter.
Exempel: "Recent posts about Docker" = semantic("Docker") + filter(datum > 2024-01-01)
Lösning: Använd LLM för att tolka frågan i semantiska + metadata filter.
public async Task<SearchQuery> ParseSelfQueryAsync(string naturalLanguageQuery)
{
var parsingPrompt = $@"
Parse this search query into:
1. Semantic search query (what the user is looking for)
2. Metadata filters (category, date range, etc.)
User Query: {naturalLanguageQuery}
Output JSON:
{{
""semantic_query"": ""the core concept"",
""filters"": {{
""category"": ""...",
""date_after"": ""..."",
""date_before"": ""...""
}}
}}
";
var jsonResponse = await _llm.GenerateAsync(parsingPrompt);
var parsed = JsonSerializer.Deserialize<SearchQuery>(jsonResponse);
return parsed;
}
// Use parsed query
var parsedQuery = await ParseSelfQueryAsync("Recent ASP.NET posts about authentication");
// semantic_query: "authentication"
// filters: { category: "ASP.NET", date_after: "2024-01-01" }
var results = await _vectorStore.SearchAsync(
embedding: await _embeddingService.GenerateEmbeddingAsync(parsedQuery.SemanticQuery),
filter: BuildFilter(parsedQuery.Filters)
);
3. Multi-Query RAG
Problem: En enda fråga kan missa relevanta dokument på grund av frasering.
Lösning: Generera flera varianter av frågan, söka med alla, kombinera resultat.
public async Task<List<SearchResult>> MultiQuerySearchAsync(string query)
{
// Generate query variations
var variations = await _llm.GenerateAsync($@"
Generate 3 different ways to phrase this search query:
Original: {query}
Variations (one per line):
");
var queries = variations.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Prepend(query) // Include original
.ToList();
// Search with all variations
var allResults = new List<SearchResult>();
foreach (var q in queries)
{
var embedding = await _embeddingService.GenerateEmbeddingAsync(q);
var results = await _vectorStore.SearchAsync(embedding, limit: 10);
allResults.AddRange(results);
}
// Deduplicate and merge scores
var merged = allResults
.GroupBy(r => r.Id)
.Select(g => new SearchResult
{
Id = g.Key,
Text = g.First().Text,
Title = g.First().Title,
Score = g.Max(r => r.Score) // Take best score
})
.OrderByDescending(r => r.Score)
.ToList();
return merged;
}
4. Sammanhangskompression
Problem: Återtagna bitar innehåller irrelevant information.
Lösning: Använd en mindre LLM för att komprimera hämtad sammanhang till endast relevanta delar.
public async Task<string> CompressContextAsync(
string query,
List<SearchResult> retrievedDocs)
{
var compressed = new List<string>();
foreach (var doc in retrievedDocs)
{
var compressionPrompt = $@"
Extract only the sentences from this document that are relevant to answering the question.
Question: {query}
Document:
{doc.Text}
Relevant excerpts (maintain original wording):
";
var relevantExcerpt = await _smallLLM.GenerateAsync(compressionPrompt);
if (!string.IsNullOrWhiteSpace(relevantExcerpt))
{
compressed.Add($"From '{doc.Title}':\n{relevantExcerpt}");
}
}
return string.Join("\n\n", compressed);
}
5. Iterativ återhämtning (Multi-Hop RAG)
Problem: Komplexa frågor kräver information från flera källor som behöver anslutas.
Exempel: "Vilken databas använder bloggen och hur implementeras semantisk sökning?"
- Hop 1: Hitta vilken databas som används (PostgreSQL)
- Hop 2: Hitta hur semantisk sökning fungerar med den databasen
Lösning: Iterativ hämtning och syntes.
public async Task<string> MultiHopRAGAsync(string complexQuery, int maxHops = 3)
{
var currentQuery = complexQuery;
var allContext = new List<SearchResult>();
for (int hop = 0; hop < maxHops; hop++)
{
// Retrieve for current query
var results = await SearchAsync(currentQuery, limit: 5);
allContext.AddRange(results);
// Check if we have enough information
var synthesisPrompt = $@"
Original question: {complexQuery}
Context so far:
{FormatContext(allContext)}
Can you answer the original question with this context?
If yes, provide the answer.
If no, what additional information do you need? (be specific)
";
var synthesis = await _llm.GenerateAsync(synthesisPrompt);
if (synthesis.Contains("yes", StringComparison.OrdinalIgnoreCase))
{
// We have enough information
return ExtractAnswer(synthesis);
}
// Extract what we need for next hop
currentQuery = ExtractNextQuery(synthesis);
}
// Final synthesis with all gathered context
return await GenerateFinalAnswerAsync(complexQuery, allContext);
}
6. Långsiktigt sammanhangsminne med RAG + sammanfattning
Problem: Hur bygger du AI-system som minns samtal från månader eller år sedan? Traditionella chatbots förlorar sammanhang efter varje session.
Lösning: Kombinera RAG med progressiv summering för att skapa ihållande, sökbart minne.
Detta är den metod som används i DiSE (riktad syntetisk utveckling) - ett avancerat system jag bygger som använder RAG-baserat sammanhangsminne för att upprätthålla delad konversationshistoria på obestämd tid.
Exempel på scenario:
User (Today): "Remember George's specs?"
AI: "Yes, you discussed George's prescription requirements in our conversation
from 5 years ago (2019-03-15). He needed progressive lenses with..."
Hur det fungerar:
flowchart TB
A[User Message] --> B[Store in RAG Memory]
B --> C[Extract Key Entities & Topics]
C --> D[Link to Past Conversations]
E[Periodic Summarization] --> F[Summarize Old Conversations]
F --> G[Store Summary with High-Level Tags]
G --> H[Keep Original for Retrieval]
I[Future Query: 'George's specs'] --> J[Semantic Search in RAG]
J --> K[Find: 2019 conversation]
K --> L[Retrieve Original Context]
L --> M[LLM generates response with 5-year-old context!]
style B stroke:#f9f,stroke-width:3px
style J stroke:#bbf,stroke-width:3px
Genomförandemetod:
public class LongTermConversationalMemory
{
private readonly IVectorStoreService _vectorStore;
private readonly IEmbeddingService _embeddingService;
public async Task StoreConversationAsync(
string conversationId,
string userId,
List<ConversationTurn> turns,
DateTime timestamp)
{
// Extract key entities and topics
var entities = await ExtractEntitiesAsync(turns);
var topics = await ExtractTopicsAsync(turns);
// Create searchable representation
var conversationText = string.Join("\n", turns.Select(t =>
$"{t.Speaker}: {t.Message}"));
// Generate embedding
var embedding = await _embeddingService.GenerateEmbeddingAsync(
conversationText);
// Store in RAG with rich metadata
await _vectorStore.IndexDocumentAsync(
id: $"conv_{conversationId}",
embedding: embedding,
metadata: new Dictionary<string, object>
{
["user_id"] = userId,
["timestamp"] = timestamp.ToString("O"),
["entities"] = entities, // ["George", "specs", "prescription"]
["topics"] = topics, // ["healthcare", "eyewear"]
["full_text"] = conversationText,
["turn_count"] = turns.Count
}
);
}
public async Task<List<PastContext>> RetrieveRelevantPastAsync(
string currentQuery,
string userId,
int limit = 5)
{
// Embed the current query
var queryEmbedding = await _embeddingService.GenerateEmbeddingAsync(
currentQuery);
// Search past conversations
var results = await _vectorStore.SearchAsync(
queryEmbedding,
limit: limit,
filter: new Filter
{
Must =
{
new Condition { Field = "user_id", Match = new Match { Keyword = userId } }
}
}
);
return results.Select(r => new PastContext
{
ConversationId = r.Id,
Timestamp = DateTime.Parse(r.Metadata["timestamp"].ToString()),
Entities = (List<string>)r.Metadata["entities"],
FullText = r.Metadata["full_text"].ToString(),
Relevance = r.Score
}).ToList();
}
// Periodic summarization to keep memory manageable
public async Task SummarizeOldConversationsAsync(DateTime olderThan)
{
var oldConversations = await _vectorStore.FindByDateRangeAsync(
endDate: olderThan);
foreach (var conv in oldConversations)
{
// Generate summary using LLM
var summary = await _llm.GenerateAsync($@"
Summarize this conversation, preserving key facts and entities:
{conv.FullText}
Summary:");
// Update document with summary while keeping original
await _vectorStore.UpdateAsync(
id: conv.Id,
additionalMetadata: new Dictionary<string, object>
{
["summary"] = summary,
["summarized_at"] = DateTime.UtcNow.ToString("O")
}
);
}
}
}
Varför detta är kraftfullt:
- Obegränsad minnesrymd - Samtal från förr är lika lättillgängliga som gårdagens.
- Semantisk sökning - "Georges specifikationer" finner konversationen även om den lagras som "Georges receptbelagda glasögonkrav"
- Sammanlänkning mellan enheter - Alla samtal som nämner "George" är sammankopplade.
- Sekretessbevarande - Kan implementera minnesisolering per användare
- Kostnadseffektivt - Summarisering förhindrar lagringsexplosion samtidigt som sökbarheten bibehålls
Real-world exempel från DiSE:
DiSE använder denna metod för att komma ihåg:
- Verktygsanrop från månader sedan (vad fungerade, vad misslyckades)
- Kodmönster som varit framgångsrika i tidigare projekt
- Användarinställningar etablerade i tidigare samtal
- Domänkunskap ackumulerad över tid
Detta skapar ett AI-system som verkligen "lär sig" från varje interaktion och bygger institutionellt minne, snarare än att starta färska varje session.
Utmaningar att begrunda:
- Sekretess: Gamla samtal måste vara ordentligt isolerade per användare
- Relevans förfall: Inte all gammal information förblir relevant
- Lagringskostnader: Behöver sammanfattande strategi för skalan
- Överensstämmelse: Hålla sammanfattningar i linje med original
- Räkenskapsprecision: Balansering mellan den senaste och historiskt viktiga kontexten
Denna teknik omvandlar RAG från "sök mina dokument" till "kom ihåg allt vi någonsin diskuterat" - en spel-växlare för långsiktiga AI assistenter.
När du inte ska använda RAG
RAG är inte alltid svaret.
1. Allmänna kunskapsfrågor
- "Vad är Frankrikes huvudstad?" - LLM vet redan detta
- RAG lägger till latens och kostnad för ingen nytta
2. Kreativt skrivande
- Poesi, fiktion, brainstorming
- RAG begränsar kreativiteten genom att använda befintlig text
3. Behov av realtidsdata
- Börskurser, live sport poäng
- Bättre betjänas av API-integrationer, inte RAG
4. Matematiska resonemang
- "Beräkna 15% dricks på $ 83,47"
- LLMs kan göra aritmetisk; RAG hjälper inte
5. Mycket små kunskapsbaser
- Om din kunskapsbas passar in i sammanhangsfönstret, bara inkludera allt
- RAG overhead är inte värt det för 5 dokument
6. När du styr LLM:s träning
- Om du tränar en modell från grunden på dina data, finjustering kan vara bättre
- RAG är bäst när du använder förtränade modeller som du inte kontrollerar
Komma igång med RAG: En praktisk guide
Vill du bygga ditt eget RAG-system? Här är en steg-för-steg-strategi.
Vecka 1: Börja enkelt
Mål: Få grundläggande hämtning att arbeta med ingen LLM.
// 1. Choose an embedding service (start with API for simplicity)
var openAI = new OpenAIClient(apiKey);
// 2. Embed a few test documents
var docs = new[]
{
"Docker is a containerization platform",
"Kubernetes orchestrates containers",
"Entity Framework is an ORM for .NET"
};
var embeddings = new List<float[]>();
foreach (var doc in docs)
{
var response = await openAI.GetEmbeddingsAsync(
new EmbeddingsOptions("text-embedding-3-small", new[] { doc })
);
embeddings.Add(response.Value.Data[0].Embedding.ToArray());
}
// 3. Implement basic search (in-memory for now)
var query = "container orchestration";
var queryEmbedding = await GetEmbeddingAsync(query);
var results = embeddings
.Select((emb, idx) => new
{
Text = docs[idx],
Score = CosineSimilarity(queryEmbedding, emb)
})
.OrderByDescending(r => r.Score)
.ToList();
// 4. Verify search works
foreach (var result in results)
{
Console.WriteLine($"{result.Score:F3}: {result.Text}");
}
// Expected: Kubernetes scores highest
Vecka 2: Lägg till vektordatabas
Mål: Skala till verkliga dokumentsamlingar.
Nästa steg för att genomföra:
- Kör Qdrant i Docker
- Indexera dina dokument
- Implementera sökslutpunkt
Jag täcker detta i detalj i en kommande artikel om vektordatabaser.
Vecka 3: Lägg till LLM Generation
Mål: Slutför RAG-ledningen.
// 1. Retrieve context
var context = await SearchAsync(query, limit: 3);
// 2. Build prompt
var prompt = $@"
Answer the question using this context:
{FormatContext(context)}
Question: {query}
Answer:";
// 3. Generate (start with API)
var response = await openAI.GetChatCompletionsAsync(new ChatCompletionsOptions
{
Messages =
{
new ChatMessage(ChatRole.System, "You are a helpful assistant."),
new ChatMessage(ChatRole.User, prompt)
},
Temperature = 0.7f,
MaxTokens = 500
});
return response.Value.Choices[0].Message.Content;
Vecka 4: Optimera och polska
- Lägg till caching (spara på API-kostnader)
- Förbättra styckningsstrategin
- Lägg till metadatafiltrering
- Genomföra hybridsökning
- Lägg till citeringsextraktion
Att gå lokalt (frivilligt)
När grunderna fungerar, migrera till lokala slutsatser (Jag ska behandla detta i kommande artiklar):
- Lokal LLM-slutsats med GPU-acceleration
- CPU-vänliga inbäddningar med ONNX Runtime
Slutsatser
RAG (Retrieval-Augmented Generation) är en kraftfull teknik för att göra LLMs mer exakt, uppdaterad och pålitlig genom att basera sina svar i faktiska dokument. Istället för att förlita sig på modellens träningsdata ensam, RAG-system:
- Hämta relevant information från en kunskapsbas med semantisk sökning
- Förstoring LLM: s snabb med den hämtade sammanhang
- Generera svar som är baserade i verkliga källor med citeringar
Viktiga fördelar med RAG:
- Alltid uppdaterad (bara uppdatera din kunskapsbas)
- Grundad i källor (minskar hallucinationer)
- Förklaras (kan citera källor)
- Kostnadseffektiv (ingen dyr omskolning)
- Sekretessbevarande (kan köras helt lokalt)
- Domänspecifika (använd dina egna dokument)
När du ska använda RAG:
- Kunskapsintensiva uppgifter
- Information som ofta uppdateras
- Privata/proprietära uppgifter
- Behovet av citeringar och revisionsförmåga
- Kostnad och enkelhet materia
När RAG ska undvikas:
- Gge-baser
Fältet utvecklas snabbt med avancerade tekniker som HyDE, multi-query hämtning och kontextuell kompression, men kärnkonceptet förblir enkelt: ge LLMs tillgång till rätt information vid rätt tidpunkt.
Starta enkelt, mäta resultat, och iterera. RAG är ett av de mest praktiska sätten att bygga tillförlitliga AI-system idag.
Slutsats: Från teori till produktion
Du har nu avslutat tre-delars RAG-serien:
Del 1: Ursprung och grundförutsättningar
- Vad RAG är och varför det betyder något
- Historia från sökordssökning till semantisk förståelse
- RAG vs finjustering, lång kontext och andra metoder
Del 2: Arkitektur och interner
- Komplett RAG rörledning: indexering, hämtning, generering
- LLM internals: polletter, KV-cache, sammanhangsfönster
- Tekniska genomförandeuppgifter med C#-kod
Del 3: RAG i praktiken (denna artikel)
- Verkliga implementationer (relaterade inlägg, semantisk sökning, skrivassistent)
- Vanliga utmaningar och lösningar (knäppning, inbäddning av kvalitet, hallucinationer)
- Avancerade tekniker (HyDE, Själv-Query, Multi-Query, Contextual Compression, Långsiktigt minne)
- När ska INTE RAG användas?
- Komma igång guide
Du har nu hela bilden: Från att förstå RAG:s ursprung till att bygga produktionssystem med avancerade optimeringar.
Vad är nästa?
Nu när du förstår RAG från teori till praktik kommer kommande artiklar visa dig hur du bygger kompletta, produktionsklara RAG-system i C#:
Kommer snart:
- processorvänlig semantisk sökning - Bygga semantisk sökning med ONNX inbäddningar som körs på alla VPS utan GPU krav
- Självstämplade Vektordatabaser - Komplett installationsguide för Qdrant med Docker, inklusive indexering, sökning och optimeringsstrategier
- Att bygga en skrivassistent till RAG - En komplett serie om att skapa en AI-driven skrivassistent som använder ditt befintliga innehåll som kunskapsbas
Dessa artiklar kommer att ta dig från teori till praktik, med komplett arbetskod, distributionsstrategier, och verkliga optimeringar baserat på att köra dessa system i produktion på denna blogg.
Håll ögonen öppna för praktiska implementeringsguider som omvandlar denna RAG-kunskap till fungerande system!
Resurser
Grunddokument:
- Retrieval-Augmented Generation for Knowledge-Intensive NLP uppgifter - Den ursprungliga RAG papper
- Dense Passage Retrieval för öppen-domänfrågor svar - DPR (återhämtningsstiftelse)
- Uppmärksamhet är allt du behöver - Transformatorer (inbäddad grund)
Verktyg och ramar:
- Qdrant Ordförande - Vektordatabas jag använder
- ONNX körtid - För lokala inbäddningar
- LLamaSharp Ordförande - För lokala LLM-slutsatser
- Dömande Transformatorer - Lägga in modeller
Läs vidare:
- Antropisk: Sammanhangsmässig återhämtning - Avancerade RAG-tekniker
- Hur Neural Machine översättning fungerar - Förstå AI-modellerna bakom inbäddningar
Denna RAG-serie:
- Del 1: Ursprung och grundförutsättningar - Historia, motivation, kärnkoncept
- Del 2: Arkitektur och interner - Teknisk djupdykning
- Del 3: RAG i praktiken (den här artikeln) - Bygga verkliga system
Glad byggnad!