Vai al contenuto

I mattoni di CrewAI

8Il tuo primo crew

Costruisci riga per riga il tuo primo crew, due agenti che scrivono una mini-guida di viaggio, impari il Python che serve per leggerlo, lo esegui con uv e scopri perché il risultato, pur arrivando, contiene informazioni inventate.

Tempo di lettura: 30 minuti

È il momento di far lavorare una squadra vera. In questo capitolo scrivi un 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 con due agenti: un ricercatore di viaggi trova tre cose da fare in una città, un autore di guide le trasforma in un breve testo da leggere. È come una piccola redazione: il cronista raccoglie le notizie, il redattore le mette in bella copia.

Non serve saper programmare. Leggiamo lo Script: Un file di codice che si esegue dall'inizio alla fine con un solo comando, per esempio uv run primo_crew.py. glossario una riga alla volta e impariamo solo il Python: Linguaggio di programmazione molto diffuso, leggibile e usatissimo nell'intelligenza artificiale. CrewAI è scritto in Python e si usa da Python. glossario che serve per capirlo. Alla fine lo eseguiamo davvero e guardiamo con onestà che cosa è uscito: vedrai che una squadra può funzionare perfettamente e consegnare comunque un testo sbagliato.

Prepara la cartella#

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 chiamata primo-crew e mettici dentro il file .env che hai preparato nel capitolo 7. Ti basta una riga MODEL e, se usi un servizio online, la sua chiave:

.env
# Scegli UNA riga MODEL (capitolo 7) e cancella o commenta l'altra.
MODEL=openai/gpt-4.1-mini
# MODEL=ollama/qwen2.5:7b
OPENAI_API_KEY=incolla-qui-la-tua-chiave
CREWAI_DISABLE_TELEMETRY=true

Se usi Ollama, la chiave OpenAI non serve: basta che Ollama sia avviato e che il modello sia scaricato. Poi apri 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, crea nella stessa cartella un file vuoto chiamato primo_crew.py e copia i pezzi di codice man mano che li spieghiamo (in fondo trovi lo script intero).

Lo script, riga per riga#

L'intestazione per uv#

Python
# /// script
# requires-python = ">=3.10,<3.14"
# dependencies = ["crewai[tools]==1.15.21"]
# ///
"""Il primo crew: un ricercatore e un autore scrivono una mini-guida del weekend."""

Le righe che iniziano con # sono Commento (nel codice): Una nota per chi legge il codice, che il computer ignora. In Python inizia con # e arriva fino alla fine della riga. glossario: Python le ignora, servono a chi legge. Queste quattro, però, hanno un compito speciale: uv: Programma gratuito che installa Python e i pacchetti in modo rapido e ordinato. È lo strumento consigliato dalla documentazione di CrewAI. glossario le legge per sapere quale versione di Python usare e quale 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 installare, cioè CrewAI 1.15.21 con i suoi strumenti. Grazie a loro non devi installare niente a mano: se ne occupa uv al primo avvio.

La riga tra tre virgolette è una descrizione dello script. Non fa nulla, ma chi apre il file capisce subito a cosa serve.

Gli import#

Python
import os

from crewai import Agent, Crew, Process, Task

Un programma Python parte quasi vuoto: le cose che servono vanno prese dalle librerie. Questo si chiama 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. import os porta il modulo os (una raccolta di codice già inclusa in Python), che sa leggere le variabili del sistema. La seconda riga prende da CrewAI i quattro mattoni che userai: Agent, Task, Crew e Process.

Questi quattro nomi sono Classe e oggetto: Una classe è uno stampo (per esempio Agent); un oggetto è una cosa concreta creata con quello stampo (il tuo agente ricercatore). glossario: stampi con cui crei oggetti concreti. Agent è lo stampo generico «agente»; il tuo ricercatore sarà un oggetto creato con quello stampo.

Il modello, in una variabile#

Python
# 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")

