0Pricing
AI Agents · درس

تصميم حلقة البحث متعددة الخطوات

التخطيط ← البحث ← القراءة ← الاستخراج ← التركيب ← التكرار حتى بلوغ العمق الكافي.

تصميم حلقة البحث متعددة الخطوات درس مجاني في AI Agents على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Agents، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Agents 4 دروس في المجموع.

ما حلقة البحث؟

حلقة البحث هي نمط وكيل ينفّذ فيه LLM عمليات التخطيط والبحث والقراءة والاستخراج وتحديد الفجوات والتكرار إلى أن يتحقق هدف البحث.

على خلاف البحث لمرة واحدة، تتكيف الحلقة بناءً على ما تجده — فتتبع المسارات غير المتوقعة وتتخلى عن الطرق المسدودة.

المرحلة 1: تفكيك السؤال

تتمثل الخطوة الأولى في تقسيم سؤال البحث إلى أسئلة فرعية. ينشئ ذلك خطة بحث موجهة ويمنع الوكيل من التصفح العشوائي بلا هدف.

import openai, json

client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')

def decompose_question(question: str) -> list[str]:
    prompt = (
        f'Break this research question into 3-5 focused sub-questions.\n'
        f'Each sub-question should be independently searchable.\n'
        f'Question: "{question}"\n'
        f'Return JSON: {{"sub_questions": ["..."]}}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': prompt}],
        response_format={'type': 'json_object'}
    )
    return json.loads(resp.choices[0].message.content)['sub_questions']

questions = decompose_question('What are the main causes of inflation in 2024?')
print(questions)

المرحلة 2: البحث على الويب

نفّذ بحثًا على الويب لكل سؤال فرعي. استخدم واجهة بحث API مثل Serper أو Brave أو Bing للحصول على قائمة بعناوين URL ومقتطفات. لا تقرأ جميع الصفحات — أعط الأولوية للمصادر عالية الجودة أولًا.

import requests

SERPER_KEY = 'YOUR_SERPER_API_KEY'

def search_web(query: str, num_results: int = 5) -> list[dict]:
    resp = requests.post(
        'https://google.serper.dev/search',
        headers={'X-API-KEY': SERPER_KEY, 'Content-Type': 'application/json'},
        json={'q': query, 'num': num_results}
    )
    resp.raise_for_status()
    results = resp.json().get('organic', [])
    return [
        {'title': r['title'], 'url': r['link'], 'snippet': r.get('snippet', '')}
        for r in results
    ]

المرحلة 3: قراءة الحقائق واستخراجها

اجلب كل عنوان URL واستخرج الحقائق الأساسية المرتبطة بالسؤال الفرعي. استخدم LLM لقراءة المقال وإنشاء قائمة بالحقائق مع عنوان URL للمصدر.

import httpx
from bs4 import BeautifulSoup

def fetch_text(url: str, max_chars: int = 4000) -> str:
    try:
        resp = httpx.get(url, timeout=10, follow_redirects=True,
                         headers={'User-Agent': 'ResearchAgent/1.0'})
        soup = BeautifulSoup(resp.text, 'html.parser')
        for tag in soup(['script', 'style', 'nav', 'footer']):
            tag.decompose()
        return soup.get_text(separator=' ', strip=True)[:max_chars]
    except Exception:
        return ''

def extract_facts(text: str, question: str, url: str) -> list[dict]:
    prompt = (
        f'Extract key facts from the text that answer: "{question}"\n'
        f'Return JSON: {{"facts": ["fact1", ...]}}\n\n'
        f'TEXT:\n{text[:3000]}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        response_format={'type': 'json_object'}
    )
    facts = json.loads(resp.choices[0].message.content).get('facts', [])
    return [{'fact': f, 'source': url} for f in facts]

المرحلة 4: تحديد فجوات المعرفة

بعد القراءة، اطلب من LLM مراجعة الحقائق المتراكمة وتحديد ما لا يزال مجهولًا. تصبح هذه الفجوات استعلامات البحث في الجولة التالية.

