Back to "RAG nella pratica: Costruire applicazioni Real-World"

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

AI AI-Article LLM Machine Learning RAG Semantic Search

RAG nella pratica: Costruire applicazioni Real-World

Saturday, 22 November 2025

Dentro Parte 1 della presente serie di orientamenti, abbiamo coperto i fondamenti di Retrieval-Augmented Generation [49] ciò che è, come funziona, e la tecnologia sottostante (imbeddings, database vettoriali, interni LLM). Ora è il momento di mettere in pratica questa conoscenza. Questo articolo mostra come costruire sistemi RAG reali con codice C# di lavoro, risolvere le sfide comuni, e utilizzare tecniche avanzate dalla ricerca recente.

Introduzione

Se non hai letto Parte 1: RAG spiegati, Consiglio di iniziare da lì per capire i concetti fondamentali:

  • Che cos'è RAG e perché è importante
  • Come funziona l'integrazione e la ricerca vettoriale
  • Interni LLM (token, cache KV, finestre di contesto)
  • RAG vs fine-tuning e altri approcci

Questo articolo presuppone di capire queste basi e si concentra su implementazione, ottimizzazione e modelli del mondo reale.

Real-World RAG Applicazioni su questo blog

Ho costruito diverse funzioni RAG-powered su questo blog. Lasciate che vi mostri esempi concreti.

Ogni post del blog può mostrare "Related Posts" utilizzando somiglianza semantica.

Come funziona:

  1. Ogni post del blog viene incorporato quando pubblicato
  2. Quando visualizziamo un post, recuperiamo il suo inserimento da Qdrant
  3. Trova le 5 iscrizioni post più simili
  4. Visualizza come "Posti correlati"

Perche' e' meglio delle etichette:

  • Le etichette richiedono la categorizzazione manuale
  • Ricerca semantica trova messaggi concettualmente correlati anche senza tag corrispondenti
  • "Docker Compose" e "Container orchestration" post sono correlati semanticamente

Snippet codice:

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. Ricerca blog semantica

La casella di ricerca su questo blog utilizza la ricerca semantica in stile RAG (anche se senza la parte di generazione - è solo il recupero).

Esperienza utente:

  • Cerca "creazione di una banca dati" → trova post su PostgreSQL, Entity Framework, migrazioni
  • Cerca "disponibilità" → trova post su Docker, hosting, CI/CD
  • Nessuna necessità di corrispondenza esatta con le parole chiave

Attuazione: Copriro' la costruzione di questo in un prossimo articolo sui database vettoriali.

3. L'assistente di scrittura "GPT avvocato"

Sto costruendo un sistema completo RAG per aiutarmi a scrivere nuovi post sul blog.

Caso d'uso: Quando inizio a scrivere su "aggiungere l'autenticazione a ASP.NET Core," il sistema:

  1. Incorpora la mia corrente bozza
  2. Ricerche post passati per contenuti correlati sull'autenticazione, ASP.NET, sicurezza
  3. Suggerisce snippet di codice che ho usato prima
  4. Auto-genera i collegamenti interni ai relativi posti
  5. Mantiene la coerenza con il mio stile di scrittura

Gasdotto completo 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;
}

Questo è RAG in azione - recupero (ricerca semantica) + aumento (aggiunta di contesto) + generazione (suggerimenti LLM).

Sfide e soluzioni comuni RAG

Costruire sistemi RAG di produzione non è banale. Qui ci sono le sfide che ho incontrato e come risolverli.

Sfida 1: Strategia di risposta

Problema: Come si dividono i documenti? Troppo piccolo = perdita di contesto. Troppo grande = informazioni irrilevanti.

Soluzione: Copertina ibrida basata sulla struttura del documento.

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

Migliori pratiche:

  • Rispettare la struttura del documento (intestazioni, paragrafi)
  • Aggiungi sovrapposizione tra pezzi (50-100 parole)
  • Preserva i blocchi di codice intatti
  • Includi intestazioni di sezione in ogni pezzo per il contesto

Sfida 2: integrare la qualità

Problema: I modelli generici di embedding potrebbero non catturare la semantica specifica del dominio.

Soluzioni:

Opzione 1: Abbinamenti di fine-tune (avanzato)

# 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)

Opzione 2: Inserzioni ibride (modelli multipli combinati)

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

Opzione 3: Aggiungi filtraggio dei metadati

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

Sfida 3: Gestione delle finestre di contesto

Problema: I LLM hanno limiti al token. Come si inserisce query + context + prompt nella finestra?

Soluzione: Selezione e sintesi dinamica del contesto.

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

Sfida 4: Allucinazioni Malgrado gli orientamenti

Problema: Anche con il contesto, LLM a volte ignorarlo e allucinazioni.

Soluzioni:

1. Prompt engineering:

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. Convalida post-generazione:

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. Raffinatezza iterativa:

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