Una Variabile: Un nome che conserva un valore nel programma: citta = "Arezzo" mette il testo Arezzo nella variabile citta. glossario è un nome che conserva un valore. Il segno = non significa «è uguale»: significa «metti a destra dentro il nome a sinistra». Qui os.getenv("MODEL", ...) legge la Variabile d'ambiente: Un valore con un nome (per esempio OPENAI_API_KEY) che i programmi leggono dal sistema invece che dal codice. Serve a tenere i segreti fuori dai file di codice. glossario MODEL dal file .env; se non la trova, usa il secondo valore, "openai/gpt-4.1-mini". Il risultato finisce nella variabile MODELLO, scritta in maiuscolo per abitudine: indica un valore che non cambia durante il programma.

"MODEL" e "openai/gpt-4.1-mini" sono Stringa: Un testo nel codice, scritto tra virgolette: "Ricercatore". glossario: testi tra virgolette. Le virgolette dicono a Python «questo è testo, non un nome di variabile».

Gli agenti: oggetti e parametri#

Python
# Primo membro della squadra: trova le idee.
ricercatore = Agent(
    role="Ricercatore di viaggi",
    goal="Trovare le 3 cose migliori da fare a {citta} in un weekend",
    backstory="Hai girato l'Italia per vent'anni e conosci i posti che valgono davvero.",
    llm=MODELLO,
    verbose=True,
)

# Secondo membro: trasforma le idee in un testo da leggere.
scrittore = Agent(
    role="Autore di guide brevi",
    goal="Trasformare appunti di viaggio in un testo chiaro e invitante",
    backstory="Scrivi guide tascabili: frasi corte, niente giri di parole.",
    llm=MODELLO,
    verbose=True,
)

Agent(...) usa lo stampo per creare un Agent (in CrewAI): Il membro della squadra. Si definisce con tre testi obbligatori — role, goal, backstory — più modello e strumenti opzionali. glossario concreto, che finisce nella variabile ricercatore. Tra le parentesi ci sono i Parametro: Un valore che passi a una funzione o a una classe tra parentesi, con un nome: Agent(role="...") — qui role è il parametro. glossario, ognuno nella forma nome=valore e separato dagli altri da una virgola:

  • role, goal e backstory sono il mestiere, l'obiettivo e la storia dell'agente: finiscono nel Prompt: Il testo che dai in ingresso a un modello: domanda, istruzioni, esempi. La qualità della risposta dipende molto dalla chiarezza del prompt. glossario e li approfondiamo nel capitolo 9;
  • llm=MODELLO dice quale modello usare: qui non ci sono virgolette, perché passiamo il contenuto della variabile, non la parola «MODELLO»;
  • verbose: Opzione che fa stampare nel terminale ogni passaggio di agenti e task. Utile mentre impari e quando qualcosa non va. glossario=True chiede all'agente di raccontare nel terminale quello che fa. True significa «sì», False «no».

La parola {citta} tra graffe è un Segnaposto {…}: Una parola tra graffe, come {citta}, dentro i testi di agenti e task. Al kickoff(inputs={...}) viene sostituita con il valore vero. glossario: un buco da riempire al momento del via. Così lo stesso crew funziona per qualunque città.

Se parti da zero

Python è pignolo con la punteggiatura. Ogni parentesi aperta va chiusa; ogni parametro finisce con una virgola; le virgolette devono essere quelle dritte " dell'editor, non quelle curve che mette un programma di videoscrittura. I rientri (gli spazi a inizio riga) contano: usa sempre 4 spazi e lascia che l'editor li metta per te. Se qualcosa non va, Python ti indica il numero della riga da guardare.

I task#

Python
# Primo compito, affidato al ricercatore.
ricerca = Task(
    description="Elenca le 3 attività migliori per un weekend a {citta}, con un motivo per ciascuna.",
    expected_output="Un elenco puntato di 3 voci, in italiano.",
    agent=ricercatore,
)

