I mattoni di CrewAI
10I task: descrivere il lavoro e il risultato
Impari a scrivere description ed expected_output precisi, a dividere il lavoro in task con un solo obiettivo, a decidere quali risultati passano da un task all'altro con context e a usare output_file, markdown, human_input e async_execution.
Un 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 è un compito preciso affidato a un agente. Se l'agente è il cuoco, il task è la comanda: dice che piatto preparare e come deve arrivare in sala. Una comanda che dice solo «qualcosa di buono» produce un piatto a caso, anche con il cuoco migliore.
La guida ufficiale di CrewAI è netta: dedica l'80% dello sforzo ai task e il 20% agli agenti. Un task ben scritto migliora anche un agente modesto; un task vago rovina anche l'agente migliore. In questo capitolo impari a scriverli e a collegarli tra loro.
Le due parti obbligatorie#
Ogni task ha due testi obbligatori: description, che dice che cosa fare, ed expected_output, che dice com'è fatto il risultato. A questi si aggiunge quasi sempre agent, cioè chi lo fa.
from crewai import Task
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, # l'agente creato prima (capitolo 9)
)description#
Secondo la guida ufficiale, una buona description contiene cinque cose:
- che cosa fare: l'azione principale;
- come farlo: passi o metodo;
- perché: a che cosa servirà il risultato;
- limiti: cosa escludere, cosa evitare;
- dati di partenza: che cosa ha a disposizione l'agente, spesso con un Segnaposto {…}: Una parola tra graffe, come
{citta}, dentro i testi di agenti e task. Alkickoff(inputs={...})viene sostituita con il valore vero. glossario.
Ecco l'esempio della guida, tradotto e scritto in Python:
ricerca = Task(
description=(
"Fai una ricerca approfondita su {argomento} per l'anno {anno}.\n"
"La ricerca deve:\n"
"1. individuare le 5 tendenze e novità principali;\n"
"2. per ogni tendenza, trovare almeno 2 fonti affidabili;\n"
"3. segnalare controversie o punti di vista opposti;\n"
"4. stimare l'impatto sul settore (alto, medio, basso).\n"
"Considera solo gli sviluppi degli ultimi 6 mesi. "
"NON inserire ipotesi o affermazioni non verificate. "
"Il risultato servirà a un report per {pubblico}."
),
expected_output=(
"Una sintesi strutturata in 5 sezioni, una per tendenza, in italiano. "
"Ogni sezione contiene: nome della tendenza, riassunto di 2-3 paragrafi, "
"fonti citate, impatto (alto/medio/basso) e livello di fiducia."
),
agent=ricercatore,
)La sequenza \n dentro una Stringa: Un testo nel codice, scritto tra virgolette: "Ricercatore". glossario significa «vai a capo». Nota che la descrizione chiede fonti: senza uno strumento di ricerca (capitolo 12) l'agente non può trovarle davvero e rischia di inventarle. Descrivere bene il lavoro non basta se mancano i mezzi per farlo.
expected_output#
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 è il criterio con cui l'agente capisce di aver finito. Specifica formato (elenco, paragrafi, tabella), struttura (sezioni, ordine), lunghezza (parole o numero di voci) e segni di qualità (fonti, livelli di fiducia, campi precisi). Nel corso aggiungiamo sempre «in italiano»: i modelli piccoli, altrimenti, cambiano lingua, come hai visto nel capitolo 8.
| Risultato atteso scritto male | Risultato atteso scritto bene |
|---|---|
Un report di ricerca | Una sintesi strutturata in 5 sezioni, ognuna con: nome della tendenza, riassunto di 2-3 paragrafi, fonti citate e valutazione dell'impatto |
Un'analisi dei dati | Una tabella in Markdown con le colonne: nome della metrica, valore attuale, andamento a 30 giorni, azione consigliata. Almeno 10 metriche. |
Un articolo per il blog | Un articolo tecnico di 1000-1500 parole con: titolo, introduzione, 3-4 sezioni principali con esempi di codice e una conclusione con i prossimi passi |
Specifico non vuol dire impossibile. Se chiedi «esattamente 17 fonti del 2026 su un tema di nicchia», l'agente può girare a vuoto fino a max_iter o inventare per accontentarti. Chiedi cose precise ma raggiungibili, e prova prima con un'esecuzione semplice.
Un task, un obiettivo#
La seconda regola della guida ufficiale: ogni task ha un solo obiettivo. Un «task tuttofare» che chiede di cercare, analizzare, scrivere e correggere produce tutto a metà.
# Da evitare: quattro obiettivi in un task solo.
tuttofare = Task(
description="Fai una ricerca su {argomento}, analizza i risultati, scrivi un articolo e correggi gli errori.",
expected_output="Un articolo rifinito su {argomento}.",
agent=ricercatore,
)Meglio dividerlo in task con un compito ciascuno. Ognuno ha il suo criterio di arrivo e, se qualcosa va storto, sai subito dove.
ricerca = Task(
description="Fai una ricerca su {argomento} e individua i 5 sviluppi principali.",
expected_output="Una sintesi in 5 sezioni sulle tendenze principali, in italiano.",
agent=ricercatore,
)
articolo = Task(
description="Usando i risultati della ricerca, scrivi un articolo su {argomento}.",
expected_output="Un articolo di 1000-1500 parole in italiano con introduzione, sezioni e conclusione.",
agent=scrittore,
)
revisione = Task(
description="Rileggi l'articolo e correggi grammatica, chiarezza e coerenza.",
expected_output="L'articolo corretto in italiano, più una breve nota con l'elenco delle modifiche.",
agent=revisore,
)Un task per obiettivo non significa un agente per task: lo stesso agente può fare più task, come fa il copywriter nello script più avanti.
A chi va il lavoro: agent#
agent dice quale agente esegue il task. Nel processo sequenziale (quello usato finora) è obbligatorio: se manca, CrewAI 1.15.21 si ferma subito con l'errore Sequential process error: Agent is missing in the task. Nel processo gerarchico, invece, i task si lasciano senza agente perché li assegna un manager: lo vediamo nel capitolo 11.
Come i risultati passano da un task all'altro#
Nel primo crew lo scrittore ha ricevuto l'elenco del ricercatore senza che tu scrivessi nulla. Ecco la regola, verificata nel codice di CrewAI 1.15.21:
- senza
context, nel processo sequenziale ogni task riceve i risultati di tutti i task già eseguiti, uniti uno dopo l'altro; - con
context=[...]scegli tu: il task riceve solo i risultati dei task nella lista; - con
context=[](lista vuota) il task non riceve nessun risultato precedente.
Il parametro 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 serve quindi quando le dipendenze non sono una semplice fila: un task che deve leggere solo il primo, oppure due task indipendenti che confluiscono in un terzo.
Lo script qui sotto realizza lo schema di destra per una bottega online di tè (inventata). Il primo task descrive tre tè scelti da un catalogo che gli passiamo noi; il secondo propone gli oggetti dell'email senza leggere il primo; il terzo scrive la newsletter usando entrambi.
# /// script
# requires-python = ">=3.10,<3.14"
# dependencies = ["crewai[tools]==1.15.21"]
# ///
"""Tre task con context esplicito: schede dei tè, oggetti email e newsletter finale."""
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")
esperto = Agent(
role="Esperto di tè di una piccola bottega online",
goal="Descrivere i tè del catalogo in modo corretto e comprensibile a chi non li conosce",
backstory="Assaggi e vendi tè da dodici anni. Non inventi mai caratteristiche che non conosci.",
llm=MODELLO,
)
copywriter = Agent(
role="Copywriter di newsletter per piccoli negozi",
goal="Scrivere email brevi e cordiali che la gente apre e legge fino in fondo",
backstory="Scrivi newsletter da anni per botteghe di quartiere: tono caldo, niente esagerazioni.",
llm=MODELLO,
)
# Task 1: lavora solo sul catalogo che gli passiamo noi con il segnaposto {catalogo}.
schede = Task(
description=(
"Scegli 3 tè adatti all'autunno da questo catalogo: {catalogo}. "
"Per ciascuno scrivi il gusto e come prepararlo. Usa solo i tè del catalogo."
),
expected_output=(
"Un elenco di 3 voci in italiano. Ogni voce: nome del tè, gusto in una frase, "
"temperatura dell'acqua e minuti di infusione."
),
agent=esperto,
)
# Task 2: non ha bisogno delle schede, quindi context=[] (nessun risultato precedente).
oggetti = Task(
description="Proponi 3 oggetti per la newsletter d'autunno del negozio {negozio}.",
expected_output="3 righe in italiano, ciascuna di massimo 60 caratteri, senza emoji.",
agent=copywriter,
context=[],
)
# Task 3: legge esplicitamente i risultati dei task 1 e 2.
newsletter = Task(
description=(
"Scrivi la newsletter d'autunno di {negozio}. Scegli uno degli oggetti proposti "
"e presenta i 3 tè delle schede, senza aggiungere tè o prezzi."
),
expected_output=(
"Una email in italiano di massimo 200 parole: la riga 'Oggetto:', un saluto, "
"un paragrafo per ogni tè e una frase finale di invito."
),
agent=copywriter,
context=[schede, oggetti],
markdown=True, # chiede all'agente di formattare il testo in Markdown
output_file="newsletter.md",
)
crew = Crew(
agents=[esperto, copywriter],
tasks=[schede, oggetti, newsletter],
process=Process.sequential,
verbose=True,
)
if __name__ == "__main__":
risultato = crew.kickoff(
inputs={
"negozio": "Foglie di Quartiere",
"catalogo": "Sencha verde, Assam nero, Rooibos alla vaniglia, Oolong, Camomilla e mela",
}
)
print(risultato.raw)
# Il risultato di ogni singolo task resta disponibile, nell'ordine di esecuzione.
for uscita in risultato.tasks_output:
print("-", uscita.agent, "ha scritto", len(uscita.raw.split()), "parole")Che cosa succede quando lo lanci con uv run --env-file .env newsletter_te.py:
- i segnaposto
{negozio}e{catalogo}vengono sostituiti nei testi di tutti i task; - l'esperto scrive le schede; il copywriter, nel task
oggetti, lavora senza vederle; - nel task
newsletteril copywriter riceve schede e oggetti e scrive l'email, che finisce anche nel filenewsletter.md; - il ciclo
forfinale ripete la stessa riga per ogni elemento dirisultato.tasks_output, la lista dei risultati dei singoli task: stampa chi l'ha scritto e quante parole contiene.
C'è anche una lezione sulle allucinazioni: il primo task riceve il catalogo come dato e ha il divieto di uscirne. Dare i fatti all'agente, invece di chiedergli di ricordarli, è il modo più semplice per ridurre le invenzioni.
Ogni segnaposto usato nei testi deve comparire in inputs. La guida ufficiale dice che un segnaposto mancante resta scritto così com'è nel prompt, ma in CrewAI 1.15.21 il comportamento verificato è diverso: il kickoff si ferma con l'errore Template variable 'negozio' not found in inputs dictionary. Le chiavi in più in inputs, invece, vengono ignorate. Usa le graffe singole {negozio}, non doppie.
Le altre opzioni utili#
output_file#
output_file="newsletter.md" salva il testo del risultato in un file. Puoi indicare anche una sottocartella, per esempio "uscite/newsletter.md": CrewAI la crea da sola, perché il parametro create_directory vale True se non lo cambi.
markdown#
Con markdown=True CrewAI aggiunge al prompt l'istruzione di formattare la risposta in Markdown: Un modo semplice di formattare il testo con simboli: # per i titoli, ** per il grassetto, - per gli elenchi. I file Markdown finiscono in .md. glossario: titoli con #, elenchi con -, grassetto con **. È comodo quando il risultato finisce in un file .md o in una pagina web. Il predefinito è False.
human_input#
Con human_input=True, prima di consegnare la risposta finale l'agente te la mostra nel terminale e aspetta un tuo commento: puoi approvarla o chiedere correzioni. È il modo più semplice di mettere una persona nel mezzo del processo (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), utile per i risultati importanti. Ricorda che lo script resta fermo finché non rispondi.
async_execution#
Di solito la crew aspetta che un task finisca prima di iniziare il successivo. Con async_execution=True il task parte e la crew va avanti senza aspettarlo: è l'Esecuzione asincrona: Far partire un lavoro senza aspettare che finisca prima di passare al successivo. In CrewAI un task con async_execution=True lavora mentre la crew prosegue. glossario. Un task successivo che lo nomina in context aspetterà il suo risultato. Nel nostro esempio, schede e oggetti sono indipendenti e potrebbero lavorare in parallelo:
schede = Task(..., agent=esperto, async_execution=True)
oggetti = Task(..., agent=copywriter, context=[], async_execution=True)
newsletter = Task(..., agent=copywriter, context=[schede, oggetti]) # aspetta entrambiQui i tre puntini stanno per i parametri già visti, non sono codice da copiare. Una regola verificata in 1.15.21: la lista dei task può finire con al massimo un task asincrono, altrimenti la crew non si crea (The crew must end with at most one asynchronous task). Il vantaggio si vede con i modelli online; con un modello locale su un solo computer le richieste possono comunque finire in coda.
tools nel task#
Anche un task può avere la sua lista tools, che per quel task vale al posto degli strumenti dell'agente. Ne parliamo nel capitolo 12.
Prova tu: da comanda vaga a comanda precisa
Riscrivi questo task in modo che rispetti le regole del capitolo: description="Scrivi qualcosa sui tè", expected_output="Un testo". Il lettore è un cliente che non ha mai bevuto tè sfuso. Poi chiediti: è un solo obiettivo?
Una soluzione possibile:
guida_principianti = Task(
description=(
"Scrivi una guida per chi non ha mai preparato tè sfuso, usando solo questi tè: {catalogo}. "
"Spiega quanta foglia usare, a che temperatura e per quanti minuti, e l'errore più comune. "
"Non consigliare tè fuori dal catalogo e non indicare prezzi. "
"La guida sarà stampata e messa nei pacchi spediti."
),
expected_output=(
"Un testo in italiano di 250-300 parole con: un titolo, una breve introduzione, "
"un paragrafo per ogni tè del catalogo e un riquadro finale 'Errore da evitare'."
),
agent=esperto,
markdown=True,
output_file="guida_principianti.md",
)Sì, è un solo obiettivo (la guida). Se volessi anche una versione breve per i social, sarebbe un secondo task con il suo expected_output, magari con context=[guida_principianti].
- Un task ha due testi obbligatori:
description(che cosa, come, perché, limiti, dati) edexpected_output(formato, struttura, lunghezza, qualità, «in italiano»). - Un task, un obiettivo: dividi i «task tuttofare».
- Nel sequenziale ogni task riceve tutti i risultati precedenti; con
context=[...]scegli quali, concontext=[]nessuno. - Dai i dati all'agente con i segnaposto: inventa meno. Ogni segnaposto deve stare in
inputs, altrimenti 1.15.21 si ferma con un errore. output_filesalva il risultato,markdown=Truelo formatta,human_input=Trueti chiede un parere,async_execution=Truelo fa partire senza aspettare.
Nel prossimo capitolo mettiamo insieme agenti e task nella crew e scopriamo il secondo modo di organizzarli: il processo gerarchico, con un manager.
Corso indipendente, non affiliato a CrewAI Inc. Contenuti verificati su CrewAI 1.15.21 il 15 settembre 2026.