0Pricing
AI Agents · Lezione

Progettazione di cicli di ricerca multi-passaggio

Pianificare → cercare → leggere → estrarre → sintetizzare → ripetere fino a raggiungere una profondità sufficiente.

Progettazione di cicli di ricerca multi-passaggio è una lezione AI Agents gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Agents, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Agents include 4 lezioni in totale.

Che cos'è un ciclo di ricerca?

Un ciclo di ricerca è uno schema agentico in cui un LLM pianifica, cerca, legge, estrae, individua le lacune e ripete il processo finché l'obiettivo della ricerca non è stato raggiunto.

A differenza di una ricerca eseguita una sola volta, il ciclo si adatta in base ai risultati ottenuti: segue piste inattese e abbandona quelle senza sbocchi.

Fase 1: scomposizione della domanda

Il primo passaggio consiste nel suddividere la domanda di ricerca in sotto-domande. In questo modo si crea un piano di ricerca mirato e si impedisce all'agente di navigare senza una direzione.

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)

Fase 2: ricerca sul Web

Per ogni sotto-domanda, esegua una ricerca sul Web. Utilizzi un'API di ricerca (Serper, Brave, Bing) per ottenere un elenco di URL e snippet. Non legga tutte le pagine: dia prima la priorità alle fonti di alta qualità.

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
    ]

Fase 3: lettura ed estrazione dei fatti

Recuperi ogni URL ed estragga i fatti principali pertinenti alla sotto-domanda. Utilizzi un LLM per leggere l'articolo e produrre un elenco di fatti con l'URL della fonte.

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]

Fase 4: individuazione delle lacune informative

Dopo la lettura, chieda all'LLM di esaminare i fatti raccolti e individuare ciò che ancora non è noto. Queste lacune diventano le query di ricerca del ciclo successivo.

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', [])

Condizioni di terminazione

Il ciclo deve avere una condizione di arresto chiara, per evitare che venga eseguito all'infinito. Lo interrompa quando:

  • non vengono individuate nuove lacune
  • nell'ultima iterazione non sono stati aggiunti nuovi fatti
  • è stato raggiunto il numero massimo di iterazioni, il limite di sicurezza
  • il numero totale di fatti supera una soglia, indicando una profondità sufficiente
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))

Eliminazione dei duplicati tra i fatti

Spesso più fonti riportano lo stesso fatto. Elimini i duplicati chiedendo all'LLM di unire i fatti semanticamente equivalenti e conservando la versione supportata dalle fonti migliori.

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)]

Il ciclo di ricerca completo

Combini tutte le fasi in un'unica funzione research() che gestisca il ciclo fino alla terminazione.

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}

Tracciamento della provenienza

Ogni fatto deve contenere l'URL della fonte, affinché il report finale possa includere le citazioni. Non elimini mai i metadati della fonte durante l'elaborazione: sono necessari per il livello delle citazioni.

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']})")

Ricerca parallela per aumentare la velocità

Le ricerche sequenziali sono lente: 5 query × 3 URL × 1 recupero ciascuna equivalgono a 15 chiamate HTTP sequenziali. Utilizzi concurrent.futures per parallelizzare i recuperi all'interno di ogni iterazione.

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

Monitoraggio dell'avanzamento del ciclo

Registri ogni iterazione per poter analizzare il motivo per cui il ciclo si è interrotto o è durato più del previsto. Includa il numero di query, i fatti aggiunti e le lacune individuate.

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'])

Che cosa termina il ciclo di ricerca quando non vengono trovate nuove informazioni?

Comprendere le condizioni di terminazione evita i cicli infiniti e assicura che l'agente fornisca i risultati entro un intervallo di tempo ragionevole.

Riepilogo della progettazione del ciclo di ricerca

Il ciclo di ricerca in più fasi segue questo schema: scomporre → cercare → leggere → estrarre → individuare le lacune → ripetere. Lo interrompa quando non ci sono più lacune, non vengono trovati nuovi fatti oppure viene raggiunto un limite rigido di iterazioni.

Tracci sempre gli URL delle fonti insieme a ogni fatto, parallelizzi le ricerche per aumentare la velocità ed elimini i duplicati prima della sintesi.

Domande Frequenti

La lezione «Progettazione di cicli di ricerca multi-passaggio» è gratuita?

Sì — il testo completo di «Progettazione di cicli di ricerca multi-passaggio» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Agents, passa a CoddyKit PRO. Il corso AI Agents include 4 lezioni in totale.

Cosa imparerò in «Progettazione di cicli di ricerca multi-passaggio»?

Pianificare → cercare → leggere → estrarre → sintetizzare → ripetere fino a raggiungere una profondità sufficiente. Eserciti AI Agents con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare AI Agents?

Non è richiesta alcuna esperienza precedente. AI Agents su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Progettazione di cicli di ricerca multi-passaggio»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione AI Agents?

Sì. Ogni lezione AI Agents include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Progettazione di cicli di ricerca multi-passaggio
  2. Verifica delle fonti e citazioni
  3. Generazione di report strutturati
  4. Verifica dei fatti e prevenzione delle allucinazioni
← Torna a AI Agents