Vai al contenuto

10 esempi pratici

28Esempio 10 · Selezione del personale

Due agenti leggono un annuncio e dei CV inventati e preparano, per ogni candidato, una scheda requisito per requisito. Il punteggio lo calcola il codice, la decisione resta a una persona. Con i rischi di discriminazione e le regole europee da conoscere prima di usarlo davvero.

Tempo di lettura: 30 minuti

Un piccolo studio contabile pubblica un annuncio e riceve ottanta CV in una settimana. Chi si occupa della selezione deve leggerli tutti, controllare i requisiti e decidere chi chiamare al colloquio. È un lavoro lungo, ripetitivo e facile da sbagliare quando si è stanchi. Per questo molti provano ad affidarne una parte agli agenti.

È anche l'esempio più delicato del corso. Una valutazione sbagliata qui non produce un post brutto: può escludere una persona da un lavoro. Per questo l'esempio è costruito con tre scelte precise. Gli agenti non vedono i dati anagrafici. Gli agenti non danno voti: dicono quali requisiti sono soddisfatti e perché. Il punteggio lo calcola il codice con una formula che chiunque può leggere. E alla fine c'è sempre una persona che decide.

Problema
Confrontare ogni CV con i requisiti di un annuncio e preparare una scheda chiara per chi seleziona, senza farsi influenzare da caratteristiche personali.
La squadra (agenti)
Analista di annunci (estrae i requisiti) e selezionatore (confronta un CV con i requisiti).
Strumenti
Nessuno strumento esterno: i file li legge il codice Python. Due modelli Pydantic, un guardrail, una callback.
Chiavi e costi
Solo la chiave del modello, oppure nessuna con un modello locale (consigliato con dati veri). La crew gira una volta per ogni CV: il costo cresce con il numero di candidati.
Cosa impari di nuovo
La callback di un task per raccogliere i risultati di kickoff_for_each (capitolo 11), un punteggio calcolato dal codice invece che dal modello, e come ridurre (non eliminare) i rischi di discriminazione.

Perché è tra i più usati#

La selezione del personale è al 10° posto nella classifica del corso, con 26,2 punti. È il posto più conteso: con pesi diversi dei segnali, al suo posto entra la preparazione delle riunioni. I dati principali, raccolti il 15 settembre 2026:

  • Tre crew ufficiali. Il repository crewAI-examples ne contiene tre sul tema: job-posting (scrivere un annuncio), recruitment (cercare e valutare candidati) e match_profile_to_positions (confrontare un CV con delle posizioni). Il primo corso di DeepLearning.AI su CrewAI ha una lezione per adattare una candidatura a un annuncio.
  • Molti progetti su GitHub. 350 repository con "crewai job" nel nome o nella descrizione e 289 con "crewai resume". Tra i progetti che sembrano applicazioni, il 6,9% riguarda HR e CV.
  • Assente dai sondaggi aziendali. Nessuno dei cinque sondaggi considerati mette la selezione tra gli usi principali degli agenti. È un caso molto amato da chi impara e sperimenta, meno documentato nelle aziende.

Lo schema#

Schema dell'esempio 10: schede di confronto tra CV e annuncio Il codice Python toglie i dati anagrafici da ogni CV. Per ogni CV la crew esegue due task: l'analista estrae i requisiti dall'annuncio, il selezionatore confronta il CV con i requisiti. Un guardrail rifiuta le schede che citano caratteristiche personali. Il punteggio lo calcola il codice con una formula visibile. Le schede arrivano a una persona, che legge i CV, fa i colloqui e decide. annuncio.txt inventato cv_C01.txt ... tre CV inventati con dati anagrafici togli_dati_anagrafici Python: nome, nascita, famiglia non passano Crew, una volta per ogni CV kickoff_for_each 1 · Analista annunci estrae i requisiti obbligatori e preferenziali output_pydantic=Requisiti 2 · Selezionatore requisito per requisito, con la prova dal CV output_pydantic=Valutazione nessun_dato_personale guardrail: se la scheda cita età, genere, figli... il task si rifà CV senza anagrafica punteggio(v) Python: 70 punti obbligatori + 30 preferenziali Una persona decide legge i CV interi, fa i colloqui, può ignorare le schede nessuna classifica automatica
Il codice toglie i dati anagrafici, la crew gira una volta per CV, un guardrail blocca le schede che citano caratteristiche personali, il punteggio lo calcola Python e la decisione arriva a una persona.

