Naar de inhoud
trending

Handleidingen

Tickets routeren met AI: Strands Decider 2B in Python

Automatisch helpdesktickets routeren met beslismodel Strands Decider 2B in Python: betrouwbaarheidsscores, escalatie naar mensen en valkuilen uit de praktijk.

Tekst: 8 min leestijd
Tickets routeren met AI: Strands Decider 2B in Python

👁 112 keer gelezen

Een beslismodel (decision model) leent zich voor het routeren van helpdesktickets veel beter dan een chatbot: het kiest een afdeling uit je eigen lijst, geeft een betrouwbaarheidsscore en twijfelgevallen stuur je automatisch door naar een medewerker. Hieronder vind je een werkend Python-script rond het opensourcemodel Strands Decider 2B dat we lokaal hebben getest, inclusief de testresultaten en twee valkuilen die we tegenkwamen. Wat beslismodellen precies zijn en hoe ze presteerden in een test met 30 Poolse tickets, lees je in onze gids Beslismodellen in AI.

De bedragen in EUR zijn indicatieve omrekeningen van de gepubliceerde USD-prijzen tegen de ECB-referentiekoers van 2026-10-12: 1 EUR = 1.1181 USD. Dit zijn geen bevestigde lokale eindprijzen inclusief belastingen. De aanbieder en de bank kunnen andere koersen of kosten hanteren. ECB.

Wat we gaan bouwen

Een script dat voor ieder binnenkomend ticket het volgende bepaalt:

  • afdeling - een van de 6 gekozen categorieën (betalingen, bezorging, retouren, account, storingen, sales) of “overig”,
  • zekerheid - een betrouwbaarheidsscore van 0 tot 1,
  • wachtrij - de specifieke afdeling als de zekerheid hoog genoeg is, of anders do_czlowieka (naar een mens),
  • urgentie - of het ticket vandaag nog een reactie vereist.

De output is één JSON-regel per ticket, wat eenvoudig te koppelen is aan een helpdeskpakket, spreadsheet of geautomatiseerde workflow.

Wat heb je nodig?

  • Python 3.10 of nieuwer.
  • PyTorch - bij voorkeur met CUDA-ondersteuning als je een NVIDIA-kaart hebt (het model gebruikt iets minder dan 4 GB videogeheugen). Zonder videokaart draait het op de processor, maar dan een stuk trager.
  • Het package strands-decider (Apache-2.0). Bij de eerste run downloadt het ongeveer 4,4 GB: het basismodel Qwen3.5-2B-Base en een adapter van AWS.
pip install strands-decider

Op Windows kan de pip-installatie PyTorch ongemerkt vervangen door een versie zonder CUDA. Controleer dit met het commando python -c "import torch; print(torch.cuda.is_available())". Geeft dit False, installeer PyTorch dan opnieuw via de package index van pytorch.org voor jouw specifieke CUDA-versie. Dat overkwam ons tijdens de test ook.

Het Python-script: routing.py

# Routing van tickets met het beslismodel Strands Decider 2B (lokaal, Apache-2.0).
# pip install strands-decider   (plus PyTorch met CUDA als je een NVIDIA-kaart hebt)
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   # onder deze zekerheidsscore gaat het ticket naar een mens
PROG_PILNE = 0.40   # drempelwaarde gekozen op basis van onze 30 testtickets - controleer met eigen data

silnik = load_engine(MODEL, device="cuda")  # "cpu" werkt ook, alleen trager

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 "do_czlowieka"
    return {"kolejka": kolejka, "dzial": dzial, "pewnosc": round(pewnosc, 2),
            "pilne": p_pilne >= PROG_PILNE, "p_pilne": round(p_pilne, 2)}

if __name__ == "__main__":
    for linia in sys.stdin:
        if linia.strip():
            print(json.dumps({"tekst": linia.strip()[:60], **rozdziel(linia)}, ensure_ascii=False))

De geteste criteria in het script betekenen vertaald: platnosci (betalingen, facturen, creditcardafschrijvingen, abonnement), dostawa (verzending, koerier, pakket traceren, afleveradres), zwrot (retouren, klachten, ruilen, beschadigd product), konto (inloggen, wachtwoord, accountgegevens, accountbeveiliging, AVG), awaria (app- of websitefouten, storingen, integraties, prestaties), sprzedaz (vragen over aanbod, abonnementen, kortingen, zakelijke implementaties) en inne (uit de tekst valt niet op te maken waar het ticket over gaat). De instructies vragen respectievelijk naar welke afdeling het ticket moet en of er vandaag actie nodig is wegens een storing, geldverlies, inbraak of harde deadline.

