# RAGs in Practice: Building Real-World Applications

Το [Μέρος 1](/blog/rag-primer) και [Μέρος 2](/blog/rag-architecture) από αυτή τη σειρά, καλύψαμε τις ρίζες, τα βασικά στοιχεία, και την τεχνική αρχιτεκτονική του RAG. Καταλαβαίνετε τι είναι, γιατί έχει σημασία, και πώς λειτουργεί κάτω από το καπό. Τώρα ήρθε η ώρα να θέσει αυτή τη γνώση σε εφαρμογή. Αυτό το άρθρο σας δείχνει πώς να οικοδομήσουμε πραγματικά συστήματα RAG με την εργασία κώδικα C#, την επίλυση κοινών προκλήσεων, και τη χρήση προηγμένων τεχνικών από την πρόσφατη έρευνα.

<datetime class="hidden">2025-11-22T10:00</datetime>

<!-- category -- AI, RAG, Machine Learning, Semantic Search, LLM, AI-Article -->
# Εισαγωγή

**Πλοήγηση σειράς:** Αυτό είναι το μέρος 3 της σειράς RAG:

- [Μέρος 1: Προέλευση και Θεμελιώδη Στοιχεία](/blog/rag-primer) - Ιστορία, κίνητρα και βασικές έννοιες
- [Μέρος δεύτερο: Αρχιτεκτονική και Εσωτερικά](/blog/rag-architecture) - Τεχνική βαθιά κατάδυση στο πώς λειτουργεί RAG
- **Μέρος 3: Οι ΚΓΠΕ στην πράξη** (αυτό το άρθρο) - Κατασκευή πραγματικών συστημάτων, προκλήσεων και προηγμένων τεχνικών
- [Μέρος 4α: Εφαρμογή ONNX & Qdrant](/blog/semantic-search-with-onnx-and-qdrant) - ΚΜΕ-φιλικό θεμέλιο σημασιολογικής αναζήτησης
- [Μέρος 4β: Σημαντική Αναζήτηση σε Δράση](/blog/semantic-search-in-action) - Typenaward, υβριδική αναζήτηση, και UI
- [Μέρος 5: Υβριδική Αναζήτηση & Auto-Indexing](/blog/rag-hybrid-search-and-indexing) - Τρόποι ενσωμάτωσης στην παραγωγή
- [Μέρος 6: GraphRAG](/blog/graphrag-knowledge-graphs-for-rag) - Γραφήματα γνώσης για την κατανόηση του επιπέδου του σώματος

Αν δεν έχετε διαβάσει τα μέρη 1 και 2, προτείνω να ξεκινήσετε από εκεί για να καταλάβετε:

- Τι είναι οι ΚΓΠΕ και γιατί έχει σημασία (μέρος 1)
- Η ιστορία από την αναζήτηση λέξεων-κλειδιών στη σημασιολογική κατανόηση (Μέρος 1)
- Πλήρης αγωγός ΚΓΠΕ: ευρετηρίαση, ανάκτηση, παραγωγή (μέρος 2)
- Εσωτερικά LLM: μάρκες, KV cache, παράθυρα πλαισίου (μέρος 2)
- Άρθρο 4 παράγραφος 1 στοιχείο β) σημείο ii) και άρθρο 4 παράγραφος 1 στοιχείο β) του ΚΚΑ

Αυτό το άρθρο υποθέτει ότι καταλαβαίνετε αυτά τα βασικά και εστιάζει σε **εφαρμογή, βελτιστοποίηση και πρότυπα πραγματικού κόσμου**.

[TOC]

# Real-World RAG Applications σε αυτό το blog

Έχω φτιάξει αρκετά RAG-powered χαρακτηριστικά σε αυτό το blog. Επιτρέψτε μου να σας δείξω συγκεκριμένα παραδείγματα.

## 1. Σχετικές Δημοσιεύσεις Σύσταση

Κάθε δημοσίευση στο blog μπορεί να δείξει "Σχετικές Δημοσιεύσεις" χρησιμοποιώντας σημασιολογική ομοιότητα.

