RAG (Generación aumentada mediante recuperación)

Proporciona a tus agentes una base de conocimientos para que puedan responder preguntas utilizando tus propios datos.


¿Qué es RAG?

RAG permite que tu agente consulte información relevante de tus documentos antes de responder una pregunta. En lugar de basarse únicamente en aquello con lo que se entrenó LLM, el agente busca en tus datos y utiliza los resultados como contexto.

El flujo es el siguiente:

  1. Ingesta — Los documentos se dividen en fragmentos, se convierten en embeddings vectoriales y se almacenan
  2. Consulta — Una pregunta se convierte en un embedding y se compara con los fragmentos almacenados según su similitud
  3. Generación — Los fragmentos más relevantes se envían a LLM como contexto para su respuesta

Esto significa que tu agente puede responder preguntas sobre documentación de productos, políticas de la empresa, bases de código o cualquier texto que le proporciones.

Aspectos destacados:

  • 📄 Ingiere cualquier texto — documentación de productos, markdown, código, políticas
  • 🔍 Búsqueda semántica — encuentra contenido relevante por su significado, no solo por palabras clave
  • 🤖 Recuperación agéntica — el agente decide cuándo buscar mediante RagTool
  • 🔌 Backends intercambiables — cambia los proveedores de embeddings y los almacenes vectoriales sin modificar el código

Instalación

[dependencies]
# Core only (in-memory store, all chunkers, no external deps)
adk-rag = "2.1.0"

# With Gemini embeddings (recommended for getting started)
adk-rag = { version = "2.1.0", features = ["gemini"] }

Paso 1: Crear un pipeline

Un RagPipeline conecta cuatro componentes: un segmentador, un proveedor de embeddings, un almacén vectorial y un reranker opcional.

use std::collections::HashMap;
use std::sync::Arc;
use adk_rag::{
    Document, FixedSizeChunker, InMemoryVectorStore,
    RagConfig, RagPipeline, EmbeddingProvider,
};

// Mock embedder for demos — no API key needed.
// In production, use GeminiEmbeddingProvider or OpenAIEmbeddingProvider.
struct MockEmbedder;

#[async_trait::async_trait]
impl EmbeddingProvider for MockEmbedder {
    async fn embed(&self, text: &str) -> adk_rag::Result<Vec<f32>> {
        let hash = text.bytes().fold(0u64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64));
        let mut v = vec![0.0f32; 64];
        for (i, x) in v.iter_mut().enumerate() {
            *x = ((hash.wrapping_add(i as u64)) as f32).sin();
        }
        let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
        if norm > 0.0 { v.iter_mut().for_each(|x| *x /= norm); }
        Ok(v)
    }
    fn dimensions(&self) -> usize { 64 }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pipeline = RagPipeline::builder()
        .config(RagConfig::builder()
            .chunk_size(256)
            .chunk_overlap(50)
            .top_k(3)
            .build()?)
        .embedding_provider(Arc::new(MockEmbedder))
        .vector_store(Arc::new(InMemoryVectorStore::new()))
        .chunker(Arc::new(FixedSizeChunker::new(256, 50)))
        .build()?;

    // Create a collection and ingest a document
    pipeline.create_collection("docs").await?;
    pipeline.ingest("docs", &Document {
        id: "intro".into(),
        text: "Rust is a systems programming language focused on safety and speed.".into(),
        metadata: HashMap::from([("topic".into(), "rust".into())]),
        source_uri: None,
    }).await?;

    // Query
    let results = pipeline.query("docs", "safe programming").await?;
    for r in &results {
        println!("[{:.3}] {}", r.score, r.chunk.text);
    }
    Ok(())
}

Cómo funciona:

  1. FixedSizeChunker divide el documento en fragmentos de 256 caracteres con una superposición de 50 caracteres
  2. MockEmbedder convierte cada fragmento en un vector de 64 dimensiones
  3. InMemoryVectorStore almacena los vectores y realiza búsquedas mediante similitud coseno
  4. query() convierte la pregunta en un embedding, encuentra los fragmentos más cercanos y los devuelve ordenados por puntuación

Paso 2: Añadir RAG a un agente

El verdadero poder de RAG se manifiesta cuando un agente lo utiliza como herramienta. RagTool encapsula el pipeline como un adk_core::Tool; el agente llama a rag_search cuando necesita información.

Cuando utilizas RagTool con agentes respaldados por Gemini, ADK normaliza automáticamente el resultado de la herramienta en una respuesta de función compatible con Gemini. Esto es importante porque rag_search devuelve de forma natural una lista de fragmentos, mientras que Gemini espera que functionResponse.response sea un objeto JSON en la transmisión.

