検索も分類も比較も判断も、とりあえずLLMに渡せばよいという扱いを見かける。自然文で依頼すれば何か返ってくるし、次の質問もそのまま続けられる。その手軽さは実際に便利で、自分もつい何でも任せたくなる。だからこそ、LLMなら何でも解けるような感覚にもなりやすい。

一方、Jevのような判断モデルを使うときにも、向いていない仕事を渡してしまうことがある。まだ材料が集まっていないのに情報を探させたり、候補が決まっていないのに方針全体を考えさせたりするのは、限定された判断をする道具に調査まで求めている。既知の語を探すだけなら、そもそもモデルを呼ばずに文字列検索で終わる。

「頼めば何か返ってくる」ことと、その仕事に合った手段であることは別だ。似た資料を集めるならEmbedding modelも候補になるが、類似度だけで資料の正しさは決まらない。LLM、Jev、Embedding model、古典的な検索やテキスト処理について、自分が何を任せたいかを整理しておく。製品間の精度比較ではなく、まず入出力と仕事の種類の話である。

探すことと判断することを分ける

以下では、自分がよく扱う開発の例で考える。例えばテストが失敗して、「関係するコードを探したい」とする。この段階の答えはファイルや行の候補だ。一方、「この失敗は今の差分に起因するか」は、差分と失敗ログを見比べる判断である。「原因を調べて直す」は、追加の情報収集や実験まで含む作業になる。

似た文を入力しても、欲しい出力はそれぞれ違う。

手段まず渡すもの主な出力自分が使いたい場面
ルール・文字列検索・BM25など語、パターン、文書集合一致箇所や検索順位名前やエラー文が分かっている、候補を機械的に絞れる
Embedding model検索文と文書ベクトルと類似度による候補言い換えが多く、語が一致しない資料も探したい
Jev集めた証拠と型付きの質問Yesの確率、選択肢、段階評価候補と評価基準を決めた上で、小さな判断をしたい
LLM問題、資料、必要ならツール説明、仮説、コード、次の調査問題を分解し、足りない証拠を集め、実装や検証を進めたい

この表は能力の限界を示すものではない。LLMに分類させることも、Embeddingを特徴量にして分類器を作ることもできる。ただ、素の類似度を判断の確率として読み替えたり、単純な検索をするたびにLLMを起動したりする必要はない、という区別である。

欲しい答えから作業を分ける

例えば、変更後にAPIのテストがタイムアウトしたとする。最初はテスト名やエラー文から関係するコードを探したいかもしれない。ログと差分が揃った後は「今回の変更が原因か」を判断したいし、原因が分からなければ追加調査が必要になる。同じ失敗でも、いま欲しい答えによって作業が変わる。

自分なら、ツール名から選ぶのではなく、まず「欲しい答えは何か」と「その答えを出す材料は手元にあるか」を確認する。以下はその流れで、四つを順番に全部使う手順ではない。

flowchart LR
    START{"関連する資料やコードを<br/>探す段階か"}
    START -->|はい| FIND{"語や条件で探せるか"}
    FIND -->|はい| SEARCH["文字列検索・ルール・BM25<br/>手元: エラー名・関数名・検索条件"]
    FIND -->|いいえ| EMBED["Embeddingとキーワード検索<br/>手元: 症状の説明・探す資料群"]
    START -->|いいえ| RESOLVED{"手元の情報を読めば<br/>答えが確定するか"}
    RESOLVED -->|はい| DONE["ここで終了"]
    RESOLVED -->|いいえ| JUDGE{"証拠が揃い<br/>問いを閉じられるか"}
    JUDGE -->|はい| JEV["Jevで限定された判断<br/>手元: ログ・差分・判断する選択肢"]
    JUDGE -->|いいえ| LLM["LLMとツールで調査・実装<br/>手元: 問題と追加調査の手段"]

まず、必要なのが候補の発見なのか、手元の証拠を使った判断や実装なのかを分ける。候補を探す場合、エラー文、関数名、テスト名のような既知の語があれば文字列検索を使う。大量の文書を語で順位付けしたいならBM25も候補になる。見つかった箇所を読んで答えが確定するなら、そこで終わってよい。

候補が足りず、同じ事柄が別の言い方で書かれている資料を探したいならEmbeddingを検討する。ただし固有名詞やコード上の識別子までEmbeddingだけで探す必要はない。語による検索と併用し、得られた類似文書を「確認すべき候補」として扱う。

候補と証拠が手元にあり、「この差分は失敗に関係するか」「次に調べるならA、B、unknownのどれか」のように問いを閉じられるなら、Jevへ切り出せる。材料が欠けている状態でJevに選ばせても、欠けた情報は補われない。回答が出た後もコードやテストで確かめる。

原因候補すら定まらない、追加の観測が必要、修正して回帰テストまで進めたい、といった場合はLLMを使うエージェントの出番になる。調査中に識別子が判明したら検索へ戻り、比較したい候補が揃ったら必要に応じてJevへ小さな問いを渡す。LLMを選んだら他の手段を使わない、という分岐ではない。

