Downloads · 30 days
78
100% of all-time downloads
NagaYu/unquote-small
unquote-small is a token classification model from NagaYu. Use it when you need labels on individual words, such as names. It is set up for unquote. The card lists the license as apache-2.0.
Strip the quoted history off an email without deleting the part you wrote.
Downloads · 30 days
78
100% of all-time downloads
All-time downloads
78
Public
Repo size
281 MB
Likes
0
Public
Click a slice to open those files.
.data124 MB · 39%
From the Hugging Face model README
Strip the quoted history off an email without deleting the part you wrote.
Quoted replies, signatures and legal boilerplate are most of a mail thread and none of its meaning. Removing them before you pay a model to read the thread is easy money — right up until the cleaner eats a paragraph the sender actually wrote, and you have no way to notice.
unquote-small is a 31M-parameter CPU model that tags every line of an email
as one of eight zones. It is built around one asymmetry: leaving quoted text
in costs you tokens; taking body text out costs you the message. Every
decision is biased toward keeping.
Measured on 1,200 held-out messages (synthetic + real public mailing-list mail). Splits are disjoint by thread, so no reply shares a split with the parent it quotes.
| System | body loss ↓ | macro F1 ↑ | token reduction | ms/msg |
|---|---|---|---|---|
| talon (rule-based standard) | 4.178% | 0.592 | 38.2% | 0.5 |
| email-reply-parser (rule-based) | 8.207% | 0.719 | 47.5% | 0.1 |
| Unquote (fp32, gated — shipped default) | 0.008% | 0.844 | 35.9% | 17.6 |
| Unquote (int8 ONNX) | 0.008% | 0.844 | 35.9% | 6.4 |
| Unquote (fp32, gate off) | 1.176% | 0.951 | 42.3% | 19.0 |
| Unquote heuristic (no weights) | 0.243% | 0.337 | 37.2% | 2.1 |
body loss is the fraction of true BODY lines a system deletes — the number
that decides whether a cleaner is safe to run unattended.
| System | en | fr | de | ja | ru | pt | ko | es | nl | it |
|---|---|---|---|---|---|---|---|---|---|---|
| talon (rule-based standard) | 0.510 | 0.568 | 0.459 | 0.480 | 0.467 | 0.458 | 0.459 | 0.387 | 0.523 | 0.299 |
| email-reply-parser (rule-based) | 0.700 | 0.756 | 0.600 | 0.751 | 0.632 | 0.747 | 0.612 | 0.669 | 0.662 | 0.677 |
| Unquote (fp32, gated — shipped default) | 0.823 | 0.873 | 0.874 | 0.906 | 0.914 | 0.899 | 0.866 | 0.846 | 0.873 | 0.879 |
| Unquote (int8 ONNX) | 0.825 | 0.873 | 0.879 | 0.905 | 0.914 | 0.899 | 0.866 | 0.846 | 0.874 | 0.878 |
| Unquote (fp32, gate off) | 0.955 | 0.973 | 0.969 | 0.997 | 0.984 | 0.973 | 0.948 | 0.964 | 0.975 | 0.989 |
| Unquote heuristic (no weights) | 0.229 | 0.226 | 0.266 | 0.411 | 0.252 | 0.265 | 0.194 | 0.255 | 0.256 | 0.230 |
Scored on the synthetic split because its labels are correct by construction. Non-English real mail in the evaluation corpus has pattern-derived gold for the trailer zones, which would be circular to score a rule-based baseline against.
Rule-based extractors carry English rule tables; this model has no such centre of gravity. English-vs-rest macro-F1 gap: talon +0.049, Unquote −0.059.
from unquote import ZoneTagger
tagger = ZoneTagger.from_pretrained("NagaYu/unquote-small")
print(tagger.clean(raw_email)) # body only
pip install "unquote[export] @ git+https://github.com/NagaYu/unquote"
unquote clean mail.txt --keep body,attribution
Files in this repo: model.pt (torch), model.onnx (int8, 6.4 ms/msg),
model.fp32.onnx, tokenizer.json, config.json, gate.json (the fitted
safety thresholds), sweep.json (the full calibration curve).
It will not delete your body text, bought in four places you can inspect:
| Mechanism | Strength |
|---|---|
BODY is not in the removable set; --keep quote still keeps body | structural |
The gate only ever moves a label toward BODY | structural, property-tested |
At gate threshold 1.0 nothing is removed at all | mathematical |
| Per-zone thresholds fitted for zero body loss on held-out calibration data | empirical |
That last row is the honest one. A confidence gate cannot stop a confidently wrong prediction. On deliberately damaged mail at threshold 0.999 the measured body loss was mean 0.0005 / worst 0.0625, affecting 1 message in 120. The claim is "fitted to zero body loss and measured on held-out data", not an impossibility proof.
It does not promise a perfect split. Leftover quoted lines are an accepted outcome of that trade. If you need every quoted byte gone and can tolerate losing prose, this is the wrong tool.
The gate ships fitted for zero body loss. That is one point on a measured curve:
| body-loss budget | threshold | measured body loss | token reduction |
|---|---|---|---|
| 0.00% | 0.9999 | 0.000% | 34.7% |
| 0.05% | 0.99 | 0.045% | 52.0% |
| 0.20% | 0.915 | 0.193% | 53.4% |
| 0.50% | 0.705 | 0.499% | 54.2% |
| 1.00% | 0 | 0.728% | 54.7% |
The curve is steep — accepting roughly one lost line in 2,200 nearly doubles the
savings. Move along it with --threshold, or refit with
scripts/train.py --target-body-loss 0.002.
Cost of the strict end: at zero body loss the gate rescues signatures the model
was 89% sure about, so SIGNATURE F1 falls from 0.897 ungated to 0.429 gated.
That is the trade working as designed, but it leaves more signature text in
place than the model is capable of removing.
BODY (never removable), QUOTE, ATTRIBUTION, SIGNATURE, DISCLAIMER,
MOBILE_FOOTER, AUTOREPLY, FORWARD_HEADER.
Two conventions: everything inside a quoted message collapses to QUOTE
(including its signature — a quoted signature is not this author's), and the
introducer line keeps its own zone at every nesting depth.
tokens of one line -> embedding -> shift-mix -> masked mean+max pool
concat 88 structural features (quote depth, script, wrap width, distance from end)
-> transformer over the LINE sequence <- where the evidence actually is
-> per-line emissions + learned transitions
-> Viterbi over (zone, seen_trailer) <- "no body after a signature", enforced
8 document layers, d_model 512, vocab 21,079, 31.1M parameters. Position is encoded from both ends: trailer zones are anchored to the end of a message, so the encoder gets distance-from-end alongside distance-from-start.
Decoding runs Viterbi over the augmented state (zone, seen_trailer), which
latches once a signature/disclaimer/mobile-footer line is emitted; BODY is
-inf in the latched half. No body line can follow a signature no matter what
the network scores — including via the SIGNATURE -> QUOTE -> BODY path that a
pairwise transition ban cannot block.
Public mailing-list archives only — Apache (lists.apache.org), Python
(mail.python.org), GNU (lists.gnu.org) and the localized Debian lists in 15
languages — plus synthetic threads from unquote.compose whose labels are
correct by construction.
Excluded on purpose: Enron and every corpus like it. Freely downloadable, and still a collection of private messages whose authors never consented to publication. Public availability is not consent. The dataset builder enforces a deny-list.
Privacy. Every real message is scrubbed before training: addresses become
RFC 2606 reserved domains, phone numbers move to the +1-555-01xx fictional
range, and every participant's name is replaced — not just the sender's, since
attribution lines and quoted signatures name third parties. Archive-obfuscated
addresses (a <at> b.com) are handled too. Replacements are
structure-preserving, so the tagging task is unchanged.
SIGNATURE/DISCLAIMER is pattern-derived; those rows are
flagged in the benchmark output and are not admissible evidence against a
rule-based baseline.Code, benchmark harness, dataset builder and the four guarantee tests: https://github.com/NagaYu/unquote
Licensed Apache-2.0. Archive content remains under the terms of the archives it came from.