topic_scope[border]
The topic_scope detector for border, an embeddable library that inspects the text going into and coming out of an LLM and returns a structured decision plus an audit-grade evidence record.
flowxai/topic-scope on the hub. It is one detector of 28, and it is not a general purpose topic_scope classifier: it was trained for this library's policy, is read at the operating point below, and reports through the evidence record rather than returning a bare score.
This card is generated from the evaluation and export artifacts of the training run, so every number on it is reproducible from this repository rather than asserted.
What it is
- Base model: FacebookAI/xlm-roberta-base
- Head: single_label_classification
- Labels: not recorded
- Artifact:
onnx/model.int8.onnx, 533 MB, opset 17 - Trained at: 96 tokens
Operating point
This head is read with argmax and has no threshold.
How to use it
Through the library, which is what this model is for. It loads the artifact below, applies the operating point above, and returns a decision with an evidence record rather than a bare score.
pip install flowx-border
# policy.yaml
policy_id: default
version: 1
detectors:
topic_scope:
enabled: true
on_fail: flag
from flowx_border import load_policy, scan_input
policy = load_policy("policy.yaml")
decision = scan_input(user_text, policy)
print(decision.verdict) # allow | flag | redact | block
print([f.label for f in decision.findings if f.detector_id == "topic_scope"])
print(decision.evidence.record_id)
This detector reads the input side, so scan_input is where it fires. It is T3, so it runs only when a lower tier flags, or when the policy sets always: true. Its budget is 300 ms at 87 tokens on one CPU thread.
The weights are fetched once and cached, and a scan needs no network after that. Nothing here calls out to a hosted model, and the evidence record carries hashes rather than your text.
Without the library
The artifact is plain ONNX, so it will load in onnxruntime directly. Two things you then own yourself, and they are the reason the library exists: the operating point above is not in the graph, and neither is the chunking. Inputs longer than the trained window have to be split and recombined, or the scores past it are extrapolation.
import onnxruntime as ort
from huggingface_hub import hf_hub_download
from tokenizers import Tokenizer
repo = "flowxai/topic-scope"
session = ort.InferenceSession(hf_hub_download(repo, "onnx/model.int8.onnx"))
tokenizer = Tokenizer.from_file(hf_hub_download(repo, "tokenizer.json"))
Per language
Per language rather than an aggregate, because an aggregate across 26 languages hides the tail and the tail is the point.
| Language | Support | P | R | F1 | Note |
|---|---|---|---|---|---|
az Azerbaijani |
0 | 0.000 | 0.000 | 0.000 | |
bg Bulgarian |
0 | 0.000 | 0.000 | 0.000 | |
cs Czech |
0 | 0.000 | 0.000 | 0.000 | |
da Danish |
0 | 0.000 | 0.000 | 0.000 | |
de German |
0 | 0.000 | 0.000 | 0.000 | |
el Greek |
0 | 0.000 | 0.000 | 0.000 | |
en English |
0 | 0.000 | 0.000 | 0.000 | |
es Spanish |
0 | 0.000 | 0.000 | 0.000 | |
et Estonian |
0 | 0.000 | 0.000 | 0.000 | |
fi Finnish |
0 | 0.000 | 0.000 | 0.000 | |
fr French |
0 | 0.000 | 0.000 | 0.000 | |
ga Irish |
0 | 0.000 | 0.000 | 0.000 | |
hr Croatian |
0 | 0.000 | 0.000 | 0.000 | |
hu Hungarian |
0 | 0.000 | 0.000 | 0.000 | |
it Italian |
0 | 0.000 | 0.000 | 0.000 | |
lt Lithuanian |
0 | 0.000 | 0.000 | 0.000 | |
lv Latvian |
0 | 0.000 | 0.000 | 0.000 | |
mt Maltese |
0 | 0.000 | 0.000 | 0.000 | not in base model pretraining |
nl Dutch |
0 | 0.000 | 0.000 | 0.000 | |
pl Polish |
0 | 0.000 | 0.000 | 0.000 | |
pt Portuguese |
0 | 0.000 | 0.000 | 0.000 | |
ro Romanian |
0 | 0.000 | 0.000 | 0.000 | |
sk Slovak |
0 | 0.000 | 0.000 | 0.000 | |
sl Slovenian |
0 | 0.000 | 0.000 | 0.000 | |
sv Swedish |
0 | 0.000 | 0.000 | 0.000 | |
tr Turkish |
0 | 0.000 | 0.000 | 0.000 |
Weakest languages
Published rather than dropped. A coverage table with the bad rows removed is not a coverage table.
azAzerbaijani: F1 0.000bgBulgarian: F1 0.000csCzech: F1 0.000
Quantisation
The published artifact is INT8, and only the embedding table is quantised.
Quantising everything is what most examples do and it does not work for this base model. Measured on 300 real test texts at the detector's own threshold:
| Recipe | Size | Mean logit drift | Decisions changed |
|---|---|---|---|
| all ops (the usual default) | 279 MB | 0.68 | 51 / 300 |
| MatMul only | 856 MB | 0.64 | 48 / 300 |
| Gather only, what ships here | 535 MB | 0.0036 | 0 / 300 |
The embedding table carries the whole size win at no accuracy cost, while quantising the encoder MatMuls changes one decision in six to save 256 MB. XLM-RoBERTa has large activation outliers and per-tensor dynamic quantisation of activations is exactly what they defeat.
For this artifact specifically: ? of 200 decisions differ from the fp32 checkpoint, mean logit drift 0.0000, read as argmax. A quantised model that answers differently is a different detector, so this is measured rather than assumed.
Limitations
- Synthetic training data. Generated natively per language, never translated from English, so the sentence structure is the target language's own. It is still synthetic, and a production distribution will differ.
- Maltese is absent from XLM-RoBERTa's pretraining set. That is a fact about the base model, and it is not an explanation for a weak score. This card said "no amount of data fixes that" until 2026-08-14, which this project's own measurement disproves: the
nsfwdetector scored 0.000 in Maltese, was blamed on the base model, and went to 1.000 with perfect precision and recall when its corpus went from 2 positives per language to 10. Nothing about the model changed. So where a language scores badly here, read the support column first. - This is not a compliance product. It produces evidence about controls that were applied. It does not make anyone compliant with anything, and the obligations under the EU AI Act sit with the provider or deployer of a system, not with a model or a library.
Licence
Apache-2.0, declared in the metadata above as well as here, so that a tool reading the repository can attest it rather than a human having to read prose.
- Downloads last month
- 11