def identify_gaps(original_question: str, facts: list[dict]) -> list[str]:
    fact_text = '\n'.join(f'- {f["fact"]}' for f in facts[:20])
    prompt = (
        f'Original question: "{original_question}"\n'
        f'Facts gathered so far:\n{fact_text}\n\n'
        f'What key aspects are still unanswered? '
        f'Return 0-3 follow-up search queries (0 if research is complete).\n'
        f'JSON: {{"gaps": ["search query 1", ...]}}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': prompt}],
        response_format={'type': 'json_object'}
    )
    return json.loads(resp.choices[0].message.content).get('gaps', [])

شروط الإنهاء

تحتاج الحلقة إلى شرط توقف واضح لتجنب استمرارها إلى ما لا نهاية. أوقفها عندما:

  • لا يتم تحديد أي فجوات جديدة
  • لا تُضاف أي حقائق جديدة في التكرار الأخير
  • يتم بلوغ الحد الأقصى للتكرارات (حد أمان)
  • يتجاوز إجمالي الحقائق حدًا معينًا (عمق كافٍ)
def should_continue(gaps: list[str], new_facts_this_round: int,
                    iteration: int, total_facts: int) -> bool:
    if iteration >= 5:
        return False          # Hard cap: 5 iterations
    if new_facts_this_round == 0:
        return False          # No new information found
    if not gaps:
        return False          # LLM says research is complete
    if total_facts >= 50:
        return False          # Sufficient depth reached
    return True

if __name__ == '__main__':
    print('Continue (has gaps, new facts)?', should_continue(['gap1'], 4, iteration=1, total_facts=10))
    print('Continue (no new facts)?', should_continue(['gap1'], 0, iteration=1, total_facts=10))

إزالة تكرار الحقائق

غالبًا ما تورد مصادر متعددة الحقيقة نفسها. أزل التكرار بطلب دمج الحقائق المتكافئة دلاليًا من LLM، مع الاحتفاظ بالنسخة المدعومة بأفضل مصدر.

def deduplicate_facts(facts: list[dict]) -> list[dict]:
    if len(facts) <= 3:
        return facts
    fact_text = '\n'.join(
        f'{i}: {f["fact"]} (source: {f["source"]})' for i, f in enumerate(facts)
    )
    prompt = (
        f'Remove duplicate or near-duplicate facts. Keep the most informative version.\n'
        f'Return JSON: {{"keep_indices": [0, 1, ...]}}\n\n'
        f'FACTS:\n{fact_text}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        response_format={'type': 'json_object'}
    )
    indices = json.loads(resp.choices[0].message.content).get('keep_indices', [])
    return [facts[i] for i in indices if i < len(facts)]

حلقة البحث الكاملة

اجمع جميع المراحل في دالة واحدة هي research()، لتشغيل الحلقة حتى تحقق شرط الإنهاء.

def research(question: str) -> dict:
    all_facts = []
    iteration = 0

    # Phase 1: Decompose into sub-questions
    queries = decompose_question(question)

    while True:
        new_facts_this_round = 0

        for query in queries:
            results = search_web(query, num_results=3)
            for result in results:
                text = fetch_text(result['url'])
                if not text:
                    continue
                facts = extract_facts(text, query, result['url'])
                all_facts.extend(facts)
                new_facts_this_round += len(facts)

        all_facts = deduplicate_facts(all_facts)
        gaps = identify_gaps(question, all_facts)
        iteration += 1

        if not should_continue(gaps, new_facts_this_round, iteration, len(all_facts)):
            break

        queries = gaps  # next iteration searches for the gaps

    return {'facts': all_facts, 'iterations': iteration}

تتبع مصدر المعلومة

يجب أن تحمل كل حقيقة عنوان URL لمصدرها حتى يتمكن التقرير النهائي من تضمين الاستشهادات. لا تزل بيانات مصدر المعلومة أثناء المعالجة — فهي ضرورية لطبقة الاستشهادات.

def add_fact(fact_list: list, fact_text: str, source_url: str, iteration: int):
    fact_list.append({
        'fact':      fact_text,
        'source':    source_url,
        'iteration': iteration,
        'verified':  False   # set to True after cross-referencing
    })