# Secondo compito, affidato allo scrittore: riceve l'elenco del primo e salva il testo in un file.
guida = Task(
    description="Usa l'elenco ricevuto per scrivere una mini-guida del weekend a {citta}.",
    expected_output="Un testo di massimo 120 parole in italiano, con un titolo.",
    agent=scrittore,
    output_file="guida.md",
)

Ogni 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 ha due testi: description dice che cosa fare, expected_output descrive il risultato atteso. Scriviamo sempre «in italiano»: i modelli piccoli, altrimenti, cambiano lingua (lo vedrai tra poco). agent=ricercatore assegna il task all'oggetto creato prima. output_file="guida.md" chiede di salvare il risultato del secondo task in un file nella cartella. Il capitolo 10 è tutto dedicato ai task.

La crew#

Python
# La squadra: chi c'è, che cosa deve fare, in che ordine.
crew = Crew(
    agents=[ricercatore, scrittore],
    tasks=[ricerca, guida],
    process=Process.sequential,
    verbose=True,
)

Le parentesi quadre creano una Lista: Una sequenza ordinata di valori tra parentesi quadre: [ricercatore, scrittore]. glossario: una sequenza ordinata di valori. agents è la lista dei membri, tasks la lista dei compiti, nell'ordine in cui vanno eseguiti. Process.sequential è il Process: La regola con cui la crew esegue i task: sequential (uno dopo l'altro) oppure hierarchical (un manager decide chi fa cosa). glossario sequenziale: prima il primo task, poi il secondo, e il risultato del primo passa al secondo. Il capitolo 11 mostra anche l'altro processo.

Il via: kickoff#

Python
if __name__ == "__main__":
    risultato = crew.kickoff(inputs={"citta": "Arezzo"})
    print("\n=== RISULTATO FINALE ===")
    print(risultato.raw)
    print("\n=== TOKEN USATI ===")
    print(risultato.token_usage)

La riga if __name__ == "__main__": significa «esegui il blocco qui sotto solo quando lanci questo file direttamente». Per ora prendila come una formula fissa; il blocco è quello rientrato di 4 spazi.

crew.kickoff(...) fa partire la squadra. Il punto tra crew e kickoff vuol dire «chiedi all'oggetto crew di fare questa azione»: un'azione di un oggetto si chiama Metodo: Un'azione che un oggetto sa fare, chiamata con il punto e le parentesi: crew.kickoff() chiede all'oggetto crew di partire. glossario, e questo è 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, il calcio d'inizio.

inputs={"citta": "Arezzo"} è un Dizionario: Una raccolta di coppie nome → valore tra parentesi graffe: {"citta": "Arezzo"}. glossario: coppie nome e valore tra graffe. Al via, CrewAI cerca ogni {citta} nei testi di agenti e task e lo sostituisce con Arezzo.

Quando la squadra ha finito, kickoff restituisce un 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, che salviamo nella variabile risultato. Ne usiamo due parti: risultato.raw, il testo finale, e risultato.token_usage, il conto dei Token: Il pezzetto di testo con cui ragiona un modello: una parola corta, un pezzo di parola o un segno di punteggiatura. Secondo le stime di OpenAI, in inglese un token vale in media circa ¾ di parola. I servizi a pagamento contano (e fanno pagare) i token. glossario. print li stampa nel terminale; \n dentro la stringa aggiunge una riga vuota.

Il codice completo#

Ecco lo script intero, pronto da copiare in primo_crew.py.

primo_crew.py
# /// script
# requires-python = ">=3.10,<3.14"
# dependencies = ["crewai[tools]==1.15.21"]
# ///
"""Il primo crew: un ricercatore e un autore scrivono una mini-guida del weekend."""
import os

from crewai import Agent, Crew, Process, Task

# 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")

