Routing ticket con AI: modello Strands Decider 2B in Python
Smista automaticamente i ticket di assistenza con Strands Decider 2B in Python: soglie di confidenza, escalation umana e script pronto all'uso.

👁 113 letture
Un modello decisionale e piu adatto all’instradamento delle richieste rispetto a una classica chat: assegna il reparto corretto partendo dal tuo elenco, indica un indice di confidenza e permette di inoltrare in automatico i casi dubbi a un operatore umano. Qui presentiamo uno script funzionante in Python basato sul modello open source Strands Decider 2B, testato direttamente sul nostro computer, con i risultati ottenuti e due insidie concrete emerse durante le prove. Per comprendere a fondo questi sistemi e analizzare il nostro benchmark su 30 richieste in polacco, puoi consultare la guida ai modelli decisionali AI.
Gli importi in EUR sono conversioni indicative dei prezzi pubblicati in USD, calcolate al cambio di riferimento BCE del 2026-10-12: 1 EUR = 1.1181 USD. Non sono prezzi locali confermati né importi finali comprensivi di imposte. Il fornitore e la banca possono applicare altri cambi o commissioni. ECB.
Cosa andremo a costruire
Uno script che per ogni ticket restituisce:
- reparto - uno tra 6 dipartimenti (pagamenti, spedizioni, resi, account, guasti, vendite) oppure “altro”,
- confidenza della scelta da 0 a 1,
- coda - il reparto specifico se la confidenza e sufficiente, oppure
da_operatore, - urgenza - un indicatore booleano se la pratica richiede un intervento entro la giornata.
L’output e una singola riga JSON per ciascuna richiesta, facilissima da integrare nel tuo helpdesk, in un foglio di calcolo o in una pipeline di automazione AI.
I requisiti tecnici
- Python 3.10 o versioni successive.
- PyTorch - configurato con supporto CUDA se utilizzi una scheda NVIDIA (il modello occupa poco meno di 4 GB di VRAM). In assenza di GPU dedicata gira su CPU, ma con tempi piu lunghi.
- Il pacchetto
strands-decider(licenza Apache-2.0). Al primo avvio scarichera circa 4,4 GB di dati: il modello base Qwen3.5-2B-Base e l’adattatore fornito da AWS.
pip install strands-decider
Su sistemi Windows l’installer pip puo talvolta sostituire PyTorch con una build priva di accelerazione CUDA. Conviene verificare lo stato con il comando python -c "import torch; print(torch.cuda.is_available())": se il risultato e False, reinstalla PyTorch dal repository ufficiale pytorch.org selezionando la tua versione di CUDA. Nel nostro ambiente di test si e verificato esattamente questo scenario.
Lo script routing.py
# Routing dei ticket con il modello decisionale Strands Decider 2B (in locale, Apache-2.0).
# pip install strands-decider (oltre a PyTorch con CUDA, se hai una scheda NVIDIA)
import json, sys
from strands_decider.infer import load_engine
from strands_decider.schema import SystemOneRequest
MODEL = "StrandsAgents/strands-decider-2B-hobson-v19"
DZIALY = {
"platnosci": "Płatności, faktury, obciążenia karty, abonament",
"dostawa": "Wysyłka, kurier, śledzenie paczki, adres dostawy",
"zwrot": "Zwroty towaru, reklamacje, wymiana, uszkodzony produkt",
"konto": "Logowanie, hasło, dane konta, bezpieczeństwo konta, RODO",
"awaria": "Błędy aplikacji lub strony, awarie, integracje, wydajność",
"sprzedaz": "Pytania o ofertę, plany, rabaty, wdrożenia dla firm",
"inne": "Z treści nie da się ustalić, czego dotyczy zgłoszenie",
}
PROG_DZIAL = 0.60 # sotto questa soglia il ticket passa a un operatore umano
PROG_PILNE = 0.40 # soglia calibrata sui nostri 30 ticket di test - verificala sui tuoi dati
silnik = load_engine(MODEL, device="cuda") # funziona anche con "cpu", ma piu lentamente
def rozdziel(tekst: str) -> dict:
zapytanie = SystemOneRequest(state=tekst, questions={
"dzial": {"type": "choice", "instructions": "Do którego działu trafia to zgłoszenie?", "criteria": DZIALY},
"pilne": {"type": "noul", "instructions": "Czy zgłoszenie wymaga reakcji jeszcze dziś (awaria, utrata pieniędzy, włamanie, nieprzekraczalny termin)?"},
})
odp = silnik.evaluate(zapytanie).answers
dzial, pewnosc, p_pilne = odp["dzial"].choice, odp["dzial"].confidence, odp["pilne"].noul
kolejka = dzial if pewnosc >= PROG_DZIAL and dzial != "inne" else "da_operatore"
return {"coda": kolejka, "reparto": dzial, "confidenza": round(pewnosc, 2),
"urgente": p_pilne >= PROG_PILNE, "p_urgente": round(p_pilne, 2)}
if __name__ == "__main__":
for linia in sys.stdin:
if linia.strip():
print(json.dumps({"testo": linia.strip()[:60], **rozdziel(linia)}, ensure_ascii=False))
I criteri inseriti nel codice corrispondono alle categorie testate: pagamenti (“Pagamenti, fatture, addebiti su carta, abbonamento”), spedizioni (“Spedizione, corriere, tracciamento pacco, indirizzo di consegna”), resi (“Resi merce, reclami, sostituzioni, prodotto danneggiato”), account (“Login, password, dati account, sicurezza, GDPR”), guasti (“Errori sito o app, disservizi, integrazioni, prestazioni”), vendite (“Richieste commerciali, piani, sconti, progetti enterprise”) e altro (“Dal testo non e possibile determinare l’oggetto”). Le istruzioni chiedevano: Do którego działu trafia to zgłoszenie? (“A quale reparto appartiene questa richiesta?”) e Czy zgłoszenie wymaga reakcji jeszcze dziś (awaria, utrata pieniędzy, włamanie, nieprzekraczalny termin)? (“La richiesta richiede una risposta entro oggi tra guasti, perdite economiche, accessi abusivi o scadenze perentorie?”).
Alcune scelte architetturali da evidenziare:
- Abbiamo condotto i test con descrizioni in polacco. Nei nostri riscontri le definizioni localizzate hanno mostrato la stessa precisione di quelle in lingua inglese, risultando molto piu semplici da gestire per un team operativo.
- Due domande raggruppate in un’unica chiamata. L’elaborazione del testo avviene una sola volta, mentre la valutazione delle domande aggiunge un overhead computazionale quasi nullo. Aggiungere un terzo quesito (per esempio “il cliente intende disdire?”) non incide sulla latenza.
- La logica decisionale risiede nel codice, non nel modello. La rete neurale restituisce valori numerici; le soglie e l’assegnazione alle code sono gestite in Python, permettendo modifiche immediate senza riaddestramenti.
Esecuzione e risultati del test
Le richieste vengono lette dallo standard input, una per riga:
python routing.py < ticket.txt
Ecco i riscontri ottenuti sulla nostra RTX 4090 su sei ticket di test mai mostrati prima al modello:
| Richiesta cliente | Senza opzione “altro” | Con opzione “altro” |
|---|---|---|
| Od rana nie działa logowanie przez Google, cały zespół stoi. (Da stamattina non funziona il login tramite Google, l’intero team e bloccato) | account 0,54 - da operatore, urgente | account 0,46 - da operatore, urgente |
| Proszę o fakturę VAT na firmę do zamówienia 77120. (Chiedo fattura con partita IVA intestata all’azienda per l’ordine 77120) | pagamenti 0,46 - da operatore | altro 0,63 - da operatore |
| Paczka wróciła do nadawcy, bo kurier nie mógł znaleźć adresu. (Il pacco e tornato al mittente perche il corriere non trovava l’indirizzo) | spedizioni 0,96 | spedizioni 0,90 |
| Czy macie zniżki dla szkół i uczelni? (Applicate sconti per scuole e universita?) | vendite 0,58 - da operatore | vendite 0,31 - da operatore |
| Hej, mam pytanie. Coś mi nie gra. (Ciao, ho una domanda. C’e qualcosa che non mi torna) | guasti 0,80 | guasti 0,41 - da operatore |
| Zwracam kurtkę, ale nie wiem, czy zwrot pójdzie na kartę czy na konto. (Faccio il reso del giubbotto, ma non so se il rimborso arrivera su carta o conto) | resi 0,94 | resi 0,84 |
La singola richiesta ha richiesto circa 135 ms su GPU, a fronte di 16 secondi per l’inizializzazione del modello in memoria. Ciascun ticket instradato in automatico e finito nel reparto corretto. La marcatura di urgenza e scattata unicamente per il blocco totale del login aziendale, esattamente come previsto.
Insidia 1: troppa sicurezza sui ticket vuoti
Nella nostra versione iniziale non era presente l’opzione “altro”. Di fronte al messaggio generico Hej, mam pytanie. Coś mi nie gra. (“Ciao, ho una domanda. C’e qualcosa che non mi torna”), il modello lo ha catalogato come guasto tecnico con confidenza di 0,80. Superando la nostra soglia, sarebbe finito direttamente agli sviluppatori. Non si tratta di un malfunzionamento: forzando la scelta tra sei categorie predefinite, la somma delle probabilita deve sempre dare 1. Un modello decisionale non rispondera mai “non lo so” a meno che non gli venga fornita tale opzione.
La soluzione consiste nell’introdurre la scelta inne (altro) con la descrizione “Dal testo non e possibile determinare l’oggetto” e istruire il codice a deviare sempre questo esito verso l’assistenza umana. Dopo questa correzione, il messaggio privo di contesto e sceso a 0,41 ed e stato correttamente assegnato a un operatore.
Insidia 2: l’opzione “altro” riduce la confidenza
L’aggiunta di una voce generica comporta un effetto collaterale: la confidenza cala anche sui casi lineari (la richiesta sul pacco scende da 0,96 a 0,90, il reso del giubbotto da 0,94 a 0,84) e la richiesta di fatturazione e finita tra “altro” invece che in amministrazione. Per gestire al meglio questo comportamento:
- Arricchisci le descrizioni con il lessico reale dei clienti. Se la richiesta di fattura finisce tra i casi generici, occorre aggiungere esplicitamente “fattura elettronica, fattura con partita IVA, nota di credito” nei dettagli dei pagamenti.
- Determina la soglia basandoti sul tuo storico. Prendi 50-100 ticket gia classificati, falli processare dallo script e analizza il punteggio minimo in cui il sistema non commette errori. Il valore 0,60 da noi adottato e una base di partenza, non un parametro universale.
- Monitora la quota di casi deviati a persone fisiche. Se oltrepassa il 30-40%, significa che i reparti presentano definizioni troppo vaghe o che sussistono sovrapposizioni tra le competenze.
Urgenza: anche la soglia va calibrata
Nel nostro test su 30 ticket in polacco, Strands Decider ha attribuito ai casi urgenti probabilita comprese tra 0,22 e 0,47. Mantenendo la classica soglia predefinita di 0,5 non avrebbe segnalato alcuna criticita: per questa ragione nello script abbiamo impostato 0,40. Al contrario, il modello piu capiente Clef-flash di Cloudflare ha manifestato una tendenza opposta, richiedendo una soglia di 0,95 per lavorare al meglio. La regola generale resta chiara: considera l’output numerico come una scala di priorita relativa e fissa il limite analizzando i tuoi dati concreti.
Come passare a un altro modello
Architetture come Strands Decider, Cloudflare Clef, Kev e Jev di TypeSafe impiegano la medesima struttura di payload (System One: state + questions). Lo script puo essere convertito a ciascuna di esse modificando solo l’interfaccia di chiamata:
- Clef-flash in locale (con GPU da 24 GB): tramite la funzione
systemone()nel modulojoint_schema_model.pydel repositoryCloudflare/clef-flash. Nelle nostre misurazioni e risultato piu rapido (112 ms) e piu risoluto, ma richiede 18 GB di VRAM. Abbiamo approfondito questa novita nell’articolo su Cloudflare Clef. - Workers AI: tramite richiesta
POSTverso/ai/run/@cf/cloudflare/clef-flashmantenendo la stessa struttura JSON, al costo di 0,0805 EUR (0,09 USD) per milione di token di input e con una quota giornaliera gratuita. Richiede un account Cloudflare attivo. - Server HTTP nativo di Strands: avviabile con
strands-decider serve ... --port 8000ricevendo chiamate su/v1/systemone, consentendo allo script di demandare la gestione della memoria.
Per ulteriori chiarimenti su licenza e benchmark di questo modello specifico, puoi fare riferimento all’analisi su Strands Decider 2B.
Quando non usare un modello decisionale per il routing
- Se l’obiettivo e produrre subito una risposta scritta per il cliente: in questo caso serve un LLM generativo. Il modello decisionale puo tuttavia stabilire a monte quale template di risposta preimpostato utilizzare.
- Se gestisci pochissime richieste al giorno: risulta piu snello interrogare un modello di grandi dimensioni tramite output strutturato in formato JSON (come spiegato nel pezzo sullo structured output).
- Se l’organigramma aziendale e i reparti cambiano di continuo e mancano ticket storici gia etichettati: risulterebbe impossibile calibrare soglie di confidenza affidabili.
Fonti
- Scheda di Strands Decider 2B su Hugging Face
- Repository strands-labs/strands-decider su GitHub
- Abbiamo testato lo script il 5 ottobre 2026 su scheda RTX 4090, sistema operativo Windows 11, Python 3.12, PyTorch 2.11, transformers 5.18 e strands-decider 0.1.0.
More on this from NITIDO
strumenti ia·Quale AI per la musica da video? Eleven Music, Stable Audio o SunoEleven Music, Stable Audio 3 o Suno? Confronto tra i migliori generatori AI per colonne sonore e sottofondi musicali per video, licenze e prompt.