また、外部APIへログやコードを送る構成なら、その内容を送ってよいかは選定フローとは別に確認する。答えの形が合っていても、送信できない情報を含むならその構成は使えない。

まず古典的なテキスト処理と情報検索

エラー名、テスト名、関数名が分かっているなら、最初はrgや構文に沿った検索でよい。TimeoutErrorがどこから返るか、特定のAPI名を使っている箇所はどこか、といった問いは、コードを実際に検索すれば答えが出る。ログにFAILEDがあるかどうかも、モデルに判定してもらう話ではない。

文書を大量に扱うなら、語の出現や文書長を考慮するBM25のようなランキングもある。Apache LuceneのBM25Similarityは、その実装例だ。ここでのスコアは検索順位のための値で、「この文書が原因である確率」ではない。

日本語なら形態素解析や正規化、同義語辞書を組み合わせることもできる。ただ、対象の命名規則やログ形式を知っているなら、まずその構造を使う方が再現しやすい。逆に、同じ概念がソースでは英語、Issueでは日本語で書かれている場合、語の一致だけでは拾いにくくなる。

ここで言う「古典的なNLP」には、厳密には検索やルールベースのテキスト処理もまとめている。全部を一つの手法として扱いたいわけではない。共通するのは、問題が機械的に定義できるなら、それに合う道具を先に使うという点だ。

Embedding modelは候補を探す

Embedding modelはテキストなどをベクトルへ変換する。検索文と文書を同じモデルで変換し、距離やコサイン類似度で近いものを探せる。OpenAIのEmbeddingの説明にも、コードの説明文と自然言語の問い合わせを埋め込み、類似度で関数を並べる例がある。

例えばログには「応答を待っている間に制限時間を超えた」とあり、過去のIssueには「外部APIの遅延」や「リトライ待ちが長い」と書かれている。単語が揃っていなくても、関連する候補を見つける入口にはなる。大きな文書集合から数件を取り出す用途では、検索と判断を分けて考えられる。

ただし、近い文章が見つかったことと、その文章が今回の失敗の原因であることは違う。二つのIssueが同じサブシステムを説明していれば類似度は高くても、片方の修正がもう片方のテスト失敗を起こしたとは限らない。コサイン類似度の0.8を「80%の確率で原因」とは読めない。

文書をどう分割するか、更新されたコードをいつ再インデックスするか、見落としがないかも設計する必要がある。固有の識別子は語で引く方が確実なことがあるので、キーワード検索とEmbedding検索を併用するのも自然だ。候補が数件しかなく、ファイル名も分かっているなら、ベクトル検索の準備自体が不要になる。

Jevは渡された証拠を、決めた形式で評価する

TypeSafe AIのJevは、stateと型付きの質問を受け取り、構造化された回答を返す判断モデルである。自分のjev-mcpでは、条件のYes確率を返すnoul、候補から一つ選ぶchoice、段階評価のscore、同じ材料への独立した質問をまとめるbatchを公開している。

ここで重要なのは、Jevがリポジトリを検索してくれるわけではないことだ。差分、失敗ログ、候補の意味を呼び出し側が集めてstateに渡す。Jevはその材料に対して、あらかじめ定めた問いを評価する。

例えば差分とテスト失敗を読んでもなお関係が微妙で、次にどこを調べるか選びたいとする。「関連するか」をNoulで、「最初に調べる候補はどれか」をChoiceで聞ける。ただしChoiceならunknownも候補に含め、証拠が足りないときに無理に原因候補を選ばせない。同じstateで複数の独立した問いを評価するならBatchにできる。前の問いの答えを次の問いの前提にはできないので、追加調査が必要なら新しいstateを作る。

ChoiceとScoreのconfidenceは、TypeSafeの説明では回答分布の集中度から計算される。0.9ならこのリポジトリで90%正解する、と検証された数字ではない。Noulには独立したconfidenceがない。jev-mcpが返すaccept、verify、reevaluateというpolicyも、回答をどう扱うかの補助情報であって、テストを省く許可ではない。

検索スコアとJevの確率分布は、どちらも数値だが目的が違う。前者は候補を並べる値、後者は与えた状態と質問に対するモデルの回答である。Jevの回答も原因の確定ではない。判断に影響する証拠がstateから抜けていれば、整った形式の答えが返っても判断は外れ得る。

LLMには問題の分解と実行を任せる

原因候補を探すだけなら検索で足りるかもしれない。候補が揃っていて選択肢が閉じているなら、Jevで一つの判断を切り出せる。しかし実際のバグ調査では、ログの意味を調べ、コードを読み、仮説を立て、再現条件を変え、テストする必要がある。候補そのものがまだ分からないこともある。