# Primo membro della squadra: trova le idee.
ricercatore = Agent(
    role="Ricercatore di viaggi",
    goal="Trovare le 3 cose migliori da fare a {citta} in un weekend",
    backstory="Hai girato l'Italia per vent'anni e conosci i posti che valgono davvero.",
    llm=MODELLO,
    verbose=True,
)

# Secondo membro: trasforma le idee in un testo da leggere.
scrittore = Agent(
    role="Autore di guide brevi",
    goal="Trasformare appunti di viaggio in un testo chiaro e invitante",
    backstory="Scrivi guide tascabili: frasi corte, niente giri di parole.",
    llm=MODELLO,
    verbose=True,
)

# Primo compito, affidato al ricercatore.
ricerca = Task(
    description="Elenca le 3 attività migliori per un weekend a {citta}, con un motivo per ciascuna.",
    expected_output="Un elenco puntato di 3 voci, in italiano.",
    agent=ricercatore,
)

# Secondo compito, affidato allo scrittore: riceve l'elenco del primo e salva il testo in un file.
guida = Task(
    description="Usa l'elenco ricevuto per scrivere una mini-guida del weekend a {citta}.",
    expected_output="Un testo di massimo 120 parole in italiano, con un titolo.",
    agent=scrittore,
    output_file="guida.md",
)

# La squadra: chi c'è, che cosa deve fare, in che ordine.
crew = Crew(
    agents=[ricercatore, scrittore],
    tasks=[ricerca, guida],
    process=Process.sequential,
    verbose=True,
)

if __name__ == "__main__":
    risultato = crew.kickoff(inputs={"citta": "Arezzo"})
    print("\n=== RISULTATO FINALE ===")
    print(risultato.raw)
    print("\n=== TOKEN USATI ===")
    print(risultato.token_usage)

Eseguirlo#

Apri il 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 nella cartella primo-crew e lancia questo Comando: Un'istruzione scritta nel terminale, seguita da Invio. Esempio: crewai run. glossario (è identico su Windows, in PowerShell):

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

La prima volta uv scarica Python e CrewAI: può volerci qualche minuto. Poi parte il crew. Con un servizio online bastano pochi secondi; con un modello locale su un computer senza scheda grafica dedicata ci vuole molto di più: la nostra esecuzione con ollama/qwen2.5:7b ha richiesto diversi minuti.

Nel terminale compaiono dei riquadri, stampati grazie a verbose: «Crew Execution Started», poi per ogni task «Task Started», «Agent Started», «Agent Final Answer» con la risposta dell'agente e «Task Completion». Questa è la risposta reale del ricercatore:

Terminale con i riquadri Agent Started e Agent Final Answer del Ricercatore di viaggi, che elenca tre attività per Arezzo
Il ricercatore riceve il task con Arezzo già al posto di {citta} e risponde con tre attività. La riga Finalize è un messaggio tecnico di CrewAI che puoi ignorare. screenshot del 15 settembre 2026, esecuzione reale

Poi tocca all'autore, che riceve quell'elenco e scrive la guida. In fondo lo script stampa il risultato finale e il conto dei token:

Terminale con la risposta finale dell'Autore di guide brevi, il risultato finale e la riga dei token usati: total_tokens 1232, successful_requests 2
L'autore trasforma l'elenco in una mini-guida; sotto, le righe stampate da print con il testo finale e i token. screenshot del 15 settembre 2026, esecuzione reale

La riga dei token si legge così: prompt_tokens=703 sono i token inviati al modello (i testi di agenti e task, più l'elenco passato all'autore), completion_tokens=529 quelli scritti dal modello, total_tokens=1232 la somma. successful_requests=2 dice che in questa esecuzione il modello è stato interrogato due volte, una per agente. Con un servizio a pagamento sono questi i numeri che finiscono in bolletta. Il testo della guida si trova anche nel file guida.md, comparso nella cartella. Alla fine CrewAI può mostrare anche un riquadro «Tracing Preference Saved» sulle registrazioni delle esecuzioni: è solo un avviso.

