GLiNER2.5-Decide · call actions
Reads a client's call-centre notes and picks the next action: call_now, call_later, send_email or
do_not_contact. A 486M-parameter fine-tune of Fastino's GLiNER2.5-Decide
that runs on a CPU at about 0.2 s per client.
It was trained in a recorded coding session: Claude Code on DeepSeek V4.1 Flash, served by CoralBricks, given a call-scoring model, a CRM notes export and 2,983 labelled examples, and asked to put a small decision model on top. The session and every revision are on the use case page.
Use
pip install "gliner2[local]" huggingface_hub
from datetime import date
import sys
from gliner2 import AutoExtractor
from huggingface_hub import snapshot_download
repo = snapshot_download("coral-bricks-ai/gliner2.5-decide-call-actions")
sys.path.insert(0, repo)
from decide import LABEL_DESCRIPTIONS, LABELS, render_client # the input format the model was trained on
model = AutoExtractor.from_pretrained(repo)
notes = [{"date": "2026-10-27", "note": "Spoke to her, interested but away. Ring after the 5th of Nov"}]
text = render_client(notes, call_day=date(2026, 10, 30))
print(model.classify_text(text, {"action": {"labels": LABELS, "label_descriptions": LABEL_DESCRIPTIONS}}))
# {'action': 'call_later'}
Use render_client from decide.py: the model was trained on its format (the call day, then the client's notes oldest
first, each dated relative to the call day, with any date a note mentions resolved and marked as before or after the call
day). example.py runs four short histories.
For a whole day's list, decide.py reads a CRM export and a scores file and writes client_id,action,call_rank, ranking
the call_now clients by your own score, highest first:
python decide.py example/notes.csv example/scores.csv out.csv --today 2026-10-30
notes.csv has client_id,date,note (other columns are ignored, a UTF-8 byte-order mark is fine); scores.csv has
client_id,probability. decide.py adds two rules after the model: a keyword guard that forces do_not_contact on any
note that reads like an opt-out, and a rule that moves call_later to call_now when no note names a future date.
See the results below before keeping them.
Labels
| Label | Meaning |
|---|---|
call_now |
Nothing stops a call: interested, asked for a call now, a requested date has arrived or passed, or no notes yet |
call_later |
Asked to be called on or after a date that is still in the future on the call day |
send_email |
Wants email or something in writing instead of a phone call |
do_not_contact |
Opted out, wrong number, the account holder died, or a vulnerable customer: never call |
Results
Measured on 637 labelled clients (1,168 notes) that the model never saw, written separately from the training data: 560 histories by a different writer with a different brief and style list, with any history that echoed a training note removed, and 77 written by hand. CPU only.
| Accuracy | Macro-F1 | Opt-outs recognised | |
|---|---|---|---|
| The model alone | 0.978 | 0.979 | 162 of 162 |
decide.py (the model plus its two rules) |
0.954 | 0.956 | 162 of 162 |
| The untuned base model in our reference pipeline, for comparison | 0.730 | 0.736 | 156 of 162 |
Per label, the model alone: call_now 0.970, call_later 0.957, send_email 0.992, do_not_contact 0.997 (F1).
Most of its 14 errors are call_later read as call_now (10).
The rules in decide.py cost accuracy on this test: the future-date rule turned 10 more call_later clients into
call_now, and the keyword guard blocked 5 clients who had not opted out. They never missed an opt-out either. If a
wrongly blocked client costs you less than a wrongly called one, keep the guard; otherwise the model alone is the better
choice here.
The session's own report gives 0.995 to 1.000 on held-out clients; those were written by the same writer as the training notes, so the numbers above are the ones to expect on new text.
Training
- Data: 2,983 client histories (6,442 notes) labelled
call_now1,063,call_later611,send_email566,do_not_contact743, plus the call day. Fictional: written for this demo by an LLM across 24 scenarios and 8 writing styles (agent shorthand with typos, dialler auto-notes, multi-line notes with mixed signals, third parties), labelled by fixed rules, with contradictory, duplicate and contact-detail-looking histories removed. No real people. - Method: LoRA (rank 16, alpha 32) on all module groups, encoder learning rate 5e-5, task layers 5e-4, batch 8 with 2 accumulation steps, bf16, 384 tokens, 3 epochs on 80% of the labelled clients, on one NVIDIA L4 in a few minutes; the adapter is merged into the full weights here.
Limitations
- English only, and trained on fictional notes: check it on a sample of your own notes before relying on it.
- Not a substitute for a legal suppression list (TPS, your own opt-out register): keep checking those separately.
- Relative dates ("after the 15th", "in a fortnight") are resolved by
render_clientfrom each note's date; an unusual phrasing may be missed. - It decides what to do, not who is most likely to buy: the ranking comes from your own scores.
License
Apache-2.0, as the base model. Base model: fastino/GLiNER2.5-Decide
by Fastino. decide.py is the session's own, with one change: it looks for the weights next to itself.