# Example:
facts_store = []
add_fact(facts_store, 'Global inflation peaked at 9.1% in June 2022',
         'https://bls.gov/news.release/cpi.htm', iteration=1)

print(f'Logged {len(facts_store)} fact(s):')
for f in facts_store:
    print(f"  - {f['fact']} (source: {f['source']})")

البحث المتوازي لزيادة السرعة

البحث التسلسلي بطيء — 5 استعلامات × 3 عناوين URL × عملية جلب واحدة = 15 استدعاء HTTP تسلسليًا. استخدم concurrent.futures لتشغيل عمليات الجلب بالتوازي ضمن كل تكرار.

from concurrent.futures import ThreadPoolExecutor, as_completed

def parallel_research_round(queries: list[str]) -> list[dict]:
    collected = []

    def process_query(query):
        results = search_web(query, num_results=3)
        facts = []
        for r in results:
            text = fetch_text(r['url'])
            if text:
                facts.extend(extract_facts(text, query, r['url']))
        return facts

    with ThreadPoolExecutor(max_workers=4) as ex:
        futures = {ex.submit(process_query, q): q for q in queries}
        for future in as_completed(futures):
            collected.extend(future.result())

    return collected

مراقبة تقدم الحلقة

سجّل كل تكرار حتى تتمكن من تصحيح سبب توقف الحلقة أو استمرارها مدة أطول من المتوقع. ضمّن عدد الاستعلامات والحقائق المضافة والفجوات المحددة.

import logging

log = logging.getLogger('research_loop')
import sys
logging.basicConfig(level=logging.INFO, stream=sys.stdout)

def log_iteration(iteration: int, queries: list[str],
                  new_facts: int, total_facts: int, gaps: list[str]):
    log.info(
        'Iteration %d | Queries: %d | New facts: %d | Total: %d | Gaps: %d',
        iteration, len(queries), new_facts, total_facts, len(gaps)
    )
    if gaps:
        for g in gaps:
            log.debug('  Gap query: %s', g)

if __name__ == '__main__':
    log_iteration(iteration=2, queries=['who are our top competitors?'], new_facts=4, total_facts=12, gaps=['pricing data'])

ما الذي ينهي حلقة البحث عند عدم العثور على معلومات جديدة؟

يساعد فهم شروط الإنهاء على منع الحلقات اللانهائية وضمان تسليم الوكيل للنتائج ضمن إطار زمني معقول.

مراجعة تصميم حلقة البحث

تتبع حلقة البحث متعددة الخطوات التسلسل التالي: التفكيك ← البحث ← القراءة ← الاستخراج ← تحديد الفجوات ← التكرار. أوقفها عندما تصبح الفجوات فارغة، أو لا يتم العثور على حقائق جديدة، أو يتم بلوغ حد صارم للتكرارات.

تتبّع دائمًا عناوين URL للمصادر مع كل حقيقة، وشغّل عمليات البحث بالتوازي لزيادة السرعة، وأزل تكرار الحقائق قبل تركيبها.

الأسئلة الشائعة

هل درس «تصميم حلقة البحث متعددة الخطوات» مجاني؟

نعم — نص درس «تصميم حلقة البحث متعددة الخطوات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Agents، انتقل إلى CoddyKit PRO. تتضمن دورة AI Agents 4 دروس في المجموع.

ماذا ستتعلم في «تصميم حلقة البحث متعددة الخطوات»؟

التخطيط ← البحث ← القراءة ← الاستخراج ← التركيب ← التكرار حتى بلوغ العمق الكافي. تتمرن على AI Agents مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ AI Agents؟

لا تُشترط خبرة سابقة. AI Agents على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «تصميم حلقة البحث متعددة الخطوات»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس AI Agents هذا؟

نعم. كل درس في AI Agents يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. تصميم حلقة البحث متعددة الخطوات
  2. التحقق من المصادر وإضافة الاستشهادات
  3. إنشاء التقارير المنظَّمة
  4. تدقيق الحقائق ومنع الهلوسة
← العودة إلى AI Agents