Che cosa succede quando lanci primo_crew.py Il kickoff riceve la città e la sostituisce nei segnaposto. Il task ricerca del Ricercatore manda un prompt al modello e riceve un elenco. L'elenco passa al task guida dell'Autore, che manda un nuovo prompt al modello e riceve la guida. Alla fine escono il file guida.md e l'oggetto CrewOutput con il testo e i token usati. Il modello (MODELLO) riceve un prompt, restituisce un testo 1 · kickoff inputs={"citta": "Arezzo"} 2 · task ricerca Ricercatore di viaggi {citta} → Arezzo 3 · task guida Autore di guide brevi output_file="guida.md" elenco prompt risposta prompt risposta 4 · risultato CrewOutput: raw, token_usage più il file guida.md il risultato della ricerca entra nel prompt della guida ogni prompt e ogni risposta consuma token
Il percorso completo: il kickoff riempie il segnaposto, ogni task manda un prompt al modello e riceve una risposta, il risultato del ricercatore entra nel prompt dell'autore, e alla fine escono il file e il CrewOutput.
Fatto

Se vedi «Crew Execution Completed», il testo finale e la riga dei token, il tuo primo crew ha funzionato: due agenti, due task, un risultato passato dall'uno all'altro e un file salvato.

Ha funzionato? Sì, ma il testo è inventato#

Il crew ha fatto esattamente quello che doveva: ha sostituito il segnaposto, ha fatto lavorare i due agenti in ordine, l'autore ha usato l'elenco del ricercatore, il file è stato salvato e i token contati. Ma se leggi il contenuto come un turista, la guida è da buttare:

  • il ricercatore parte dal Duomo e nella stessa frase passa alla basilica di San Francesco, che è un'altra chiesa (l'autore poi le fonde in un «Duomo di San Francesco»); attribuisce a Botticelli due affreschi con titoli inventati e non nomina l'opera per cui la basilica è famosa, la Leggenda della Vera Croce di Piero della Francesca;
  • propone una gita a Norcia «a circa 45 minuti»: Norcia è in Umbria, molto più lontana;
  • colloca Castelnuovo Berardenga, in provincia di Siena, «a soli 10 km da Arezzo»;
  • l'autore scrive «Una Giornata» invece di un weekend e infila una parola inglese («afternoon»).

Non è un caso isolato. In una prova precedente con gli stessi testi di agenti e task, lo stesso modello aveva messo ad Arezzo la Fontana Maggiore, che si trova a Perugia. Il modello ancora più piccolo, qwen2.5:3b, ha inventato uno spettacolo chiamato «Ballo a Ballerina» e ha scritto la guida in buona parte in spagnolo:

Terminale con la risposta finale dell'Autore di guide brevi scritta in parte in spagnolo, con attrazioni inventate
La stessa squadra con il modello qwen2.5:3b: frasi in spagnolo e uno spettacolo che non esiste, anche se l'expected_output chiedeva l'italiano. screenshot del 15 settembre 2026, esecuzione reale

Perché succede#

È un'Allucinazione: Quando un modello scrive con sicurezza una cosa falsa o inventata. Succede perché il modello genera testo plausibile, non verifica fatti. glossario, e qui ha tre cause precise:

  1. Il modello è piccolo. Un Modello locale: Un modello che gira sul tuo computer invece che sui server di un'azienda. Privacy e costo zero, ma di solito più lento e meno capace. glossario piccolo come qwen2.5:7b, fatto per girare su un computer normale, ricorda molte meno cose di un grande modello online, e le mescola più facilmente.
  2. Nessuno strumento di ricerca. Il nostro «ricercatore» non ha cercato niente: non ha nessuno 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 per consultare il web. Ha scritto le parole più probabili dopo «cose da fare ad Arezzo», e il testo plausibile non è per forza vero.
  3. Nessuna verifica. Nessuno controlla l'elenco prima che passi all'autore, che si fida e lo rende ancora più convincente. Un errore del primo agente diventa l'errore di tutta la squadra.