Sfida 5: mantenere l'indice aggiornato

Problema: Come si aggiungono nuovi documenti, il database vettoriale deve rimanere aggiornato.

Soluzione: Condotto automatico di indicizzazione.

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

Tecniche avanzate di RAG

Esploriamo tecniche RAG all'avanguardia di recente ricerca.

1. Inserimento di documenti ipotetici (HyDE)

Problema: Le query degli utenti sono spesso brevi e poco formate. I pezzi di documento sono dettagliati e ben scritti. Questo squilibrio danneggia il recupero.

Soluzione: Generare un documento ideale ipotetico che risponderebbe alla query, incorporarlo, quindi cercare.

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

Perché funziona: La risposta ipotetica utilizza il linguaggio e la struttura simili a documenti reali, migliorando il recupero.

2. L'auto-chiesta

Problema: Le query degli utenti spesso mixano la ricerca semantica con i filtri dei metadati.

Esempio: "Posti recenti su Docker" = semantico("Docker") + filtro(data > 2024-01-01)

Soluzione: Usa LLM per analizzare la query in filtri semantici + metadati.

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. RAG multiquery

Problema: Una singola interrogazione potrebbe perdere i documenti pertinenti a causa del fraseggio.

Soluzione: Generare più variazioni della query, cercare con tutti, unire i risultati.

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. Compressione contestuale

Problema: I pezzi recuperati contengono informazioni irrilevanti.

Soluzione: Utilizzare un LLM più piccolo per comprimere il contesto recuperato solo in parti rilevanti.

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. Recupero Iterativo (RAG multi-Hop)

Problema: Domande complesse richiedono informazioni da più fonti che devono essere collegate.

Esempio: "Quale database utilizza il blog e come viene implementata la ricerca semantica?"

  • Hop 1: Trova che database viene utilizzato (PostgreSQL)
  • Hop 2: Scopri come funziona la ricerca semantica con quel database

Soluzione: Recupero iterativo e sintesi.

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. Memoria del contesto a lungo termine con RAG + sommarizzazione

Problema: Come si costruiscono i sistemi AI che ricordano le conversazioni di mesi o anni fa? I chatbot tradizionali perdono il contesto dopo ogni sessione.

Soluzione: Combina RAG con una sintesi progressiva per creare una memoria persistente e ricercabile.

Questo è l'approccio utilizzato in DiSE (Directed Synthetic Evolution) - un sistema avanzato che sto costruendo che usa la memoria di contesto basata su RAG per mantenere la storia di conversazione condivisa indefinitamente.

Scenario di esempio:

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..."

Come funziona:

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

Approccio di attuazione:

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

Perché questo è potente:

  1. Intervalli di memoria infiniti - Le conversazioni di anni fa sono accessibili come quelle di ieri.
  2. Ricerca semantica - "Le specifiche di George" trova la conversazione anche se memorizzata come "requisiti per la prescrizione di George"
  3. Collegamento tra entità - Tutte le conversazioni che parlano di "George" sono collegate
  4. Tutela della privacy - Può implementare l'isolamento della memoria per utente
  5. Efficace sotto il profilo dei costi - La sintesi previene l'esplosione dello storage mantenendo la ricercabilità

Esempio del mondo reale da DiSE:

DiSE usa questo approccio per ricordare:

  • Invocazioni degli strumenti da mesi fa (ciò che ha funzionato, ciò che ha fallito)
  • Modelli di codice che hanno avuto successo in progetti passati
  • Preferenze dell'utente stabilito in conversazioni precedenti
  • Conoscenze di dominio accumulato nel tempo

Questo crea un sistema di IA che realmente "impara" da ogni interazione e costruisce la memoria istituzionale, piuttosto che iniziare da zero ogni sessione.

Sfide da considerare:

  1. Privacy: Le vecchie conversazioni devono essere adeguatamente isolate per utente
  2. Decadimento di pertinenza: Non tutte le vecchie informazioni rimangono rilevanti
  3. Spese di magazzinaggio: Strategia di ricapitolazione delle esigenze per la scala
  4. Coerenza: Mantenere riassunti allineati con gli originali
  5. Precisione di recupero: Bilanciamento tra contesto recente e storicamente importante

Questa tecnica trasforma RAG da "ricercare i miei documenti" in "ricordare tutto quello di cui abbiamo mai discusso" - un gioco-cambio per assistenti AI a lungo termine.

Quando NON usare RAG

RAG non è sempre la risposta. Ecco quando evitarlo:

1. Domande generali sulla conoscenza

  • "Qual è la capitale della Francia?" - LLM lo sa già
  • RAG aggiunge latenza e costo per nessun beneficio

2. Scrittura creativa

  • Poesia, finzione, brainstorming
  • Gli orientamenti limitano la creatività fondandosi sul testo esistente