Guarda dove sta il codice Python (riquadri verdi): all'ingresso, per decidere quali dati non arrivano al modello; nel mezzo, per bloccare le schede scorrette; all'uscita, per calcolare il punteggio. È lo stesso principio dell'esempio 8: dove serve una regola precisa e verificabile, la scrive il codice, non il modello.

Preparazione#

Crea una cartella, per esempio selezione, con il file .env. Con dati veri di persone ti consigliamo 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, così i CV non escono dal tuo computer (capitolo 7):

.env
MODEL=ollama/qwen2.5:7b
CREWAI_DISABLE_TELEMETRY=true

Per provare con un modello online usa invece MODEL=openai/gpt-4.1-mini e OPENAI_API_KEY=..., ma solo con i file di prova.

Scarica nella stessa cartella l'annuncio e i tre CV di esempio: annuncio.txt, cv_C01.txt, cv_C02.txt, cv_C03.txt. Sono tutti inventati: persone, aziende, date e numeri di telefono non esistono. Ogni CV ha in alto un blocco di dati anagrafici, poi una riga con tre trattini ---, poi esperienza, istruzione e competenze. Il blocco anagrafico è lì apposta, per vedere il codice che lo toglie.

Il codice, pezzo per pezzo#

1. Togliere i dati che non devono contare#

Python
def togli_dati_anagrafici(testo: str) -> str:
    """Tiene solo la parte del CV dopo la riga '---': esperienza, istruzione, competenze."""
    return testo.split("\n---\n", 1)[-1]

split taglia il testo in due pezzi alla riga --- e [-1] prende l'ultimo. Nome, data e luogo di nascita, stato civile e figli non arrivano mai al modello: non possono influenzarlo e non vengono inviati a nessun servizio esterno.

Se parti da zero

Anonimizzare un CV è più difficile di così. Nei CV di prova, anche dopo il taglio, «Impiegata» e «Segretaria» rivelano il genere, «arabo madrelingua» fa intuire un'origine, un diploma del 1987 fa capire l'età. Un modello può usare questi indizi anche senza volerlo: è il Bias (pregiudizio): Una distorsione sistematica: un modello che ha imparato da dati sbilanciati può favorire o penalizzare alcuni gruppi di persone senza che nessuno lo abbia chiesto. glossario che ha imparato dai testi con cui è stato addestrato. Il taglio riduce il problema, non lo risolve.

2. La scheda strutturata, senza voto#

Python
class Valutazione(BaseModel):
    codice_candidato: str
    obbligatori_soddisfatti: list[str] = Field(description="Ogni voce: requisito e prova presa dal CV")
    obbligatori_mancanti: list[str]
    preferenziali_soddisfatti: list[str] = Field(description="Ogni voce: requisito e prova presa dal CV")
    preferenziali_mancanti: list[str]
    motivazione: str = Field(description="Tre o quattro frasi basate solo sui requisiti")
    da_verificare_al_colloquio: list[str] = Field(description="Dubbi o informazioni mancanti da chiedere")

Questo Output strutturato: Un risultato con campi fissi (titolo, lista, numero…) invece di testo libero, così un programma può usarlo senza interpretarlo. In CrewAI: output_pydantic. glossario obbliga il selezionatore a mettere ogni requisito in una lista e a portare una prova presa dal CV. Il campo da_verificare_al_colloquio serve a una cosa importante: quando un'informazione manca, il modello deve segnalarla come domanda, non trasformarla in una penalità. Non c'è un campo «voto»: un numero scritto dal modello sarebbe difficile da spiegare a un candidato.

