newmindai/gliner2.5-kvkk-tr-v2
gliner2.5-kvkk-tr-v2
GLiNER2.5-multi trained in two stages — first on 100,000 real Turkish legal and KVKK sentences (30 labels, entities only), then on nm-kvkk-pii-6K with Turkish label and relation names — and taken at step 500 of the second stage: half an epoch of synthetic data, enough to bind the full 118-label vocabulary and the relation head without fitting the generator. The best of our models on the real notary documents and on relation extraction, and within a point of the best on the broad real set.
Part of the nm-kvkk release (https://github.com/newmindai/nm-kvkk): training recipes, the patched GLiNER2 library, the synthetic-data generator and the evaluators behind every number below.
Model details
Training recipe
Stopping the second stage early is deliberate: the final checkpoint of the same run fits the synthetic generator more and loses ground on real documents with the whole schema; this checkpoint is the one that beats the v1 reference on both entities and relations on the notary set.
How to use
This checkpoint loads and runs with the upstream gliner2 package (tested with gliner2 2.0.0 from PyPI). The patched library in the nm-kvkk repository is only needed to reproduce training (bf16 loss fix, non-finite-gradient guard) or to run the Mursit-based sibling model.
Out of the box — extract_kvkk.py ships in this repository: text in, JSON keyed by taxonomy ids out, the label names handled for you.
pip install gliner2 huggingface_hub
python extract_kvkk.py --model newmindai/gliner2.5-kvkk-tr-v2 document.txt # 19 KVKK fields + person and company names
python extract_kvkk.py --model newmindai/gliner2.5-kvkk-tr-v2 --relations all document.txt # ... plus every relation type
python extract_kvkk.py --model newmindai/gliner2.5-kvkk-tr-v2 --labels all --relations all document.txt # the whole taxonomy
python extract_kvkk.py --model newmindai/gliner2.5-kvkk-tr-v2 --text "Ahmet Yılmaz, TC kimlik numarası 12345678901, ..."(get the script with huggingface_hub.hf_hub_download("newmindai/gliner2.5-kvkk-tr-v2", "extract_kvkk.py") or from the repository.)
In your own code:
import json
from gliner2 import AutoExtractor
from huggingface_hub import hf_hub_download
repo = "newmindai/gliner2.5-kvkk-tr-v2"
model = AutoExtractor.from_pretrained(repo)
# kvkk_schema.json ships with the model: every entity label and relation type, with the exact query
# names this model was trained on, so you never need to know whether it expects Turkish or English names.
S = json.load(open(hf_hub_download(repo, "kvkk_schema.json"), encoding="utf-8"))
ent_name = {e["id"]: e["name"] for e in S["entities"]} # taxonomy id -> query name
rel_name = {r["id"]: r["name"] for r in S["relations"]}
rel_desc = {r["name"]: r["description"] for r in S["relations"]}
# ask for what you need — a shorter prompt is faster and, for these models, more accurate
wanted = S["subsets"]["stack21"] # or S["subsets"]["kvkk19"], or any list of ids
relations = ["national_id_of", "phone_of", "email_of"] # ids from S["relations"]
schema = model.create_schema().entities([ent_name[i] for i in wanted])
if S["relation_prompt"] == "desc":
schema = schema.relations({rel_name[r]: rel_desc[rel_name[r]] for r in relations})
else: # this model works best with bare relation names
schema = schema.relations([rel_name[r] for r in relations])
out = model.extract(text, schema, threshold=0.5, include_confidence=True, include_spans=True)
out["entities"] # {query name: [{text, start, end, confidence}]}
out["relation_extraction"] # {query name: [{head, tail}]}
# map the query names in the output back to taxonomy ids
name_to_id = {v: k for k, v in ent_name.items()}
entities = {name_to_id[n]: spans for n, spans in out["entities"].items()}kvkk_schema.json fields: entities[] = {id, name, group, out_of_scope, trained}, relations[] = {id, name, description}, subsets (the 19 KVKK fields and the 21-label set used in the benchmarks), label_language and relation_prompt (the prompt shape this model works best with). The same ids are used by every model of the release, so switching model only changes the names the file maps them to. The full listing is in the section below.
Benchmark results
All models below were run through the same pipeline and scored by the same code (see the repository). Scores are strict character-offset precision / recall / F1 (start, end and label must all match), micro over spans; macro F1 averages the per-label F1 over the labels present; lenient F1 accepts an overlapping span with the right label (boundary conventions differ between word-level taggers and span models). Every model is queried with the 21 labels all of them can express (19 KVKK fields + person name + company name); gold is restricted to those labels. Threshold 0.5 for the GLiNER2 models.
mixed_v2 — 118 real documents, 19 document families (1323 gold spans in the 21 shared labels, whole-document input)
vekaletname — 20 real notary documents (188 gold spans in the 21 shared labels, whole-document input)
*nm-kvkk-pii-6K test — 550 synthetic documents (in-distribution for models trained on nm-kvkk-pii-6K, marked )** (2289 gold spans in the 21 shared labels, whole-document input)
Full taxonomy and relation extraction
The same model queried with every label of the taxonomy it was trained with and every relation type, whole document in one pass — the way it is meant to be deployed. Relation F1 is strict on type and both end-points, coreference respected; the type-filtered column applies training-derived type constraints as a post-filter.
Limitations
- Trained on synthetic Turkish documents (plus, for the MIX100k models, real sentences); scores on real documents are 10–25 points below the synthetic test split. The real-document sets used here are small (20 and 118 documents).
- Company and institution names are the hardest label for every model in this family.
- Sentence-level input hurts: feed whole documents or paragraphs, not individual sentences.
- Pseudonymised real data was used for evaluation only; no real personal data was used in training.
Citation
NewMind AI, 2026 — nm-kvkk: Turkish KVKK personal-data extraction with GLiNER2.5, https://github.com/newmindai/nm-kvkk.
