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
Saturday, 22 November 2025
Το Μέρος 1 και Μέρος 2 από αυτή τη σειρά, καλύψαμε τις ρίζες, τα βασικά στοιχεία, και την τεχνική αρχιτεκτονική του RAG. Καταλαβαίνετε τι είναι, γιατί έχει σημασία, και πώς λειτουργεί κάτω από το καπό. Τώρα ήρθε η ώρα να θέσει αυτή τη γνώση σε εφαρμογή. Αυτό το άρθρο σας δείχνει πώς να οικοδομήσουμε πραγματικά συστήματα RAG με την εργασία κώδικα C#, την επίλυση κοινών προκλήσεων, και τη χρήση προηγμένων τεχνικών από την πρόσφατη έρευνα.
Πλοήγηση σειράς: Αυτό είναι το μέρος 3 της σειράς RAG:
Αν δεν έχετε διαβάσει τα μέρη 1 και 2, προτείνω να ξεκινήσετε από εκεί για να καταλάβετε:
Αυτό το άρθρο υποθέτει ότι καταλαβαίνετε αυτά τα βασικά και εστιάζει σε εφαρμογή, βελτιστοποίηση και πρότυπα πραγματικού κόσμου.
Έχω φτιάξει αρκετά RAG-powered χαρακτηριστικά σε αυτό το blog. Επιτρέψτε μου να σας δείξω συγκεκριμένα παραδείγματα.
Κάθε δημοσίευση στο blog μπορεί να δείξει "Σχετικές Δημοσιεύσεις" χρησιμοποιώντας σημασιολογική ομοιότητα.
Πώς λειτουργεί:
Γιατί είναι καλύτερο από τις ετικέτες:
Κωδικός snippet:
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();
}
Το πλαίσιο αναζήτησης σε αυτό το blog χρησιμοποιεί σημασιολογική αναζήτηση τύπου RAG (αν και χωρίς το μέρος της γενιάς - είναι απλά ανάκτηση).
Εμπειρία χρήστη:
Εφαρμογή: Θα καλύψω την κατασκευή αυτού σε ένα επερχόμενο άρθρο στις βάσεις δεδομένων διανυσματικών στοιχείων.
Φτιάχνω ένα πλήρες σύστημα RAG για να με βοηθήσει να γράψω νέα άρθρα blog.
Κιβώτιο χρήσης: Όταν αρχίζω να γράφω για "προσθήκη ταυτότητας στο ASP.NET Core," το σύστημα:
Πλήρεςς αγωγός RAG:
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;
}
Πρόκειται για RAG σε δράση - ανάκτηση (σημειακή αναζήτηση) + αύξηση (σύνθετο προσθήκης) + γενιά (προτάσεις LLM).
Εδώ είναι οι προκλήσεις που έχω αντιμετωπίσει και πώς να τα λύσω.
Πρόβλημα: Πώς μοιράζεστε τα έγγραφα; Πολύ μικρό = απώλεια του πλαισίου. Πολύ μεγάλο = άσχετες πληροφορίες.
Διάλυμα: Υβριδικό πνιγμό βασισμένο στη δομή του εγγράφου.
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;
}
}
Βέλτιστες πρακτικές:
Πρόβλημα: Γενικά μοντέλα ενσωμάτωση μπορεί να μην συλλάβει τομέα-συγκεκριμένες σημασιολογία.
Λύσεις:
Επιλογή 1: Ενσωμάτωση με λεπτό τόνο (προχωρημένο)
# 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)
Επιλογή 2: Υβριδικός συνδυασμός (σύνθετο πολλαπλά μοντέλα)
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);
}
Επιλογή 3: Προσθήκη φίλτρου μεταδεδομένα
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" } }
}
}
);
Πρόβλημα: Τα LLMs έχουν συμβολικά όρια. Πώς μπορείτε να χωρέσετε το ερώτημα + πλαίσιο + άμεση στο παράθυρο;
Διάλυμα: Δυναμική επιλογή πλαισίου και επαναπροσδιορισμός.
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);
}
Πρόβλημα: Ακόμα και με τα συμφραζόμενα, οι LLM μερικές φορές το αγνοούν και έχουν παραισθήσεις.
Λύσεις:
1. Απρόσκοπτη μηχανική:
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. Επικύρωση μετά τη γέννηση:
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. Διενεργητική φινέτσα:
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.";
}
Πρόβλημα: Καθώς προσθέτετε νέα έγγραφα, η διανυσματική βάση δεδομένων πρέπει να παραμείνει τρέχουσα.
Διάλυμα: Αυτοματοποιημένος αγωγός ευρετηρίου.
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
);
}
}
}
}
}
Ας εξερευνήσουμε τεχνικές αιχμής από την πρόσφατη έρευνα.
Πρόβλημα: Τα ερωτήματα των χρηστών είναι συχνά μικρά και άσχημα σχηματισμένα. Τα κομμάτια εγγράφων είναι λεπτομερή και καλά γραμμένα. Αυτή η αναντιστοιχία βλάπτει την ανάκτηση.
Διάλυμα: Δημιουργήστε ένα υποθετικό ιδανικό έγγραφο που θα απαντήσει στο ερώτημα, ενσωματώστε αυτό, στη συνέχεια αναζήτηση.
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);
}
Γιατί δουλεύει: Η υποθετική απάντηση χρησιμοποιεί παρόμοια γλώσσα και δομή σε πραγματικά έγγραφα, βελτιώνοντας την ανάκτηση.
Πρόβλημα: Ερωτήσεις χρηστών συχνά αναμειγνύουν σημασιολογική αναζήτηση με φίλτρα μεταδεδομένα.
Παράδειγμα: "Πρόσφατες δημοσιεύσεις για Docker" = σημασιολογικό ("Docker") + φίλτρο(ημερομηνία > 2024-01-01)
Διάλυμα: Χρησιμοποιήστε LLM για να αναλύσετε το ερώτημα σε σημασιολογικά φίλτρα + μεταδεδομένα.
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)
);
Πρόβλημα: Μια ερώτηση μπορεί να λείψει από τα σχετικά έγγραφα λόγω διατύπωσης.
Διάλυμα: Δημιουργήστε πολλαπλές παραλλαγές του ερωτήματος, αναζήτηση με όλα, συνδυάστε τα αποτελέσματα.
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;
}
Πρόβλημα: Τα ανακτημένα κομμάτια περιέχουν άσχετες πληροφορίες.
Διάλυμα: Χρησιμοποιήστε ένα μικρότερο LLM για να συμπιέσετε ανακτημένο πλαίσιο σε μόνο σχετικά μέρη.
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);
}
Πρόβλημα: Τα σύνθετα ερωτήματα απαιτούν πληροφορίες από πολλαπλές πηγές που πρέπει να συνδεθούν.
Παράδειγμα: "Ποια βάση δεδομένων χρησιμοποιεί το blog και πώς εφαρμόζεται σημασιολογική αναζήτηση;"
Διάλυμα: Διακεκριμένη ανάκτηση και σύνθεση.
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);
}
Πρόβλημα: Πώς φτιάχνετε συστήματα τεχνητής νοημοσύνης που θυμούνται συζητήσεις από μήνες ή χρόνια πριν; Τα παραδοσιακά chatbots χάνουν τα συμφραζόμενα μετά από κάθε συνεδρία.
Διάλυμα: Συνδυάστε τις ΚΓΠΕ με προοδευτική ευθυγράμμιση για να δημιουργήσετε επίμονη, αναζητήσιμη μνήμη.
Αυτή είναι η προσέγγιση που χρησιμοποιείται στο DiSE (Directed Synthetic Evolution) - ένα προηγμένο σύστημα που φτιάχνω που χρησιμοποιεί μνήμη πλαισίου βασισμένο στο RAG για να διατηρήσει την κοινή ιστορία συζήτησης επ' αόριστον.
Παράδειγμα σεναρίου:
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..."
Πώς λειτουργεί:
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
Προσέγγιση εφαρμογής:
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")
}
);
}
}
}
Γιατί αυτό είναι ισχυρό:
Παράδειγμα πραγματικού κόσμου από το DiSE:
Το DiSE χρησιμοποιεί αυτή την προσέγγιση για να θυμάται:
Αυτό δημιουργεί ένα σύστημα AI που πραγματικά "μαθαίνει" από κάθε αλληλεπίδραση και χτίζει θεσμική μνήμη, αντί να ξεκινά φρέσκα κάθε συνεδρία.
Προκλήσεις που πρέπει να ληφθούν υπόψη:
Αυτή η τεχνική μετατρέπει RAG από "αναζήτηση εγγράφων μου" σε "θυμηθείτε ό, τι έχουμε συζητήσει ποτέ" - ένα παιχνίδι-αλλαγή για μακροχρόνια βοηθούς AI.
Το RAG δεν είναι πάντα η απάντηση.
1. Ερωτήσεις γενικής γνώσης
2. Δημιουργική γραφή
3. Ανάγκες δεδομένων σε πραγματικό χρόνο
4. Μαθηματική συλλογιστική
5. Πολύ μικρές βάσεις γνώσεων
6. Όταν ελέγχετε την εκπαίδευση του LLM
Θέλεις να φτιάξεις το δικό σου σύστημα RAG;
Στόχος: Βάλε την βασική ανάκτηση να δουλεύει χωρίς 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
Στόχος: Κλίμακα σε πραγματικές συλλογές εγγράφων.
Επόμενα βήματα για την εφαρμογή:
Θα το καλύψω λεπτομερώς σε ένα επερχόμενο άρθρο για τις βάσεις δεδομένων διανυσματικών στοιχείων.
Στόχος: Συμπληρώστε τον αγωγό RAG.
// 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;
Μόλις τα βασικά δουλέψουν, μεταναστεύστε στο τοπικό συμπέρασμα (θα το καλύψω αυτό στα επόμενα άρθρα):
Το RAG (Retrieval-Augmented Generation) είναι μια ισχυρή τεχνική για να κάνει τις LLMs πιο ακριβείς, ενημερωμένες και αξιόπιστες, με βάση τις απαντήσεις τους σε πραγματικά έγγραφα. Αντί να βασίζεται μόνο στα δεδομένα κατάρτισης του μοντέλου, τα συστήματα RAG:
Βασικά πλεονεκτήματα των ΚΓΠΕ:
Πότε να χρησιμοποιήσετε τις ΚΓΠΕ:
Πότε να αποφύγετε τις ΚΓΠΕ:
Το πεδίο εξελίσσεται γρήγορα με προηγμένες τεχνικές όπως η HyDE, η πολύπλευρη ανάκτηση, και η συμφραζόμενη συμπίεση, αλλά η βασική έννοια παραμένει απλή: να δώσει πρόσβαση LLMs στις σωστές πληροφορίες την κατάλληλη στιγμή.
Ξεκινήστε απλά, μετρήστε τα αποτελέσματα, και terrate. RAGs είναι ένας από τους πιο πρακτικούς τρόπους για να οικοδομήσουμε αξιόπιστα συστήματα AI σήμερα.
Τώρα έχετε ολοκληρώσει τη σειρά τριών μερών RAG:
Μέρος 1: Προέλευση και Θεμελιώδη Στοιχεία
Μέρος δεύτερο: Αρχιτεκτονική και Εσωτερικά
Μέρος 3: ΚΓΠΕ στην πράξη (το παρόν άρθρο)
Τώρα έχετε την πλήρη εικόνα: Από την κατανόηση της προέλευσης των RAG μέχρι την κατασκευή συστημάτων παραγωγής με προηγμένες βελτιστοποιήσεις.
Τώρα που καταλαβαίνετε RAG από τη θεωρία στην πρακτική, τα επόμενα άρθρα θα σας δείξει πώς να οικοδομήσουμε πλήρη, έτοιμα για την παραγωγή συστήματα RAG στο C#:
Έρχομαι σε λίγο.
Αυτά τα άρθρα θα σας μεταφέρουν από τη θεωρία στην πρακτική, με πλήρη κώδικα εργασίας, στρατηγικές ανάπτυξης, και πραγματικά-κόσμο βελτιστοποιήσεις με βάση τη λειτουργία αυτών των συστημάτων στην παραγωγή σε αυτό το blog.
Μείνετε συντονισμένοι για οδηγούς εφαρμογής που μετατρέπουν αυτή τη γνώση RAG σε συστήματα εργασίας!
Ιδρυτικές εφημερίδες:
Εργαλεία και πλαίσια:
Περαιτέρω ανάγνωση:
Η παρούσα σειρά ΚΓΠΕ:
Χαρούμενο κτίριο!
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.