RAG en la práctica: Construyendo aplicaciones en el mundo real (Español (Spanish))

RAG en la práctica: Construyendo aplicaciones en el mundo real

Saturday, 22 November 2025

//

23 minute read

In Parte 1 y Parte 2 de esta serie, hemos cubierto los orígenes, fundamentos y arquitectura técnica de RAG. Usted entiende lo que es RAG, por qué importa, y cómo funciona bajo el capó. Ahora es el momento de poner en práctica ese conocimiento. Este artículo le muestra cómo construir sistemas reales RAG con código C# de trabajo, resolver desafíos comunes, y utilizar técnicas avanzadas de investigación reciente.

Introducción

Navegación de la serie: Esta es la parte 3 de la serie RAG:

Si no has leído las partes 1 y 2, te recomiendo que empieces por ahí para entender:

  • Qué es el GCR y por qué es importante (Parte 1)
  • La historia de la búsqueda de palabras clave a la comprensión semántica (Parte 1)
  • Gasoducto completo RAG: indexación, recuperación, generación (Parte 2)
  • Internos LLM: tokens, caché KV, ventanas de contexto (Parte 2)
  • GCR frente a ajustes y otros enfoques (Parte 1)

Este artículo asume que usted entiende esos fundamentos y se centra en implementación, optimización y patrones del mundo real.

Aplicaciones RAG del mundo real en este blog

He construido varias características impulsadas por RAG en este blog. Permíteme mostrarte ejemplos concretos.

1. Recomendación sobre puestos conexos

Cada entrada de blog puede mostrar "Related Posts" usando similitud semántica.

Cómo funciona:

  1. Cada entrada de blog se incrusta cuando se publica
  2. Al ver un post, recuperamos su incrustación de Qdrant
  3. Encontrar los 5 más similares post embeddings
  4. Mostrar como "Posts relacionados"

Por qué es mejor que las etiquetas:

  • Las etiquetas requieren categorización manual
  • Búsqueda semántica encuentra mensajes conceptualmente relacionados, incluso sin las etiquetas correspondientes
  • Los posts de "Docker Compose" y "Container Orquestration" están relacionados semánticamente

Fragmento de código:

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. Búsqueda de blogs semánticos

El cuadro de búsqueda en este blog utiliza la búsqueda semántica estilo RAG (aunque sin la parte de generación - es sólo la recuperación).

Experiencia del usuario:

  • Buscar "configurando una base de datos" → encuentra publicaciones sobre PostgreSQL, Entity Framework, migraciones
  • Buscar "despliegue" → encuentra publicaciones sobre Docker, hosting, CI/CD
  • No hay necesidad de coincidencias exactas de palabras clave

Ejecución: Cubriré la construcción de esto en un próximo artículo sobre bases de datos vectoriales.

3. El Asistente de Redacción "Abogado GPT"

Estoy construyendo un sistema completo de RAG para ayudarme a escribir nuevos posts de blog.

Caso de uso: Cuando empiezo a escribir sobre "añadir autenticación a ASP.NET Core", el sistema:

  1. Incrusta mi borrador actual
  2. Busca en publicaciones anteriores contenido relacionado sobre autenticación, ASP.NET, seguridad
  3. Sugiere fragmentos de código relevantes que he usado antes
  4. Genera automáticamente enlaces internos a publicaciones relacionadas
  5. Mantiene la coherencia con mi estilo de escritura

Gasoducto RAG completo:

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

Esto es RAG en acción - recuperación (búsqueda semántica) + aumento (añadiendo contexto) + generación (sugerencias LLM).

Retos y soluciones comunes de los GCR

Los sistemas de producción de edificios RAG no son triviales. Estos son los desafíos que he encontrado y cómo resolverlos.

Desafío 1: Estrategia de lucha contra el terrorismo

Problema: ¿Cómo dividir documentos? Demasiado pequeño = pérdida de contexto. Demasiado grande = información irrelevante.

