Vai al contenuto

10 esempi pratici

19Esempio 1 · Ricerca e report

Una crew di due agenti cerca sul web un argomento, legge le pagine migliori e scrive un report in markdown con le fonti numerate.

Tempo di lettura: 25 minuti

Una piccola società di consulenza energetica riceve ogni settimana richieste come «ci fate il punto sulle comunità energetiche per la giunta di un comune?». Ogni volta qualcuno passa mezza giornata a cercare su Google, aprire articoli, copiare numeri e scrivere due pagine con le fonti. È il lavoro più ripetitivo dell'ufficio, e quello in cui è più facile dimenticare da dove viene un dato.

In questo esempio costruisci una Crew: La squadra: un insieme di agenti e di task, più la regola con cui i task vengono eseguiti (il process). Si avvia con kickoff(). glossario che fa la prima bozza di quel lavoro: un agente cerca e legge, un altro scrive il report. Tu controlli e correggi.

Problema
Preparare in fretta un report affidabile su un argomento, con le fonti citate una per una.
La squadra
Un ricercatore che usa gli strumenti web e un redattore che scrive solo a partire dagli appunti.
Strumenti
SerperDevTool (ricerca su Google) e ScrapeWebsiteTool (lettura delle pagine).
Chiavi e costi
Una chiave per il modello (per esempio OpenAI) e una chiave di Serper, che ha un piano gratuito. Le pagine lette sono lunghe: questo esempio consuma più token degli altri.
Cosa impari di nuovo
Dividere il lavoro tra agenti in base agli strumenti, far conoscere la data all'agente con inject_date, limitare i giri con max_iter, pretendere fonti numerate.

Perché è tra i più usati#

È il primo caso della classifica del corso, con 85,5 punti su 100, ed è primo su entrambi i segnali di GitHub: 1.704 repository con «crewai research» nel nome o nella descrizione e il 28,3% delle 562 applicazioni con etichetta crewai (misure del 15 settembre 2026). Anche fuori da CrewAI la ricerca è in cima: nel sondaggio LangChain del 2024 «ricerca e sintesi» era l'uso più citato degli agenti (58%), e in quello di fine 2025 «ricerca e analisi di dati» è secondo con il 24,4%.

C'è anche un indizio più tecnico: SerperDevTool e ScrapeWebsiteTool, i due strumenti di questo esempio, sono i più presenti nel codice Python pubblico che usa CrewAI (5.144 e 2.828 file). E la crew generata da crewai create crew, che hai visto nel capitolo 14, ha proprio la forma «ricerca, poi report».

Lo schema#

