この記事でやったこと
- メールの優先度(urgent/high/medium)判定タスクで、3種類の方法を比較
- ローカルで動くDecision Model(Laya・Clef・Clef-Flash)
- 生成LLM(Qwen3.5)
- 参照基準の古典的な分類器(TF-IDF特徴量+ロジスティック回帰。調整用の782件で学習)
- 元のメール(英語は平均約500字)で、精度と速度を測定
- 英語の評価用データ3,123件と、その一部を日本語に訳した100件
- 件名+本文/本文のみ/件名のみの3通りの入力
- ラベルの説明文の書き方が、精度にどれだけ効くかを測定
- すべてRTX 5090 1枚で実行
TL;DR(分かったこと)
前提:使ったデータでは、優先度の最上位ラベル「urgent」の中身は、ほぼフィッシングメールです。
- 英語の元のメールでは、ゼロショット(例を見せない判定)のモデルは、どれも782件で学習したTF-IDF分類器に届かない。最良のQwen3.5-9Bが0.956、TF-IDFが0.983(Macro-F1)。
- Clef-FlashはQwen3.5-9Bの約4.8倍速い。代わりにMacro-F1は0.064低い。
- ラベルの説明文を「一般的な緊急度」に変えると、Macro-F1は0.30〜0.37下がる(調整用データ、Clef 27B・Clef-Flash・Qwen3.5-9B)。モデル間の差より大きい。
- 日本語訳(100件)でも、Clef 27B・Qwen3.5-9B・Clef-Flashは英語とほぼ同じ水準(Macro-F1 0.88〜0.91)。
- 英語では、件名を消しても主要なモデルの成績はほぼ変わらない。日本語では、Qwen3.5-9Bだけが下がる(0.909→0.848)。