ここはLLMを使うコーディングエージェントの仕事になる。説明文だけを生成するという意味ではなく、必要な資料を取りに行き、ツールを実行し、結果を踏まえて考え直す作業だ。LLMにJSONで分類を返させることもできるので、「分類はLLMには不可能」という話ではない。ただ、調査全体を進める役割と、閉じた一問への回答を返す役割を分けたい。

Jevの結果で候補Aが上位になっても、LLMはAのコードを読み、差分を確認し、テストで確かめる。候補が拮抗するなら、同じ質問をそのまま連投するより、どちらを区別できる観測があるか考える。この部分をJevのスコアだけで自動化するつもりはない。

一つの調査で考える

「変更後にAPIのテストがタイムアウトした」とする。自分なら、次のような順で考える。

テスト名とTimeoutErrorが分かっている
  → rgで失敗したテスト、タイムアウト設定、変更箇所を探す

過去のIssueが多く、「応答遅延」など別の表現もある
  → 必要ならBM25やEmbeddingで関連資料の候補を広げる

変更されたリトライ設定と失敗ログを集めても、関連性が微妙
  → 必要ならJevに「この変更が失敗に関係するか」を聞く

タイムアウトの条件を再現し、原因の確認や修正が必要
  → LLMを使うエージェントと実際のツールで進める

必ず四つを全部通すわけではない。テストが「期待値3、実際1」で、差分が再試行上限を3から0に変えたものなら、まずコードとテストを確認すればよい。そこでJevへ聞くための質問を組み立てる方が遠回りになる。反対に候補が大量で、説明の語彙もばらばらなら、LLMへ全件を一度に渡す前に検索層を置きたい。

最初に選んだ手段を使い続ける必要はない

ここまでの使い分けは、最初に一つ選んで固定するためのものではない。問題について分かっていることが増えれば、適した手段も変わる。最初は何を探せばよいか分からずLLMと調査したとしても、後から特徴的なエラー名や設定項目が見つかれば、次回からは文字列検索で対象を絞れる。

例えば、初めて見るAPIのタイムアウトでは、ログだけでは原因候補すら挙がらないかもしれない。LLMとコードや実行履歴を調べ、何度か再現して、特定のリトライ設定とエラーコードの組み合わせで起きると分かったとする。その後も同じ条件の検出を毎回LLMに頼む必要はない。エラーコードと設定値が構造化ログに残っていれば、通常の検索やルールで候補を拾える。条件が確定的なら、テストや監視のチェックにしてもよい。

Jevについても同じことが言える。複数の証拠を比べなければ判断できなかった問いでも、事例を重ねるうちに「このフィールドがこの値で、別のフィールドが空なら不一致」といった判定条件が明確になる場合がある。その条件をコードにできるなら、以後は決定的なルールで処理する方が、同じ入力に同じ結果を返し、判断理由も追いやすい。Jevが不要になったのではなく、Jevに聞いていた問いの一部が、より単純な手続きとして書けるようになったということだ。

Embeddingで探していた資料も、繰り返し出る言い換えが分かれば同義語辞書やキーワード検索で拾えることがある。逆に新しい言い回しが次々に現れるなら、辞書を増やすだけでは追いつかず、Embeddingを残す理由がある。どちらが上位の技術かではなく、その時点で分かっている規則をどこまで明示できるかの違いだと思う。

ただし、「何度か同じ答えが出た」だけでモデルの判断をルールに置き換えるのは危ない。入力のどの条件が結論を決めているのかを確かめ、例外と未知の入力をどう扱うかを決める必要がある。過去の事例では正しくても、新しい種類の障害まで同じルールで断定してしまえば、かえって調査を遅らせる。ルールで確定できる部分だけを機械的に処理し、当てはまらないものは追加調査や限定された判断へ戻せばよい。

つまり、最初はLLMで問題を探索し、証拠が揃った問いをJevに切り出し、さらに条件が明文化できた部分は検索やルール、テストへ移す、という変化はあり得る。ただし必ずこの順番を通るわけではないし、どの段階でも人間が資料を読めば答えが確定するなら、そこで止めてよい。手段の選択は、問題そのものよりも、今どこまで情報が揃っているかに依存する。

この流れで実際に節約できる時間、API料金、誤った候補を選ぶ頻度はまだ測っていない。jev-mcpには小さな日本語の評価fixtureを置いているが、それはこの四方式の優劣を測る比較実験ではない。評価用JSONLの記事に書いたとおり、モデルを呼ぶ経路の検証と、現場での判断品質の検証は分ける必要がある。

また、jev-mcpはローカルのMCPサーバーでも、判断内容はTypeSafe APIへ送られる。EmbeddingやLLMも外部APIを使う構成なら同様に送信内容を考える。秘密情報を含むログや未公開コードをそのまま渡してよいかは、便利さより先に確認したい。

今のところ、自分が分けたい境界は「検索で候補を得た」と「証拠から関連性を判断した」と「実際に原因を確認した」の三つである。全部をひとつのモデルへ寄せることもできるが、何を確認できたかが曖昧にならない構成にしておきたい。