**Πώς λειτουργεί:**

1. Κάθε δημοσίευση στο blog ενσωματώνεται όταν δημοσιεύεται
2. Όταν βλέπουμε μια θέση, αναλαμβάνουμε την ενσωμάτωση της από το Qdrant
3. Βρείτε τα 5 πιο παρόμοια post embeddings
4. Εμφάνιση ως "Σχετικές Θέσεις"

**Γιατί είναι καλύτερο από τις ετικέτες:**

- Οι ετικέτες απαιτούν χειροκίνητη κατηγοριοποίηση
- Σημαντική αναζήτηση βρίσκει εννοιολογικά σχετικές θέσεις ακόμη και χωρίς ταίριασμα ετικετών
- Οι δημοσιεύσεις "Docker Compose" και "Container orchestration" σχετίζονται σημασιολογικά

**Κωδικός snippet:**

```csharp
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. Semantic Blog Αναζήτηση

Το πλαίσιο αναζήτησης σε αυτό το blog χρησιμοποιεί σημασιολογική αναζήτηση τύπου RAG (αν και χωρίς το μέρος της γενιάς - είναι απλά ανάκτηση).

**Εμπειρία χρήστη:**

- Αναζήτηση για "έστηση μιας βάσης δεδομένων" → find posts about PostgreSQL, Πλαίσιο οντότητας, μεταναστεύσεις
- Αναζήτηση για "προσαρμογή" → βρίσκει δημοσιεύσεις σχετικά με Docker, φιλοξενία, CI/CD
- Δεν υπάρχει ανάγκη για ακριβή ταίριασμα λέξης κλειδί

**Εφαρμογή:** Θα καλύψω την κατασκευή αυτού σε ένα επερχόμενο άρθρο στις βάσεις δεδομένων διανυσματικών στοιχείων.

## 3. Ο "δικηγόρος GPT" βοηθός συγγραφής

Φτιάχνω ένα πλήρες σύστημα RAG για να με βοηθήσει να γράψω νέα άρθρα blog.

**Κιβώτιο χρήσης:** Όταν αρχίζω να γράφω για "προσθήκη ταυτότητας στο ASP.NET Core," το σύστημα:

1. Ενσωμάτωση του τρέχοντος σχεδίου μου
2. Αναζητεί παλαιότερες αναρτήσεις για σχετικό περιεχόμενο σχετικά με την εξακρίβωση ταυτότητας, ASP.NET, ασφάλεια
3. Προτείνει τα σχετικά μυστικά κώδικα που έχω χρησιμοποιήσει στο παρελθόν.
4. Αυτοδημιουργεί εσωτερικούς δεσμούς με σχετικές θέσεις
5. Διατηρεί συνέπεια με το στυλ γραφής μου

**Πλήρεςς αγωγός RAG:**

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

# Κοινές ΚΓΠΕ Προκλήσεις και Λύσεις

Εδώ είναι οι προκλήσεις που έχω αντιμετωπίσει και πώς να τα λύσω.

## Πρόκληση 1: Στρατηγική για το κυνήγι

**Πρόβλημα:** Πώς μοιράζεστε τα έγγραφα; Πολύ μικρό = απώλεια του πλαισίου. Πολύ μεγάλο = άσχετες πληροφορίες.

**Διάλυμα:** Υβριδικό πνιγμό βασισμένο στη δομή του εγγράφου.

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

**Βέλτιστες πρακτικές:**

- Δομή εγγράφου σεβασμού (επικεφαλίδες, παράγραφοι)
- Προσθήκη επικάλυψης μεταξύ κομματιών (50-100 λέξεις)
- Διατηρήστε τον κωδικό μπλοκ άθικτο
- Συμπεριλάβετε κεφαλίδες τμημάτων σε κάθε κομμάτι για τα συμφραζόμενα

## Πρόκληση 2: Ποιότητα ενσωμάτωσης

**Πρόβλημα:** Γενικά μοντέλα ενσωμάτωση μπορεί να μην συλλάβει τομέα-συγκεκριμένες σημασιολογία.

**Λύσεις:**

**Επιλογή 1: Ενσωμάτωση με λεπτό τόνο** (προχωρημένο)

```python
# 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: Υβριδικός συνδυασμός** (σύνθετο πολλαπλά μοντέλα)

