RAG (Geração Aumentada por Recuperação)
Forneça aos seus agentes uma base de conhecimento para que possam responder a perguntas usando seus próprios dados.
O que é RAG?
RAG permite que seu agente consulte informações relevantes em seus documentos antes de responder a uma pergunta. Em vez de depender apenas daquilo com que o LLM foi treinado, o agente pesquisa seus dados e usa os resultados como contexto.
O fluxo é:
- Ingestão — Os documentos são divididos em partes, convertidos em embeddings vetoriais e armazenados
- Consulta — Uma pergunta é convertida em embedding e comparada às partes armazenadas por similaridade
- Geração — As partes mais relevantes são passadas ao LLM como contexto para sua resposta
Isso significa que seu agente pode responder a perguntas sobre documentação de produtos, políticas da empresa, bases de código ou qualquer texto que você fornecer.
Principais destaques:
- 📄 Ingestão de qualquer texto — documentação de produtos, markdown, código, políticas
- 🔍 Pesquisa semântica — encontre conteúdo relevante pelo significado, não apenas por palavras-chave
- 🤖 Recuperação agêntica — o agente decide quando pesquisar por meio de
RagTool- 🔌 Backends intercambiáveis — troque provedores de embeddings e armazenamentos vetoriais sem alterar o código
Instalação
[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"] }
Etapa 1: Crie um pipeline
Um RagPipeline conecta quatro componentes: um particionador, um provedor de embeddings, um armazenamento vetorial e um 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(())
}
Como funciona:
FixedSizeChunkerdivide o documento em partes de 256 caracteres, com sobreposição de 50 caracteresMockEmbedderconverte cada parte em um vetor de 64 dimensõesInMemoryVectorStorearmazena os vetores e pesquisa usando similaridade de cossenoquery()converte a pergunta em embedding, encontra as partes mais próximas e as retorna classificadas por pontuação
Etapa 2: Adicione RAG a um agente
O verdadeiro poder de RAG aparece quando um agente o utiliza como uma ferramenta. RagTool encapsula o pipeline como um adk_core::Tool — o agente chama rag_search sempre que precisa de informações.
Quando você usa RagTool com agentes baseados no Gemini, ADK normaliza automaticamente o resultado da ferramenta em uma resposta de função compatível com o Gemini. Isso é importante porque rag_search naturalmente retorna uma lista de partes, enquanto o Gemini espera que functionResponse.response seja um objeto JSON na transmissão.
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(())
}
Quando um usuário pergunta "Qual é a sua política de devolução?", o agente:
- Decide que precisa pesquisar na base de conhecimento
- Chama
rag_searchcom{"query": "return policy"} - Recebe as partes relevantes com suas pontuações
- Usa as partes como contexto para gerar uma resposta natural
Etapa 3: Escolha uma estratégia de divisão em partes
A forma como você divide os documentos afeta a qualidade da recuperação. adk-rag fornece três divisores em partes:
| Divisor de blocos | Melhor para | Como divide |
|---|---|---|
FixedSizeChunker | Textos gerais, registros | A cada N caracteres com sobreposição |
RecursiveChunker | Artigos, documentação, comentários de código | Parágrafos → frases → palavras |
MarkdownChunker | Arquivos Markdown, READMEs | Por cabeçalhos, preservando a hierarquia das seções |
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 é a melhor opção padrão — ela tenta primeiro as quebras de parágrafo, depois os limites das frases e, por fim, os limites das palavras, produzindo fragmentos mais naturais do que a divisão em tamanhos fixos.
MarkdownChunker adiciona um campo de metadados header_path a cada fragmento (por exemplo, "Getting Started > Installation"), o que ajuda o agente a citar seções específicas.
Configuração
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 | O que controla | Orientação |
|---|---|---|
chunk_size | Máximo de caracteres por fragmento | 200–500 para a maioria dos casos de uso. Menor = mais preciso, maior = mais contexto |
chunk_overlap | Caracteres compartilhados entre fragmentos adjacentes | 10–20% de chunk_size evita a perda de informações nos limites |
top_k | Número de resultados retornados | Mais resultados = mais contexto para o LLM, mas maior uso de tokens |
similarity_threshold | Pontuação mínima para incluir | 0,0 retorna tudo; 0,3–0,7 filtra correspondências fracas |
Provedores de Embeddings
| Provedor | Sinalizador de recurso | Modelo | Requer |
|---|---|---|---|
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");
Você também pode implementar EmbeddingProvider para qualquer serviço de embeddings personalizado.
Backends de armazenamento vetorial
| Backend | Sinalizador de recurso | Mais adequado para |
|---|---|---|
InMemoryVectorStore | (padrão) | Desenvolvimento, testes, conjuntos de dados pequenos |
QdrantVectorStore | qdrant | Produção com banco de dados vetorial dedicado |
LanceDBVectorStore | lancedb | Banco de dados vetorial integrado (nenhum servidor necessário) |
PgVectorStore | pgvector | Quando você já usa 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?;
Reranqueador personalizado
O NoOpReranker padrão repassa os resultados sem alterações. Escreva um reranqueador personalizado para melhorar a precisão:
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()?;
Várias coleções
Use coleções separadas para diferentes domínios de conhecimento. O agente pode pesquisar coleções específicas ou você pode criar várias instâncias 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()?;
O agente também pode substituir a coleção no momento da consulta, passando "collection": "faq" na chamada da ferramenta.
Sinalizadores de recursos
Inclua apenas as dependências de que você precisa:
| Recurso | Habilita | Dependência extra |
|---|---|---|
| (padrão) | Traits principais, InMemoryVectorStore, todos os chunkers | nenhuma |
gemini | GeminiEmbeddingProvider | adk-gemini |
openai | OpenAIEmbeddingProvider | reqwest |
qdrant | QdrantVectorStore | qdrant-client |
lancedb | LanceDBVectorStore | lancedb, arrow |
pgvector | PgVectorStore | sqlx |
full | Todas as opções acima | todos |
# 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"] }
Observação: O recurso
lancedbrequer queprotocesteja instalado. Instale combrew install protobuf(macOS) ouapt install protobuf-compiler(Ubuntu).
Arquitetura
Ingestion
Documents ──→ [Chunker] ──→ [EmbeddingProvider] ──→ [VectorStore]
Query
Question ──→ [EmbeddingProvider] ──→ [VectorStore search] ──→ [Reranker] ──→ Results
│
▼
Agent uses as context
O RagPipeline orquestra ambos os fluxos. O RagTool encapsula o pipeline como um adk_core::Tool para que os agentes o chamem sob demanda.
Executar exemplos
cargo adk new rag_agent --template rag
cd rag_agent
cargo run
Práticas recomendadas
| Prática | Motivo |
|---|---|
Use RecursiveChunker como padrão | Produz limites naturais para os blocos |
| Mantenha os blocos entre 200 e 500 caracteres | Equilibra precisão e contexto |
| Use embeddings reais em produção | Use geradores de embeddings simulados apenas para testes |
Defina similarity_threshold > 0 | Filtra ruído irrelevante |
| Separe as coleções por domínio | Melhora a precisão e permite que os agentes direcionem as pesquisas |
Use InMemoryVectorStore apenas para desenvolvimento | Mude para Qdrant/pgvector em produção |
Relacionados
- Ferramentas de função - Criação de ferramentas personalizadas
- LlmAgent - Adição de ferramentas a agentes
- Memória - Memória de longo prazo (diferente de RAG)
Anterior: ← Ferramentas de UI | Próximo: Sessões →