پروژه نهایی — ساخت یه Research Agent از صفر

قسمت ۱۰ ۲۵ دقیقه

مقدمه: وقتشه یه Agent واقعی و کامل بسازیم

به اپیزود آخر سری خوش اومدی. تا اینجا همه مفاهیم رو یاد گرفتی — از اصول Agent تا ابزار، حافظه، برنامه‌ریزی، Multi-Agent، فریمورک‌ها، امنیت و تست. حالا وقتشه همه اینا رو کنار هم بذاریم و یه پروژه واقعی بسازیم.

می‌خوایم یه Research Agent بسازیم — یه Agent که بتونه وب رو جستجو کنه، اطلاعات جمع‌آوری کنه، خلاصه کنه و یه گزارش کامل بنویسه. از صفر تا آخر.

معماری کلی

Research Agent ما از ۴ بخش اصلی تشکیل شده:

  • Planner: وظیفه تحقیق رو به مراحل تقسیم می‌کنه
  • Searcher: وب رو جستجو می‌کنه و نتایج رو برمی‌گردونه
  • Analyzer: متن‌ها رو می‌خونه و اطلاعات مهم رو استخراج می‌کنه
  • Writer: گزارش نهایی رو می‌نویسه

اینا می‌تونن ۴ تا Agent جدا باشن (Multi-Agent) یا ۴ تا مرحله تو یه Agent. ما با رویکرد تک Agent و چند مرحله پیش می‌ریم — ساده‌تره و برای یادگیری بهتره.

قدم ۱: ابزارها

import httpx
import json
from openai import OpenAI
from datetime import datetime

client = OpenAI()

# ابزار ۱: جستجوی وب
async def search_web(query: str, num_results: int = 5) -> str:
    """جستجو تو وب با SerpAPI (یا Tavily)."""
    # اینجا از Tavily API استفاده می‌کنیم
    # ثبت‌نام: tavily.com — پلن رایگان ۱۰۰۰ جستجو در ماه
    
    async with httpx.AsyncClient() as http:
        resp = await http.post(
            "https://api.tavily.com/search",
            json={
                "api_key": TAVILY_API_KEY,
                "query": query,
                "max_results": num_results,
                "include_raw_content": False,
                "search_depth": "advanced",
            },
            timeout=30,
        )
        data = resp.json()
    
    results = []
    for r in data.get("results", []):
        results.append({
            "title": r["title"],
            "url": r["url"],
            "content": r["content"][:500],
        })
    
    return json.dumps(results, ensure_ascii=False, indent=2)


# ابزار ۲: خواندن محتوای صفحه وب
async def read_webpage(url: str) -> str:
    """محتوای یه صفحه وب رو می‌خونه."""
    async with httpx.AsyncClient() as http:
        try:
            resp = await http.get(
                url, 
                follow_redirects=True, 
                timeout=15,
                headers={
                    "User-Agent": "Mozilla/5.0 Research-Agent"
                }
            )
            
            # استخراج متن اصلی (ساده)
            from html.parser import HTMLParser
            
            class TextExtractor(HTMLParser):
                def __init__(self):
                    super().__init__()
                    self.text = []
                    self.skip = False
                    
                def handle_starttag(self, tag, attrs):
                    if tag in ["script", "style", "nav"]:
                        self.skip = True
                        
                def handle_endtag(self, tag):
                    if tag in ["script", "style", "nav"]:
                        self.skip = False
                        
                def handle_data(self, data):
                    if not self.skip:
                        text = data.strip()
                        if text:
                            self.text.append(text)
            
            extractor = TextExtractor()
            extractor.feed(resp.text)
            full_text = "\n".join(extractor.text)
            
            # محدود کردن طول
            return full_text[:3000]
            
        except Exception as e:
            return f"خطا در خواندن صفحه: {str(e)}"


# ابزار ۳: ذخیره یادداشت
notes = []

def save_note(content: str, source: str = "") -> str:
    """یه یادداشت ذخیره می‌کنه."""
    note = {
        "id": len(notes) + 1,
        "content": content,
        "source": source,
        "timestamp": datetime.now().isoformat(),
    }
    notes.append(note)
    return f"یادداشت #{note['id']} ذخیره شد."


def get_all_notes() -> str:
    """همه یادداشت‌ها رو برمی‌گردونه."""
    if not notes:
        return "هنوز هیچ یادداشتی نداری."
    return json.dumps(notes, ensure_ascii=False, indent=2)