Come si rimedia#

  • Un modello migliore. Con MODEL=openai/gpt-4.1-mini o un modello più grande gli errori di questo tipo diminuiscono molto, ma non spariscono: un modello, da solo, non verifica i fatti.
  • Dare gli strumenti. Un ricercatore che cerca sul web e legge le pagine lavora su fonti vere: lo facciamo nel capitolo 12.
  • Dare i dati. Se passi all'agente le informazioni corrette (per esempio un elenco di luoghi controllato da te) e gli chiedi di usare solo quelle, inventa molto meno. Il capitolo 10 mostra come.
  • Controllare il risultato. Controlli automatici che rimandano indietro il lavoro se non va, oppure una persona che approva prima di andare avanti: li vediamo nel capitolo 13 e nel capitolo 10.
Attenzione

Un crew che «finisce senza errori» non ti dice nulla sulla verità di quello che ha scritto. Prima di pubblicare, inviare o usare un risultato per decidere qualcosa, controllalo, soprattutto quando contiene nomi, numeri, date o distanze.

Prova tu: cambia città e aggiungi un revisore

Primo passo. Cambia la città: nell'ultimo blocco scrivi inputs={"citta": "Lecce"} e rilancia. Non devi toccare agenti e task, grazie al segnaposto. Il file guida.md verrà sovrascritto.

Secondo passo. Aggiungi un terzo agente che rilegge la guida. Incolla questo codice dopo il task guida:

Python
# Terzo membro: rilegge la guida prima della consegna.
revisore = Agent(
    role="Revisore di guide turistiche",
    goal="Consegnare una guida su {citta} senza errori di lingua e senza affermazioni dubbie",
    backstory="Hai corretto guide per anni e diffidi di ogni distanza o attribuzione non chiara.",
    llm=MODELLO,
    verbose=True,
)

revisione = Task(
    # Le parentesi tonde uniscono le due righe di testo in un'unica stringa.
    description=(
        "Rileggi la mini-guida su {citta}. Correggi gli errori di lingua e togli le parole straniere. "
        "Elimina le frasi su luoghi che potrebbero non trovarsi a {citta} o nei dintorni."
    ),
    expected_output="La guida corretta in italiano, poi un elenco puntato delle modifiche fatte.",
    agent=revisore,
    output_file="guida_rivista.md",
)

Poi aggiungi il revisore e il nuovo task alle due liste della crew:

Python
crew = Crew(
    agents=[ricercatore, scrittore, revisore],
    tasks=[ricerca, guida, revisione],
    process=Process.sequential,
    verbose=True,
)

Nel processo sequenziale il revisore riceve i risultati dei due task precedenti. Guarda la riga dei token: salgono, perché c'è una chiamata al modello in più con un prompt più lungo. E osserva il limite: il revisore può sistemare la lingua e forse notare incoerenze evidenti, ma usa lo stesso modello che ha inventato i luoghi e non ha strumenti per controllarli. Un revisore così migliora la forma, non garantisce i fatti.

In breve
  • Uno script CrewAI importa le classi Agent, Task, Crew e Process, crea gli oggetti con i loro parametri e li mette insieme in una crew.
  • Il Python minimo: variabili (nome = valore), stringhe tra virgolette, liste tra quadre, dizionari tra graffe, parametri nome=valore, metodi con il punto.
  • verbose=True mostra i passaggi; {citta} è un segnaposto riempito da kickoff(inputs={...}); output_file salva il risultato.
  • kickoff restituisce un CrewOutput: raw è il testo finale, token_usage il conto dei token.
  • Il crew può funzionare e il contenuto essere inventato: modello piccolo, nessuno strumento, nessuna verifica. Si rimedia con modelli migliori, strumenti, dati forniti da te e controlli.

Nel prossimo capitolo apriamo il primo mattone: come scrivere agenti che lavorano bene.

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