3. Esigenze di dati in tempo reale

  • Stock prices, live sports score
  • Meglio serviti da integrazioni API, non RAG

4. Il ragionamento matematico

  • "Calcola il 15% di mancia su 83,47 dollari"
  • I LLM possono fare l'aritmetica; RAG non aiuta

5. Basi di conoscenza molto piccole

  • Se la vostra base di conoscenze si inserisce nella finestra di contesto, basta includere tutto
  • RAG overhead non vale la pena per 5 documenti

6. Quando controlli l'allenamento della LLM

  • Se stai allenando un modello da zero sui tuoi dati, la messa a punto potrebbe essere migliore
  • RAG è meglio quando si utilizzano modelli pre-addestrati non si controlla

Iniziare con RAG: una guida pratica

Vuoi costruire il tuo sistema RAG? Ecco un approccio passo-passo.

Settimana 1: Avviare Semplice

Obiettivo: Ottenere recupero di base lavorando senza 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

Settimana 2: Aggiungi banca dati vettoriale

Obiettivo: Scala a collezioni di documenti reali.

Prossime fasi di attuazione:

  1. Esegui Qdrant in Docker
  2. Index your documents
  3. Attuare l'endpoint di ricerca

Ne parlero' nei dettagli in un prossimo articolo sui database vettoriali.

Settimana 3: Aggiungi generazione LLM

Obiettivo: Completa il gasdotto 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;

Settimana 4: Ottimizza e Polacco

  • Aggiungi cache (salva i costi delle API)
  • Migliorare la strategia di partecipazione
  • Aggiungi filtro metadati
  • Implementa la ricerca ibrida
  • Aggiungi estrazione citazione

Andare a livello locale (facoltativo)

Una volta che le basi funzionano, migra verso l'inferenza locale (lo coprirò nei prossimi articoli):

  • Inferenza LLM locale con accelerazione GPU
  • Abbinamenti CPU-friendly con ONNX Runtime

Conclusione

RAG (Retrieval-Augmented Generation) è una tecnica potente per rendere gli LLM più accurati, aggiornati e affidabili mettendo a terra le loro risposte in documenti reali. Invece di affidarsi ai soli dati di formazione del modello, i sistemi RAG:

  1. Recupera informazioni pertinenti provenienti da una base di conoscenze utilizzando la ricerca semantica
  2. Aumento il prompt di LLM con quel contesto recuperato
  3. Genera risposte che sono basate su fonti reali con citazioni

Principali vantaggi di RAG:

  • Sempre up-to-date (solo aggiornare la vostra base di conoscenze)
  • A base di fonti (riduce l'allucinazione)
  • Spiegabile (può citare fonti)
  • Economico (nessuna riqualificazione costosa)
  • Privacy-preserving (può essere eseguito interamente localmente)
  • Domain-specific (usa i tuoi documenti)

Quando usare RAG:

  • Compiti ad alta intensità di conoscenza
  • Aggiornare frequentemente le informazioni
  • Dati privati/proprietari
  • Necessità di citazioni e verificabilità
  • Costi e semplicità

Quando evitare RAG:

  • Questioni generali sulla conoscenza
  • Scrittura creativa
  • Dati in tempo reale (usare invece le API)
  • Basi di conoscenze molto piccole

Il campo si sta evolvendo rapidamente con tecniche avanzate come HyDE, recupero multi-query e compressione contestuale, ma il concetto di base rimane semplice: dare ai LLM l'accesso alle informazioni giuste al momento giusto.

Avviare semplice, misurare i risultati, e iterare. RAG è uno dei modi più pratici per costruire sistemi AI affidabili oggi.

Cosa c'e' dopo?

Ora che capisci entrambi i fondamenti (Parte 1) e applicazioni pratiche di RAG, nei prossimi articoli vi mostrerò come costruire sistemi RAG completi e pronti per la produzione in C#:

Presto:

  • Ricerca CPU-Friendly Semantic - Costruire la ricerca semantica con le inserzioni ONNX che funzionano su qualsiasi VPS senza requisiti GPU
  • Database vettoriali self-hosted - Guida completa alla configurazione di Qdrant con Docker, comprese le strategie di indicizzazione, ricerca e ottimizzazione
  • Costruire un assistente di scrittura RAG - Una serie completa sulla creazione di un assistente di scrittura AI-powered che utilizza il contenuto esistente come base di conoscenza

Questi articoli vi porteranno dalla teoria alla pratica, con codice di lavoro completo, strategie di distribuzione e ottimizzazioni del mondo reale basate sull'esecuzione di questi sistemi in produzione su questo blog.

Restate sintonizzati per guide pratiche di implementazione che trasformano questa conoscenza RAG in sistemi di lavoro!

Risorse

Documenti di fondazione:

Strumenti e quadri:

Ulteriore lettura:

Felice edificio!

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.