3. Il punteggio lo calcola il codice#

Python
def punteggio(v: Valutazione) -> int:
    """Il punteggio lo calcola il codice con una formula visibile: 70 punti agli obbligatori, 30 ai preferenziali."""
    totale_obb = len(v.obbligatori_soddisfatti) + len(v.obbligatori_mancanti)
    totale_pref = len(v.preferenziali_soddisfatti) + len(v.preferenziali_mancanti)
    quota_obb = len(v.obbligatori_soddisfatti) / totale_obb if totale_obb else 0
    quota_pref = len(v.preferenziali_soddisfatti) / totale_pref if totale_pref else 0
    return round(70 * quota_obb + 30 * quota_pref)

len conta le voci di una lista. Esempio: 2 obbligatori su 3 e 3 preferenziali su 3 danno 70 × 2/3 + 30 × 3/3 = 76,7, arrotondato a 77. Abbiamo provato la funzione proprio con questi numeri e restituisce 77. La formula è una scelta nostra: puoi cambiarla, ma chiunque la legge sa esattamente come nasce il numero.

4. Il guardrail sulle caratteristiche personali#

Python
PAROLE_SENSIBILI = re.compile(
    r"\b(età|anziana|anziano|giovane|genere|sesso|nazionalità|provenienza|etnia|religione|"
    r"gravidanza|maternità|paternità|figli|sposata|sposato|coniugata|coniugato|disabilità)\b",
    re.IGNORECASE,
)


def nessun_dato_personale(risultato) -> tuple[bool, Any]:
    """Guardrail: rifiuta le valutazioni che citano caratteristiche personali."""
    trovate = sorted({m.group(0).lower() for m in PAROLE_SENSIBILI.finditer(risultato.raw)})
    if trovate:
        return (False, f"La valutazione cita caratteristiche personali ({', '.join(trovate)}). "
                       "Valuta solo esperienza, istruzione e competenze richieste dall'annuncio.")
    return (True, risultato.raw)

re.compile prepara una ricerca di parole intere (i \b ai lati evitano di trovare «età» dentro «società»). Se la scheda del selezionatore contiene una di queste parole, il Guardrail: Un controllo automatico sul risultato di un task. Se il controllo fallisce, l'errore torna all'agente che riprova. glossario la rifiuta e il task si rifà con il messaggio d'errore (capitolo 13). L'abbiamo provato da solo: una motivazione con «giovane, senza figli» viene respinta, una con «5 anni di contabilità nella società» passa.

È un controllo grezzo. Blocca le frasi esplicite, non i ragionamenti distorti scritti con parole neutre («poco dinamica», «non in linea con il team»). Serve come rete di sicurezza, non come garanzia di equità.

5. Raccogliere le schede con una callback#

Python
valutazioni: list[Valutazione] = []  # qui si raccolgono le schede, una per CV


def raccogli(output) -> None:
    """Callback: ogni volta che una valutazione è pronta, la aggiunge all'elenco."""
    if output.pydantic is not None:
        valutazioni.append(output.pydantic)

Una callback è una funzione che CrewAI chiama da sola quando un task finisce, passandole il risultato. La colleghi al task con callback=raccogli. Abbiamo verificato con CrewAI 1.15.21 che la callback resta collegata anche nelle copie della crew create da kickoff_for_each.

6. Gli agenti e i task#

La backstory del selezionatore dice esplicitamente cosa non considerare, compresi età, genere, origine, situazione familiare e pause di carriera. Il task riceve il CV già ripulito con 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 {cv}:

Python
valuta = Task(
    description=(
        "Confronta il CV del candidato {codice} con i requisiti ricevuti. Ogni requisito va messo "
        "in una sola lista (soddisfatti o mancanti), con la prova presa dal CV. Non dare un voto.\n\n"
        "CV DEL CANDIDATO {codice}:\n{cv}"
    ),
    expected_output=(
        "Una scheda in italiano con il codice del candidato, le liste dei requisiti obbligatori e "
        "preferenziali soddisfatti e mancanti, una motivazione breve e i punti da verificare al colloquio."
    ),
    agent=valutatore,
    context=[estrai],
    output_pydantic=Valutazione,
    guardrail=nessun_dato_personale,
    callback=raccogli,
)