Solución: Trozos híbridos basados en la estructura 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;
    }
}

Prácticas óptimas:

  • Respetar la estructura del documento (encabezados, párrafos)
  • Añadir solapamiento entre trozos (50-100 palabras)
  • Preservar bloques de código intactos
  • Incluir encabezados de sección en cada trozo para contexto

Desafío 2: Incorporación de la calidad

Problema: Los modelos de incrustación genéricos pueden no capturar la semántica específica del dominio.

Soluciones:

Opción 1: Inserciones de ajuste fino (Avanzado)

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

Opción 2: Incrustaciones híbridas (combina varios modelos)

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

Opción 3: Añadir filtrado de metadatos

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

Desafío 3: Gestión de ventanas contextuales

Problema: Los LLMs tienen límites de token. ¿Cómo encaja la consulta + contexto + prompt en la ventana?

Solución: Selección y resumen dinámicos del contexto.

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

Desafío 4: Alucinación A pesar de las RAG

Problema: Incluso con el contexto, los LLM a veces lo ignoran y alucinan.

Soluciones:

1. Ingeniería inmediata:

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. Validación posterior a la generación:

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. Refinamiento iterativo:

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

Desafío 5: Mantener el índice actualizado

Problema: Al agregar nuevos documentos, la base de datos de vectores necesita mantenerse actualizada.

Solución: Gasoducto de indexación automatizado.

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

Técnicas avanzadas de GCR

Exploremos técnicas RAG de vanguardia de investigaciones recientes.

1. Incorporación de documentos hipotéticos (HyDE)

Problema: Las consultas de los usuarios a menudo son cortas y están mal formadas. Los trozos de documento están detallados y bien escritos.

Solución: Generar un documento ideal hipotético que responda a la consulta, incrustar que, a continuación, buscar.

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

Por qué funciona: La respuesta hipotética utiliza lenguaje y estructura similares a los documentos reales, mejorando la recuperación.

2. Self-Queerying

Problema: Las consultas de los usuarios a menudo mezclan búsqueda semántica con filtros de metadatos.

Ejemplo: "Publicaciones recientes sobre Docker" = semántica("Docker") + filtro(fecha > 2024-01-01)

Solución: Utilice LLM para analizar la consulta en filtros semánticos + metadatos.

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

Problema: Una sola consulta podría perder los documentos pertinentes debido a la fraseación.

Solución: Generar múltiples variaciones de la consulta, buscar con todos, combinar los resultados.

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. Compresión contextual

Problema: Los trozos recuperados contienen información irrelevante. Enviándolo a todos los residuos tokens.

Solución: Utilice un LLM más pequeño para comprimir el contexto recuperado sólo a partes relevantes.

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. Recuperación iterativa (GCR multi-Hop)

Problema: Las preguntas complejas requieren información de múltiples fuentes que necesitan ser conectadas.

Ejemplo: "¿Qué base de datos utiliza el blog y cómo se implementa la búsqueda semántica?"

  • Hop 1: Encuentre qué base de datos se utiliza (PostgreSQL)
  • Hop 2: Encuentra cómo funciona la búsqueda semántica con esa base de datos

Solución: Recuperación iterativa y síntesis.

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 de contexto a largo plazo con RAG + Resumen

Problema: ¿Cómo se construyen sistemas de IA que recuerdan conversaciones de meses o años atrás? Los chatbots tradicionales pierden contexto después de cada sesión.

Solución: Combine RAG con resumen progresivo para crear memoria persistente y buscable.

Este es el enfoque utilizado en DiSE (Evolución sintética directa) - un sistema avanzado que estoy construyendo que utiliza memoria de contexto basada en RAG para mantener una historia de conversación compartida indefinidamente.

Escenario de ejemplo:

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

Cómo funciona:

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

Enfoque de aplicación:

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