```csharp
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: Προσθήκη φίλτρου μεταδεδομένα**

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

## Πρόκληση 3: Διαχείριση παραθύρου πλαισίου

**Πρόβλημα:** Τα LLMs έχουν συμβολικά όρια. Πώς μπορείτε να χωρέσετε το ερώτημα + πλαίσιο + άμεση στο παράθυρο;

**Διάλυμα:** Δυναμική επιλογή πλαισίου και επαναπροσδιορισμός.

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

## Πρόκληση 4: Ψευδαίσθηση Παρά τις ΚΓΠΕ

**Πρόβλημα:** Ακόμα και με τα συμφραζόμενα, οι LLM μερικές φορές το αγνοούν και έχουν παραισθήσεις.

**Λύσεις:**

**1. Απρόσκοπτη μηχανική:**

```csharp
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. Επικύρωση μετά τη γέννηση:**

```csharp
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. Διενεργητική φινέτσα:**

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

## Πρόκληση 5: Διατήρηση του ευρετηρίου Up-to-Date

**Πρόβλημα:** Καθώς προσθέτετε νέα έγγραφα, η διανυσματική βάση δεδομένων πρέπει να παραμείνει τρέχουσα.

**Διάλυμα:** Αυτοματοποιημένος αγωγός ευρετηρίου.

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

# Προηγμένες τεχνικές ΚΓΠΕ

Ας εξερευνήσουμε τεχνικές αιχμής από την πρόσφατη έρευνα.

## 1. Υποθετικές προσθήκες εγγράφων (HyDE)

**Πρόβλημα:** Τα ερωτήματα των χρηστών είναι συχνά μικρά και άσχημα σχηματισμένα. Τα κομμάτια εγγράφων είναι λεπτομερή και καλά γραμμένα. Αυτή η αναντιστοιχία βλάπτει την ανάκτηση.

**Διάλυμα:** Δημιουργήστε ένα υποθετικό ιδανικό έγγραφο που θα απαντήσει στο ερώτημα, ενσωματώστε αυτό, στη συνέχεια αναζήτηση.

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

**Γιατί δουλεύει:** Η υποθετική απάντηση χρησιμοποιεί παρόμοια γλώσσα και δομή σε πραγματικά έγγραφα, βελτιώνοντας την ανάκτηση.

## 2. Αυτοκριτική

**Πρόβλημα:** Ερωτήσεις χρηστών συχνά αναμειγνύουν σημασιολογική αναζήτηση με φίλτρα μεταδεδομένα.

Παράδειγμα: "Πρόσφατες δημοσιεύσεις για Docker" = σημασιολογικό ("Docker") + φίλτρο(ημερομηνία > 2024-01-01)

**Διάλυμα:** Χρησιμοποιήστε LLM για να αναλύσετε το ερώτημα σε σημασιολογικά φίλτρα + μεταδεδομένα.

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

**Πρόβλημα:** Μια ερώτηση μπορεί να λείψει από τα σχετικά έγγραφα λόγω διατύπωσης.

**Διάλυμα:** Δημιουργήστε πολλαπλές παραλλαγές του ερωτήματος, αναζήτηση με όλα, συνδυάστε τα αποτελέσματα.

```csharp
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. Πλαίσιο συμπίεσης

**Πρόβλημα:** Τα ανακτημένα κομμάτια περιέχουν άσχετες πληροφορίες.

**Διάλυμα:** Χρησιμοποιήστε ένα μικρότερο LLM για να συμπιέσετε ανακτημένο πλαίσιο σε μόνο σχετικά μέρη.