7. Una crew per ogni CV#

Python
crew.kickoff_for_each(inputs=ingressi)  # la stessa crew, una volta per ogni CV

ingressi è una Lista: Una sequenza ordinata di valori tra parentesi quadre: [ricercatore, scrittore]. glossario di Dizionario: Una raccolta di coppie nome → valore tra parentesi graffe: {"citta": "Arezzo"}. glossario, uno per CV, con annuncio, codice e testo ripulito. kickoff_for_each, che hai visto nel capitolo 11, esegue la crew una volta per ciascuno. Ogni CV viene valutato da solo, senza vedere gli altri: il modello non può fare confronti tra persone né farsi influenzare dall'ordine in cui legge i CV. Alla fine lo script stampa le schede in ordine di codice, non di punteggio, per non trasformarle in una classifica.

Il codice completo#

Salva il file come confronta_cv.py nella cartella con .env, l'annuncio e i CV.

confronta_cv.py
# /// script
# requires-python = ">=3.10,<3.14"
# dependencies = ["crewai[tools]==1.15.21"]
# ///
"""Confronta CV inventati con un annuncio: estrae i requisiti e prepara una scheda per ogni candidato. Decide una persona."""
import os
import re
from pathlib import Path
from typing import Any

from crewai import Agent, Crew, Process, Task
from pydantic import BaseModel, Field

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

# annuncio.txt e i file cv_*.txt stanno nella stessa cartella dello script.
CARTELLA = Path(__file__).parent


class Requisiti(BaseModel):
    obbligatori: list[str] = Field(description="Requisiti obbligatori, copiati dall'annuncio")
    preferenziali: list[str] = Field(description="Requisiti preferenziali, copiati dall'annuncio")


class Valutazione(BaseModel):
    codice_candidato: str
    obbligatori_soddisfatti: list[str] = Field(description="Ogni voce: requisito e prova presa dal CV")
    obbligatori_mancanti: list[str]
    preferenziali_soddisfatti: list[str] = Field(description="Ogni voce: requisito e prova presa dal CV")
    preferenziali_mancanti: list[str]
    motivazione: str = Field(description="Tre o quattro frasi basate solo sui requisiti")
    da_verificare_al_colloquio: list[str] = Field(description="Dubbi o informazioni mancanti da chiedere")


def togli_dati_anagrafici(testo: str) -> str:
    """Tiene solo la parte del CV dopo la riga '---': esperienza, istruzione, competenze."""
    return testo.split("\n---\n", 1)[-1]


# Parole che non devono comparire in una valutazione basata sui requisiti.
PAROLE_SENSIBILI = re.compile(
    r"\b(età|anziana|anziano|giovane|genere|sesso|nazionalità|provenienza|etnia|religione|"
    r"gravidanza|maternità|paternità|figli|sposata|sposato|coniugata|coniugato|disabilità)\b",
    re.IGNORECASE,
)


def nessun_dato_personale(risultato) -> tuple[bool, Any]:
    """Guardrail: rifiuta le valutazioni che citano caratteristiche personali."""
    trovate = sorted({m.group(0).lower() for m in PAROLE_SENSIBILI.finditer(risultato.raw)})
    if trovate:
        return (False, f"La valutazione cita caratteristiche personali ({', '.join(trovate)}). "
                       "Valuta solo esperienza, istruzione e competenze richieste dall'annuncio.")
    return (True, risultato.raw)


