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:
- Ingesta — Los documentos se dividen en fragmentos, se convierten en embeddings vectoriales y se almacenan
- Consulta — Una pregunta se convierte en un embedding y se compara con los fragmentos almacenados según su similitud
- 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:
FixedSizeChunkerdivide el documento en fragmentos de 256 caracteres con una superposición de 50 caracteresMockEmbedderconvierte cada fragmento en un vector de 64 dimensionesInMemoryVectorStorealmacena los vectores y realiza búsquedas mediante similitud cosenoquery()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:
- Decide que necesita buscar en la base de conocimientos
- Llama a
rag_searchcon{"query": "return policy"} - Recibe los fragmentos relevantes con sus puntuaciones
- 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:
| Fragmentador | Ideal para | Cómo divide |
|---|---|---|
FixedSizeChunker | Texto general, registros | Cada N caracteres con solapamiento |
RecursiveChunker | Artículos, documentación, comentarios de código | Párrafos → oraciones → palabras |
MarkdownChunker | Archivos Markdown, README | Por 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ámetro | Qué controla | Orientación |
|---|---|---|
chunk_size | Máximo de caracteres por fragmento | 200–500 para la mayoría de los casos de uso. Menos = más precisión, más = más contexto |
chunk_overlap | Caracteres compartidos entre fragmentos adyacentes | El 10–20 % de chunk_size evita perder información en los límites |
top_k | Número de resultados devueltos | Más resultados = más contexto para LLM, pero mayor uso de tokens |
similarity_threshold | Puntuación mínima para incluir | 0.0 devuelve todo; 0.3–0.7 filtra las coincidencias débiles |
Proveedores de embeddings
| Proveedor | Indicador de funcionalidad | Modelo | Requiere |
|---|---|---|---|
GeminiEmbeddingProvider | gemini | gemini-embedding-2 | GOOGLE_API_KEY |
OpenAIEmbeddingProvider | openai | text-embedding-3-small | OPENAI_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
| Backend | Indicador de funcionalidad | Ideal para |
|---|---|---|
InMemoryVectorStore | (predeterminado) | Desarrollo, pruebas, conjuntos de datos pequeños |
QdrantVectorStore | qdrant | Producción con una base de datos vectorial dedicada |
LanceDBVectorStore | lancedb | Base de datos vectorial integrada (no se necesita servidor) |
PgVectorStore | pgvector | Cuando 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ón | Habilita | Dependencia adicional |
|---|---|---|
| (predeterminado) | Rasgos principales, InMemoryVectorStore, todos los fragmentadores | ninguna |
gemini | GeminiEmbeddingProvider | adk-gemini |
openai | OpenAIEmbeddingProvider | reqwest |
qdrant | QdrantVectorStore | qdrant-client |
lancedb | LanceDBVectorStore | lancedb, arrow |
pgvector | PgVectorStore | sqlx |
full | Todo lo anterior | todo |
# 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
lancedbrequiere tenerprotocinstalado. Instálalo conbrew install protobuf(macOS) oapt 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áctica | Motivo |
|---|---|
Usar RecursiveChunker de forma predeterminada | Produce límites de fragmentos naturales |
| Mantener los fragmentos entre 200 y 500 caracteres | Equilibra la precisión y el contexto |
| Usa incrustaciones reales en producción | Los incrustadores simulados son solo para pruebas |
Establece similarity_threshold > 0 | Filtra el ruido irrelevante |
| Separa las colecciones por dominio | Mejora la precisión y permite que los agentes orienten las búsquedas |
Usa InMemoryVectorStore solo para desarrollo | Cambia a Qdrant/pgvector en producción |
Relacionado
- Herramientas de funciones - Creación de herramientas personalizadas
- LlmAgent - Añadir herramientas a los agentes
- Memoria - Memoria a largo plazo (diferente de RAG)
Anterior: ← Herramientas de UI | Siguiente: Sesiones →