図1 英語の評価用データでのMacro-F1と、元のメール1通あたりのレイテンシ中央値(ms、対数軸)。灰色はTF-IDF(CPU上のプロセス内呼び出し)。TF-IDFは最も速く最も高い。Clef-FlashはQwen3.5-9Bの約4.8倍速く、0.892に届く
対象読者
- メールやチケットの振り分けを、ローカルGPU上で済ませたい人
- llama.cppのDecision Model(選択肢ごとの確率を返すモデル)と、生成LLMにJSONで答えさせる方式のどちらを使うか迷っている人
この記事は前編です。メールに無関係な段落(背景文)を足した長文での精度と、入力が長いときの速度・GPUメモリは、後編で扱います。
注意点:評価に使ったのはAIが生成した合成データで、ラベルが人間の判断と合うかは検証していません(Claudeによる確認のみ)。実験コードは公開していません。結果に影響するプロンプトと設定は、末尾の「再現に必要な設定」にすべて載せています。
本記事の用語は次のとおりです。
-
urgent/high/medium:メールの優先度のラベル。このデータでは次の意味で使われている。
- urgent:至急の行動を迫る、不審・詐欺的なメール(フィッシング)。アカウント停止や課金をちらつかせて、リンクを開かせる。
- high:仕事で対応が必要な正規のメール。顧客対応のトラブル、経理・財務の承認や期限、会議の依頼。
- medium:個人的なやりとりと、旅行の連絡。返信は求めるが、仕事上の緊急性はない。
-
Decision Model:文章を生成せず、入力を1回読んで各選択肢の確率を返す専用モデル1。生成トークンが要らないぶん速い。本記事ではLaya(convaiinnovations)とClef/Clef-Flash(Cloudflare)を、llama.cppの
/v1/systemoneかLaya SDKから呼ぶ。 - ラベルの説明文:モデルに渡す、各ラベルの意味の説明。モデルは例を見ず、この説明だけで判定する(ゼロショット)。
- TF-IDF:文字の並び(2〜4文字)の出現頻度から文章を数値の特徴量にする古典的な方法。それ自体は判定をしない。本記事では、この特徴量とロジスティック回帰を組み合わせ、調整用データで学習した分類器を略して「TF-IDF」と呼ぶ。
- urgent再現率:urgentメールのうちurgentと判定できた割合。1から引いたものが見逃し率。
- FPR(誤警報率):urgentでないメールをurgentと判定した割合。
- Macro-F1:3つのラベル(urgent/high/medium)それぞれのF1の平均。
- 調整用データ/評価用データ:データの分割。調整用(782件)でラベルの説明文を決め、TF-IDFを学習する。評価用(3,123件)で最終評価する。
比べた条件
前編・後編で、次の条件を比べています。前編は「元のメール(背景文なし)」の回です。
| 比較の軸 | 前編(元のメール) | 後編(長いメール) |
|---|---|---|
| 長さ | 元のまま(英語で平均約500字) | 背景文を足して256〜2,048トークン相当 |
| 言語 | 英語3,123件、日本語訳100件 | 英語・日本語訳とも元メール100件から作成 |
| 件名 | 件名+本文/本文のみ/件名のみ | 件名あり/件名なし |
| ラベルの説明文 | データに合わせた説明/一般的な緊急度 | データに合わせた説明のみ |
| 本文の切れ | なし | 先頭256トークンだけを渡す条件 |
| 速度 | 元のメール | 入力長ごと、GPUメモリも |
実験環境
表1 実験環境
| 項目 | 内容 |
|---|---|
| GPU | NVIDIA GeForce RTX 5090(VRAM 32GB、電力上限575 W) |
| CPU・メモリ | AMD Ryzen 9 9950X3D2(16コア)、31GiB(WSL2に割り当てられた量) |
| OS | Windows上のWSL2(Ubuntu 26.04.1 LTS、カーネル6.18.40.1-microsoft-standard-WSL2) |
| ドライバ・CUDA | NVIDIAドライバ616.92(CUDAドライバAPI 13.4)、CUDA Toolkit 12.8 |
| 推論エンジン | llama.cpp v0.6.0(commit d812350)2を、GCC 14.4.0でCUDA有効・sm_120 向けにソースビルド |
| Python環境 | Python 3.12.15、PyTorch 2.11.0+cu128、Laya SDK 0.4.2(transformers 5.19.0)、scikit-learn 1.9.1。公式実装との照合だけtransformers 5.10.2 |
| 測定条件 | 並列数1の逐次処理。他のGPUジョブは速度測定のあいだ停止 |
| 実行日 | 2026-10-10〜11 |
速度はWSL2上で測っているので、絶対値は他の環境にそのまま当てはまりません。同じ環境の中での比較として読んでください。
比較したもの
表2 比較したモデル
| 表示名 | 方式 | サイズ | エンジン・量子化 | チェックポイント |
|---|---|---|---|---|
| Laya(英語) | Decision Model | 421M | llama.cpp・BF16 | ggml-org/Laya-GGUF |
| Laya(多言語) | Decision Model | 322M | Laya SDK・FP16 | convaiinnovations/laya |
| Clef-Flash 9B | Decision Model | 9B | llama.cpp・Q4_K_M | ggml-org/Clef-Flash-GGUF |
| Clef 27B | Decision Model | 27B | llama.cpp・Q4_K_M | ggml-org/Clef-GGUF |
| Qwen3.5-0.8B | 生成LLM | 0.8B | llama.cpp・Q4_K_M | unsloth/Qwen3.5-0.8B-GGUF |
| Qwen3.5-9B | 生成LLM | 9B | llama.cpp・Q4_K_M | unsloth/Qwen3.5-9B-GGUF |
GGUFはllama.cppで動かすためのモデルファイルの形式、Q4_K_Mは4ビット量子化、BF16・FP16は量子化しない16ビット精度です。
- Clef-FlashはQwen3.5-9Bから、Clef 27BはQwen3.8-27B(比較したQwen3.5とは別世代)から追加学習したモデルです。
- 入力は全モデル共通の
Subject: …とBody: …です。Decision Modelは1回で複数の質問に答えられますが、今回は優先度の質問1問だけを渡しました。 - Qwenは2つの出力方式(自由生成のJSON、JSON schemaで制約)で測りましたが、出力が全件で同一だったので、1系列として扱います。
- Laya(多言語)は、日本語の評価だけで使います。英語の評価用データ3,123件では測っていません。
Qwenの推論設定
- 毎リクエスト
enable_thinking=false、temperature=0、seed=42、max_tokens=32、cache_prompt=falseを指定しました。出力にthinkブロックが無いことは確認済みです。 - 自由生成のJSONは厳密にパースしました(救済なし)。JSON schemaのenum制約でも、出力は全件で同一でした。
GGUF版のClef-Flashは、Cloudflareの公式PyTorch実装(BF16)3と調整用データ30件で照合し、29件で判定が一致しました。
GGUF版の照合の詳細
- 照合した理由:「GGUFにはjoint head(答えを出す専用の出力層)が無い」という第三者の指摘4がありました。そのため、公式実装と結果が変わるおそれがありました。
- 確率の差は中央値0.006、最大0.065で、量子化の範囲に収まっています。
- Clef 27BはBF16で54GBあり、RTX 5090(32GB)に載らないため、照合していません。
データセットの準備
元にしたのはweijianzhg/email-triage-action-seedです(3,905件、Claude Opus 4.6による合成、Apache-2.0)5。英語のメールに、優先度とカテゴリが付いています。
このデータを、調整用(782件)と評価用(3,123件)に分けました。調整用は、ラベルの説明文を決めることと、TF-IDFの学習に使います。評価用は、最後の評価にだけ使います。似たメールが両方に入らないように分けています。
表3 データの例(カテゴリごとに1件。件数は全3,905件の内訳)
| 優先度 | カテゴリ | 件数 | 件名の例 |
|---|---|---|---|
| urgent | phishing(フィッシング) | 500 | Your Zoom account will be deactivated - Verify within 6 hours |
| high | customer_support(顧客対応) | 577 | URGENT: Order #78432 charged twice - need immediate refund |
| high | finance(経理・財務) | 767 | Payroll Funding Shortfall - Tomorrow's Run |
| high | meeting_request(会議の依頼) | 887 | Urgent: Need to schedule board presentation review - Tomorrow? |
| medium | personal(個人) | 821 | Can you water my plants next week? |
| medium | travel(旅行) | 353 | Booking conflict: Two trips overlapping on your calendar |
urgent(phishing)のメールを1件、モデルに渡す形のまま示します(URLだけ無効化しています)。
Subject: Your Zoom account will be deactivated - Verify within 6 hours
Body:
Dear User,
Due to our recent terms of service update, all accounts must be re-verified.
Your Zoom account (linked to this email) will be permanently deactivated
if not verified within 6 hours.
All scheduled meetings, recordings, and contacts will be lost.
Verify your account: hxxp://zoom-account-verify[.]click/reactivate
Thank you for your cooperation.
Zoom Trust & Safety Team
表3の件名の例では、highの件名にURGENTが付き、urgentの件名には付いていません。件名にURGENT/ASAP/verify/expireなどの緊急表現が入る率は、urgentで44%、highで20%です。件名の緊急表現だけでは、urgentとhighを見分けられません。
優先度は、カテゴリで完全に決まります。
- urgentはphishing(フィッシング)だけ
- highはcustomer_support・finance・meeting_request
- mediumはpersonal・travel
カテゴリだけから優先度を当てる分類器は、評価用データでMacro-F1 1.000になります。「重要メール(urgent)の見逃し」は、実質「フィッシング・詐欺メールの見逃し」です。 本物のシステム障害や訴訟リスクのような緊急の仕事メールは、customer_supportなどのカテゴリとして high に入っています。
日本語のデータは、評価用データから元メールを100件選び(urgent 50/high 25/medium 25)、Claude Opus 5.5で訳して作りました。意味・期日・依頼・金額・URL・固有名詞を変えないことを条件にしています。訳文がラベルを変えていないかは、翻訳に使っていないClaude 2体(Haiku 5.5・Sonnet 5.5)に原文と訳文を渡して確かめました。優先度と事実の保持は、100件すべてで問題ありませんでした。後編の長文は、この100件(英語の原文と日本語訳)に背景文を足して作ります。作ったデータはHugging Faceで公開しています。
ラベルの説明文
モデルには、各ラベルの意味を説明した文を渡します。説明文は、調整用データの事例だけを見て書きました。urgentは「至急の行動を迫る、不審または詐欺的なメッセージ」です。比較のため、「即時対応が必要か」を基準にした、一般的な緊急度の説明文も用意しました。
説明文の全文
モデルには次の英文をそのまま渡しています。指示文は両方とも "Classify the priority of this email using the fixed rubric." です。
データに合わせた説明文:
- urgent: Suspicious or fraudulent message that pressures the recipient to act immediately (verify an account, click a link, pay a fee, confirm a transfer) under threat of suspension, loss or charges, typically impersonating a known company or service.
- high: Legitimate work email that needs action from the recipient or their team: customer-support escalations, finance and accounting approvals or deadlines, and business meeting requests.
- medium: Personal correspondence or travel logistics (family and friends, social plans, itineraries, bookings); may ask for a reply but has no business-critical consequence.
一般的な緊急度の説明文:
- urgent: Needs immediate attention: an ongoing incident, an imminent deadline, or serious harm if not handled right away.
- high: Requires action soon or has a stated deadline, or a serious consequence if ignored.
- medium: Needs a response or action but without strong time pressure.
説明文の書き方の影響
2つの説明文を、調整用データで比べました(図2)。一般的な緊急度の説明文にすると、Clef 27B・Clef-Flash・Qwen3.5-9BではMacro-F1が0.30〜0.37下がります。この下げ幅は、データに合わせた説明文でのモデル間の差(Laya 0.74〜Qwen3.5-9B 0.96の0.22)より大きくなりました。以降の結果はすべて、データに合わせた説明文を使っています。
図2 説明文の違いによる、調整用データ(782件)でのMacro-F1。一般的な緊急度の説明文にすると、全モデルで下がる
判定の目安
実用の目安として、「フィッシングの見逃しが5%以下(urgent再現率0.95以上)」かつ「誤警報が5%以下(FPR 0.05以下)」を、結果を見る前に決めました。後編で長さごとに崩れたかどうかを判断するときも、この目安を使います。
英語の結果
どのモデルも、調整用データで学習したTF-IDFに届きませんでした(表4)。「目安」の列は、目安を満たすかどうかです。
表4 英語の評価用データでの結果(3,123件)
| モデル | Macro-F1 | urgent再現率 | FPR | 目安 | TF-IDFとの差 [95%区間] |
|---|---|---|---|---|---|
| Qwen3.5-9B | 0.956 | 0.990 | 0.003 | ✓ | −0.027 [−0.035, −0.020] |
| Clef 27B | 0.916 | 1.000 | 0.028 | ✓ | −0.067 [−0.079, −0.057] |
| Clef-Flash 9B | 0.892 | 1.000 | 0.031 | ✓ | −0.092 [−0.104, −0.079] |
| Laya(英語) | 0.741 | 0.733 | 0.166 | ✗ | −0.242 |
| Qwen3.5-0.8B | 0.449 | 0.422 | 0.014 | ✗ | −0.535 |
| 参照:TF-IDF(件名+本文) | 0.983 | 0.970 | 0.000 | ||
| 参照:TF-IDF(件名のみ) | 0.945 | 0.930 | 0.008 |
TF-IDFとの差は「モデル − TF-IDF(件名+本文)」です。区間を示した3モデルは区間がすべて0を下回り、TF-IDFより低い結果です。目安を満たさない2モデルは、差が大きいため区間を省きました。なお、件名だけで学習したTF-IDF(0.945)は、Qwen3.5-9B(0.956)を下回ります。
これだけで、LLM系が弱いとまでは言えません。このデータは文体と語彙がカテゴリごとに揃っています。文字の並びの頻度という表面的な手がかりだけで解けるほど、易しいデータだと考えられます。
速度
精度と速度の関係は図1のとおりです。元のメール(入力は約250〜365トークン)での、1通あたりのレイテンシ中央値は次のとおりです。
- TF-IDF:約0.5 ms(件名のみは約0.3 ms)
- Laya:約12 ms
- Clef-Flash:40 ms
- Qwen3.5-0.8B:約76 ms
- Clef 27B:約108 ms
- Qwen3.5-9B:約190 ms
Clef-Flashは、同じ9BのQwen3.5-9Bの約4.8倍の速さです。Decision Modelは生成トークンが要らない分、入力が短いほど差が大きくなります。LLMの中ではLayaが最速ですが、精度は目安に届きません。
TF-IDFは、ほかのモデルと同じ30件・同じ手順(ウォームアップ20件、3周)で、同じマシンのCPU(Ryzen 9 9950X3D2)上で1通ずつ測りました。TF-IDFはプロセス内の呼び出しで、HTTPの往復を含みません。LLM側の往復の分は1〜5 msなので、それを差し引いても、TF-IDFは1桁以上速い結果です。
日本語の結果
日本語訳の100件(urgent 50件・非urgent 50件)での結果は表5のとおりです。TF-IDFは英語の調整用データで学習したため、日本語は対象外です。
表5 日本語訳での結果(100件)
| モデル | Macro-F1 | urgent再現率 | FPR |
|---|---|---|---|
| Clef 27B | 0.909 | 1.00 | 0.02 |
| Qwen3.5-9B | 0.909 | 1.00 | 0.02 |
| Clef-Flash 9B | 0.881 | 1.00 | 0.02 |
| Laya(多言語) | 0.508 | 0.80 | 0.18 |
| Laya(英語) | 0.393 | 0.70 | 0.36 |
| Qwen3.5-0.8B | 0.325 | 0.36 | 0.02 |
- Clef 27B・Qwen3.5-9B・Clef-Flashは、英語とほぼ同じ水準で、3つとも目安を満たします。
- Laya(多言語)は、日本語でも目安に届きません。
- 100件なので、1件で再現率・FPRが0.02動きます。モデル間の小さな差は、傾向としての示唆にとどめます。
件名の有無の影響
件名が判定にどれだけ効いているかを見るため、同じデータで、入力を2通りに変えて測りました。
-
本文のみ:
Subject:の行を消し、Body:以降だけを渡す -
件名のみ:
Subject:の行だけを渡す
TF-IDFは、それぞれの入力で学習し直した分類器です。ラベルの説明文とプロンプトは変えていません。結果は表6のとおりです。
表6 入力を変えたときの結果(英語は評価用データ3,123件、日本語は訳した100件)
| モデル | 言語 | Macro-F1 件名+本文 → 本文のみ → 件名のみ |
urgent再現率 件名+本文 → 本文のみ → 件名のみ |
FPR 件名+本文 → 本文のみ → 件名のみ |
|---|---|---|---|---|
| Qwen3.5-9B | 英語 | 0.956 → 0.950 → 0.903 | 0.990 → 0.972 → 0.798 | 0.003 → 0.003 → 0.013 |
| 日本語 | 0.909 → 0.848 → 0.831 | 1.00 → 0.92 → 0.72 | 0.02 → 0.02 → 0.02 | |
| Clef 27B | 英語 | 0.916 → 0.910 → 0.847 | 1.000 → 1.000 → 0.960 | 0.028 → 0.032 → 0.085 |
| 日本語 | 0.909 → 0.909 → 0.861 | 1.00 → 1.00 → 0.94 | 0.02 → 0.02 → 0.06 | |
| Clef-Flash 9B | 英語 | 0.892 → 0.882 → 0.883 | 1.000 → 1.000 → 0.920 | 0.031 → 0.040 → 0.046 |
| 日本語 | 0.881 → 0.888 → 0.855 | 1.00 → 1.00 → 0.92 | 0.02 → 0.06 → 0.08 | |
| Laya(英語) | 英語 | 0.741 → 0.795 → 0.457 | 0.733 → 0.835 → 0.972 | 0.166 → 0.117 → 0.616 |
| 日本語 | 0.393 → 0.350 → 0.476 | 0.70 → 0.68 → 0.96 | 0.36 → 0.36 → 0.66 | |
| Laya(多言語) | 日本語 | 0.508 → 0.466 → 0.496 | 0.80 → 0.88 → 0.92 | 0.18 → 0.50 → 0.42 |
| Qwen3.5-0.8B | 英語 | 0.449 → 0.392 → 0.495 | 0.422 → 0.258 → 0.378 | 0.014 → 0.006 → 0.051 |
| 日本語 | 0.325 → 0.223 → 0.284 | 0.36 → 0.14 → 0.32 | 0.02 → 0.00 → 0.12 | |
| 参照:TF-IDF | 英語 | 0.983 → 0.982 → 0.945 | 0.970 → 0.968 → 0.930 | 0.000 → 0.000 → 0.008 |
英語では、件名を消しても、Clef系・Qwen3.5-9B・TF-IDFはほとんど変わりません。英語の元のメールでは、どれも本文から判定できています。TF-IDFに届かないという結果も、件名の有無に関係なく同じです。
日本語では事情が違い、件名を消すとQwen3.5-9Bに見逃しが出ます(再現率0.92)。日本語の本文だけでは、urgentと言い切れないメールがあるようです。Clef系は再現率1.00のままですが、Clef-FlashはFPRが0.06(50件中3件)になり、目安をわずかに外れます。
件名だけにすると、英日とも、Clef系とQwen3.5-9Bで振る舞いが分かれます。Clef系はurgentと言い続け(再現率0.92〜0.96)、代わりに誤警報が増えます。Qwen3.5-9Bは本文がないとurgentと言いにくくなり(再現率は英語0.80、日本語0.72)、誤警報はほとんど増えません。
Laya(英語)は、英語では件名を消すと成績が上がります。逆に件名だけを渡すと、英日ともほぼすべてをurgentにします(FPR 0.62〜0.66)。件名の緊急表現に強く引っ張られていると考えられます(推測で、原因は確かめていません)。
件名の効き方の違いは、後編の「本文が切れたとき」で効いてきます。
まとめ
元のメールに限ると、選び方は次のようになります。
- ラベル付きの事例が数百件あるなら:まずTF-IDF+ロジスティック回帰を試す価値があります。このデータでは最も強く、学習も一瞬でした。
- 事例がないなら:精度を優先するならQwen3.5-9B(0.956)、速度を優先するならClef-Flash(0.892、約4.8倍速い)です。どちらも目安を満たし、日本語訳でも同じ傾向でした。
- どのモデルでも:ラベルの説明文は、データの事例を見て書く必要があります。一般的な緊急度の説明では、モデル選びの差より大きく崩れました。
- LayaとQwen3.5-0.8B:ゼロショットのままでは目安に届きません。Layaのモデルカードは、自分の領域のデータでfine-tuneすると精度が大きく上がると説明しています6。同カードの課題では、ゼロショットの0.362がfine-tune後に0.766になっています。今回の結果は、これらのモデルの総合能力を示すものではありません。
ただし、メールが長くなると、この順位は入れ替わります(後編)。
限界
- ラベルの意味:urgentはフィッシングを指すため、この結果は一般的な緊急メールの検出性能ではありません。ラベルの説明文は調整用データの事例から書いたため、実質的にはカテゴリの説明文です。
- 日本語は翻訳100件です(Claude Opus 5.5による翻訳)。日本語で新規に書いたメールでの評価は未実施です。
- 確認に使ったのはClaude系だけです。データを作ったAI(Claude Opus 4.6)と同系統で、別の系統のAIや人手による確認は入っていません。
- 比較の交絡:基盤・学習・エンジン・量子化が同時に違うので、サイズだけの効果は主張しません。Clef-FlashとQwen3.5-9Bの差も、タスク特化の効果とは言い切れません(学習データは非公開)。
- 公開済みのモデルだけの検証:Decision Modelは、Unslothを使えば、QwenやGemmaなどから自分のタスク向けに学習できます7。メールの判別に特化させたモデルのほうが、今回の公開モデルより、精度が高く速くなる可能性があります。今回は試していません。学習したモデルをllama.cppで動かせるかも未確認です。
- 未実施:Clef 27Bの公式実装との照合。
再現に必要な設定
結果に影響する要素は、次のとおりすべて載せます。
入力とプロンプト
入力テキストは、全モデル共通です。
Subject: {件名}
Body:
{本文}
Decision Model(llama.cppの /v1/systemone)には、次の内容を送りました。ラベルの説明文は、前の節の「データに合わせた説明文」です。選択肢の順番は high・medium・urgent です。返ってきた answers.priority.choice を予測として使い、probabilities のキーが3ラベルと一致しない場合は無効としました。
{
"state": "Subject: …\n\nBody:\n…",
"questions": {
"priority": {
"type": "choice",
"instructions": "Classify the priority of this email using the fixed rubric.",
"criteria": {
"high": "Legitimate work email that needs action …",
"medium": "Personal correspondence or travel logistics …",
"urgent": "Suspicious or fraudulent message that pressures …"
}
}
}
}
Laya(多言語)はSDKをプロセス内で呼びました。質問は上と同じ内容で、入力の上限は1,024トークンです。
agent = laya.load("convaiinnovations/laya", subfolder="multilingual", revision="7b928d8…")
out = agent.system_one(入力テキスト, questions, max_len=1024)
Qwen3.5(llama.cppのチャット補完)のシステムプロンプトは次のとおりです。{...} の部分にラベルの説明文(上と同じ英文)が入ります。ユーザーメッセージは入力テキストそのものです。
You are an email triage assistant. Classify the priority of this email using the fixed rubric.
Priority labels:
- high: {highの説明文}
- medium: {mediumの説明文}
- urgent: {urgentの説明文}
Answer with a single JSON object and nothing else: {"priority": "<label>"} where <label> is one of: high, medium, urgent.
リクエストのパラメータは temperature=0、seed=42、max_tokens=32、cache_prompt=false、chat_template_kwargs={"enable_thinking": false} です。出力は json.loads で厳密にパースし(コードフェンスの除去などの救済なし)、priority の値が3ラベルにない場合は無効としました。JSON schemaで制約した場合は、次の指定を加えています(結果は同一でした)。
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "priority", "strict": true,
"schema": {
"type": "object",
"properties": {"priority": {"type": "string", "enum": ["high", "medium", "urgent"]}},
"required": ["priority"], "additionalProperties": false
}
}
}
llama-serverの起動引数
全モデルに --host 127.0.0.1 を付けています。速度の測定のときだけ、--metrics を加えました。
表7 llama-serverの起動引数
| モデル | 引数 |
|---|---|
| Laya(英語) | -ngl 99 -c 4096 -b 4096 -ub 4096 --parallel 1 --no-webui |
| Clef-Flash 9B、Clef 27B | -ngl 99 -c 8192 -b 8192 -ub 8192 --parallel 1 --no-webui |
| Qwen3.5-0.8B、Qwen3.5-9B | -ngl 99 -c 8192 -b 8192 -ub 2048 --parallel 1 --jinja --no-webui |
TF-IDFの設定
調整用データの782件だけで学習しました。ハイパーパラメータの探索はしていません。
make_pipeline(
TfidfVectorizer(analyzer="char_wb", ngram_range=(2, 4), min_df=2, sublinear_tf=True),
LogisticRegression(max_iter=2000, C=4.0, class_weight="balanced"),
)
「件名+本文」は入力テキスト全体を、「件名のみ」は件名の文字列だけを入力にしました。scikit-learn 1.9.1です。
データの作り方
-
調整用と評価用の分割:似たメール(文字5-gramのMinHashでJaccard 0.7以上、完全一致を含む)を同じグループにまとめました。そのうえで、優先度で層化して5分割し(
StratifiedGroupKFold、シード42)、1つを調整用(782件)、残りを評価用(3,123件)にしました。 - Claude 3体による確認:ラベルの確認では、件名・本文と、データに合わせた説明文だけを渡し、ラベルを1つ選ばせました。正解ラベルとカテゴリは見せていません。
集計と速度測定
- 予測できなかった場合(出力が解釈できない、選択肢にない値、通信エラー)は、誤りとして数え、母数から外しません。この記事の評価では0件でした。
- 指標:Macro-F1は3ラベルのF1の平均です。urgent再現率とFPRは、urgentを陽性、highとmediumを陰性として、予測ラベルから計算しました。Decision Modelの予測は、確率が最大の選択肢です。
- 95%区間:パーセンタイル法です。似たメールのグループ単位で、モデルとTF-IDFの予測を組にしたまま1,000回再標本化しました(ペアのブートストラップ)。
- 速度の測定対象:英語の元メールから30件を無作為に選び(シード42)、全モデルで同じ入力を使いました。
- 手順:ウォームアップ20件のあと、30件を3周しました。各周で順序を入れ替え、同じ入力が連続しないようにしています。
- 時間の測り方:1リクエストの往復の壁時計時間です。
-
件名の有無の比較:本文のみは
Body:\n{本文}、件名のみはSubject: {件名}を入力にしました。Qwen3.5は自由生成のJSONだけで測っています(2つの出力方式は、件名+本文で全件同一だったため)。
データの出典はweijianzhg/email-triage-action-seed(Apache-2.0)です。日本語訳100件・長文・調整用と評価用の分割は、aoyama2222222222/email-triage-long-en-ja(Apache-2.0)で公開しています。
参考文献
- llama.cppサーバーのREADME(
POST /v1/systemoneの仕様), commitd81235049384534c167caea52b85a694f6103d14. https://github.com/ggml-org/llama.cpp/blob/d81235049384534c167caea52b85a694f6103d14/tools/server/README.md - ggml-org/Clef-Flash-GGUF, revision
4a192915ef971886004b5b13294f2b4c7a7fc39d. https://huggingface.co/ggml-org/Clef-Flash-GGUF - ggml-org/Clef-GGUF, revision
63840a1a68cb7084c88610cffc328509356b04cb. https://huggingface.co/ggml-org/Clef-GGUF - ggml-org/Laya-GGUF, revision
22265007700297ba9e128297e82540cf28c5d7d4. https://huggingface.co/ggml-org/Laya-GGUF - convaiinnovations/laya(multilingualを含む), revision
7b928d828b7b0e022f929d9bd2e44165aa270148. https://huggingface.co/convaiinnovations/laya - unsloth/Qwen3.5-9B-GGUF, revision
3885219b6810b007914f3a7950a8d1b469d598a5. https://huggingface.co/unsloth/Qwen3.5-9B-GGUF - unsloth/Qwen3.5-0.8B-GGUF, revision
6ab461498e2023f6e3c1baea90a8f0fe38ab64d0. https://huggingface.co/unsloth/Qwen3.5-0.8B-GGUF
-
Xuan-Son Nguyen, Victor Mustar「New in llama.cpp: Decision Models」(ggml-org、2026-10-02). https://huggingface.co/blog/ggml-org/decision-models-in-llamacpp ↩
-
ggml-org/llama.cpp v0.6.0, commit
d81235049384534c167caea52b85a694f6103d14. https://github.com/ggml-org/llama.cpp ↩ -
Cloudflare/clef-flash(PyTorch参照実装
joint_schema_model.pyを含む), revision8b2e5fd17c09fd49bc2880b805d5323ec7ff4ff3. https://huggingface.co/Cloudflare/clef-flash ↩ -
innFactory「Cloudflare Clef」(第三者解説。GGUFのdecision head、言語、メモリについて). https://innfactory.ai/en/ai-models/cloudflare-clef/ ↩
-
weijianzhg/email-triage-action-seed, revision
8c98baa8b10a1dddd676c860d9e1b86c89141c38, Apache-2.0. https://huggingface.co/datasets/weijianzhg/email-triage-action-seed ↩ -
convaiinnovations/layaモデルカード「Fine-tune for better accuracy」. https://huggingface.co/convaiinnovations/laya ↩
-
Unsloth Docs「Unslothで独自の決定モデルを学習する」. https://unsloth.ai/docs/jp/ji-chu/unslothdenomoderuwosuru ↩