use std::sync::Arc;
use adk_agent::LlmAgentBuilder;
use adk_model::gemini::GeminiModel;
use adk_rag::{
    Document, GeminiEmbeddingProvider, InMemoryVectorStore,
    RagConfig, RagPipeline, RagTool, RecursiveChunker,
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;

    // Build pipeline with real embeddings
    let pipeline = Arc::new(
        RagPipeline::builder()
            .config(RagConfig::builder().chunk_size(300).chunk_overlap(50).top_k(3).build()?)
            .embedding_provider(Arc::new(GeminiEmbeddingProvider::new(&api_key)?))
            .vector_store(Arc::new(InMemoryVectorStore::new()))
            .chunker(Arc::new(RecursiveChunker::new(300, 50)))
            .build()?,
    );

    // Ingest documents
    pipeline.create_collection("kb").await?;
    pipeline.ingest("kb", &Document {
        id: "returns".into(),
        text: "Our return policy allows returns within 30 days with a receipt.".into(),
        metadata: Default::default(),
        source_uri: None,
    }).await?;

    // Wrap pipeline as a tool and attach to an agent
    let agent = LlmAgentBuilder::new("support")
        .instruction("Answer questions using the rag_search tool. Cite your sources.")
        .model(Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?))
        .tool(Arc::new(RagTool::new(pipeline, "kb")))
        .build()?;

    // The agent now calls rag_search automatically when it needs knowledge base info
    adk_cli::console::run_console(Arc::new(agent), "app".into(), "user1".into()).await?;
    Ok(())
}

Cuando un usuario pregunta «¿Cuál es su política de devoluciones?», el agente:

  1. Decide que necesita buscar en la base de conocimientos
  2. Llama a rag_search con {"query": "return policy"}
  3. Recibe los fragmentos relevantes con sus puntuaciones
  4. Utiliza los fragmentos como contexto para generar una respuesta natural

Paso 3: Elegir una estrategia de fragmentación

La forma en que divides los documentos afecta a la calidad de la recuperación. adk-rag proporciona tres fragmentadores:

FragmentadorIdeal paraCómo divide
FixedSizeChunkerTexto general, registrosCada N caracteres con solapamiento
RecursiveChunkerArtículos, documentación, comentarios de códigoPárrafos → oraciones → palabras
MarkdownChunkerArchivos Markdown, READMEPor encabezados, conservando la jerarquía de secciones
use adk_rag::{FixedSizeChunker, RecursiveChunker, MarkdownChunker};

// Fixed: simple, predictable chunks
let chunker = FixedSizeChunker::new(512, 100);

// Recursive: respects natural text boundaries
let chunker = RecursiveChunker::new(512, 100);

// Markdown: each section becomes a chunk with header_path metadata
let chunker = MarkdownChunker::new(512, 100);

RecursiveChunker es la mejor opción predeterminada: prueba primero con los saltos de párrafo, después con los límites de las oraciones y, por último, con los límites de las palabras, lo que produce fragmentos más naturales que la división en tamaños fijos.

MarkdownChunker añade un campo de metadatos header_path a cada fragmento (por ejemplo, "Getting Started > Installation"), lo que ayuda al agente a citar secciones específicas.


Configuración

use adk_rag::RagConfig;

let config = RagConfig::builder()
    .chunk_size(256)            // max characters per chunk (default: 512)
    .chunk_overlap(50)          // overlap between chunks (default: 100)
    .top_k(5)                   // results to return (default: 10)
    .similarity_threshold(0.5)  // minimum score to include (default: 0.0)
    .build()?;
ParámetroQué controlaOrientación
chunk_sizeMáximo de caracteres por fragmento200–500 para la mayoría de los casos de uso. Menos = más precisión, más = más contexto
chunk_overlapCaracteres compartidos entre fragmentos adyacentesEl 10–20 % de chunk_size evita perder información en los límites
top_kNúmero de resultados devueltosMás resultados = más contexto para LLM, pero mayor uso de tokens
similarity_thresholdPuntuación mínima para incluir0.0 devuelve todo; 0.3–0.7 filtra las coincidencias débiles

Proveedores de embeddings

ProveedorIndicador de funcionalidadModeloRequiere
GeminiEmbeddingProvidergeminigemini-embedding-2GOOGLE_API_KEY
OpenAIEmbeddingProvideropenaitext-embedding-3-smallOPENAI_API_KEY
// Gemini
use adk_rag::GeminiEmbeddingProvider;
let embedder = GeminiEmbeddingProvider::new(&api_key)?;

// OpenAI
use adk_rag::OpenAIEmbeddingProvider;
let embedder = OpenAIEmbeddingProvider::new(&api_key, "text-embedding-3-small");

También puedes implementar EmbeddingProvider para cualquier servicio de embeddings personalizado.


Backends de almacenamiento vectorial

BackendIndicador de funcionalidadIdeal para
InMemoryVectorStore(predeterminado)Desarrollo, pruebas, conjuntos de datos pequeños
QdrantVectorStoreqdrantProducción con una base de datos vectorial dedicada
LanceDBVectorStorelancedbBase de datos vectorial integrada (no se necesita servidor)
PgVectorStorepgvectorCuando ya usas PostgreSQL
// In-memory (no setup needed)
use adk_rag::InMemoryVectorStore;
let store = InMemoryVectorStore::new();