# تعریف ابزارها برای OpenAI
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "search_web",
            "description": "جستجو در اینترنت. برای پیدا کردن اطلاعات جدید استفاده کن.",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "عبارت جستجو (به انگلیسی بهتره)"
                    },
                    "num_results": {
                        "type": "integer",
                        "description": "تعداد نتایج",
                        "default": 5
                    }
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "read_webpage",
            "description": "محتوای یه صفحه وب رو می‌خونه",
            "parameters": {
                "type": "object",
                "properties": {
                    "url": {
                        "type": "string",
                        "description": "آدرس صفحه وب"
                    }
                },
                "required": ["url"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "save_note",
            "description": "یه یادداشت مهم ذخیره کن. اطلاعات کلیدی رو اینجا بنویس.",
            "parameters": {
                "type": "object",
                "properties": {
                    "content": {
                        "type": "string",
                        "description": "محتوای یادداشت"
                    },
                    "source": {
                        "type": "string",
                        "description": "منبع اطلاعات (URL یا عنوان)"
                    }
                },
                "required": ["content"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_all_notes",
            "description": "همه یادداشت‌هایی که تا الان ذخیره کردی رو ببین",
            "parameters": {
                "type": "object",
                "properties": {}
            }
        }
    },
]

# مپ توابع
TOOL_MAP = {
    "search_web": search_web,
    "read_webpage": read_webpage,
    "save_note": save_note,
    "get_all_notes": get_all_notes,
}

قدم ۲: موتور Agent با حلقه ReAct

import asyncio

SYSTEM_PROMPT = """تو یه Research Agent هستی. 
وظیفه‌ات تحقیق درباره موضوعات مختلفه.

روش کارت:
۱. اول یه برنامه تحقیق بنویس (چه سوالاتی باید جواب بدی)
۲. جستجو کن و اطلاعات جمع کن
۳. صفحات مهم رو بخون
۴. نکات کلیدی رو تو یادداشت ذخیره کن
۵. وقتی اطلاعات کافی جمع کردی، گزارش نهایی بنویس

قوانین:
- حداقل ۳ منبع مختلف بررسی کن
- اطلاعات کلیدی رو حتماً تو یادداشت ذخیره کن
- گزارش نهایی باید ساختاریافته باشه (عنوان، مقدمه، بخش‌ها، نتیجه‌گیری)
- گزارش رو به فارسی بنویس
- منابع رو ذکر کن
- وقتی تحقیق تموم شد و گزارش آماده‌ست، پیامت رو با «گزارش نهایی:» شروع کن
"""

MAX_ITERATIONS = 15  # حداکثر تعداد حلقه


async def run_agent(research_topic: str) -> str:
    """Agent رو اجرا می‌کنه تا تحقیق کامل بشه."""
    
    global notes
    notes = []  # ریست یادداشت‌ها
    
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": 
         f"درباره این موضوع تحقیق کن و گزارش بنویس:\n{research_topic}"},
    ]
    
    for iteration in range(MAX_ITERATIONS):
        print(f"\n--- Iteration {iteration + 1} ---")
        
        # فراخوانی LLM
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=TOOLS,
            temperature=0.3,
        )
        
        msg = response.choices[0].message
        
        # اگه ابزار می‌خواد استفاده کنه
        if msg.tool_calls:
            # اضافه کردن پیام assistant
            messages.append({
                "role": "assistant",
                "content": msg.content,
                "tool_calls": [
                    {
                        "id": tc.id,
                        "type": "function",
                        "function": {
                            "name": tc.function.name,
                            "arguments": tc.function.arguments,
                        }
                    }
                    for tc in msg.tool_calls
                ]
            })
            
            # اجرای همه ابزارها
            for tool_call in msg.tool_calls:
                func_name = tool_call.function.name
                func_args = json.loads(
                    tool_call.function.arguments
                )
                
                print(f"  Tool: {func_name}({func_args})")
                
                # اجرای ابزار
                func = TOOL_MAP[func_name]
                if asyncio.iscoroutinefunction(func):
                    result = await func(**func_args)
                else:
                    result = func(**func_args)
                
                print(f"  Result: {str(result)[:100]}...")
                
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": str(result),
                })
        else:
            # پاسخ متنی بدون ابزار
            content = msg.content or ""
            messages.append({
                "role": "assistant", 
                "content": content
            })
            
            # چک کن آیا گزارش نهایی آماده‌ست
            if "گزارش نهایی" in content:
                print("\nتحقیق تموم شد!")
                return content
    
    # اگه به حد حلقه رسیدیم
    return (
        "تحقیق به حداکثر تعداد مراحل رسید. "
        "آخرین وضعیت:\n" + messages[-1].get("content", "")
    )

قدم ۳: نوشتن گزارش ساختاریافته

