18 KiB
راهنمای پیادهسازی RAG در Agno Agent
مقدمه
در این سند، به بررسی مشکل استفاده از گزینه search_knowledge در کلاس Agent فریمورک Agno و راهحل پیشنهادی برای آن میپردازیم. همچنین سیر تکامل پیادهسازی از رویکرد اولیه (Override کردن متدها) به رویکرد فعلی (استفاده از Pre-Hook) را شرح میدهیم.
مشکل استفاده از search_knowledge
توضیح مشکل
وقتی از کلاس Agent در فریمورک Agno استفاده میکنیم، گزینه search_knowledge به مدل اجازه میدهد تا برای پیدا کردن جواب به صورت مستقیم به وکتور دیتابیس متصل شود و اطلاعات را جستجو کند.
# مثال استفاده از search_knowledge
agent = Agent(
model=model,
knowledge=knowledge_base,
search_knowledge=True, # این گزینه ممکن است با همه مدلها سازگار نباشد
# ...
)
چالشها
- سازگاری مدلها: همه مدلهای زبان از ابزارهای جستجو (tools/functions) پشتیبانی نمیکنند
- خطاهای احتمالی: مدلهایی که از ابزارها پشتیبانی نمیکنند ممکن است در مواجه با دستورات جستجو دچار خطا شوند
- کنترل کمتر: مدل به صورت خودکار تصمیم میگیرد چه زمانی جستجو کند
رویکرد اولیه (منسوخ): Override کردن متدهای Agent
توضیح رویکرد قدیمی
در پیادهسازی اولیه، یک کلاس ContextAwareAgent ساخته بودیم که از Agent ارثبری میکرد و متدهای run و print_response را override میکرد:
# ⚠️ رویکرد قدیمی - دیگر استفاده نمیشود
class ContextAwareAgent(Agent):
def run(self, message, **kwargs):
"""Override run to always use RAG pipeline"""
rag_prompt = build_rag_prompt(message)
return super().run(rag_prompt, **kwargs)
def print_response(self, message, **kwargs):
"""Override print_response to always use RAG pipeline"""
rag_prompt = build_rag_prompt(message)
return super().print_response(rag_prompt, **kwargs)
مشکلات رویکرد قدیمی
- خطر خراب شدن رفتار Agent: با override کامل متدهای
runوprint_response، ممکن بود منطق داخلی فریمورک Agno (مثل guardrails، hooks، history management و ...) نادیده گرفته شود یا به شکل غیرمنتظرهای رفتار کند. - وابستگی به ساختار داخلی: هر تغییری در نسخه جدید Agno روی متدهای
runیاprint_responseمیتوانست پیادهسازی ما را خراب کند. - عدم ترکیبپذیری: اضافه کردن منطقهای دیگر (مثل sync config، guardrails و ...) به این کلاس، آن را پیچیده و شکننده میکرد.
رویکرد فعلی: استفاده از Pre-Hook برای تزریق RAG
چرا Pre-Hook؟
فریمورک Agno مکانیزم pre_hooks را فراهم کرده که اجازه میدهد قبل از اجرای اصلی Agent، روی ورودی کاربر تغییراتی اعمال کنیم. با این رویکرد:
- هیچ متدی override نمیشود و رفتار اصلی Agent دستنخورده باقی میماند
- منطق RAG به صورت یک تابع مستقل و قابل تست پیادهسازی میشود
- میتوان چندین hook را به صورت زنجیرهای ترکیب کرد (مثلاً guardrail + config sync + RAG injection)
مزایای این رویکرد نسبت به Override
| ویژگی | Override متدها (قدیمی) | Pre-Hook (فعلی) |
|---|---|---|
| ایمنی رفتار Agent | پایین - ممکن است منطق داخلی خراب شود | بالا - رفتار اصلی حفظ میشود |
| سازگاری با نسخههای جدید Agno | شکننده | مقاوم |
| ترکیبپذیری با سایر منطقها | دشوار | آسان - فقط hook اضافه کنید |
| قابلیت تست | متوسط | بالا - هر hook مستقلاً قابل تست است |
| سازگاری مدلها | بالا | بالا |
| کنترل توسعهدهنده | بالا | بالا |
| قابلیت debug | بالا | بالا |
پیادهسازی فعلی
ساختار فایلها
1. src/utils/hooks.py - هسته اصلی Pre-Hook ها
این فایل حاوی hook های مختلفی است که قبل از اجرای Agent فراخوانی میشوند:
from agno.run.agent import RunInput
from src.utils.search_knowledge import build_rag_prompt
def rag_injection_hook(run_input: RunInput, **kwargs):
"""
Intercepts the user input and injects RAG context.
"""
print("🪝 Hook: Injecting RAG Context...")
# Modify the input content in place
original_input = run_input.input_content
run_input.input_content = build_rag_prompt(original_input)
# Don't return anything - modifications are in-place
نکته مهم: این hook شیء RunInput را در جا (in-place) تغییر میدهد. یعنی input_content اصلی کاربر را با prompt غنیشده از RAG جایگزین میکند، بدون اینکه نیازی به بازگرداندن مقداری باشد.
همچنین یک hook دیگر برای sync کردن تنظیمات agent از دیتابیس وجود دارد:
def sync_config_hook(run_input: RunInput, **kwargs):
"""
Agno Pre-Hook: Fetches the latest Django DB config and
injects it into the agent before the run starts.
"""
agent = kwargs.get("agent")
if not agent:
return
config = get_active_agent_config()
if config and config.get("system_prompts"):
new_prompts = config["system_prompts"]
agent.instructions = new_prompts
return run_input
2. src/utils/search_knowledge.py - منطق RAG و جستجو
این فایل حاوی تابع build_rag_prompt است که مسئولیت جستجوی دانش، reranking نتایج و ساخت prompt را بر عهده دارد:
def build_rag_prompt(user_question: str, embedder_model_name: str = None) -> str:
"""RAG pipeline با Qdrant - استفاده از سیستم embedding جدید"""
global knowledge_base
try:
# Lazy initialization of knowledge base
if knowledge_base is None:
embed_factory = EmbeddingFactory()
embedder = embed_factory.get_embedder(embedder_model_name)
vector_db = get_qdrant_store(embedder=embedder)
knowledge_base = Knowledge(vector_db=vector_db)
# Search for relevant documents
initial_results = knowledge_base.search(query=user_question, max_results=7)
if not initial_results:
context_str = "No information found in database."
else:
# Reranking: ارسال نتایج اولیه به Jina برای انتخاب بهترینها
relevant_docs = rerank_documents(
query=user_question,
documents=initial_results,
top_n=3
)
# ساخت context با اطلاعات منبع و امتیاز
context_parts = []
for doc in relevant_docs:
meta = getattr(doc, "meta_data", {}) or {}
source = meta.get('source', 'Unknown')
score = meta.get('rerank_score', 0)
content = f"[Source: {source} | Relevance: {score:.2f}]\n{doc.content}"
context_parts.append(content)
context_str = "\n\n".join(context_parts)
except Exception as e:
context_str = "Knowledge base temporarily unavailable."
final_prompt = (
"Here is the context from the database:\n"
"---------------------\n"
f"{context_str}\n"
"---------------------\n"
f"User Question: {user_question}"
)
return final_prompt
3. src/agents/base_agent.py - تنظیمات Agent با Pre-Hook
در این فایل، Agent استاندارد Agno با پارامتر pre_hooks ساخته میشود. دیگر از ContextAwareAgent استفاده نمیشود:
from agno.agent import Agent
from src.utils.hooks import sync_config_hook, rag_injection_hook
class IslamicScholarAgent:
def __init__(self, model, knowledge_base, custom_instructions=None, db_url=None):
# ...
self.agent = Agent(
name="Islamic Scholar Agent",
model=model,
instructions=self.custom_instructions,
markdown=True,
search_knowledge=False,
db=PostgresDb(db_url=self.db_url) if self.db_url else None,
add_history_to_context=True,
reasoning=False,
knowledge=None, # داده از طریق pre_hooks تزریق میشود
debug_mode=False,
pre_hooks=[
PromptInjectionGuardrail(),
InputLimitGuardrail(),
sync_config_hook,
rag_injection_hook,
],
)
نکتههای کلیدی:
knowledge=None: چون دادهها از طریقrag_injection_hookبه prompt تزریق میشوند، نیازی به پاس دادن knowledge به Agent نیست.search_knowledge=False: مدل نباید خودش تصمیم بگیرد چه زمانی جستجو کند.pre_hooks=[...]: زنجیرهای از hook ها و guardrail ها که به ترتیب قبل از اجرای Agent اعمال میشوند.
4. src/agents/islamic_scholar_agent.py
کلاس تخصصی که از IslamicScholarAgent پایه ارثبری میکند:
from .base_agent import IslamicScholarAgent as BaseIslamicScholarAgent
class IslamicScholarAgent(BaseIslamicScholarAgent):
"""Specialized Islamic Scholar Agent"""
def __init__(self, model, knowledge_base, custom_instructions=None, db_url=None):
super().__init__(model, knowledge_base, custom_instructions, db_url)
منطق پیادهسازی
1. جریان کاری RAG با Pre-Hook
graph TD
A[سوال کاربر] --> B[pre_hooks اجرا میشوند]
B --> B1[PromptInjectionGuardrail]
B1 --> B2[InputLimitGuardrail]
B2 --> B3[sync_config_hook]
B3 --> B4[rag_injection_hook]
B4 --> C[build_rag_prompt فراخوانی میشود]
C --> D[جستجو در Qdrant - دریافت 7 نتیجه]
D --> E{یافتن نتایج؟}
E -->|بله| F[Reranking با Jina - انتخاب 3 نتیجه برتر]
E -->|خیر| G[پیام عدم وجود اطلاعات]
F --> H[ساخت prompt نهایی با منبع و امتیاز]
G --> H
H --> I[جایگزینی input_content در RunInput]
I --> J[Agent اصلی Agno با prompt غنیشده اجرا میشود]
J --> K[پاسخ مدل بر اساس context]
2. ترتیب اجرای Pre-Hook ها
وقتی کاربر یک سوال ارسال میکند، قبل از رسیدن به مدل، زنجیره زیر اجرا میشود:
PromptInjectionGuardrail: بررسی ورودی برای حملات prompt injectionInputLimitGuardrail: بررسی محدودیت طول ورودیsync_config_hook: دریافت آخرین تنظیمات (system prompts) از دیتابیس Django و اعمال آنهاrag_injection_hook: جستجو در وکتور دیتابیس و تزریق context به سوال کاربر
3. مراحل پیادهسازی RAG
مرحله ۱: Lazy Initialization با EmbeddingFactory
if knowledge_base is None:
embed_factory = EmbeddingFactory()
embedder = embed_factory.get_embedder(embedder_model_name)
vector_db = get_qdrant_store(embedder=embedder)
knowledge_base = Knowledge(vector_db=vector_db)
مرحله ۲: جستجوی اولیه در Qdrant
# دریافت 7 نتیجه اولیه از وکتور دیتابیس
initial_results = knowledge_base.search(query=user_question, max_results=7)
مرحله ۳: Reranking نتایج
# ارسال نتایج اولیه به Jina Reranker برای انتخاب 3 نتیجه برتر
relevant_docs = rerank_documents(
query=user_question,
documents=initial_results,
top_n=3
)
مرحله ۴: ساخت Context با اطلاعات منبع
context_parts = []
for doc in relevant_docs:
meta = getattr(doc, "meta_data", {}) or {}
source = meta.get('source', 'Unknown')
score = meta.get('rerank_score', 0)
content = f"[Source: {source} | Relevance: {score:.2f}]\n{doc.content}"
context_parts.append(content)
context_str = "\n\n".join(context_parts)
مرحله ۵: ساخت Prompt نهایی و تزریق به RunInput
# در build_rag_prompt:
final_prompt = (
"Here is the context from the database:\n"
"---------------------\n"
f"{context_str}\n"
"---------------------\n"
f"User Question: {user_question}"
)
# در rag_injection_hook:
run_input.input_content = build_rag_prompt(original_input)
مدیریت خطاها
استراتژی مدیریت خطا
try:
# منطق جستجو و reranking
initial_results = knowledge_base.search(query=user_question, max_results=7)
relevant_docs = rerank_documents(query=user_question, documents=initial_results, top_n=3)
# ...
except Exception as e:
print(f"⚠️ Knowledge Base error (continuing without RAG): {e}")
context_str = "Knowledge base temporarily unavailable. dont answer the question because you have no related information in the database."
مزایای این رویکرد
- مقاومت در برابر خطا: سیستم با وجود مشکل در Knowledge Base ادامه کار میدهد
- شفافیت: خطاها به صورت log ثبت میشوند
- graceful degradation: در صورت مشکل، پیام مناسبی به مدل ارسال میشود تا از پاسخگویی بدون اطلاعات خودداری کند
مزایای کلی رویکرد Pre-Hook
مقایسه سه رویکرد
| ویژگی | search_knowledge=True |
Override متدها (قدیمی) | Pre-Hook (فعلی) |
|---|---|---|---|
| سازگاری مدلها | محدود | بالا | بالا |
| کنترل توسعهدهنده | کم | بالا | بالا |
| ایمنی رفتار Agent | بالا | پایین | بالا |
| ترکیبپذیری | کم | کم | بالا |
| سازگاری با نسخههای جدید | متوسط | پایین | بالا |
| قابلیت debug | متوسط | بالا | بالا |
| انعطافپذیری | کم | متوسط | بالا |
| مدیریت خطا | خودکار | سفارشی | سفارشی |
مزایای کلیدی
- ایمنی رفتار Agent: هیچ متدی override نمیشود و رفتار اصلی Agno حفظ میشود
- ترکیبپذیری: میتوان چندین hook (guardrails، config sync، RAG) را به صورت زنجیرهای اجرا کرد
- سازگاری جهانی: با همه مدلهای زبان کار میکند
- مقاومت در برابر تغییرات فریمورک: چون از API رسمی Agno (pre_hooks) استفاده میشود، با آپدیتهای آینده سازگار خواهد بود
- قابل تست: هر hook یک تابع مستقل است و به راحتی قابل unit test است
- شفافیت: امکان مشاهده و debug هر مرحله از زنجیره hooks
- Reranking هوشمند: نتایج اولیه از Qdrant با Jina Reranker فیلتر و رتبهبندی میشوند
نکات پیادهسازی
۱. تنظیمات محیطی
اطمینان حاصل کنید که متغیرهای محیطی زیر تنظیم شدهاند:
QDRANT_URL=your_qdrant_url
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_HOST=your_db_host
DB_PORT=your_db_port
DB_NAME=your_db_name
۲. تنظیمات Collection
نام collection در Qdrant به صورت اتوماتیک توسط get_qdrant_store مدیریت میشود.
۳. تنظیمات جستجو و Reranking
max_results=7: تعداد نتایج اولیه از Qdranttop_n=3: تعداد نتایج نهایی پس از reranking با Jina- Reranker نتایج را بر اساس ارتباط معنایی با سوال کاربر رتبهبندی میکند
۴. زبان و Encoding
- اطمینان از سازگاری encoding دادهها
- پشتیبانی از زبانهای مختلف (فارسی، عربی، انگلیسی)
نتیجهگیری
پیادهسازی RAG در این پروژه از سه مرحله تکاملی عبور کرده است:
search_knowledge=True: رویکرد پیشفرض Agno - محدود به مدلهای خاص- Override متدها (
ContextAwareAgent): حل مشکل سازگاری مدلها، اما با خطر خراب شدن رفتار Agent - Pre-Hook (
rag_injection_hook): رویکرد فعلی - ایمن، ترکیبپذیر و سازگار با فریمورک
رویکرد فعلی Pre-Hook بهترین تعادل بین کنترل، ایمنی و انعطافپذیری را فراهم میکند:
- بدون override: رفتار اصلی Agent دستنخورده باقی میماند
- زنجیرهای: Guardrails، Config Sync و RAG Injection همگی به صورت مرتب و قابل مدیریت اجرا میشوند
- Reranking هوشمند: با استفاده از Jina، کیفیت نتایج بازگشتی بهبود یافته
- مقاوم: سیستم در صورت خطا در هر مرحله، به صورت graceful ادامه کار میدهد
این پیادهسازی در فایلهای موجود به خوبی کار میکند و میتواند به عنوان الگوی مناسبی برای پروژههای مشابه استفاده شود.