def punteggio(v: Valutazione) -> int:
    """Il punteggio lo calcola il codice con una formula visibile: 70 punti agli obbligatori, 30 ai preferenziali."""
    totale_obb = len(v.obbligatori_soddisfatti) + len(v.obbligatori_mancanti)
    totale_pref = len(v.preferenziali_soddisfatti) + len(v.preferenziali_mancanti)
    quota_obb = len(v.obbligatori_soddisfatti) / totale_obb if totale_obb else 0
    quota_pref = len(v.preferenziali_soddisfatti) / totale_pref if totale_pref else 0
    return round(70 * quota_obb + 30 * quota_pref)


valutazioni: list[Valutazione] = []  # qui si raccolgono le schede, una per CV


def raccogli(output) -> None:
    """Callback: ogni volta che una valutazione è pronta, la aggiunge all'elenco."""
    if output.pydantic is not None:
        valutazioni.append(output.pydantic)


estrattore = Agent(
    role="Analista di annunci di lavoro",
    goal="Estrarre dall'annuncio i requisiti obbligatori e preferenziali, senza aggiungerne",
    backstory="Lavori in un ufficio del personale. Riporti i requisiti come sono scritti, senza interpretarli.",
    llm=MODELLO,
    verbose=True,
)

valutatore = Agent(
    role="Selezionatore che confronta CV e requisiti",
    goal="Indicare, requisito per requisito, se il CV lo soddisfa, con la prova presa dal testo del CV",
    backstory=(
        "Segui regole di equità: giudichi solo esperienze, titoli e competenze legati ai requisiti. "
        "Non consideri età, genere, origine, situazione familiare né pause di carriera. "
        "Se un'informazione manca, la segnali come punto da verificare invece di penalizzare."
    ),
    llm=MODELLO,
    verbose=True,
)

estrai = Task(
    description="Leggi questo annuncio di lavoro ed elenca i requisiti.\n\nANNUNCIO:\n{annuncio}",
    expected_output="I requisiti obbligatori e quelli preferenziali, in italiano, copiati dall'annuncio.",
    agent=estrattore,
    output_pydantic=Requisiti,
)

valuta = Task(
    description=(
        "Confronta il CV del candidato {codice} con i requisiti ricevuti. Ogni requisito va messo "
        "in una sola lista (soddisfatti o mancanti), con la prova presa dal CV. Non dare un voto.\n\n"
        "CV DEL CANDIDATO {codice}:\n{cv}"
    ),
    expected_output=(
        "Una scheda in italiano con il codice del candidato, le liste dei requisiti obbligatori e "
        "preferenziali soddisfatti e mancanti, una motivazione breve e i punti da verificare al colloquio."
    ),
    agent=valutatore,
    context=[estrai],
    output_pydantic=Valutazione,
    guardrail=nessun_dato_personale,
    callback=raccogli,
)

crew = Crew(
    agents=[estrattore, valutatore],
    tasks=[estrai, valuta],
    process=Process.sequential,
    verbose=True,
)

if __name__ == "__main__":
    file_annuncio = CARTELLA / "annuncio.txt"
    file_cv = sorted(CARTELLA.glob("cv_*.txt"))
    if not file_annuncio.exists() or not file_cv:
        raise SystemExit("Metti annuncio.txt e i file cv_*.txt nella stessa cartella dello script.")

    annuncio = file_annuncio.read_text(encoding="utf-8")
    ingressi = [
        {
            "annuncio": annuncio,
            "codice": f.stem.removeprefix("cv_"),  # cv_C01.txt -> C01
            "cv": togli_dati_anagrafici(f.read_text(encoding="utf-8")),
        }
        for f in file_cv
    ]
    crew.kickoff_for_each(inputs=ingressi)  # la stessa crew, una volta per ogni CV

    print("\n=== SCHEDE PER CHI SELEZIONA (in ordine di codice, non di punteggio) ===")
    for v in valutazioni:
        print(f"\n{v.codice_candidato}: {punteggio(v)}/100")
        print("  Obbligatori mancanti:", "; ".join(v.obbligatori_mancanti) or "nessuno")
        print("  Motivazione:", v.motivazione)
        print("  Da verificare al colloquio:", "; ".join(v.da_verificare_al_colloquio) or "nulla")
    print("\nQueste schede sono un aiuto alla lettura dei CV. La decisione spetta a una persona.")

