キーワードを数千件並べたリストで、テキストを分類する仕組みがありました。 何年も人手で足し続けてきたもので、表記のゆれを見つけるたびに行が増えていきます。 この保守がこの先ずっと続くのが、正直しんどいなと思っていました。

そこで、判定に特化したモデルで置き換えられないか試してみました。 使ったのはTypeSafe AIのJevというモデルです。

Introducing System One Models & Jev - TypeSafe AI Blog TypeSafe AI is an AI lab building machine-native intelligence infrastructure for automation, designed to make decisio… typesafe.ai

結論から書くと、置き換えは見送りました。 ただ、そう決めるまでの過程のほうが、たぶん役に立ちます。

一番効いたのは、プロンプトの言い回しを推敲することではありませんでした。 モデルに「何を聞くか」のほうでした。

Jevは文章を書かずに分布を返す

Jevは文章を生成しません。 エンドポイントは1本で、そこに投げる質問の型が3種類あります。 選択肢から1つ選ばせるChoice、はい/いいえの確率を返すNoul、段階評価のScore。

今回はChoiceだけを使いました。 はい/いいえで聞けるならNoulでも良さそうですが、Noulには確信度が返りません。

Choiceで返ってくるのは、選ばれたラベルと全ラベルの確率、そして確信度です。 確信度は確率分布から導かれるとだけ説明されていて、算出式は公開されていません。

自分の手元では1件あたり0.2〜0.6秒、費用は1,000件で0.04ドルほどでした。 詳しいことは公式の紹介記事とAPI reference - TypeSafe AIにまとまっています。

「どの話題か」と「どうすべきか」は別の質問だった

自分が最初に書いたのは、素直に「このテキストはどのカテゴリの話題か」と聞く質問でした。 選択肢にカテゴリを並べ、それぞれに説明文を添える形です。

これがうまくいきませんでした。 そのカテゴリの単語を含むだけで中身は別物、というテキストを軒並み拾ってしまいます。 しかもラベルの確率が0.9前後で返ってくるので、間違っている自覚がないように見えました。

説明文の言い回しをいじっても、拾いすぎが減ると拾うべきものまで落ちて、しばらくモヤモヤしていました。

そこで、質問そのものを変えました。 業務の話は書けないので、問い合わせメールの振り分けに置き換えると、こういう変更です。

変更前は、こちら:

質問: この問い合わせはどのカテゴリの話題か
選択肢:
  請求: 請求や支払いについて書かれている
  該当なし: 上記にあてはまらない

変更後は、こちら:

質問: この問い合わせに、こちらから対応する必要があるか
選択肢:
  請求: 送信者が請求や支払いで困っている
  該当なし: 対応の必要がない。事例紹介や資料請求、社内の共有を含む

聞いているのが「話題」から「こちらが何をすべきか」に変わっています。 あわせて説明文も「〜について書かれている」から「送信者が〜している」に書き換えました。 この2つを同時に入れたので、どちらがどれだけ効いたのかは切り分けられていません。

それでも拾いすぎは消え、拾うべきものはラベルの確率0.95以上を保ったままです。 同じ材料でも、話題を聞くか意図を聞くかで別のモデルのように振る舞うのだなぁ、と思いました。

もう1つ手応えがあったのが「該当なし」の説明文です。 「こういう場合は対応不要」を書き足すほど、誤判定がそちらに流れていきます。 捨てたいものの定義を直すほうが動く、というのがしっくりきました。

モデルの性能を疑う前に、自分が何を聞いているのかを疑うほうが早いのではないか、と思っています。

確信度は、閾値にする前に分布を見る

確信度が返るので、「一定以下は判定なしにする」という使い方をしました。

ただ、質問の枠組みが合っていないときの誤判定は、確信度が高いまま返ってきます。 0.87から0.98あたりに誤判定が並び、正しい判定の帯と重なっていました。 こうなると、閾値をどこに置いてもきれいには分けられません。

考えてみれば当たり前で、確信度が表すのは「投げた質問に対する確からしさ」です。 質問がずれていれば、ずれたまま自信を持って答えてくるだけでした。

質問を直したあとで測り直すと分布は分かれて見えましたが、これも十数件での話です。 閾値は分布が分かれてから決めるもの、という順番だけを持ち帰りました。

5,000件を全部かけてみた

ここまでは、手で選んだ十数件を見ながらの判断で、手応えは「かなり使える」でした。

そこで、リストが拾ってきたテキスト5,000件強を全部Jevにかけてみました。 同時実行数を12にして、待ち時間も含めて全体で5分。 費用は0.2ドルほど、APIのエラーはゼロ件です。

結果、リストが拾っていたもののうち、Jevも同じカテゴリで拾えたのは7割弱でした。 閾値は、十数件で決めたものをそのまま当てています。 残りの3割が問題で、中身に偏りがありました。

略語や社内用語のたぐい、漢字をひらがなで書いた表記、そして遠回しな言い方です。 正式名称なら拾えるのに3文字の略称だと拾えない、といった具合でした。 どれも、リストを人手で育ててきた価値が一番高い部分です。

ここで、自分の検証の甘さにいくつか気づきました。

1つは、質問文を調整したのも、直ったことを確認したのも同じ十数件だったことです。 その十数件で良くなるように直したのだから、良く見えて当たり前でした。

次に、母数が「リストが拾ったもの」だけだったことです。 リストが拾わずJevだけが拾えるものは、この測り方では1件も観測できません。

3つめに、測れたのは「リストが拾ったものを拾えるか」だけだったことです。 中心の主張である「拾いすぎが消えた」ほうは、あの十数件の印象のままでした。

最後に、リストは言葉の一致で拾うのに、Jevには「対応が必要か」を聞いています。 リストが拾うけれど対応は要らないものをJevが外すと、それも3割に数えられます。

それでも、取りこぼしの3割が偏っている以上、リストを捨てる判断はできないと考えて見送りました。

まとめ

試してわかったことを並べておきます。

  • 言い回しを磨くより、問いの立て方と「捨てる側」の定義を変えるほうが効いた
  • 枠組みがずれているときの誤判定は、高い確信度で返ってきた。確信度が測るのは、投げた質問に対する確からしさでしかない
  • 手で選んだ十数件で質問文を直し、同じ十数件で良し悪しも見ていた。件数より、選び方のほうが問題だった

数千件なら全部かけられるとわかったのは収穫でした。 Claude Code でCSV分析を効率化: 1000万件のデータを自然言語で探索するのときにも思いましたが、この値段ならサンプリングする理由がありません。

リストとJevの苦手が裏返しなのかどうかは、今回の測り方では確かめられていません。 今はリストを残したまま、リストが外したものだけJevに回す形を試しています。 そこで初めて、Jevだけが拾えるものが見えるからです。

この形にすると、保守する対象がリストから質問文に移ります。 しんどいと思っていたリストの保守は減りますが、質問文のほうが軽いとは限りません。

どこまでをリストに任せ、どこからをJevに聞くのか。 結局ここでも「何を聞くか」を決める話に戻ってきました。