Routing zgłoszeń z AI: model decyzyjny w Pythonie krok po kroku
Skrypt w Pythonie, który przypisuje zgłoszenia do działów, ocenia pilność i odsyła niepewne sprawy do człowieka. Przetestowany lokalnie, z wynikami i pułapkami.

👁 116 przeczytań
- Model decyzyjny Strands Decider 2B nadaje się do routingu zgłoszeń, bo zwraca wybrany dział z 6 opcji oraz pewność od 0 do 1, a niepewne sprawy kieruje do człowieka.
- Jedno zapytanie na karcie RTX 4090 trwało ok. 135 ms, a załadowanie modelu 16 s; model bazowy Qwen3.5-2B-Base z adapterem zajmuje ok. 4,4 GB po pobraniu.
- Bez opcji "inne" model przypisał puste zgłoszenie do awarii z pewnością 0,80, bo prawdopodobieństwa zawsze sumują się do 1 i model nie może odpowiedzieć "nie wiem".
Model decyzyjny nadaje się do routingu zgłoszeń lepiej niż czat: wybiera dział z Twojej listy, podaje pewność, a niepewne sprawy możesz automatycznie odesłać do człowieka. Poniżej pokazuję działający skrypt w Pythonie na otwartym modelu Strands Decider 2B, który sprawdziłem na swoim komputerze, razem z wynikami i dwoma pułapkami, na które trafiłem. Czym są modele decyzyjne i jak wypadły w teście na 30 polskich zgłoszeniach, opisałem w przewodniku Modele decyzyjne AI.
Co zbudujemy
Skrypt, który dla każdego zgłoszenia zwraca:
- dział - jeden z 6 (płatności, dostawa, zwroty, konto, awarie, sprzedaż) albo „inne”,
- pewność wyboru od 0 do 1,
- kolejkę - dział, jeśli pewność jest wystarczająca, albo
do_czlowieka, - pilność - czy sprawa wymaga reakcji jeszcze dziś.
Wynik to jedna linia JSON na zgłoszenie, którą łatwo podpiąć pod helpdesk, arkusz albo automatyzację.
Czego potrzebujesz
- Python 3.10 lub nowszy.
- PyTorch - z CUDA, jeśli masz kartę NVIDIA (model zajmuje niecałe 4 GB pamięci karty). Bez karty działa na procesorze, tylko wolniej.
- Pakiet
strands-decider(Apache-2.0). Przy pierwszym uruchomieniu pobierze ok. 4,4 GB: model bazowy Qwen3.5-2B-Base i adapter od AWS.
pip install strands-deciderNa Windowsie instalator pip potrafi podmienić PyTorch na wersję bez CUDA. Sprawdź to poleceniem python -c "import torch; print(torch.cuda.is_available())" - jeśli zwraca False, doinstaluj PyTorch z indeksu pytorch.org dla swojej wersji CUDA. U mnie tak właśnie się stało.
Skrypt routing.py
# Routing zgłoszeń modelem decyzyjnym Strands Decider 2B (lokalnie, Apache-2.0).
# pip install strands-decider (do tego PyTorch z CUDA, jeśli masz kartę 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 # poniżej tej pewności zgłoszenie idzie do człowieka
PROG_PILNE = 0.40 # próg dobrany na moich 30 zgłoszeniach - sprawdź na swoich danych
silnik = load_engine(MODEL, device="cuda") # "cpu" też działa, tylko wolniej
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))Kilka decyzji projektowych:
- Opisy działów są po polsku. W moim teście polskie opisy działały tak samo dobrze jak angielskie, a łatwiej je utrzymać zespołowi.
- Dwa pytania w jednym zapytaniu. Model liczy zgłoszenie raz, a pytania doliczane są prawie bez kosztu, więc dodanie trzeciego pytania (np. „czy klient grozi odejściem?”) nie wydłuży czasu.
- Decyzję podejmuje kod, nie model. Model zwraca liczby, a progi i kolejki są w Pythonie - łatwo je zmienić bez ruszania modelu.
Uruchomienie i wyniki
Zgłoszenia trafiają na standardowe wejście, po jednym w linii:
python routing.py < zgloszenia.txtWyniki z mojego RTX 4090 dla sześciu zgłoszeń, których model wcześniej nie widział:
| Zgłoszenie | Bez opcji „inne” | Z opcją „inne” |
|---|---|---|
| Od rana nie działa logowanie przez Google, cały zespół stoi. | konto 0,54 - do człowieka, pilne | konto 0,46 - do człowieka, pilne |
| Proszę o fakturę VAT na firmę do zamówienia 77120. | płatności 0,46 - do człowieka | inne 0,63 - do człowieka |
| Paczka wróciła do nadawcy, bo kurier nie mógł znaleźć adresu. | dostawa 0,96 | dostawa 0,90 |
| Czy macie zniżki dla szkół i uczelni? | sprzedaż 0,58 - do człowieka | sprzedaż 0,31 - do człowieka |
| Hej, mam pytanie. Coś mi nie gra. | awaria 0,80 | awaria 0,41 - do człowieka |
| Zwracam kurtkę, ale nie wiem, czy zwrot pójdzie na kartę czy na konto. | zwrot 0,94 | zwrot 0,84 |
Jedno zapytanie trwało na karcie ok. 135 ms, a załadowanie modelu 16 s. Wszystkie zgłoszenia, które model rozdzielił automatycznie, trafiły do właściwego działu. Pilność oznaczył tylko przy awarii logowania całego zespołu - i o to chodziło.
Pułapka 1: pewny siebie przy pustym zgłoszeniu
Nie przepłacaj za te subskrypcje
Prowadzę sklep z rocznymi dostępami do narzędzi AI - te same konta, o których piszę wyżej, tylko taniej niż w cenniku producenta.
W pierwszej wersji nie miałem opcji „inne”. Zgłoszenie „Hej, mam pytanie. Coś mi nie gra.” model przypisał do awarii z pewnością 0,80 - wyżej niż mój próg, więc trafiłoby do zespołu technicznego. To nie błąd modelu: kazałem mu wybrać jeden z sześciu działów, a prawdopodobieństwa zawsze sumują się do 1. Model decyzyjny nie powie „nie wiem”, jeśli nie dasz mu takiej opcji.
Rozwiązanie: opcja inne z opisem „Z treści nie da się ustalić, czego dotyczy zgłoszenie” i reguła w kodzie, że inne zawsze idzie do człowieka. Po zmianie puste zgłoszenie spadło do 0,41 i poszło do człowieka.
Pułapka 2: opcja „inne” zabiera pewność
Dodatkowa opcja ma koszt: pewność spadła także przy oczywistych zgłoszeniach (paczka z 0,96 do 0,90, kurtka z 0,94 do 0,84), a prośba o fakturę VAT trafiła do „inne” zamiast do płatności. Co z tym zrobić:
- Dopisz do opisów słowa, których używają Twoi klienci. Jeśli „faktura VAT” trafia do „inne”, w opisie płatności powinno być „faktura VAT, korekta faktury”.
- Ustal próg na własnych danych. Weź 50-100 oznaczonych zgłoszeń z historii, puść przez skrypt i sprawdź, przy jakim progu automat się nie myli. Mój próg 0,60 to punkt startu, nie reguła.
- Licz odsetek „do człowieka”. Jeśli przekracza 30-40%, opisy działów są za ogólne albo działy na siebie zachodzą.
Pilność: próg też trzeba ustawić
W teście na 30 polskich zgłoszeniach Strands Decider przypisywał pilnym sprawom prawdopodobieństwo od 0,22 do 0,47. Przy domyślnym progu 0,5 nie oznaczyłby jako pilnego niczego - stąd w skrypcie próg 0,40. Większy model Clef-flash od Cloudflare miał odwrotną skłonność i najlepiej działał przy progu 0,95. Zasada jest jedna: liczby z modelu traktuj jako ranking, a granicę wyznacz na swoich przykładach.
Jak przełączyć się na inny model
Strands Decider, Cloudflare Clef, Kev i Jev od TypeSafe używają tego samego formatu zapytań (System One: state + questions). Skrypt da się przenieść na każdy z nich, zmieniając tylko sposób wywołania:
- Clef-flash lokalnie (karta 24 GB): funkcja
systemone()z plikujoint_schema_model.pyw repozytoriumCloudflare/clef-flash. Na moim teście był nieco szybszy (112 ms) i pewniejszy, ale potrzebuje 18 GB pamięci karty. O premierze piszę w tekście Cloudflare Clef. - Workers AI:
POSTna/ai/run/@cf/cloudflare/clef-flashz tym samym JSON-em, 0,09 USD za milion tokenów wejścia i darmowy przydział dzienny. Wymaga konta Cloudflare. - Serwer HTTP Strands:
strands-decider serve ... --port 8000i żądania na/v1/systemone- wtedy skrypt nie musi ładować modelu sam.
Więcej o samym Strands Decider, jego licencji i wynikach w tekście Strands Decider 2B.
Kiedy nie używać modelu decyzyjnego do routingu
- Gdy chcesz od razu wygenerować odpowiedź dla klienta - do tego potrzebny jest LLM. Model decyzyjny może za to wybrać, który szablon odpowiedzi zastosować.
- Gdy zgłoszeń jest kilka dziennie - prościej poprosić duży model o JSON (opisuję to w tekście Structured output).
- Gdy działy zmieniają się co tydzień i nie masz historii z etykietami - nie ustawisz wtedy sensownych progów.
Źródła
- Strands Decider 2B na Hugging Face
- strands-labs/strands-decider na GitHubie
- Skrypt przetestowałem 5.10.2026 na RTX 4090, Windows 11, Python 3.12, PyTorch 2.11, transformers 5.18, strands-decider 0.1.0.
Cały tydzień w AI, w jednym mailu
Wybrane premiery, narzędzia i analizy. Raz w tygodniu, prosto do skrzynki.
Zapisz się za darmo →Najczęstsze pytania
Jak zainstalować Strands Decider 2B do routingu zgłoszeń w Pythonie?
Wystarczy uruchomić pip install strands-decider, a przy pierwszym uruchomieniu pobrane zostanie ok. 4,4 GB danych. Na Windowsie instalator pip może podmienić PyTorch na wersję bez CUDA, co sprawdzisz poleceniem python -c "import torch; print(torch.cuda.is_available())".
Jaki próg pewności ustawić, żeby zgłoszenie trafiło do człowieka, a nie do działu?
W skrypcie domyślny próg to 0,60 - zgłoszenia z pewnością poniżej tej wartości trafiają do kolejki do_czlowieka. Autor zaznacza, że to punkt startu, a właściwy próg należy wyznaczyć na 50-100 własnych oznaczonych zgłoszeniach.
Czy model decyzyjny działa bez karty graficznej NVIDIA?
Tak, model działa na procesorze, tylko wolniej - w kodzie wystarczy zmienić parametr device z "cuda" na "cpu". Model zajmuje niecałe 4 GB pamięci karty, gdy działa na GPU.
Ile kosztuje użycie Cloudflare Clef zamiast lokalnego Strands Decider?
Workers AI pobiera 0,09 USD za milion tokenów wejścia i oferuje darmowy przydział dzienny, ale wymaga konta Cloudflare. Lokalnie Clef-flash był nieco szybszy niż Strands Decider (112 ms vs 135 ms), lecz potrzebuje 18 GB pamięci karty.