Eseguirlo#

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

Vedrai la crew partire tre volte, una per CV, ciascuna con i due task. Se il guardrail blocca una scheda, compare un riquadro rosso «Guardrail Failed» con le parole trovate e il task riparte. Alla fine lo script stampa un riepilogo come questo (esempio illustrativo, il tuo sarà diverso):

Output
=== SCHEDE PER CHI SELEZIONA (in ordine di codice, non di punteggio) ===

C02: 77/100
  Obbligatori mancanti: Almeno 2 anni di esperienza in contabilità o amministrazione (18 mesi di stage)
  Motivazione: Ha il diploma richiesto e usa i fogli di calcolo...
  Da verificare al colloquio: Livello reale con le tabelle pivot; mansioni contabili nel lavoro in libreria

Queste schede sono un aiuto alla lettura dei CV. La decisione spetta a una persona.

Guarda il caso di C02 nell'esempio: 18 mesi di stage più quattro anni con chiusure di cassa e ordini ai fornitori. Sono «2 anni di esperienza in amministrazione»? Una persona può rispondere di sì. Il modello, applicando la regola alla lettera, può dire di no. È esattamente il tipo di giudizio che non va lasciato a un programma.

Non abbiamo eseguito questo esempio dal vivo con un modello. È stato verificato «a secco» con CrewAI 1.15.21 (costruzione della crew e tre avvii di kickoff_for_each con i file di prova), e sono state provate da sole le funzioni togli_dati_anagrafici, punteggio e il guardrail.

Come migliorarlo#

  • Estrarre i requisiti una volta sola. Oggi l'annuncio viene riletto per ogni CV. Con un flow estrai i requisiti nel primo passo, li salvi nello Stato (di un flow): I dati che un flow porta con sé da un passo all'altro, per esempio l'argomento scelto e la bozza scritta. Si leggono e scrivono con self.state. glossario e poi valuti i CV: meno chiamate e requisiti identici per tutti.
  • Una persona controlla le schede. Aggiungi human_input=True al task di valutazione: prima di chiudere la scheda il programma la mostra e aspetta un commento (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).
  • Prove di equità. Prendi un CV e creane due copie identiche che cambiano solo in un indizio (per esempio «Impiegata» invece di «Impiegato», o una lingua madre diversa). Se le due schede sono diverse, il modello si lascia influenzare: annotalo e cambia modello o istruzioni.
  • Leggere PDF e Word. I CV veri non sono file di testo. Puoi convertirli in testo prima dello script, oppure usare gli strumenti per documenti del capitolo 12. Il taglio dei dati anagrafici però va rifatto: nei CV veri non c'è una riga ---.
  • Scrivere l'annuncio. Il crew ufficiale job-posting fa il lavoro opposto: parte dal ruolo e scrive l'annuncio. Aggiungi un revisore con un guardrail che blocca requisiti non legati al lavoro, come età o genere.
Attenzione: discriminazione, AI Act e GDPR

Il rischio di discriminazione è concreto. I modelli imparano da enormi quantità di testi scritti da persone, con i loro stereotipi. Possono favorire o penalizzare candidati per genere, età, origine o pause di carriera anche quando nessuno lo chiede e anche con i dati anagrafici tolti. Un punteggio con i decimali sembra oggettivo, ma non lo è.

Per l'AI Act: Il regolamento (UE) 2024/1689 sull'intelligenza artificiale: classifica i sistemi di IA per livello di rischio e fissa obblighi per chi li produce e per chi li usa. glossario è un sistema ad alto rischio. Il regolamento (UE) 2024/1689 considera ad alto rischio i sistemi elencati nell'Allegato III (articolo 6, paragrafo 2). Il punto 4, lettera a), dell'Allegato III elenca testualmente «i sistemi di IA destinati a essere utilizzati per l'assunzione o la selezione di persone fisiche, in particolare per pubblicare annunci di lavoro mirati, analizzare o filtrare le candidature e valutare i candidati». Un'azienda che usa davvero uno strumento come questo per selezionare persone rientra in quella categoria, con obblighi precisi: per esempio i datori di lavoro devono informare i lavoratori e i loro rappresentanti prima di usare un sistema ad alto rischio sul luogo di lavoro (articolo 26, paragrafo 7). L'articolo 6, paragrafo 3, prevede alcune eccezioni limitate: non decidere da solo di rientrarci, chiedi a un esperto.