async def generate_report(
    topic: str, notes: list
) -> str:
    """از یادداشت‌ها یه گزارش حرفه‌ای تولید می‌کنه."""
    
    notes_text = "\n".join(
        f"- [{n['source']}] {n['content']}" 
        for n in notes
    )
    
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": """
            تو یه نویسنده گزارش حرفه‌ای هستی.
            از یادداشت‌های تحقیقاتی، یه گزارش کامل 
            و ساختاریافته بنویس.
            
            ساختار گزارش:
            ۱. عنوان
            ۲. خلاصه اجرایی (۳-۴ جمله)
            ۳. مقدمه
            ۴. یافته‌های اصلی (چند بخش)
            ۵. تحلیل و بررسی
            ۶. نتیجه‌گیری
            ۷. منابع
            
            به فارسی بنویس. لحن حرفه‌ای ولی خوانا.
            """},
            {"role": "user", "content": 
             f"موضوع: {topic}\n\nیادداشت‌ها:\n{notes_text}"},
        ],
        temperature=0.4,
        max_tokens=3000,
    )
    
    return response.choices[0].message.content

قدم ۴: اجرای کامل

async def research(topic: str):
    """فرآیند کامل تحقیق."""
    
    print(f"شروع تحقیق: {topic}")
    print("=" * 60)
    
    # اجرای Agent
    report = await run_agent(topic)
    
    # ذخیره گزارش
    filename = f"report_{datetime.now():%Y%m%d_%H%M}.md"
    with open(filename, "w") as f:
        f.write(report)
    
    print(f"\nگزارش ذخیره شد: {filename}")
    print(f"تعداد یادداشت‌ها: {len(notes)}")
    
    return report

# اجرا
# asyncio.run(research(
#     "تأثیر هوش مصنوعی بر بازار کار ایران در سال ۲۰۲۵"
# ))

قدم ۵: اضافه کردن Guardrails

class ResearchGuardrails:
    """محافظ‌های مخصوص Research Agent."""
    
    MAX_SEARCHES = 10
    MAX_PAGE_READS = 8
    MAX_COST = 0.50  # دلار
    BLOCKED_DOMAINS = [
        "facebook.com", "instagram.com", 
        "tiktok.com",  # شبکه‌های اجتماعی منبع معتبر نیستن
    ]
    
    def __init__(self):
        self.search_count = 0
        self.read_count = 0
        self.estimated_cost = 0.0
    
    def check_search(self, query: str) -> tuple[bool, str]:
        if self.search_count >= self.MAX_SEARCHES:
            return False, "به حداکثر جستجو رسیدی."
        self.search_count += 1
        return True, ""
    
    def check_read(self, url: str) -> tuple[bool, str]:
        if self.read_count >= self.MAX_PAGE_READS:
            return False, "به حداکثر خواندن صفحه رسیدی."
        
        for domain in self.BLOCKED_DOMAINS:
            if domain in url:
                return False, f"دامنه {domain} مجاز نیست."
        
        self.read_count += 1
        return True, ""
    
    def check_cost(self, tokens: int) -> tuple[bool, str]:
        # تخمین هزینه gpt-4o
        cost = tokens * 0.005 / 1000
        self.estimated_cost += cost
        
        if self.estimated_cost >= self.MAX_COST:
            return False, "به حداکثر بودجه رسیدی."
        return True, ""

بهبودهای پیشرفته

این نسخه پایه‌ست. چند ایده برای بهترش کردن:

۱. حافظه بلندمدت: نتایج تحقیقات قبلی رو تو Vector Database ذخیره کن. دفعه بعد که موضوع مشابهی بخوای تحقیق کنی، از نتایج قبلی هم استفاده کنه.

۲. Multi-Agent: به جای یه Agent، ۳ تا Agent جدا بساز — Searcher، Analyzer، Writer — و یه Orchestrator بذار هماهنگشون کنه.

۳. منابع بیشتر: علاوه بر وب، از API های خبری، ویکی‌پدیا، و دیتابیس‌های علمی (مثل arXiv) هم جستجو کنه.

۴. فرمت خروجی: گزارش رو به فرمت PDF، HTML یا حتی اسلاید تبدیل کن.

۵. ارزیابی خودکار: بعد از نوشتن گزارش، یه Agent دیگه کیفیتش رو بررسی کنه و اگه ضعیف بود، دوباره بنویسه.

جمع‌بندی سری — از اپیزود ۱ تا ۱۰

بذار یه خلاصه از کل سری بدم:

اپیزود ۱: Agent چیه و چه فرقی با Chatbot داره — فهمیدیم Agent تصمیم می‌گیره و عمل می‌کنه.

اپیزود ۲: Tool Use — بهش دست و پا دادیم.

اپیزود ۳: RAG و حافظه — بهش مغز دادیم.

اپیزود ۴: Planning — بهش قدرت فکر کردن دادیم.

اپیزود ۵: Multi-Agent — یه تیم از Agent ها ساختیم.

اپیزود ۶: فریمورک‌ها — ابزارهای آماده رو شناختیم.

اپیزود ۷: امنیت — Agent رو امن کردیم.

اپیزود ۸: بات تلگرامی — یه Agent واقعی ساختیم.

اپیزود ۹: تست و دیباگ — یاد گرفتیم مطمئن بشیم کار می‌کنه.

اپیزود ۱۰ (همین): Research Agent — یه پروژه کامل از صفر.

قدم بعدی: بهترین راه یادگیری، ساختنه. یه پروژه شخصی انتخاب کن و Agent بساز. می‌تونه یه دستیار شخصی باشه، یه ابزار تحلیل داده، یه بات تلگرامی، یا هر چیز دیگه‌ای. مهم اینه که شروع کنی.

ممنون که تا اینجا همراه من بودی. امیدوارم این سری بهت کمک کرده باشه مفاهیم Agent رو عمیق بفهمی و عملی پیاده‌سازی کنی. اگه سوالی داری، زیر همین اپیزود بنویس.

نظرات

هنوز نظری ثبت نشده. اولین نفر باشید!

نظر خود را بنویسید