Enkele ontwerpkeuzes achter dit script:

  • We hebben getest in het Pools. Omdat het testmateriaal Pools was, stonden de afdelingsbeschrijvingen ook in het Pools. Dat werkte even betrouwbaar als Engelse definities. Voor je eigen implementatie kun je de criteria uiteraard direct in het Nederlands invoeren.
  • Twee vragen in één prompt. Het model verwerkt het ticket eenmalig en beantwoordt extra vragen vrijwel zonder snelheidsverlies. Een derde vraag toevoegen (zoals: “dreigt de klant op te stappen?”) kost nauwelijks extra rekentijd.
  • De code beslist, niet het model. Het model levert uitsluitend getallen (waarschijnlijkheden). De drempelwaarden en routeringsregels beheer je in Python, waardoor je ze direct kunt bijsturen zonder het model aan te raken.

Uitvoering en testresultaten

Tickets worden regel voor regel via standard input aangeboden:

python routing.py < zgloszenia.txt

Dit waren de resultaten op onze RTX 4090 voor zes Poolstalige tickets die het model niet eerder had gezien:

Ticket Zonder de optie “overig” Met de optie “overig”
Od rana nie działa logowanie przez Google, cały zespół stoi. (Google-inlog werkt sinds vanochtend niet, heel het team ligt stil) account 0,54 - naar mens, urgent account 0,46 - naar mens, urgent
Proszę o fakturę VAT na firmę do zamówienia 77120. (Graag een btw-factuur op bedrijfsnaam voor bestelling 77120) betalingen 0,46 - naar mens overig 0,63 - naar mens
Paczka wróciła do nadawcy, bo kurier nie mógł znaleźć adresu. (Pakket retour afzender omdat koerier het adres niet kon vinden) bezorging 0,96 bezorging 0,90
Czy macie zniżki dla szkół i uczelni? (Hebben jullie korting voor scholen en universiteiten?) sales 0,58 - naar mens sales 0,31 - naar mens
Hej, mam pytanie. Coś mi nie gra. (Hee, ik heb een vraag. Er klopt iets niet.) storing 0,80 storing 0,41 - naar mens
Zwracam kurtkę, ale nie wiem, czy zwrot pójdzie na kartę czy na konto. (Ik stuur jas retour, maar weet niet of terugbetaling naar kaart of rekening gaat) retour 0,94 retour 0,84

Eén evaluatie duurde op de videokaart circa 135 ms; het inladen van het model nam 16 seconden in beslag. Alle tickets die het model automatisch doorstuurde, kwamen bij de juiste afdeling terecht. Het label urgent werd alleen toegekend aan de inlogstoring die het hele team blokkeerde - precies zoals bedoeld.

Valkuil 1: te zelfverzekerd bij een vaag ticket

In de eerste opzet hadden we de optie “overig” weggelaten. Een nietszeggend ticket als Hej, mam pytanie. Coś mi nie gra. (“Hee, ik heb een vraag. Er klopt iets niet.”) koppelde het model doodleuk aan storingen met een zekerheidsscore van 0,80. Dat was ruim boven onze drempelwaarde, waardoor het bericht direct bij developers zou belanden. Dit is geen fout in het model: we dwongen het om te kiezen uit zes afdelingen, en kansen tellen samen altijd op tot 1. Een beslismodel zegt niet uit zichzelf “ik weet het niet” als je die optie niet expliciet aanbiedt.

De oplossing: een categorie inne (overig) toevoegen met als definitie “uit de inhoud valt niet op te maken waar het ticket over gaat”, gecombineerd met een regel in de code dat deze categorie altijd naar een menselijke medewerker gaat. Na die aanpassing daalde de score voor het vage ticket naar 0,41 en werd het keurig geëscaleerd.

Valkuil 2: de optie “overig” drukt de zekerheidsscore

