RAG (التوليد المعزَّز بالاسترجاع)
امنح وكلاءك قاعدة معرفية حتى يتمكنوا من الإجابة عن الأسئلة باستخدام بياناتك الخاصة.
ما هو RAG؟
يتيح RAG لوكيلك البحث عن المعلومات ذات الصلة في مستنداتك قبل الإجابة عن سؤال. وبدلًا من الاعتماد فقط على ما تم تدريب LLM عليه، يبحث الوكيل في بياناتك ويستخدم النتائج كسياق.
تتم العملية كما يلي:
- الاستيعاب — تُقسَّم المستندات إلى أجزاء، وتُحوَّل إلى تضمينات متجهية، ثم تُخزَّن
- الاستعلام — يُحوَّل السؤال إلى تضمين، وتتم مطابقته مع الأجزاء المخزَّنة استنادًا إلى التشابه
- التوليد — تُمرَّر الأجزاء الأكثر صلة إلى LLM كسياق لإجابته
وهذا يعني أن وكيلك يستطيع الإجابة عن الأسئلة المتعلقة بوثائق المنتجات، وسياسات الشركة، وقواعد التعليمات البرمجية، أو أي نص تقدمه له.
أبرز الميزات:
- 📄 استيعاب أي نص — وثائق المنتجات، وmarkdown، والتعليمات البرمجية، والسياسات
- 🔍 البحث الدلالي — العثور على المحتوى ذي الصلة حسب المعنى، وليس الكلمات المفتاحية فقط
- 🤖 الاسترجاع الوكيلي — يقرر الوكيل متى يبحث عبر
RagTool- 🔌 واجهات خلفية قابلة للتبديل — تبديل موفري التضمينات ومخازن المتجهات دون تغيير التعليمات البرمجية
التثبيت
[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"] }
الخطوة 1: بناء مسار معالجة
يربط RagPipeline أربعة مكونات معًا: مقسّمًا إلى أجزاء، وموفرًا للتضمينات، ومخزنًا للمتجهات، ومعيد ترتيب اختياري.
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(())
}
آلية العمل:
- يقسّم
FixedSizeChunkerالمستند إلى أجزاء مكوّنة من 256 حرفًا مع تداخل يبلغ 50 حرفًا - يحوّل
MockEmbedderكل جزء إلى متجه ذي 64 بُعدًا - يخزّن
InMemoryVectorStoreالمتجهات ويبحث باستخدام تشابه جيب التمام - يُضمّن
query()السؤال، ويعثر على الأجزاء الأقرب، ثم يعيدها مرتبة حسب الدرجة
الخطوة 2: إضافة RAG إلى وكيل
تكمن القوة الحقيقية لـ RAG عندما يستخدمه agent كأداة. يغلّف RagTool خط الأنابيب باعتباره adk_core::Tool — ويستدعي agent rag_search كلما احتاج إلى معلومات.
عند استخدام RagTool مع agents المدعومة من Gemini، يعمل ADK تلقائيًا على تسوية نتيجة الأداة إلى استجابة دالة متوافقة مع Gemini. وهذا مهم لأن rag_search يعيد طبيعيًا قائمة من المقاطع، بينما يتوقع Gemini أن تكون functionResponse.response كائن JSON على مستوى النقل.
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(())
}
عندما يسأل المستخدم «ما سياسة الإرجاع لديكم؟»، يقوم agent بما يلي:
- يقرر أنه يحتاج إلى البحث في قاعدة المعرفة
- يستدعي
rag_searchباستخدام{"query": "return policy"} - يحصل على المقاطع ذات الصلة مع درجاتها
- يستخدم المقاطع كسياق لإنشاء إجابة طبيعية
الخطوة 3: اختيار استراتيجية تقسيم المقاطع
يؤثر تقسيم المستندات في جودة الاسترجاع. يوفر adk-rag ثلاثة مقسّمات للمقاطع:
| المجزئ | الأنسب لـ | كيفية التقسيم |
|---|---|---|
FixedSizeChunker | النصوص العامة، والسجلات | كل N من الأحرف مع تداخل |
RecursiveChunker | المقالات، والوثائق، وتعليقات التعليمات البرمجية | الفقرات ← الجمل ← الكلمات |
MarkdownChunker | ملفات Markdown وREADME | حسب العناوين، مع الحفاظ على التسلسل الهرمي للأقسام |
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 هو الخيار الافتراضي الأفضل — إذ يحاول أولًا استخدام فواصل الفقرات، ثم حدود الجمل، ثم حدود الكلمات، مما ينتج أجزاءً أكثر طبيعية من التقسيم إلى أحجام ثابتة.
يضيف MarkdownChunker حقل بيانات وصفية header_path إلى كل جزء (مثل "Getting Started > Installation")، مما يساعد الوكيل على الاستشهاد بأقسام محددة.
الإعداد
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()?;
| المعلَمة | ما الذي تتحكم فيه | الإرشادات |
|---|---|---|
chunk_size | الحد الأقصى لعدد الأحرف في كل جزء | من 200 إلى 500 لمعظم حالات الاستخدام. الأقل = دقة أكبر، والأكثر = سياق أوسع |
chunk_overlap | الأحرف المشتركة بين الأجزاء المتجاورة | تمنع نسبة 10–20% من chunk_size فقدان المعلومات عند الحدود |
top_k | عدد النتائج المُعادة | نتائج أكثر = سياق أكبر لـ LLM ولكن استخدام أعلى للرموز |
similarity_threshold | الحد الأدنى للدرجة للإدراج | 0.0 يعيد كل شيء؛ 0.3–0.7 يرشّح التطابقات الضعيفة |
موفرو التضمين
| المزوّد | علامة الميزة | النموذج | يتطلّب |
|---|---|---|---|
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");
يمكنك أيضًا تنفيذ EmbeddingProvider لأي خدمة تضمين مخصصة.
الواجهات الخلفية لمخازن المتجهات
| الواجهة الخلفية | علامة الميزة | الأفضل لـ |
|---|---|---|
InMemoryVectorStore | (افتراضي) | التطوير والاختبار ومجموعات البيانات الصغيرة |
QdrantVectorStore | qdrant | بيئات الإنتاج مع قاعدة بيانات متجهات مخصصة |
LanceDBVectorStore | lancedb | قاعدة بيانات متجهات مضمّنة (لا حاجة إلى خادم) |
PgVectorStore | pgvector | عندما تستخدم 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?;
مُعيد ترتيب مخصّص
يمرّر NoOpReranker الافتراضي النتائج دون تغيير. اكتب مُعيد ترتيب مخصّصًا لتحسين الدقة:
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()?;
مجموعات متعددة
استخدم مجموعات منفصلة لمجالات معرفية مختلفة. يمكن للوكيل البحث في مجموعات محددة، أو يمكنك إنشاء مثيلات متعددة من 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()?;
يمكن للوكيل أيضًا تجاوز المجموعة في وقت الاستعلام عن طريق تمرير "collection": "faq" في استدعاء الأداة.
علامات الميزات
لا تسحب سوى التبعيات التي تحتاج إليها:
| الميزة | تُمكّن من | تبعية إضافية |
|---|---|---|
| (الافتراضي) | السمات الأساسية، InMemoryVectorStore، جميع المقسِّمات | لا توجد |
gemini | GeminiEmbeddingProvider | adk-gemini |
openai | OpenAIEmbeddingProvider | reqwest |
qdrant | QdrantVectorStore | qdrant-client |
lancedb | LanceDBVectorStore | lancedb, arrow |
pgvector | PgVectorStore | sqlx |
full | كل ما سبق | الكل |
# 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"] }
ملاحظة: تتطلب ميزة
lancedbتثبيتprotoc. ثبّتها باستخدامbrew install protobuf(macOS) أوapt install protobuf-compiler(Ubuntu).
البنية
Ingestion
Documents ──→ [Chunker] ──→ [EmbeddingProvider] ──→ [VectorStore]
Query
Question ──→ [EmbeddingProvider] ──→ [VectorStore search] ──→ [Reranker] ──→ Results
│
▼
Agent uses as context
ينسّق RagPipeline كلا التدفقين. ويغلّف RagTool خط المعالجة باعتباره adk_core::Tool، لكي تستدعيه الوكلاء عند الطلب.
تشغيل الأمثلة
cargo adk new rag_agent --template rag
cd rag_agent
cargo run
أفضل الممارسات
| الممارسة | السبب |
|---|---|
استخدم RecursiveChunker كإعداد افتراضي | ينتج حدودًا طبيعية للمقاطع |
| حافظ على المقاطع بطول 200–500 حرف | يوازن بين الدقة والسياق |
| استخدم التضمينات الفعلية في بيئة الإنتاج | مولدات التضمين الوهمية مخصصة للاختبار فقط |
اضبط similarity_threshold على قيمة أكبر من 0 | يستبعد الضوضاء غير ذات الصلة |
| افصل المجموعات حسب المجال | يحسّن الدقة ويتيح للوكلاء توجيه عمليات البحث |
استخدم InMemoryVectorStore للتطوير فقط | انتقل إلى Qdrant/pgvector في بيئة الإنتاج |
ذات صلة
- أدوات الدوال - إنشاء أدوات مخصصة
- LlmAgent - إضافة أدوات إلى الوكلاء
- الذاكرة - ذاكرة طويلة الأمد (مختلفة عن RAG)
السابق: ← أدوات واجهة المستخدم | التالي: الجلسات →