```csharp
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. Αναδρομική ανάκτηση (Multi-Hop RAG)

**Πρόβλημα:** Τα σύνθετα ερωτήματα απαιτούν πληροφορίες από πολλαπλές πηγές που πρέπει να συνδεθούν.

Παράδειγμα: "Ποια βάση δεδομένων χρησιμοποιεί το blog και πώς εφαρμόζεται σημασιολογική αναζήτηση;"

- Hop 1: Βρείτε ποια βάση δεδομένων χρησιμοποιείται (PostgreSQL)
- Hop 2: Βρείτε πώς σημασιολογική αναζήτηση λειτουργεί με αυτή τη βάση δεδομένων

**Διάλυμα:** Διακεκριμένη ανάκτηση και σύνθεση.

```csharp
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. Μακροχρόνια μνήμη πλαισίου με RAG + Summarization

**Πρόβλημα:** Πώς φτιάχνετε συστήματα τεχνητής νοημοσύνης που θυμούνται συζητήσεις από μήνες ή χρόνια πριν; Τα παραδοσιακά 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..."
```

**Πώς λειτουργεί:**

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

**Προσέγγιση εφαρμογής:**

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

**Γιατί αυτό είναι ισχυρό:**

1. **Άπειρο εύρος μνήμης** - Οι συζητήσεις από χρόνια πριν είναι τόσο προσβάσιμες όσο και οι χθεσινές
2. **Σημασιολογική αναζήτηση** - "Οι προδιαγραφές του Τζορτζ" βρίσκουν τη συνομιλία ακόμα και αν αποθηκεύονται ως "απαιτούμενα γυαλιά συνταγής του Τζορτζ"
3. **Συνδέοντας τον φορέα** - Όλες οι συζητήσεις που αναφέρουν το "Τζώρτζ" συνδέονται
4. **Προστασία της ιδιωτικής ζωής** - Μπορεί να εφαρμόσει την απομόνωση μνήμης ανά χρήστη
5. **Αποτελεσματικό ως προς το κόστος** - Η ανακεφαλαίωση αποτρέπει την έκρηξη αποθήκευσης ενώ διατηρεί την αναζήτηση

**Παράδειγμα πραγματικού κόσμου από το DiSE:**

Το DiSE χρησιμοποιεί αυτή την προσέγγιση για να θυμάται:

- **Επικλήσεις εργαλείων** πριν από μήνες (τι δούλεψε, τι απέτυχε)
- **Μοντέλα κώδικα** που ήταν επιτυχείς σε προηγούμενα έργα
- **Προτιμήσεις χρηστών** εδραιώθηκε σε προηγούμενες συζητήσεις
- **Γνώση τομέα** συσσωρευμένη με την πάροδο του χρόνου

Αυτό δημιουργεί ένα σύστημα AI που πραγματικά "μαθαίνει" από κάθε αλληλεπίδραση και χτίζει θεσμική μνήμη, αντί να ξεκινά φρέσκα κάθε συνεδρία.

**Προκλήσεις που πρέπει να ληφθούν υπόψη:**

1. **Απόρρητο**: Οι παλιές συζητήσεις πρέπει να απομονώνονται κατάλληλα ανά χρήστη
2. **Συνάφεια διάσπασης**: Δεν παραμένουν όλες οι παλιές πληροφορίες σχετικές
3. **Δαπάνες αποθήκευσης**: Απαραίτητη στρατηγική για τον καθορισμό της κλίμακας
4. **ΣΥΝΕΧΕΙΑ**: Διατήρηση περιλήψεων ευθυγραμμισμένων με τα πρωτότυπα
5. **Ακρίβεια ανάκτησης**: Εξισορρόπηση μεταξύ πρόσφατου και ιστορικά σημαντικού πλαισίου

Αυτή η τεχνική μετατρέπει RAG από "αναζήτηση εγγράφων μου" σε "θυμηθείτε ό, τι έχουμε συζητήσει ποτέ" - ένα παιχνίδι-αλλαγή για μακροχρόνια βοηθούς AI.

# Όταν ΔΕΝ χρησιμοποιούνται ΚΓΠΕ

Το RAG δεν είναι πάντα η απάντηση.

**1. Ερωτήσεις γενικής γνώσης**

- "Ποια είναι η πρωτεύουσα της Γαλλίας;" - LLM ήδη γνωρίζει αυτό
- Οι ΚΓΠΕ προσθέτουν καθυστέρηση και κόστος χωρίς όφελος

**2. Δημιουργική γραφή**

- Ποίηση, μυθιστόρημα, μυαλό
- Οι ΚΓΠΕ περιορίζουν τη δημιουργικότητα με βάση το υπάρχον κείμενο

**3. Ανάγκες δεδομένων σε πραγματικό χρόνο**

- Χρηματιστηριακές τιμές, ζωντανές αθλητικές βαθμολογίες
- Σερβίρεται καλύτερα από την ενσωμάτωση API, όχι RAG

**4. Μαθηματική συλλογιστική**

- "Υπολογίστε 15% φιλοδώρημα στα 83,47 δολάρια"
- LLMs μπορεί να κάνει αριθμητική? RAGs δεν βοηθά

**5. Πολύ μικρές βάσεις γνώσεων**

- Αν η βάση γνώσεων σας ταιριάζει στο παράθυρο πλαίσιο, απλά συμπεριλάβετε τα όλα
- Δεν αξίζει τον κόπο για 5 έγγραφα.

**6. Όταν ελέγχετε την εκπαίδευση του LLM**

- Εάν εκπαιδεύετε ένα μοντέλο από το μηδέν για τα δεδομένα σας, η ρύθμιση των λεπτών μπορεί να είναι καλύτερη
- RAG είναι καλύτερο όταν χρησιμοποιείτε προ-εκπαιδευμένα μοντέλα δεν ελέγχετε

# Ξεκινώντας με τις ΚΓΠΕ: Ένας Πρακτικός Οδηγός

Θέλεις να φτιάξεις το δικό σου σύστημα RAG;

## Εβδομάδα 1: Έναρξη Απλό

**Στόχος:** Βάλε την βασική ανάκτηση να δουλεύει χωρίς LLM.

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

## Εβδομάδα 2: Προσθήκη βάσης δεδομένων Vector

**Στόχος:** Κλίμακα σε πραγματικές συλλογές εγγράφων.

Επόμενα βήματα για την εφαρμογή:

1. Εκτέλεση Qdrant σε Docker
2. Δείξε τα έγγραφά σου.
3. Εφαρμογή τελικού σημείου αναζήτησης

Θα το καλύψω λεπτομερώς σε ένα επερχόμενο άρθρο για τις βάσεις δεδομένων διανυσματικών στοιχείων.

## Εβδομάδα 3: Προσθήκη γενιάς LLM

**Στόχος:** Συμπληρώστε τον αγωγό RAG.

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

## Εβδομάδα 4: Βελτιστοποίηση και Πολωνική

- Προσθέστε caching (αποθήκευση στο κόστος API)
- Βελτίωση της στρατηγικής πνιγμού
- Προσθήκη φιλτραρίσματος μεταδεδομένα
- Εφαρμογή υβριδικής αναζήτησης
- Προσθήκη εξαγωγής παραπομπής

## Πηγαίνοντας τοπικά (προαιρετικά)

Μόλις τα βασικά δουλέψουν, μεταναστεύστε στο τοπικό συμπέρασμα (θα το καλύψω αυτό στα επόμενα άρθρα):

- Τοπικό συμπέρασμα LLM με επιτάχυνση GPU
- Φιλικές με CPU προσθήκες με το ONNX Runtime

# Συμπέρασμα

Το RAG (Retrieval-Augmented Generation) είναι μια ισχυρή τεχνική για να κάνει τις LLMs πιο ακριβείς, ενημερωμένες και αξιόπιστες, με βάση τις απαντήσεις τους σε πραγματικά έγγραφα. Αντί να βασίζεται μόνο στα δεδομένα κατάρτισης του μοντέλου, τα συστήματα RAG:

1. **Ανάκτηση** σχετικές πληροφορίες από μια βάση γνώσεων με τη χρήση σημασιολογικής αναζήτησης
2. **Αύξηση** η προτροπή της LLM με αυτό το ανακτημένο πλαίσιο
3. **Δημιουργία** απαντήσεις που βασίζονται σε πραγματικές πηγές με αναφορές

**Βασικά πλεονεκτήματα των ΚΓΠΕ:**

- Πάντα ενημερωμένη (ενημέρωση της βάσης γνώσεων σας)
- Γραμμένο σε πηγές (μειώνει την ψευδαίσθηση)
- Εξήγητο (μπορεί να αναφέρει πηγές)
- Αποτελεσματικό κόστος (χωρίς ακριβή επανεκπαίδευση)
- Απόρρητο-συντήρηση (μπορεί να λειτουργήσει εξ ολοκλήρου τοπικά)
- Ειδικός τομέας (χρησιμοποιήστε τα δικά σας έγγραφα)

**Πότε να χρησιμοποιήσετε τις ΚΓΠΕ:**

- Εργασίες έντασης γνώσης
- Πληροφορίες συχνής ενημέρωσης
- Δεδομένα ιδιωτικής/ιδιοκτησίας
- Ανάγκη για αναφορές και δυνατότητα ελέγχου
- Κόστος και απλότητα

**Πότε να αποφύγετε τις ΚΓΠΕ:**

- Βάσεις Gge

Το πεδίο εξελίσσεται γρήγορα με προηγμένες τεχνικές όπως η HyDE, η πολύπλευρη ανάκτηση, και η συμφραζόμενη συμπίεση, αλλά η βασική έννοια παραμένει απλή: να δώσει πρόσβαση LLMs στις σωστές πληροφορίες την κατάλληλη στιγμή.

Ξεκινήστε απλά, μετρήστε τα αποτελέσματα, και terrate. RAGs είναι ένας από τους πιο πρακτικούς τρόπους για να οικοδομήσουμε αξιόπιστα συστήματα AI σήμερα.

# Συμπέρασμα: Από τη Θεωρία στην Παραγωγή

Τώρα έχετε ολοκληρώσει τη σειρά τριών μερών RAG:

**[Μέρος 1: Προέλευση και Θεμελιώδη Στοιχεία](/blog/rag-primer)**

- Τι είναι το RAG και γιατί έχει σημασία
- Ιστορία από αναζήτηση λέξεων-κλειδιών στη σημασιολογική κατανόηση
- RAG vs fine-tuning, long context, and other approachs

**[Μέρος δεύτερο: Αρχιτεκτονική και Εσωτερικά](/blog/rag-architecture)**

- Πλήρης αγωγός RAG: ευρετηρίαση, ανάκτηση, παραγωγή
- Εσωτερικά LLM: μάρκες, KV cache, παράθυρα πλαισίου
- Λεπτομέρειες τεχνικής εφαρμογής με κωδικό C#

**Μέρος 3: ΚΓΠΕ στην πράξη (το παρόν άρθρο)**

- Εφαρμογές Πραγματικού Κόσμου (Σχετικές Δημοσιεύσεις, Σημαντική Αναζήτηση, Βοηθός συγγραφής)
- Κοινές προκλήσεις και λύσεις (chunking, ποιότητα ενσωμάτωσης, ψευδαίσθηση)
- Προχωρημένες τεχνικές (HyDE, Self-Query, Multi-Query, Contexual Συμπίεση, Μακροχρόνια Μνήμη)
- Όταν ΔΕΝ χρησιμοποιούνται ΚΓΠΕ
- Ξεκινώντας οδηγός

**Τώρα έχετε την πλήρη εικόνα:** Από την κατανόηση της προέλευσης των RAG μέχρι την κατασκευή συστημάτων παραγωγής με προηγμένες βελτιστοποιήσεις.

# Ποιο είναι το επόμενο;

Τώρα που καταλαβαίνετε RAG από τη θεωρία στην πρακτική, τα επόμενα άρθρα θα σας δείξει πώς να οικοδομήσουμε πλήρη, έτοιμα για την παραγωγή συστήματα RAG στο C#:

**Έρχομαι σε λίγο.**

- **CPU-Friendly Semantic Search** - Κατασκευή σημασιολογική αναζήτηση με ONNX ενσωμάτωση που τρέχει σε οποιοδήποτε VPS χωρίς απαιτήσεις GPU
- **Αυτό-Hosted Vector Database** - Πλήρης οδηγός εγκατάστασης για Qdrant με Docker, συμπεριλαμβανομένης ευρετηρίασης, αναζήτησης, και βελτιστοποίησης στρατηγικές
- **Κατασκευή ενός βοηθού συγγραφής ΚΓΠΕ** - Μια πλήρης σειρά για τη δημιουργία ενός βοηθού γραφής με ισχύ AI που χρησιμοποιεί το υπάρχον περιεχόμενό σας ως βάση γνώσεων

Αυτά τα άρθρα θα σας μεταφέρουν από τη θεωρία στην πρακτική, με πλήρη κώδικα εργασίας, στρατηγικές ανάπτυξης, και πραγματικά-κόσμο βελτιστοποιήσεις με βάση τη λειτουργία αυτών των συστημάτων στην παραγωγή σε αυτό το blog.

Μείνετε συντονισμένοι για οδηγούς εφαρμογής που μετατρέπουν αυτή τη γνώση RAG σε συστήματα εργασίας!

## Πόροι

**Ιδρυτικές εφημερίδες:**

- [Ανάκτηση-Αυξανόμενη Γενιά για Γνώση-Εντατικά καθήκοντα NLP](https://arxiv.org/abs/2005.11401) - Το πρωτότυπο χαρτί RAG
- [Αναζήτηση Passage Dense για απάντηση σε ερωτήσεις Open-Domain](https://arxiv.org/abs/2004.04906) - DPR (ιδρυτικό ίδρυμα)
- [Η προσοχή είναι το μόνο που χρειάζεστε.](https://arxiv.org/abs/1706.03762) - Μετασχηματιστές (εμπρόσθιο ίδρυμα)

**Εργαλεία και πλαίσια:**

- [QdrantCity name (optional, probably does not need a translation)](https://qdrant.tech/) - Βάση δεδομένων Vector που χρησιμοποιώ
- [ONNX Runtime](https://onnxruntime.ai/) - Για την τοπική ενσωμάτωση
- [LLamaSharpCity name (optional, probably does not need a translation)](https://github.com/SciSharp/LLamaSharp) - Για τοπικές παρεμβολές LLM
- [Μετασχηματιστές ποινή](https://www.sbert.net/) - Μοντέλα ενσωμάτωσης

**Περαιτέρω ανάγνωση:**

- [Ανθρωπιστική: Περιεκτική Ανάκτηση](https://www.anthropic.com/index/contextual-retrieval) - Προηγμένες τεχνικές RAG
- [Πώς λειτουργεί η Νευρική Μετάφραση Μηχανών](/blog/how-neural-machine-translation-works) - Κατανόηση των μοντέλων AI πίσω από την ενσωμάτωση

**Η παρούσα σειρά ΚΓΠΕ:**

- [Μέρος 1: Προέλευση και Θεμελιώδη Στοιχεία](/blog/rag-primer) - Ιστορία, κίνητρα, βασικές έννοιες
- [Μέρος δεύτερο: Αρχιτεκτονική και Εσωτερικά](/blog/rag-architecture) - Τεχνική βαθιά κατάδυση
- **Μέρος 3: Οι ΚΓΠΕ στην πράξη** (αυτό το άρθρο) - Κατασκευή πραγματικών συστημάτων

Χαρούμενο κτίριο!