Por qué esto es poderoso:

  1. Espacio de memoria infinito - Las conversaciones de hace años son tan accesibles como las de ayer.
  2. Búsqueda semántica - "Las especificaciones de George" encuentra la conversación incluso si se almacena como "Requisitos de gafas recetadas de George"
  3. Entidad que vincula - Todas las conversaciones que mencionan "George" están conectadas
  4. Preservación de la privacidad - Puede implementar el aislamiento de memoria por usuario
  5. Eficaz en función de los costos - Resumen evita explosión de almacenamiento mientras se mantiene la búsqueda

Ejemplo del mundo real de DiSE:

DiSE utiliza este enfoque para recordar:

  • Invocaciones de herramientas de hace meses (lo que funcionó, lo que falló)
  • Patrones de código que tuvieron éxito en proyectos anteriores
  • Preferencias de usuario establecido en conversaciones anteriores
  • Conocimientos de dominio acumulados a lo largo del tiempo

Esto crea un sistema de IA que realmente "aprende" de cada interacción y construye memoria institucional, en lugar de comenzar de nuevo cada sesión.

Retos a tener en cuenta:

  1. Privacidad: Las conversaciones antiguas deben estar adecuadamente aisladas por usuario
  2. Decaimiento de la pertinencia: No toda la información antigua sigue siendo relevante
  3. Costes de almacenamiento: Estrategia de resumen de las necesidades para la escala
  4. Coherencia: Mantener los resúmenes alineados con los originales
  5. Precisión de obtención: Equilibrio entre el contexto reciente e históricamente importante

Esta técnica transforma a RAG de "buscar mis documentos" en "recordar todo lo que hemos discutido" - un cambio de juego para los asistentes de IA a largo plazo.

Cuándo NO usar RAG

RAG no siempre es la respuesta. Aquí está cuando para evitarlo:

1. Cuestiones relativas a los conocimientos generales

  • "¿Cuál es la capital de Francia?" - LLM ya lo sabe
  • RAG agrega latencia y costo sin beneficio

2. Escritura creativa

  • Poesía, ficción, lluvia de ideas
  • El GCR limita la creatividad al fundamentarse en el texto existente

3. Necesidades de datos en tiempo real

  • Precios de las acciones, resultados deportivos en vivo
  • Mejor servida por integraciones API, no por RAG

4. Razonamiento matemático

  • "Calcular 15% de punta en $83.47"
  • Los LLMs pueden hacer aritmética; RAG no ayuda

5. Bases de conocimientos muy pequeñas

  • Si su base de conocimientos encaja en la ventana de contexto, sólo tiene que incluir todo
  • Los gastos generales de RAG no valen la pena por 5 documentos

6. Cuando usted controla el entrenamiento de la LLM

  • Si estás entrenando un modelo desde cero en tus datos, el ajuste podría ser mejor
  • RAG es mejor cuando utilizas modelos pre-entrenados que no controlas

Comenzando con RAG: Una guía práctica

¿Quieres construir tu propio sistema RAG? Aquí tienes un enfoque paso a paso.

Semana 1: Inicio sencillo

Objetivo: Obtener recuperación básica trabajando sin 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

Semana 2: Añadir base de datos vectorial

Objetivo: Escalar a colecciones de documentos reales.

Próximas medidas para aplicar:

  1. Ejecutar Qdrant en Docker
  2. Indexe sus documentos
  3. Implementar el punto final de búsqueda

Cubriré esto en detalle en un próximo artículo sobre bases de datos vectoriales.

Semana 3: Añadir generación LLM

Objetivo: Completa el gasoducto 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;

Semana 4: Optimizar y Polaco

  • Añadir caché (guardar en los costos de API)
  • Mejorar la estrategia de troceado
  • Añadir filtrado de metadatos
  • Implementar búsqueda híbrida
  • Añadir extracción de citación

Ir a nivel local (opcional)

Una vez que lo básico funcione, migra a la inferencia local (lo cubriré en los próximos artículos):

  • Inferencia local de LLM con aceleración de GPU
  • Incrustaciones fáciles de usar con la CPU con ONNX Runtime