Le date. Nel testo originale gli obblighi per i sistemi ad alto rischio dell'Allegato III si applicavano dal 2 agosto 2026. Il regolamento (UE) 2026/1744 dell'8 luglio 2026 («Omnibus digitale sull'IA») li ha spostati al 2 dicembre 2027. Il divieto di usare sistemi di IA per inferire le emozioni delle persone sul luogo di lavoro (articolo 5, paragrafo 1, lettera f) vale invece già dal 2 febbraio 2025. Le regole cambiano: controlla sempre la versione aggiornata su EUR-Lex.

La decisione è sempre umana. Per il GDPR: Il Regolamento generale sulla protezione dei dati (Regolamento UE 2016/679): le regole europee su come si raccolgono, usano e conservano i dati personali. glossario (regolamento (UE) 2016/679, articolo 22) una persona ha il diritto di non essere sottoposta a una decisione basata unicamente su un trattamento automatizzato che la riguarda in modo significativo, salvo eccezioni precise; e anche in quei casi deve poter ottenere l'intervento umano e contestare la decisione. Usa le schede per leggere meglio i CV, mai per scartare qualcuno senza che una persona abbia letto il suo CV.

I CV sono dati personali. Informa i candidati di come tratti i loro dati, conserva solo ciò che serve e non inviare CV veri a un servizio online senza un accordo che lo consenta: con un modello locale restano sul tuo computer. Nei CV possono esserci anche dati particolari, come quelli sulla salute, che l'articolo 9 del GDPR tutela in modo più rigido.

Fonti ufficiali verificate il 15 settembre 2026: regolamento (UE) 2024/1689 su EUR-Lex, regolamento (UE) 2026/1744 su EUR-Lex, regolamento (UE) 2016/679 (GDPR) su EUR-Lex. Questa pagina non è una consulenza legale.

Prova tu: rendi il guardrail più severo con le pause di carriera

Il CV di C03 contiene «Pausa dal lavoro per motivi familiari». La backstory dice di non considerarla, ma il guardrail non lo controlla. Fai in modo che una scheda che nomina la pausa nella motivazione venga rifiutata, mentre può comparire tra i punti da verificare solo se legata ai requisiti.

Soluzione: la via più semplice è aggiungere pausa e interruzione all'elenco di PAROLE_SENSIBILI. Così però la parola viene bloccata ovunque, anche tra i punti da verificare. Per controllare solo la motivazione, dentro il guardrail trasforma il testo in una Valutazione come nell'esempio 9 (prendendo il pezzo tra la prima { e l'ultima }) e cerca le parole solo in scheda.motivazione. Poi chiediti: una pausa di due anni dice qualcosa sulla capacità di registrare fatture? Se la risposta è no, non deve contare.

In breve
  • I dati anagrafici li toglie il codice prima che il modello veda il CV, ma indizi indiretti restano.
  • Il selezionatore compila una scheda requisito per requisito, con prove; il punteggio lo calcola Python con una formula visibile.
  • kickoff_for_each valuta ogni CV da solo; una callback raccoglie le schede.
  • Il guardrail blocca le parole esplicite su caratteristiche personali: è una rete di sicurezza, non una garanzia.
  • Per l'AI Act i sistemi di selezione del personale sono ad alto rischio (Allegato III, punto 4); per il GDPR la decisione non può essere solo automatica. Decide una persona.

Hai visto dieci modi diversi di far lavorare gli agenti. Nell'appendice B trovi tutte le fonti usate dal corso e i passi per continuare da solo.

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