Due agenti, due Task: Un compito preciso da assegnare a un agente. Ha due testi obbligatori: description (cosa fare) ed expected_output (come deve essere il risultato). glossario e un Process: La regola con cui la crew esegue i task: sequential (uno dopo l'altro) oppure hierarchical (un manager decide chi fa cosa). glossario Processo sequenziale: I task si eseguono nell'ordine in cui li scrivi; il risultato di ciascuno passa come contesto ai successivi. È il processo predefinito. glossario. Il ricercatore è l'unico che tocca il web; il redattore riceve i suoi appunti tramite context e non ha strumenti, così non può andare a pescare altro.

Schema della crew di ricerca e report Gli inputs argomento e pubblico entrano in una crew sequenziale. Il ricercatore usa SerperDevTool per cercare su Google e ScrapeWebsiteTool per leggere le pagine, e produce un elenco di fatti con URL. Il redattore riceve quegli appunti come context, senza strumenti, e scrive report.md con le fonti numerate. inputs {argomento} {pubblico} Il web 1. cerca con Serper 2. legge le pagine Crew process = sequential Ricercatore SerperDevTool ScrapeWebsiteTool Redattore nessuno strumento: scrive dagli appunti Task 1 · ricerca 6-10 fatti con URL Task 2 · report context=[ricerca] appunti report.md con le fonti numerate il risultato finale
La crew di ricerca: guarda come solo il ricercatore parla con il web e come il report nasce solo dagli appunti del task 1.

Perché due agenti e non uno? La skill ufficiale di CrewAI sul progetto degli agenti consiglia di partire da un solo agente e di aggiungerne un altro solo se serve davvero, per esempio quando i due lavori richiedono strumenti diversi o un modo di scrivere diverso. Qui succedono entrambe le cose: cercare e leggere pagine è un lavoro con strumenti, scrivere per una giunta comunale è un lavoro di stile. Invece «cercare» e «leggere le pagine» restano nello stesso agente: sono due strumenti usati in fila dalla stessa persona.

Preparazione#

Crea una Cartella di progetto: La cartella del computer che contiene tutti i file di un tuo lavoro. I comandi si lanciano stando "dentro" questa cartella. glossario ricerca-report e dentro un File .env: File di testo nella cartella del progetto con le variabili d'ambiente, una per riga: NOME=valore. Non va mai condiviso né caricato online. glossario come quello del capitolo 7, con in più la chiave di Serper: Servizio online (serper.dev) che restituisce i risultati di ricerca di Google a un programma. Lo usa SerperDevTool; serve la chiave SERPER_API_KEY. glossario.

.env
MODEL=openai/gpt-4.1-mini
OPENAI_API_KEY=sk-...la-tua-chiave...
SERPER_API_KEY=...la-tua-chiave-serper...
CREWAI_DISABLE_TELEMETRY=true
Se parti da zero

Serper è un servizio che fa le ricerche su Google al posto tuo e restituisce i risultati a un programma. Ti registri su serper.dev, copi la chiave dal pannello e la incolli nel file .env dopo SERPER_API_KEY=. Come ogni Chiave API: Una lunga password personale che identifica chi usa un'API e a chi addebitare i costi. Va tenuta segreta: mai pubblicarla o condividerla. glossario, non va mai pubblicata.

Questo esempio non ha bisogno di file di dati: le informazioni arrivano dal web. Proprio per questo, al posto di un modello locale conviene un modello online. Un modello piccolo sul tuo computer spesso sbaglia il modo di chiamare gli strumenti o si perde nelle pagine lunghe: puoi provarlo, ma aspettati risultati deboli.

Il codice, pezzo per pezzo#

Il modello e gli strumenti#

Le prime righe sono le stesse di ogni script del corso: le Dipendenza: Un pacchetto di cui il tuo progetto ha bisogno per funzionare. Le dipendenze si elencano in pyproject.toml o in testa allo script. glossario per uv, gli Import: La riga from ... import ... che rende disponibili in un file Python le parti di una libreria, per esempio Agent e Task di CrewAI. glossario e il modello letto dal file .env. In più importi i due strumenti da crewai_tools.

Python
import os

from crewai import Agent, Crew, Process, Task
from crewai_tools import ScrapeWebsiteTool, SerperDevTool

MODELLO = os.getenv("MODEL", "openai/gpt-4.1-mini")

Il ricercatore#

Il ricercatore ha i due Strumento (tool): Una funzione che l'agente può chiamare per agire fuori dal modello: cercare sul web, leggere un file, interrogare un database, inviare una email. glossario. Tre parametri meritano attenzione.

Python
ricercatore = Agent(
    role="Ricercatore web specializzato in {argomento}",
    goal="Trovare informazioni recenti e verificabili su {argomento}, sempre con l'indirizzo della fonte",
    backstory=(
        "Lavori da anni come documentalista per una società di consulenza. "
        "Preferisci fonti ufficiali e giornali affidabili, e scarti le pagine senza data o senza autore."
    ),
    tools=[SerperDevTool(n_results=5), ScrapeWebsiteTool()],
    llm=MODELLO,
    inject_date=True,  # l'agente conosce la data di oggi: utile per capire cosa è recente
    max_iter=15,       # un tetto ai giri di ragionamento, così una ricerca confusa non costa troppo
    verbose=True,
)
  • SerperDevTool(n_results=5) chiede 5 risultati per ricerca invece dei 10 predefiniti: meno testo da leggere, meno token.
  • inject_date=True aggiunge la data di oggi alle istruzioni. Senza, il modello non sa in che anno siamo e può presentare come «recente» una notizia di anni fa.
  • max_iter=15 abbassa il max_iter: Numero massimo di giri di ragionamento che un agente può fare su un task prima di dare la risposta migliore che ha. In CrewAI 1.15 il valore predefinito è 25. glossario predefinito di 25. Se l'agente non trova quello che cerca, si ferma prima e ti dà la risposta migliore che ha.

Nel role: Il ruolo dell'agente, cioè il suo mestiere: "Analista finanziario senior". Orienta il modo in cui il modello ragiona. glossario e nel goal: L'obiettivo personale dell'agente, con il criterio di qualità: "Trovare i 5 rischi principali con le fonti". glossario c'è il Segnaposto {…}: Una parola tra graffe, come {citta}, dentro i testi di agenti e task. Al kickoff(inputs={...}) viene sostituita con il valore vero. glossario {argomento}: lo stesso agente diventa esperto di qualunque tema gli passi al kickoff.

Il redattore#

Il redattore non ha tools. La backstory: La storia e lo stile dell'agente: esperienza, valori, modo di lavorare. È il suo "carattere" nel prompt. glossario insiste su un punto: non scrivere nulla che non sia negli appunti.

Python
redattore = Agent(
    role="Analista e redattore di report",
    goal="Trasformare appunti di ricerca in un report chiaro e onesto per {pubblico}",
    backstory=(
        "Scrivi report per chi non ha tempo: vai al punto, separi i fatti dalle opinioni "
        "e non scrivi nulla che non sia negli appunti."
    ),
    llm=MODELLO,
    verbose=True,
)

I due task#

Il task di ricerca descrive i passi uno per uno: quante ricerche fare, quante pagine aprire, cosa annotare. Più il compito è preciso, meno l'agente improvvisa. L'expected_output: La descrizione del risultato atteso di un task: formato, lunghezza, struttura. È il criterio con cui l'agente capisce di aver finito. glossario chiede per ogni fatto l'URL della pagina: è la base delle fonti del report.

Python
ricerca = Task(
    description=(
        "Cerca sul web informazioni su {argomento}.\n"
        "1. Fai 2 o 3 ricerche con parole diverse.\n"
        "2. Apri e leggi le 3 o 4 pagine più affidabili (non fermarti ai titoli dei risultati).\n"
        "3. Annota solo fatti verificabili: numeri, date, decisioni, nomi di enti.\n"
        "Non inventare indirizzi: ogni fatto deve venire da una pagina che hai davvero letto."
    ),
    expected_output=(
        "Un elenco in italiano di 6-10 fatti. Per ognuno: il fatto in una frase, "
        "la data se c'è, l'indirizzo (URL) della pagina da cui viene."
    ),
    agent=ricercatore,
)

report = Task(
    description=(
        "Scrivi un report su {argomento} per {pubblico}, usando solo i fatti raccolti dal ricercatore.\n"
        "Accanto a ogni affermazione importante metti il numero della fonte tra parentesi quadre, per esempio [2].\n"
        "Se due fonti non sono d'accordo, dillo."
    ),
    expected_output=(
        "Un report in markdown, in italiano, di 400-700 parole, con: un titolo; una sintesi di 3 righe; "
        "3 o 4 sezioni con un titoletto; una sezione \"Cosa tenere d'occhio\"; "
        "un elenco numerato \"Fonti\" con gli URL usati."
    ),
    agent=redattore,
    context=[ricerca],
    output_file="report.md",
)

Nel processo sequenziale il risultato del primo task passa comunque al secondo. Scrivere context=[ricerca] (il context (di un task): L'elenco di task precedenti il cui risultato viene passato a questo task. Serve quando un task deve leggere il lavoro di uno specifico altro task. glossario del capitolo 10) lo rende esplicito: chi legge il codice capisce subito da dove prende le informazioni il redattore. output_file="report.md" salva il report in un file.

La crew e il kickoff#

La crew mette in fila i due task. Al kickoff(): Il metodo che fa partire il lavoro di una crew, di un agente o di un flow. Il nome viene dal calcio d'inizio. glossario passi l'argomento e il pubblico: cambiandoli ottieni un report diverso senza toccare altro.

Python
crew = Crew(
    agents=[ricercatore, redattore],
    tasks=[ricerca, report],
    process=Process.sequential,
    verbose=True,
)

if __name__ == "__main__":
    risultato = crew.kickoff(inputs={
        "argomento": "le comunità energetiche rinnovabili in Italia",
        "pubblico": "la giunta di un piccolo comune",
    })
    print("\n=== REPORT (salvato anche in report.md) ===")
    print(risultato)  # stampa il testo finale, come risultato.raw

print(risultato) stampa il testo finale del CrewOutput: L'oggetto restituito da crew.kickoff(): contiene il testo finale (raw), l'eventuale output strutturato, i risultati dei singoli task e i token usati. glossario: quando non c'è un output strutturato, è lo stesso testo di risultato.raw.

Il codice completo#

Copia tutto in un file ricerca_report.py dentro la cartella ricerca-report.

ricerca_report.py
# /// script
# requires-python = ">=3.10,<3.14"
# dependencies = ["crewai[tools]==1.15.21"]
# ///
"""Cerca sul web un argomento e scrive un report in markdown con le fonti numerate."""
import os

from crewai import Agent, Crew, Process, Task
from crewai_tools import ScrapeWebsiteTool, SerperDevTool

# Il modello si sceglie nel file .env (capitolo 7). Se MODEL manca, si usa quello scritto qui.
MODELLO = os.getenv("MODEL", "openai/gpt-4.1-mini")

ricercatore = Agent(
    role="Ricercatore web specializzato in {argomento}",
    goal="Trovare informazioni recenti e verificabili su {argomento}, sempre con l'indirizzo della fonte",
    backstory=(
        "Lavori da anni come documentalista per una società di consulenza. "
        "Preferisci fonti ufficiali e giornali affidabili, e scarti le pagine senza data o senza autore."
    ),
    tools=[SerperDevTool(n_results=5), ScrapeWebsiteTool()],
    llm=MODELLO,
    inject_date=True,  # l'agente conosce la data di oggi: utile per capire cosa è recente
    max_iter=15,       # un tetto ai giri di ragionamento, così una ricerca confusa non costa troppo
    verbose=True,
)

redattore = Agent(
    role="Analista e redattore di report",
    goal="Trasformare appunti di ricerca in un report chiaro e onesto per {pubblico}",
    backstory=(
        "Scrivi report per chi non ha tempo: vai al punto, separi i fatti dalle opinioni "
        "e non scrivi nulla che non sia negli appunti."
    ),
    llm=MODELLO,
    verbose=True,
)

ricerca = Task(
    description=(
        "Cerca sul web informazioni su {argomento}.\n"
        "1. Fai 2 o 3 ricerche con parole diverse.\n"
        "2. Apri e leggi le 3 o 4 pagine più affidabili (non fermarti ai titoli dei risultati).\n"
        "3. Annota solo fatti verificabili: numeri, date, decisioni, nomi di enti.\n"
        "Non inventare indirizzi: ogni fatto deve venire da una pagina che hai davvero letto."
    ),
    expected_output=(
        "Un elenco in italiano di 6-10 fatti. Per ognuno: il fatto in una frase, "
        "la data se c'è, l'indirizzo (URL) della pagina da cui viene."
    ),
    agent=ricercatore,
)

report = Task(
    description=(
        "Scrivi un report su {argomento} per {pubblico}, usando solo i fatti raccolti dal ricercatore.\n"
        "Accanto a ogni affermazione importante metti il numero della fonte tra parentesi quadre, per esempio [2].\n"
        "Se due fonti non sono d'accordo, dillo."
    ),
    expected_output=(
        "Un report in markdown, in italiano, di 400-700 parole, con: un titolo; una sintesi di 3 righe; "
        "3 o 4 sezioni con un titoletto; una sezione \"Cosa tenere d'occhio\"; "
        "un elenco numerato \"Fonti\" con gli URL usati."
    ),
    agent=redattore,
    context=[ricerca],
    output_file="report.md",
)

crew = Crew(
    agents=[ricercatore, redattore],
    tasks=[ricerca, report],
    process=Process.sequential,
    verbose=True,
)

if __name__ == "__main__":
    risultato = crew.kickoff(inputs={
        "argomento": "le comunità energetiche rinnovabili in Italia",
        "pubblico": "la giunta di un piccolo comune",
    })
    print("\n=== REPORT (salvato anche in report.md) ===")
    print(risultato)  # stampa il testo finale, come risultato.raw

Eseguirlo#

Dal Terminale: Finestra in cui si scrivono comandi testuali al computer invece di cliccare. Su Windows si chiama PowerShell o Prompt dei comandi, su macOS e Linux Terminale. glossario, dentro la cartella ricerca-report:

Terminale
uv run --env-file .env ricerca_report.py

La prima volta uv scarica CrewAI e gli strumenti: può volerci un minuto. Poi, grazie a verbose=True, vedrai scorrere i riquadri già incontrati nel capitolo 8. Durante il primo task compariranno le chiamate agli strumenti con i loro nomi interni: Search the internet with Serper quando l'agente cerca e Read website content quando apre una pagina, ciascuna con l'indirizzo o le parole usate. È il momento migliore per capire come ragiona l'agente: quali ricerche ha scelto, quali pagine ha aperto e quali ha scartato.

Alla fine trovi il report nel terminale e nel file report.md, che puoi aprire con l'Editor di codice: Programma per scrivere e modificare file di codice, con colori e suggerimenti. Il più usato e gratuito è Visual Studio Code. glossario o con qualunque programma che legge il markdown.

Attenzione

Per questo esempio non mostriamo uno screenshot: il laboratorio del corso gira senza chiavi di servizi esterni, e senza la chiave di Serper l'agente non può cercare. Non ti mostriamo un risultato inventato: l'output lo vedrai sul tuo computer.

Come controllare il report#

Un report con le fonti numerate è facile da verificare, e va verificato. Apri due o tre link dell'elenco «Fonti» e cerca nella pagina il numero o la frase citata. Se un link non esiste o non dice quello che il report gli attribuisce, hai trovato un'Allucinazione: Quando un modello scrive con sicurezza una cosa falsa o inventata. Succede perché il modello genera testo plausibile, non verifica fatti. glossario: succede soprattutto quando il ricercatore non è riuscito ad aprire una pagina e ha «ricordato» il contenuto invece di leggerlo.

Come migliorarlo#

  1. Un controllo automatico sulle fonti. Aggiungi al task report un Guardrail: Un controllo automatico sul risultato di un task. Se il controllo fallisce, l'errore torna all'agente che riprova. glossario (capitolo 13) che conta gli indirizzi http nel testo e rimanda indietro il report se sono meno di tre.
  2. La tua approvazione prima della fine. Con human_input=True sul task report la crew si ferma e ti chiede un commento prima di chiudere: è il Human in the loop: Un punto del processo in cui una persona controlla o approva prima che il lavoro prosegua. In CrewAI: human_input=True o @human_feedback. glossario del capitolo 13.
  3. Fonti in forma di dati. Dai al task di ricerca un output_pydantic con una lista di fatti, ognuno con testo, data e url: potrai controllare gli URL con un programma prima di passarli al redattore.
  4. Più argomenti in una volta. crew.kickoff_for_each(inputs=[...]) esegue la stessa crew su una lista di dizionari, per esempio tre argomenti diversi.
  5. Un vero progetto. Quando il report diventa un lavoro fisso, sposta agenti e task nei file del progetto generato da crewai create crew (capitolo 14): la struttura ricerca-report è già quella.
Attenzione

Le pagine web possono contenere istruzioni. Il testo letto da ScrapeWebsiteTool finisce nel prompt dell'agente: una pagina scritta con cattive intenzioni può provare a dargli ordini. Qui il danno è limitato perché l'agente sa solo cercare e leggere, ma non dare mai a questa crew strumenti che scrivono, inviano email o spendono soldi (vedi il capitolo 17).

Costi. Ogni pagina letta può valere migliaia di token. Tieni bassi n_results e max_iter, e guarda il consumo con risultato.token_usage.

Rispetto dei siti. Leggi solo pagine pubbliche e rispetta le condizioni d'uso dei siti. Un report automatico è una bozza: la responsabilità di quello che pubblichi resta tua.

Prova tu: blocca i report senza fonti

Scrivi una funzione guardrail per il task report che rifiuta il testo se contiene meno di 3 indirizzi che iniziano con http, e collegala al task.

Soluzione. La funzione riceve il risultato del task, conta gli indirizzi e restituisce una coppia: True e il testo se va bene, False e il motivo se no. Il motivo torna all'agente, che riscrive il report.

Python
def controlla_fonti(risultato):
    # conta le parole che iniziano con http (gli indirizzi delle fonti)
    indirizzi = [parola for parola in risultato.raw.split() if "http" in parola]
    if len(indirizzi) < 3:
        return (False, f"Il report cita solo {len(indirizzi)} indirizzi: servono almeno 3 fonti con URL.")
    return (True, risultato.raw)

report = Task(
    description="...come prima...",
    expected_output="...come prima...",
    agent=redattore,
    context=[ricerca],
    output_file="report.md",
    guardrail=controlla_fonti,
    guardrail_max_retries=2,
)

Ricorda che il redattore non può cercare nuove fonti: se il guardrail fallisce spesso, il problema è nel task di ricerca, non nel report.

In breve
  • Ricerca e report è il caso d'uso più diffuso: primo in classifica con 85,5 punti.
  • Due agenti perché servono due mestieri diversi: chi usa gli strumenti web e chi scrive.
  • Descrizioni a passi, fonti obbligatorie e context esplicito rendono il report verificabile.
  • n_results, max_iter e inject_date tengono sotto controllo costi e date.
  • Controlla sempre qualche fonte a mano: il report è una bozza, non una verità.

Nel prossimo esempio le informazioni non arrivano dal web ma dai tuoi documenti: un agente risponde alle domande su un regolamento aziendale citando l'articolo giusto.

Corso indipendente, non affiliato a CrewAI Inc. Contenuti verificati su CrewAI 1.15.21 il 15 settembre 2026.