はじめに
以前、【Genkit】AIエージェントの司令塔|「意図分類(Intent Classification)」でコスト削減とレスポンス高速化を実現する という記事を書きました。
軽量モデルで、ユーザーの入力を事前に分類し、最適な処理経路へ振り分けることでコストとレイテンシを下げる、というアーキテクチャについてでした。
その中で、
記憶参照が必要な時だけ、重いVector Searchを実行する
と記載していましたが、この「必要な時だけ」の判断をロジック内の分類器に任せた結果、ユーザーが明示的に指定した参照先を無視するという問題が起きました。
社内向けのAIチャットアプリに、組織のナレッジを紐付ける機能を実装していて、サンプルのナレッジを紐付けた状態で「リモートワークのルールについて教えて」と聞いたところ、ナレッジとしてVector Searchに登録した社内規程を一切参照せず、一般論だけを返してきました。
この記事では、その調査と、そこから得た「意図分類でゲートしてよいもの・いけないもの」の切り分けをまとめます。
対象読者
- 意図分類やルーティングでLLMアプリのコストを最適化している方
- RAGを組み込んだが「参照されているのか分からない」という状態に悩んでいる方
- 事前分類の結果で機能をON/OFFする設計を採用している方
課題:ナレッジを指定したのに一般論が返ってきていた
サンプルの社内規程のカテゴリを紐付けた上で、下記のように質問した場合の再現です。
リモートワークルールについて教えて
返ってきたのは、下記のような内容です。
リモートワークのルールですね。制度の設計や見直し、運用の確認など、
押さえておきたい重要ポイントを整理しました。
一般的に企業で整備されるリモートワーク(テレワーク)の規程やガイドラインは、
主に以下の要素で構成されています。
### 1. 対象者・適用範囲
* 対象者の条件: 職種、雇用形態、試用期間終了後といった適用条件
...
想定したナレッジからの参照ではなく一般論で返ってきました。
サンプルの社内規程には「週2回まで」という具体的な記述を登録しておいたのですが、そこには一切触れていません。
具体的に、再度聞き直してみると、
弊社のルールについてです
下記のように想定した動きになりました。
社内のリモートワーク規程ですね。失礼しました。
現在の社内規程では、以下のように定められています。
* リモートワークの利用上限
* 週2回まで利用できます。
* 出社の義務
* 週3回以上の出社が優先とされています。
データは登録されていて、検索もできる。
にもかかわらず1回目は使われなかったので、検索精度の問題なのだろうか?
というところから調査を始めました。
検証:ログで何が起きていたか
Cloud Logging で該当時刻のログを確認しました。
11:45:41 [Intent Classified] GENERAL_KNOWLEDGE (ContextRef: false, WebSearch: true)
11:45:52 [Grounding] 検索は実行されませんでした (intent=GENERAL_KNOWLEDGE)
→ 一般論を回答
11:46:02 [Intent Classified] MEMORY_RECALL (ContextRef: true, WebSearch: false)
11:46:03 [Retrieval] Attached knowledge categories: 社内規程, サービス詳細
11:46:04 [Knowledge Search] Retrieved latest=3 history=15 reference=0
11:46:04 [Retrieval] ナレッジ 最新3件 / 経緯15件 を取得しました
1回目は GENERAL_KNOWLEDGE(一般知識の質問)に分類され、ナレッジ検索が一度も走っていません。
2回目は「弊社の」という語で MEMORY_RECALL に分類され、そこで初めて検索されています。
// 修正前
const shouldSearchKnowledge = intent === 'MEMORY_RECALL';
該当のコードを確認すると、組織のナレッジを引くかどうかが、意図分類の結果だけで決まっていました。
原因:分類器は「紐付けの有無」を知らなかった
意図分類フローに渡していたのは、ユーザーの入力・直近のAI発言・今日の日付だけです。
const { intent, hasContextReference, requiresWebSearch } =
await personalIntentClassifierFlow({ query, lastAiMessage, currentDate });
「このチャットスレッドには社内規程が紐付いている」という情報は渡していません。
ロジック内の分類器はそれを知らないまま、文面だけで「一般知識の質問だ」と判断しました。
なぜ文面だけでは判断できていなかったのか
リモートワークルールについて教えて
この一文は、社内規程を尋ねているとも、一般論を尋ねているとも読めるので、文面には手がかりがありません。
ロジック内の分類器に「ナレッジが紐付いている」と教えれば解決するとも考えましたが、ナレッジを紐付けたスレッドで一般的な質問をすることもあるため、結局は同じ曖昧さが残ります。
解決策
今回のチャットスレッドは、ユーザーが自ら参照したいナレッジを指定する作りにしているため、スレッドに社内規程を紐付けるという操作こそが、「ここでは社内の情報を見てほしい」という明示的な意思表示となっています。
1. 明示的な指定は分類より優先する
そのため、判定条件を、意図分類ではなく紐付けの有無に変えました。
// 修正後
const shouldSearchKnowledge = resolvedKnowledgeCategories.length > 0;
紐付けカテゴリの解決は検索処理の内側で行っていたため、判定に使えるよう前段へ移しました。
// スレッド文脈の取得時に、紐付けカテゴリも解決して返す
let resolvedKnowledgeCategories: string[] = attachedKnowledgeCategories || [];
if (resolvedKnowledgeCategories.length === 0) {
const threadSnap = await db.collection('threads').doc(currentThreadId).get();
resolvedKnowledgeCategories = threadSnap.data()?.attachedKnowledgeCategories || [];
}
intent による除外条件はあえて一切付けていません。
「雑談なら検索しない」といった条件を足したくなりますが、それを入れた瞬間に同じ問題が形を変えて再発します。
雑談として分類されるアイデア出しの場面こそ、社内ナレッジを踏まえてほしいからです。
2. 検索しただけでは足りない
修正して検索が走るようになっても、まだ足りませんでした。
プロンプトは意図ごとにスタンスを切り替える作りにしており、GENERAL_KNOWLEDGE の指示は下記のようになっていました。
[現在のスタンス]
博識なコンシェルジュとして、一般的な知識をユーザーに差し出す場面です。
必要ならその場でWebを調べ、確かな情報を渡してください。
[行動指針]
- 時間とともに変わる事柄を問われたら、自分の記憶に頼らず必ずWeb検索してから答えてください。
組織のナレッジへの言及が一切なく、検索結果を渡しても、モデルは一般論とWeb検索で答えようとします。
そこで、ナレッジを引けたときは意図によらず優先順位を明示するようにしました。
if (hasRetrievedKnowledge && intent !== 'MEMORY_RECALL') {
specificGuidelines += `
[組織のナレッジについて(今回は参照先が指定されています)]
- このスレッドには利用者が**明示的に組織のナレッジを紐付けています**。
**[組織のナレッジ(最新情報)]** に答えが含まれていないか必ず先に確認してください。
- 含まれていた場合は**それを根拠として答えてください**。
一般論やWeb検索の結果を先に置かないでください。
- 含まれていなかった場合に限り、一般的な知識で答えてください。
その際は「社内のナレッジには該当する記載が見当たらなかった」と一言添えてください。`;
}
「検索する」と「使う」は別の問題で、前者だけ直しても症状は変わりません。
同じ失敗を、すでに2回していた
修正しながらコードを読み返して気づいたのですが、同じ構造の失敗を過去に2回踏んでいました。
ひとつはWeb検索です。
// 以前は事前分類の requiresWebSearch でゲートしていたが、false と誤判定された
// 瞬間にモデルから検索手段が消え(tools も空のため)、古い知識で断言する事故が
// 起きていた。分類結果は FAST PATH の振り分けにだけ使う。
googleSearchRetrieval: opts.withSearch && !hasAttachments ? {} : undefined,
もうひとつはナレッジの更新経緯(「なぜこの規程になったのか」の記録)です。
// 経緯が必要かの事前判定はしない(判定を外すとAIから手段が消えるため、
// Web検索で同じ失敗をした経緯がある。両方渡してAIに選ばせる)。
Web検索では気づいて直し、更新経緯でも同じ判断をしていたのに、組織のナレッジ本体だけが古い設計のまま残っていました。
設計原則:分類は「振り分け」に使い、「遮断」に使わない
ここまでハマった部分をまとめると、
| 分類結果の使い方 | 可否 | 理由 |
|---|---|---|
| どの経路で処理するか(FAST PATH / 通常パス) | ✅ | 誤っても品質が落ちるだけで、手段は残る |
| どのモデルを使うか | ✅ | 同上 |
| 検索・ツールを使えるようにするか | ❌ | 誤った瞬間、AIから手段そのものが消える |
誤分類したときに何が起きるかで線を引きました。
経路やモデルの選択を誤っても、AIは持っている手段の中で最善を尽くせます。
しかし検索をゲートすると、AIは「調べる」という選択肢を失ったまま、それに気づかず断言します。
利用者から見ると「知らないこと」と「調べられないこと」の区別が付きません。
コスト最適化としては、ゲートするほうが効きます。
ベクトル検索が毎回走る分、遅くもなります。
それでも、手段を奪う方向の最適化は割に合いません。
判断の主体をAIに残し、材料を渡した上で選ばせる。
ロジック内の分類器は「どの部屋に通すか」までを決め、「何を持たせるか」は決めない、という切り分けです。
おわりに
意図分類は、コストとレイテンシに効く有効な手法です。
ただ、「必要な時だけ実行する」 を誰が判断するのかを、もう一段考える必要がありました。
今回のように利用者が明示的に指定している場合、その意思はロジック内の分類器の推測より確実な情報です。
それを推測で上書きしていたのが今回の原因でした。
対策として有効なのは以下の3点です。
-
利用者の明示的な操作を、分類結果より優先する
スレッドへの紐付け、ファイルの添付、機能の明示的な選択は、推測の必要がない確定情報なので、分類器の判断より上に置きます -
手段を奪うゲートを置かない
誤分類したときに「品質が落ちる」で済むか、「できなくなる」のかで判断し、後者になるゲートは、コストと引き換えにしても入れないほうが安全です -
「渡す」と「使う」を別々に確認する
検索が走っていても、プロンプトに使う指示が無ければ結果は変わらないので、ログで取得件数を確認するだけでなく、回答が実際に根拠を使っているかまで見ます
同じ失敗を3回繰り返して得た結論なので、どこかで同じような設計をしている方の役に立てば幸いです。
