チャット欄に一行が届く。
「あのタイムアウトの原因、まとまってるやつある?」
数秒後、AI PMが答える。決済がなぜ止まったのか、いつ頻発したのか、チームがどう対応することにしたのかを一つずつ挙げ、最後には参照した資料の名前まで添えて。
おかしな話だ。このAIはうちの会社に入社したこともなければ、うちの決済モジュールを覗いたこともない。それなのに、どうして「自社プロジェクト」を知っているかのように答えられるのか。賢いからではない。質問が来るたびに関連する資料を探し、答えの材料として挟み込む仕組みが裏で動いているからだ。この仕組みの名前がRAG(retrieval-augmented generation、検索拡張生成)である。AIが自分の頭で覚えた知識の代わりに、その都度外部の資料を探して参照しながら答えるやり方だ。
入社したばかりの優秀なコンサルタントを思い浮かべてほしい。経営・財務・マーケティングの理論には詳しい。ところが「うちの会社、有給は何日まで繰り越せますか?」と聞かれると答えられない。頭が悪いからではない。この会社の規程集をまだ読んでいないからだ。
優秀な新人は、ここででっち上げたりしない。「少々お待ちを、規程集を調べます」と言って人事部のフォルダから該当の条項を開き、そのページを指しながら答える。RAGがやっているのは、まさにこの三つの動作だ。質問が来たら関連する資料を探し、その資料を机に広げて質問と一緒にモデルへ渡し、モデルは広げられた資料を読んで答えを書く。裏ではこの三つを、それぞれ検索(retrieval)・注入(augmentation)・生成(generation)と呼ぶ。用語は覚えなくていい。探して、広げて、書く。これがすべてだ。
大規模言語モデル(LLM、膨大な文章で訓練されたAI)は、学習時点までに選び抜かれ整えられた大量の文章で訓練される。インターネット全体をまるごと覚えるわけではなく、学んだ中身をすべて記憶しているわけでもない。それでも一般常識、コーディングの文法、文章作法はよく身についている。コンサルタントの頭の中の理論に当たる。これを「モデルの頭の中の知識」と呼ぼう。
問題は、昨日書いたうちのチームの企画書、先週の会議で決めたルール、うちの会社でしか使わない用語だ。そもそも学習データになかった。ないものを知ることはできない。さらに厄介なのは、知らないときでも知ったかぶりをする点だ。もっともらしいが間違った答えを自信満々に出してくること、これを幻覚(hallucination)という。
だから知識は二つの場所に分けて置いておく必要がある。一つは先ほど述べたモデルの頭の中の知識だ。一度固まると、そのうちの一つだけを選んで直すのは難しい。もう一つは、モデルの外、机の上に載せておく資料だ。倉庫の資料を差し替えれば中身は即座に変わる。うちのプロジェクトの資料はここに入る。
「自社プロジェクトを知らない問題」を解く道も、ここで分かれる。モデルをうちのデータで学習し直して、頭の中の知識そのものを書き換える道が一つ(ファインチューニング)。お金と時間がかかるうえ、資料が一日に何度も変わるプロジェクトには向かない。今日変わった日程を反映するために、毎回モデルを焼き直すわけにはいかない。もう一つの道がRAGだ。モデルはそのままにして、机の上の資料だけを差し替える。倉庫に資料を入れておけば、質問が来るたびに取り出して使う。
まとめると、モデルは「考え方」を担い、資料は「自分たちだけの事実」を担う。新人が理論と規程集を併用するように。
新人が規程集を探すには、規程集がフォルダに整理されていなければならない。RAGも、質問を受け取る前に探すための材料を整えておく。
まず資料を細切れにする。企画書一本は数十ページある。まるごとでは探すのも、モデルに入れるのも手に余る。そこで小さな断片、チャンク(chunk、資料を切り分けた一片)に分ける。そうすれば質問に接した断片だけを拾い出せるし、モデルが一度に読める分量の中に収まる。
肝心なのは、どこで切るかだ。規程集の一ページが「上記三つの理由により日程を—」で裂かれ、次の断片が「—2週間延期する」で始まると、どちらの断片も単独では意味が通らない。
切る位置を変えるだけで、答えはどう変わるか。弱いやり方は、文字数500字で機械的に区切る。文の真ん中で切れて、断片が文脈を失う。強いやり方は、段落や小見出しの境目で切り、隣り合う断片どうしで一、二文を重ねておく。重なった部分が切れた文脈をつなぎ、断片一つだけ読んでも意味が立つ。
切ったあとは、探しやすいように整理する。どの単語がどの断片にあるかを記した一覧(索引)を作り、各断片の意味を数字の列に変えたもの(ベクトル)も一緒に保存する。なぜ二とおりで保存するのかは、すぐあとの検索の段階で明らかになる。
準備が整った。冒頭のあの場面に戻ろう。ユーザーがついさっきまで決済モジュールの話をしていて、こう尋ねる。
「あのタイムアウトの原因、まとまってるやつある?」
タイムアウトとは、外部システムに要求を送って、決めておいた時間までに応答が来なければ失敗として処理することをいう。決済がそうやって何度も失敗した、という話だ。この一行が四つのステップを経て、出典つきの答えになって返ってくる。
「あのタイムアウト」だけ見ても、何を探せばいいかわからない。「あの」が指すものは、ついさっきまでやり取りした会話の中にある。だから検索語を立てるとき、二つを一緒に見る。たった今届いた質問と、直前までの会話だ。
ここでは直前の話題が決済モジュールだったので、「あのタイムアウト」を「決済APIタイムアウト」とほどいて検索語を立てる。(APIはプログラムどうしがデータをやり取りするための取り決めだ。)新人が「あれ、いつまででしたっけ?」を直前の話題に照らして聞き取るのと同じだ。
「あの」が何かを取りこぼすと、どうなるか。弱いやり方は、「あのタイムアウト」を文脈なしでそのまま検索し、見当違いの「あれ」を引いてくる。強いやり方は、直前の会話の核になる名前(決済モジュール)を検索語に足して正確に絞り込む。ただし過去の会話に寄りかかりすぎると、すでに終わった話題に検索が引っ張られるので、直近の文脈を軽く足す程度にとどめる。
検索語が立ったら、資料の倉庫から関連する断片を取り出す。取り出す道は二つ。どちらも、新人が規程集を探すやり方にそのまま対応している。
一つは単語で探す。新人が「第14条」や「決済APIタイムアウト」のように正確な言葉でフォルダを漁るやり方だ。技術用語ではキーワードマッチング(BM25が代表的だ)という。速くて正確だが、資料に同じ言葉が書かれていなければ見落とす。資料が「ペイメント要求の遅延」と書いていたら、「決済APIタイムアウト」では見つけられない。
もう一つは意味で探す。新人が「有給の繰り越し、あの内容」のように、単語ではなく意味で記憶して探すやり方だ。先に保存しておいた数字の列(ベクトル)がここで使われる。質問の意味に近い断片を選ぶので、「決済APIタイムアウト」と「ペイメント要求の遅延」を、単語が違ってもつないでくれる。技術用語では意味検索(dense retrieval)だ。意味で探すこのやり方が、単語だけで探すよりも関連資料をうまく拾い出すことは、2020年のDPR(Dense Passage Retrieval)研究で広く知られるようになった。
では意味で探すだけ使えばいいのか。そうではない。「ERR_4092」のような正確なエラーコードを探すときは、単語で探すほうがいい。意味で探すと「似た感じのエラー」を引っぱってきかねないが、本当に必要なのはそのコードがズバリ入った資料一件だ。だから実務では両方を同時に回す。これをハイブリッド検索という。単語で探すほうが「ERR_4092」がそのまま入った資料をつかみ、意味で探すほうが表現の違う「ペイメント要求の遅延」の資料を引き上げる。二つを合わせれば、片方だけでは見落としていた資料まで候補に上がる。
検索方式のわずかな違いが、どんな結果につながるか。弱いやり方は、意味だけで探し、「ERR_4092」を聞いても番号の違う「似た」エラーを上に上げる。強いやり方は、単語と意味を一緒に回し、コードが正確に入った資料も、意味の近い資料も、どちらも候補に立たせる。
検索が終わると、倉庫から三つの断片が候補に上がってきた(実際はもっと多いが、三つだけ見よう)。
断片1 — [決済モジュール課題履歴] 「決済APIタイムアウト(ERR_4092):外部決済会社の応答が遅いとき、リトライなしですぐに失敗へ落ちる問題。対応として、リトライ処理を入れ、応答待ち時間を延ばすことにした。」
断片2 — [決済関連の議事録] 「ペイメント要求の遅延は、ユーザーが集中する時間帯に特に多かった。アラートのしきい値を調整して、先に検知することにした。」
断片3 — [決済モジュールのリリースノート] 「決済完了画面のボタンの色と案内文言を変更して反映。」
断片に見慣れない言葉が二つある。リトライ処理とは、一度失敗してもすぐに諦めず、あと数回要求してみる仕掛けだ。アラートのしきい値とは、「この数値を超えたら警報を鳴らせ」という基準線のこと。ユーザーが集中して遅延が増えたら、その線を下げてもっと早く気づこう、という意味だ。
ここからが設計の本当の勝負どころだ。上がってきた候補をそのまま使うわけではない。検索エンジンは断片ごとに関連度スコアをつける。質問に接するほど高く、かすめるだけなら低い。このスコアで選び、捨てる。
断片1は「タイムアウト」と「ERR_4092」が質問に正面から引っかかってスコアが高い。先頭に置く。断片2は同じ出来事を別の言葉で書いている。単語は重ならなくても意味が近く、スコアが支えてくれる。原因を広げてくれるので後ろに付ける。断片3は「決済」という単語が重なっただけで、タイムアウトとは赤の他人だからスコアが低い。捨てる。
この捨てる判断こそが核心だ。新人でいえば、探してきた規程集のページのうち、質問に合う二枚だけを机に広げ、関係ない一枚は伏せる作業だ。無関係な資料まで一緒に広げておくと、モデルがそこまで読んで的外れなことを混ぜてしまう。だから、モデルに実際に渡される入力は、おおよそこんな形をしている。
[指示] 以下の参考資料だけに基づいて答えよ。資料にない内容はでっち上げず、わからないと答えよ。 [参考資料] 1) 決済APIタイムアウト(ERR_4092):外部決済会社の応答が遅いとき、リトライなしですぐに失敗へ落ちる問題。対応=リトライ処理の追加+応答待ち時間の引き上げ。 2) ペイメント要求の遅延は、ユーザーが集中する時間帯に特に多かった。アラートのしきい値調整で先に検知することにした。 [質問] 決済APIタイムアウトの原因、まとまってるやつある?
最後に、モデルが参考資料と質問を一緒に読んで答えを書く。原則は一つ。頭の中の一般知識ではなく、たった今机に広げたうちの資料を根拠にさせる。新人が頭の中の一般論を並べる代わりに、広げた規程集を指して「ここを見ると」と言うのと同じだ。
だから、よくできたRAGは答えの最後に、どの資料を見たかを添える。この場面の答えは、こう返ってくる。
「決済APIタイムアウト(ERR_4092)は、外部決済会社の応答が遅いとき、リトライなしですぐに失敗へ落ちるのが原因としてまとめられています。ユーザーが集中する時間帯に特に多かったようです。対応としては、リトライ処理を入れて応答待ち時間を延ばし、アラートのしきい値を調整して先に検知することになっています。(参考:決済モジュール課題履歴、決済関連の議事録)」
出典が添えられているので、ユーザーは信じる前に原文を開いて確かめられる。冒頭の「まとまってるやつある?」が、ここに来てようやく根拠のある答えになった。
資料の倉庫をどれだけうまく満たしても、検索結果が貧弱だったり、そもそも空だったりする瞬間は来る。そして、よく設計されたシステムは、まさにこの瞬間を自ら成長する機会として使う。フォールバック(fallback、正常な経路が塞がれたときの備えの動作)がその仕掛けだ。
まず、システムはいつ「根拠がない」と判断するのか。先ほどつけた関連度スコアをもう一度使う。上がってきた断片のうち、最も高いスコアすら基準線を下回れば、使える根拠がないと見なす。無理やりかき集めたりしない。
このとき最も危ういのは、モデルがうちの資料に答えがないからといって、頭の中の一般知識で空欄を埋めてしまうことだ。新人が規程集で見つけられず、他社の慣行を「うちもそうだと思います」とでっち上げる状況だ。その場はもっともらしい。その言葉を信じた顧客が誤った判断を下せば、責任は会社に返ってくる。
よいフォールバックは、でっち上げる代わりに正直に言う。「関連する資料が見つかりませんでした。資料をアップロードいただければ、次回から反映します」。この一言が、二つのことを同時にやってのける。一つ、知らないことを知らないと言えると示して信頼を積む。二つ、倉庫のどこが空いているかをユーザーに知らせる。その案内に従って上がってきた資料は、次の質問の検索対象になる。だからフォールバックは失敗メッセージではなく、倉庫を厚く育てる好循環の最初の一回しなのだ。
では、この原理を実際のプロダクトはどう実装しているのか。これまで「資料の倉庫」と呼んできたものを、ONTLEでは「ナレッジ・オントル」という機能が担う。
プロジェクトごとに専用のナレッジ・オントルができる。資料をアップロードすると、先に見たとおり切り分けられて保存される。AI PMに尋ねると、その瞬間、該当プロジェクトのナレッジ・オントルの中だけで探して答えに反映する。もう一度あの決済タイムアウトの場面に戻ろう。ユーザーが「あのタイムアウトの原因、まとまってるやつある?」と打つと、ONTLEはそのプロジェクトの倉庫から決済課題の履歴と議事録を引き上げ、リリースノートのように単語だけ重なった断片は取り除いたうえで、出典を添えて答える。ユーザーの目には、ただ「自社プロジェクトを知っているAI PM」に映るが、裏ではあの四つのステップが質問ごとに回っている。
だからONTLEが力を注ぐのは「資料をたくさん入れること」ではなく「正しい資料を選び出すこと」だ。資料が積み上がったからといって、精度が勝手に上がるわけではない。むしろ、無関係だったり古かったりする資料が一緒に検索されて混ざれば、誤答が増えかねない(関連する実験)。断片3のように単語だけ重なった資料を、その都度取り除く作業が重要なのは、そのためだ。
フォールバックもプロダクトに組み込まれている。根拠がなければでっち上げる代わりに、見つからなかったと明かして資料の補強を求める。モデルを学習し直さず、資料を上げるだけで参照する材料が増える——机の上の資料に立ち返るRAGの利点そのものだ。
うちのプロジェクトの文脈を覚えてくれるAI PMが必要なら、今すぐONTLEで始めてみよう。https://ontle.kr
資料が数えるほどしかなくて短いなら、それでもいい。だが、プロジェクトの資料が積み上がると、モデルが一度に読める分量をすぐに超えてしまう。おまけに毎回すべて貼ると、質問と無関係な資料まで入り込んで誤答を招く。RAGはその都度、関連する断片だけを選んで入れるので、資料がいくら増えても質問に合う資料だけをより分けて使う。
目的が違う。ファインチューニングは、口調や回答の形式のような「身についた癖」を変えるときに使う。RAGは、しょっちゅう変わる「事実」を供給するときに使う。両方を併用することもある。ただし、日ごとに変わるプロジェクトの知識をファインチューニングで押し込もうとすると、維持が大変だ。そういう知識は資料の倉庫に置くほうがいい。
探す工程が加わるぶん、純粋に生成するだけのときよりは手間がかかる。ただし倉庫をまるごと洗うのではなく、あらかじめ作っておいた索引とベクトルで候補を絞り込んでから、少数の断片だけをモデルに渡すので、体感の遅延は大きくない。むしろ長い資料をまるごと押し込むほうが、読む量が多くて遅くなりうる。