Zo’n extra categorie brengt wel een nadeel met zich mee: de betrouwbaarheidsscore daalde ook bij overduidelijke berichten (bezorging ging van 0,96 naar 0,90; de retour van 0,94 naar 0,84) en de vraag om een btw-factuur belandde zelfs in “overig” in plaats van bij betalingen. Zo los je dat op:

  • Gebruik de exacte termen van je klanten in de omschrijving. Wordt een btw-factuur niet herkend, voeg dan expliciet termen als “btw-factuur, factuurcorrectie, creditnota” toe aan de definitie van de afdeling betalingen.
  • Stel drempelwaarden vast op basis van eigen data. Pak 50 tot 100 handmatig gelabelde tickets uit je archief, haal ze door het script en bepaal bij welke score het model geen fouten meer maakt. Onze grenswaarde van 0,60 is puur een startpunt.
  • Monitor het percentage dat naar medewerkers gaat. Komt meer dan 30 tot 40% bij een mens terecht, dan zijn de categorieomschrijvingen waarschijnlijk te algemeen of vertonen ze te veel overlap.

Urgentie: ook die drempelwaarde moet je afstellen

Tijdens onze test met 30 Poolse tickets kende Strands Decider aan urgente situaties een waarschijnlijkheidsscore toe tussen 0,22 en 0,47. Bij een standaard drempelwaarde van 0,5 zou er dus geen enkel ticket als urgent zijn gemarkeerd; vandaar de drempel van 0,40 in ons script. Het grotere model Clef-flash van Cloudflare vertoonde juist het tegenovergestelde gedrag en vereiste een drempel van 0,95. De stelregel: zie de getallen uit het model als een ranking en bepaal het kantelpunt altijd proefondervindelijk op je eigen tickets.

Overstappen naar een ander beslismodel

Strands Decider, Cloudflare Clef en modellen zoals Kev en Jev van TypeSafe hanteren hetzelfde queryformaat (System One: state + questions). Je kunt het script daarom overzetten naar een ander model door alleen de aanroep te veranderen:

  • Clef-flash lokaal (videokaart met 24 GB VRAM): via de functie systemone() uit het bestand joint_schema_model.py in de GitHub-repo Cloudflare/clef-flash. In onze test was dit model net iets sneller (112 ms) en trefzekerder, maar het vereist 18 GB videogeheugen. Meer details lees je in ons artikel over Cloudflare Clef.
  • Workers AI: via een POST-request naar /ai/run/@cf/cloudflare/clef-flash met dezelfde JSON. Dit kost 0,0805 EUR (0,09 USD) per miljoen inputtokens, met een gratis dagelijkse bundel via een Cloudflare-account.
  • Strands HTTP-server: start via strands-decider serve ... --port 8000 en stuur requests naar /v1/systemone. Zo hoeft het Python-script het model niet telkens zelf in het geheugen te laden.

Meer achtergronden over licenties en prestaties vind je in onze review van Strands Decider 2B.

Wanneer een beslismodel geen geschikte keuze is

  • Wanneer je direct een inhoudelijke reactie voor de klant wilt genereren - daarvoor heb je een generatief LLM nodig. Een beslismodel kan wel selecteren welk antwoordsjabloon passend is.
  • Wanneer je slechts een handvol tickets per dag ontvangt - dan is een regulier taalmodel met JSON-uitvoer vaak sneller ingericht (zie onze gids over Structured output).
  • Wanneer afdelingen wekelijks veranderen en er geen gelabelde geschiedenis is - zonder referentiemateriaal kun je immers geen betrouwbare drempelwaarden kalibreren.
Google · Jouw bronnenHoger in Google - met één klikToevoegen aan voorkeursbronnen →

Bronnen

Kinetyra

// read next

More on this from HELDER

Helder

Helder

ADMINISTRATOR
lees ookWelke AI voor filmmuziek? Eleven Music, Stable Audio of SunohandleidingenWelke AI voor filmmuziek? Eleven Music, Stable Audio of SunoVoor achtergrondmuziek in video's werkt Eleven Music het best. Bekijk de beste AI-muziekgeneratoren, commerciële licenties, kosten en een kant-en-klare prompt.

Meest gelezen

  1. 1Anthropics biolab ontdekt nieuw enzym via bijna 950 Claude-agents
  2. 2Meta onthult sleutelhanger-apparaatje met AI-assistent Muse
  3. 3OpenAI richt wiskundeadviesgroep op na oplossen van meer dan 100 open problemen
// sitemap

Deze pagina in andere talen6

helder in cijfers 0teksten in archief0nieuwsberichten van de afgelopen 7 dagen0videomodellen in observatorium
× ‹ vergroting ›