Conclusión

RAG (Retrieval-Augmentated Generation) es una poderosa técnica para hacer que los LLMs sean más precisos, actualizados y confiables basando sus respuestas en documentos reales. En lugar de confiar solo en los datos de entrenamiento del modelo, los sistemas RAG:

  1. Recuperar información relevante de una base de conocimientos utilizando la búsqueda semántica
  2. Aumento el prompt del LLM con ese contexto recuperado
  3. Generar respuestas basadas en fuentes reales con citas

Principales ventajas de los GCR:

  • Siempre actualizado (solo actualice su base de conocimientos)
  • Fundamentado en fuentes (reduce la alucinación)
  • Explicable (puede citar fuentes)
  • Eficaz en función de los costos (sin costos de readiestramiento)
  • Preservación de la privacidad (puede ejecutarse en su totalidad a nivel local)
  • Dominio específico (utiliza tus propios documentos)

Cuándo usar RAG:

  • Tareas intensivas en conocimientos
  • Información de actualización frecuente
  • Datos privados/propietarios
  • Necesidad de citaciones y capacidad de auditoría
  • El coste y la simplicidad importan

Cuándo evitar RAG:

  • Bases de Gge

El campo está evolucionando rápidamente con técnicas avanzadas como HyDE, recuperación de múltiples consultas y compresión contextual, pero el concepto central sigue siendo simple: dar acceso a los LLMs a la información correcta en el momento adecuado.

Comience simple, mida los resultados e iterate. RAG es una de las maneras más prácticas de construir sistemas de IA confiables hoy en día.

Conclusión: De la teoría a la producción

Ahora has completado la serie RAG de tres partes:

Parte 1: Orígenes y fundamentos

  • Qué es RAG y por qué importa
  • Historia de la búsqueda de palabras clave a la comprensión semántica
  • RAG vs ajuste, contexto largo y otros enfoques

Parte 2: Arquitectura e Interiores

  • Gasoducto completo RAG: indexación, recuperación, generación
  • Internos LLM: tokens, caché KV, ventanas de contexto
  • Datos técnicos de implementación con código C#

Parte 3: Los GCR en la práctica (este artículo)

  • Implementaciones en el mundo real (puestos relacionados, búsqueda semántica, asistente de redacción)
  • Retos y soluciones comunes (recorte, integración de la calidad, alucinación)
  • Técnicas avanzadas (HyDE, Self-Query, Multi-Query, Compresión contextual, Memoria a largo plazo)
  • Cuándo NO usar RAG
  • Guía para empezar

Ahora tienes la imagen completa: Desde entender los orígenes de RAG hasta construir sistemas de producción con optimizaciones avanzadas.

¿Qué sigue?

Ahora que usted entiende RAG de la teoría a la práctica, los próximos artículos le mostrarán cómo construir sistemas RAG completos, listos para la producción en C#:

Próximamente:

  • Búsqueda semántica amigable con la CPU - Creación de búsqueda semántica con incrustaciones ONNX que se ejecutan en cualquier VPS sin requisitos GPU
  • Bases de datos de vectores alojadas por sí mismas - Guía de configuración completa para Qdrant con Docker, incluyendo estrategias de indexación, búsqueda y optimización
  • Construyendo un asistente de escritura RAG - Una serie completa sobre la creación de un asistente de escritura con IA que utiliza su contenido existente como base de conocimientos

Estos artículos te llevarán de la teoría a la práctica, con código de trabajo completo, estrategias de implementación y optimizaciones del mundo real basadas en la ejecución de estos sistemas en producción en este blog.

¡Manténgase atento para las guías de implementación prácticas que convierten este conocimiento de RAG en sistemas de trabajo!

Recursos

Documentos Fundacionales:

Herramientas y marcos:

Más información:

Esta serie RAG:

¡Feliz edificio!

Finding related posts...
logo

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