// Qdrant (requires running Qdrant server)
use adk_rag::QdrantVectorStore;
let store = QdrantVectorStore::new("http://localhost:6334").await?;

// pgvector (requires PostgreSQL with pgvector extension)
use adk_rag::PgVectorStore;
let store = PgVectorStore::new("postgres://user:pass@localhost/db").await?;

Reordenador personalizado

El NoOpReranker predeterminado pasa los resultados sin cambios. Escribe un reordenador personalizado para mejorar la precisión:

use adk_rag::{Reranker, SearchResult};

struct KeywordBoostReranker { boost: f32 }

#[async_trait::async_trait]
impl Reranker for KeywordBoostReranker {
    async fn rerank(
        &self,
        query: &str,
        mut results: Vec<SearchResult>,
    ) -> adk_rag::Result<Vec<SearchResult>> {
        let keywords: Vec<String> = query.split_whitespace()
            .filter(|w| w.len() > 3)
            .map(|w| w.to_lowercase())
            .collect();

        for r in &mut results {
            let text = r.chunk.text.to_lowercase();
            let hits = keywords.iter().filter(|kw| text.contains(kw.as_str())).count();
            r.score += hits as f32 * self.boost;
        }
        results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
        Ok(results)
    }
}

// Add to pipeline
let pipeline = RagPipeline::builder()
    .config(config)
    .embedding_provider(embedder)
    .vector_store(store)
    .chunker(chunker)
    .reranker(Arc::new(KeywordBoostReranker { boost: 0.1 }))
    .build()?;

Varias colecciones

Usa colecciones separadas para diferentes dominios de conocimiento. El agente puede buscar en colecciones específicas o puedes crear varias instancias de RagTool:

// Create collections for different content types
pipeline.create_collection("docs").await?;
pipeline.create_collection("faq").await?;
pipeline.create_collection("changelog").await?;

// Ingest into each
pipeline.ingest("docs", &setup_doc).await?;
pipeline.ingest("faq", &faq_doc).await?;
pipeline.ingest("changelog", &release_doc).await?;

// One tool per collection — the agent picks which to search
let docs_tool = RagTool::new(pipeline.clone(), "docs");
let faq_tool = RagTool::new(pipeline.clone(), "faq");

let agent = LlmAgentBuilder::new("support")
    .instruction("Search 'docs' for how-to questions, 'faq' for common questions.")
    .model(Arc::new(model))
    .tool(Arc::new(docs_tool))
    .tool(Arc::new(faq_tool))
    .build()?;

El agente también puede anular la colección en el momento de la consulta pasando "collection": "faq" en la llamada a la herramienta.


Indicadores de funciones

Incluye únicamente las dependencias que necesites:

FunciónHabilitaDependencia adicional
(predeterminado)Rasgos principales, InMemoryVectorStore, todos los fragmentadoresninguna
geminiGeminiEmbeddingProvideradk-gemini
openaiOpenAIEmbeddingProviderreqwest
qdrantQdrantVectorStoreqdrant-client
lancedbLanceDBVectorStorelancedb, arrow
pgvectorPgVectorStoresqlx
fullTodo lo anteriortodo
# Just core
adk-rag = "2.1.0"

# With Gemini embeddings
adk-rag = { version = "2.1.0", features = ["gemini"] }

# Everything
adk-rag = { version = "2.1.0", features = ["full"] }

Nota: La función lancedb requiere tener protoc instalado. Instálalo con brew install protobuf (macOS) o apt install protobuf-compiler (Ubuntu).


Arquitectura

                        Ingestion
Documents ──→ [Chunker] ──→ [EmbeddingProvider] ──→ [VectorStore]

                          Query
Question ──→ [EmbeddingProvider] ──→ [VectorStore search] ──→ [Reranker] ──→ Results
                                                                               │
                                                                               ▼
                                                                    Agent uses as context

RagPipeline orquesta ambos flujos. RagTool encapsula la canalización como un adk_core::Tool para que los agentes lo invoquen según sea necesario.


Ejecutar ejemplos

cargo adk new rag_agent --template rag
cd rag_agent
cargo run

Prácticas recomendadas

PrácticaMotivo
Usar RecursiveChunker de forma predeterminadaProduce límites de fragmentos naturales
Mantener los fragmentos entre 200 y 500 caracteresEquilibra la precisión y el contexto
Usa incrustaciones reales en producciónLos incrustadores simulados son solo para pruebas
Establece similarity_threshold > 0Filtra el ruido irrelevante
Separa las colecciones por dominioMejora la precisión y permite que los agentes orienten las búsquedas
Usa InMemoryVectorStore solo para desarrolloCambia a Qdrant/pgvector en producción


Anterior: ← Herramientas de UI | Siguiente: Sesiones →

RAG (Generación aumentada mediante recuperación) - Documentación ADK-Rust | ADK-Rust