Pattern del ciclo di ricerca approfondita
Query → lettura → estrazione → sintesi → nuova query: ricerca multi-hop
Pattern del ciclo di ricerca approfondita è una lezione AI Agents gratuita su CoddyKit. Questa è la lezione 3 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'è il ciclo di ricerca approfondita?
Un ciclo di ricerca approfondita è uno schema di agente in cui l'agente cerca, legge, individua le lacune nelle conoscenze e cerca nuovamente in modo iterativo, finché non ha raccolto informazioni sufficienti per scrivere una risposta completa.
A differenza di una singola ricerca, questo schema riproduce il modo di lavorare di un ricercatore umano: esplorare → imparare → scoprire gli aspetti sconosciuti → approfondire.
Panoramica dell'architettura del ciclo
Il ciclo di ricerca approfondita comprende cinque fasi che si ripetono:
- Query iniziale — eseguire la ricerca con la domanda originale dell'utente
- Leggere i risultati principali — estrarre i fatti chiave dalle 3 fonti migliori
- Individuare le lacune — quali domande importanti restano senza risposta?
- Query successive — generare ricerche mirate per colmare le lacune
- Sintetizzare — combinare tutti i fatti raccolti in una risposta finale
def deep_research(question, max_iterations=3):
all_facts = []
queries_used = [question]
for iteration in range(max_iterations):
results = search_and_rank(queries_used[-1])
facts = extract_facts(results, question)
all_facts.extend(facts)
gaps = identify_knowledge_gaps(question, all_facts)
if not gaps:
print(f'Research complete after {iteration + 1} iterations')
break
follow_up = generate_follow_up_query(gaps)
queries_used.append(follow_up)
print(f'Iteration {iteration + 1}: {follow_up}')
return synthesize_answer(question, all_facts)Fase 1: ricerca iniziale
La prima ricerca utilizza la domanda originale dell'utente. Recuperi 3-5 risultati ed estragga il contenuto testuale grezzo. Questa fase riguarda l'ampiezza: ottenere una panoramica del contesto dell'argomento.
from tavily import TavilyClient
import os
client = TavilyClient(api_key=os.getenv('TAVILY_API_KEY'))
def initial_search(question, n_results=5):
results = client.search(
query=question,
max_results=n_results,
search_depth='advanced' # deeper extraction for research tasks
)
return results.get('results', [])
# Also capture the direct answer if available
def get_direct_answer(question):
results = client.search(query=question, max_results=3)
return results.get('answer', '') # Tavily's synthesized answerFase 2: estrazione dei fatti chiave
Passi i risultati di ricerca grezzi all'LLM e gli chieda di estrarre fatti specifici e verificabili pertinenti alla domanda di ricerca. I fatti devono essere atomici: un'unica affermazione per elemento.
EXTRACT_FACTS_PROMPT = '''You are a research assistant.
From the search results below, extract all factual claims relevant to the question.
Question: {question}
Search results:
{results_text}
Return a JSON list of facts:
["GPT-4 was released in March 2023",
"GPT-4 supports 128k context window",
...]
Only include verifiable facts. No opinions or summaries. JSON:'''
def extract_facts(results, question):
import json
results_text = '\n\n'.join(
f"[{i+1}] {r['title']}\n{r['content'][:600]}"
for i, r in enumerate(results)
)
response = llm_call(EXTRACT_FACTS_PROMPT.format(
question=question, results_text=results_text
))
return json.loads(response)Fase 3: individuazione delle lacune nelle conoscenze
Dopo ogni iterazione della ricerca, chieda all'LLM: considerando ciò che sappiamo finora, quali aspetti importanti della domanda originale non sono ancora stati affrontati?
Queste lacune diventano le query per il ciclo di ricerca successivo.
GAPS_PROMPT = '''You are a research analyst.
Original research question: {question}
Facts gathered so far:
{facts}
What important aspects of the question are still NOT covered by these facts?
List 2-3 specific knowledge gaps as a JSON array of strings.
If the question is fully answered, return an empty array [].
JSON:'''
def identify_knowledge_gaps(question, facts):
import json
facts_text = '\n'.join(f'- {f}' for f in facts)
response = llm_call(GAPS_PROMPT.format(
question=question, facts=facts_text
))
return json.loads(response)Fase 4: generazione delle query successive
Trasformi le lacune nelle conoscenze in query di ricerca efficaci. Una lacuna come «Non conosciamo il modello di prezzo» diventa una query mirata come «prezzo dell'API di GPT-4 per token 2024».
QUERY_GEN_PROMPT = '''Convert these knowledge gaps into specific web search queries.
Original topic: {topic}
Knowledge gaps:
{gaps}
Return a JSON array of search query strings.
Make each query specific and searchable.
JSON:'''
def generate_follow_up_queries(topic, gaps):
import json
gaps_text = '\n'.join(f'- {g}' for g in gaps)
response = llm_call(QUERY_GEN_PROMPT.format(
topic=topic, gaps=gaps_text
))
queries = json.loads(response)
return queries[:3] # max 3 follow-up queries per iterationCriteri di terminazione
Senza criteri di terminazione chiari, il ciclo viene eseguito all'infinito. Si interrompa quando: (1) non sono state individuate lacune, (2) è stato raggiunto il numero massimo di iterazioni, (3) il budget di token è esaurito oppure (4) i nuovi fatti sono troppo simili a quelli già esistenti (rendimenti decrescenti).
MAX_ITERATIONS = 4
MAX_FACTS = 50
MIN_NEW_FACTS_TO_CONTINUE = 3
def should_continue(gaps, all_facts, new_facts, iteration):
if iteration >= MAX_ITERATIONS:
print(f'Stopping: max iterations ({MAX_ITERATIONS}) reached')
return False
if not gaps:
print('Stopping: no knowledge gaps found')
return False
if len(all_facts) >= MAX_FACTS:
print(f'Stopping: fact budget ({MAX_FACTS}) reached')
return False
if len(new_facts) < MIN_NEW_FACTS_TO_CONTINUE:
print(f'Stopping: diminishing returns ({len(new_facts)} new facts)')
return False
return True
if __name__ == '__main__':
print('Continue?', should_continue(gaps=['gap1'], all_facts=['f'] * 10, new_facts=['f1', 'f2', 'f3'], iteration=1))
print('Continue?', should_continue(gaps=[], all_facts=['f'] * 10, new_facts=['f1'], iteration=1))
Fase 5: sintesi della risposta finale
Al termine del ciclo, passi tutti i fatti raccolti all'LLM per la sintesi. Il compito dell'LLM è organizzare i fatti in una risposta coerente e ben strutturata, con l'attribuzione delle fonti.
SYNTHESIS_PROMPT = '''You are a research writer.
Original question: {question}
Researched facts (gathered from {n_iterations} search iterations):
{facts}
Write a comprehensive, well-structured answer based on these facts.
Format with clear sections. Be specific and cite facts where relevant.
If any aspect of the question could not be fully answered, say so.
Answer:'''
def synthesize_answer(question, all_facts, n_iterations):
facts_text = '\n'.join(f'- {f}' for f in all_facts)
return llm_call(SYNTHESIS_PROMPT.format(
question=question,
facts=facts_text,
n_iterations=n_iterations
))Tracciamento delle fonti tra le iterazioni
In un ciclo con più iterazioni, raccoglie fatti da molte fonti diverse. Tenga traccia della fonte da cui proviene ciascun fatto, così la risposta finale potrà citarle correttamente.
def extract_facts_with_sources(results, question):
import json
PROMPT = '''Extract facts from these search results. For each fact include the source URL.
Question: {question}
Results: {results_text}
Return JSON list:
[{{"fact": "GPT-4 supports 128k context", "source": "https://openai.com/..."}}, ...]
JSON:'''
results_text = '\n\n'.join(
f"[URL: {r['url']}]\n{r['content'][:500]}"
for r in results
)
response = llm_call(PROMPT.format(
question=question, results_text=results_text
))
return json.loads(response)Esecuzione parallela delle ricerche
Quando dispone di più query successive, le esegua in parallelo utilizzando asyncio per ridurre il tempo totale di ricerca. In questo modo un ciclo di 3 iterazioni può passare da 30 a 15 secondi.
import asyncio
async def async_search(client, query):
# Tavily has async support
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(
None,
lambda: client.search(query=query, max_results=3)
)
return result.get('results', [])
async def parallel_search(queries):
tasks = [async_search(client, q) for q in queries]
results_list = await asyncio.gather(*tasks)
# Flatten
return [r for results in results_list for r in results]
# Run in event loop
all_results = asyncio.run(parallel_search(follow_up_queries))Deduplicazione dei fatti
Fonti diverse spesso esprimono lo stesso fatto con parole differenti. Prima della sintesi, deduplichi i fatti utilizzando la similarità tra embedding per evitare di ripetere più volte le stesse informazioni nella risposta finale.
def deduplicate_facts(facts, similarity_threshold=0.92):
if not facts:
return facts
# Get embeddings for all facts
embeddings = [embed(f) for f in facts]
unique_indices = [0] # always keep first
for i in range(1, len(facts)):
is_duplicate = False
for j in unique_indices:
sim = cosine_similarity(embeddings[i], embeddings[j])
if sim > similarity_threshold:
is_duplicate = True
break
if not is_duplicate:
unique_indices.append(i)
unique_facts = [facts[i] for i in unique_indices]
print(f'Deduplicated: {len(facts)} -> {len(unique_facts)} facts')
return unique_factsVerifica delle conoscenze
Qual è la condizione di terminazione corretta per un ciclo di ricerca approfondita quando non vengono trovate lacune nelle conoscenze?
Riepilogo: schema del ciclo di ricerca approfondita
Lo schema del ciclo di ricerca approfondita è: cercare → estrarre i fatti → individuare le lacune → generare query successive → ripetere → sintetizzare. Questo schema rispecchia il modo di lavorare di un ricercatore umano.
Decisioni progettuali fondamentali: criteri di terminazione (numero massimo di iterazioni, assenza di lacune, rendimenti decrescenti), tracciamento delle fonti per l'attribuzione, ricerche parallele per aumentare la velocità e deduplicazione dei fatti prima della sintesi. Limiti il ciclo a 3-4 iterazioni per bilanciare profondità, costi e latenza.
Domande Frequenti
La lezione «Pattern del ciclo di ricerca approfondita» è gratuita?
Sì — il testo completo di «Pattern del ciclo di ricerca approfondita» è 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 «Pattern del ciclo di ricerca approfondita»?
Query → lettura → estrazione → sintesi → nuova query: ricerca multi-hop 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 3 di 4.
Quanto tempo richiede la lezione «Pattern del ciclo di ricerca approfondita»?
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
- Tavily e SerpAPI per la ricerca degli agenti
- Classificazione e filtraggio dei risultati di ricerca
- Pattern del ciclo di ricerca approfondita
- Combinazione della ricerca web con RAG