全文検索とベクトル検索の役割分担
本文を戻れる形にしたら、次に考えるのは検索である。
どの世界の、どの作品の、どの章の、どの段落か。
そこまで本文を整理しても、必要な場所へ辿り着けなければ意味がない。
検索botにとって、検索は中心機能である。
けれど、一口に検索と言っても、探し方には種類がある。
正確な言葉で探したいとき。
曖昧な記憶から探したいとき。
人物名で登場箇所を探したいとき。
「この依頼って何だったっけ」のように、場面や関係から探したいとき。
同じ「探す」でも、必要な能力は少しずつ違う。
第1章の「人食い蜂・人喰い蜂・人攫い蜂」の例は、その違いをよく表している。
人攫い蜂 という正しい語を持っていれば、キーワード検索で探せる。
けれど、覚えている語が 人食い蜂 や 人喰い蜂 だった場合、検索結果は出てこない。
本文にない語で探しているからだ。
これは、キーワード検索が壊れているわけではない。
むしろ、キーワード検索は正直である。
本文にある文字列を探し、本文にない文字列は返さない。
その正直さは、強みでもあり、弱みでもある。
キーワード検索は、正確な語に強い
まず必要なのは、普通の全文検索である。
このbotでは、そのために MeiliSearch を使っている。
MeiliSearchは、本文の中から指定した語を含む段落を探す。
人物名、地名、魔物名、台詞の一部、固有名詞。
こうしたものを探すとき、全文検索はとても強い。
たとえば、ワイバーン と検索する。
本文にその語があれば、該当する段落が返ってくる。
どの作品か。
どの章か。
どの段落か。
本文抜粋はどこか。
それが分かる。
このとき、検索botはとても素直な道具になる。
覚えている語を入れる。
本文から探す。
結果を返す。
これは、創作確認では頻繁に使う。
「あの人物、どこで出てきた?」
「あの地名、前に説明していた?」
「あの技名、表記はこれで合っていた?」
「あの台詞、どこにあった?」
こういうとき、キーワード検索は頼りになる。
特に小説では、固有名詞が重要である。
人物名、国名、組織名、魔物名、道具名。
それらは物語の中で何度も参照される。
正確な語を持っているなら、全文検索は速い。
AIに聞くまでもない。
意味を解釈させる必要もない。
本文にあるかどうかを探せばいい。
そういう場面では、まずキーワード検索が一番強い。
しかし、記憶は正確ではない
問題は、人間がいつも正確な語を覚えているとは限らないことだ。
第1章では、人食い蜂 と 人喰い蜂 で検索して、どちらも該当しなかった。
実際の表記は 人攫い蜂 だった。
この場合、キーワード検索は役に立たない。
少なくとも、最初の検索語では目的地に辿り着けない。
検索語が間違っているからである。
長編創作では、こういうことがよく起きる。
漢字が違う。
読みは似ている。
意味が近い。
表記ゆれかと思ったら、そもそも別の単語だった。
頭の中では覚えているのに、本文に書いた形とは違う。
また、覚えているのが単語ですらないこともある。
「あの依頼」
「初めて会った場面」
「師匠が何か言っていたところ」
「主人公が一度だけ怒った場面」
「あの町に着く前に出てきた魔物」
こうなると、キーワード検索に何を入れればいいのか分からない。
検索欄に入れるべき言葉が分からないとき、全文検索は入り口を失う。
人間の記憶は、データベースのキーではない。
物語の記憶は、もっと曖昧で、感覚的で、場面に結びついている。
意味で探す
そこで、ベクトル検索が欲しくなる。
ベクトル検索は、ざっくり言えば「意味の近さ」で探す検索である。
このbotでは、本文段落を埋め込みに変換し、Qdrantに入れている。
ユーザーの質問も同じように埋め込みに変換し、意味が近そうな段落を探す。
たとえば、ルークスの受けた依頼は? と聞く。
この質問は、本文にそのまま出てくる文字列ではない。
本文に「ルークスの受けた依頼は?」という文があるわけではない。
それでも、質問の意味に近い段落を探せれば、依頼について書かれた場面に辿り着ける可能性がある。
これが、キーワード検索との違いである。
キーワード検索は、文字列の一致に強い。
ベクトル検索は、意味の近さに強い。
もちろん、これは魔法ではない。
意味の近さで探すということは、近そうに見える別の段落も拾う可能性がある。
質問の言い方によって結果が変わることもある。
本文の中で明示されていないことを、近い文脈から拾ってしまう危険もある。
それでも、検索語が分からないときには強い。
「この場面」
「あの依頼」
「この人物が怒った理由」
「この二人の関係が分かるところ」
こういう曖昧な入口から本文へ近づくには、キーワードだけでは足りない。
ベクトル検索は、その曖昧さを受け止めるための道具である。
ただし、ベクトル検索だけでも足りない
では、全文検索をやめて、ベクトル検索だけにすればいいのか。
そうではない。
ベクトル検索は便利だが、万能ではない。
特に、固有名詞や正確な表記を探すときには、キーワード検索の方が強いことがある。
たとえば、人物名を探す。
地名を探す。
一度だけ出てきた道具名を探す。
特殊な表記の魔物名を探す。
こういうとき、意味の近さよりも、文字列そのものが重要になる。
人攫い蜂 という語が本文に何回出てくるかを知りたいなら、まず文字列として探したい。
その語が初めて出る段落を知りたいなら、本文上でその語を含む段落を順番に並べたい。
意味が近い別の語を拾ってしまうと、かえって困る。
また、ベクトル検索は「それっぽい」結果を返すことがある。
検索した人間から見ると、なぜこれが出てきたのか分かりにくいこともある。
創作確認では、この「それっぽい」が危うい。
欲しいのは、雰囲気として近いものではなく、本文上の根拠である。
特に、固有名詞や回数、初出、最終登場の確認では、正確さが必要になる。
だから、全文検索も必要である。
キーワード検索だけでは足りない。
けれど、ベクトル検索だけでも足りない。
両方ほしい。
MeiliSearchとQdrant
そこで、このbotでは全文検索に MeiliSearch、ベクトル検索に Qdrant を使っている。
MeiliSearchは、文字列を探す係である。
本文に含まれる語を見つける。
固有名詞や表記確認に強い。
検索結果を本文順に並べることもできる。
Qdrantは、意味の近さで探す係である。
質問文と本文段落を埋め込みとして扱い、近いものを探す。
キーワードが曖昧なとき、場面や関係から探したいときに役に立つ。
役割が違う。
だから、どちらか片方を選ぶのではなく、両方使う。
検索語が正確なら、MeiliSearchが強い。
検索語が曖昧なら、Qdrantが助けになる。
両方に引っかかる段落は、かなり有力な候補になる。
この組み合わせによって、検索botは少ししぶとくなる。
単語を覚えているときも探せる。
単語を忘れているときも探せる。
完全に正確ではない記憶からでも、本文へ近づける。
結果を混ぜる
ただ、全文検索とベクトル検索を両方使うと、次の問題が出てくる。
結果をどう混ぜるか。
MeiliSearchはMeiliSearchなりの順位で結果を返す。
QdrantはQdrantなりの順位で結果を返す。
では、最終的にどの段落を上位にするのか。
このbotでは、RRFという方法で結果を混ぜている。
RRFは、Reciprocal Rank Fusionの略である。
名前は少し難しいが、考え方はそれほど複雑ではない。
それぞれの検索結果で上位にいるものを高く評価し、複数の検索で上に来たものをさらに有力な候補として扱う。
たとえば、ある段落が全文検索でも上位に来て、ベクトル検索でも上位に来たなら、その段落はかなり有力である。
文字列としても合っていて、意味としても近いからだ。
逆に、片方でしか出てこない段落も、完全には捨てない。
キーワードでは拾えるが、意味検索では下がるものもある。
意味検索では近いが、キーワードとしては直接含まないものもある。
RRFは、そうした結果を一つにまとめるための交通整理係である。
この仕組みによって、検索botは「文字列で見つけた候補」と「意味で見つけた候補」を一緒に扱えるようになる。
検索は一発必中ではない
ここで大事なのは、検索を一発必中の魔法だと思わないことである。
検索botは、必ず最初の一回で正解を出す道具ではない。
第1章の例でもそうだった。
人食い蜂 では出ない。
人喰い蜂 でも出ない。
ワイバーン で検索機能が生きていることを確認する。
ルークスの受けた依頼は? と聞く。
人攫い蜂 だと分かる。
その語で回数を数える。
これは、回り道に見える。
けれど、創作確認ではこの回り道が大事である。
一回の検索で答えを得るのではなく、検索を重ねて本文へ近づく。
キーワードを試す。
別の語で確認する。
文脈から尋ねる。
出てきた表記で再検索する。
必要なら前後の段落を読む。
検索botは、その往復を支える。
この考え方を持っておくと、検索結果がゼロでも慌てなくて済む。
ゼロ件は終わりではない。
検索語を変える合図かもしれない。
検索方法を変える合図かもしれない。
本文にはないと確認するための入口かもしれない。
検索とRAGの間
この章で扱っているのは、検索である。
けれど、実際にはここからRAGにつながる。
RAG回答では、まず本文から関連しそうな段落を探す。
その段落を文脈としてLLMに渡す。
LLMは、その本文だけを根拠に答える。
つまり、RAGの前には必ず検索がある。
検索が悪ければ、RAG回答も悪くなる。
関係のない段落を渡せば、LLMは関係のない文脈から答えようとする。
必要な段落が入っていなければ、本文に答えがあっても回答には出てこない。
だから、検索はRAGの土台である。
LLMに賢く答えさせる前に、まず本文のどこを読ませるかを決めなければならない。
そのために、MeiliSearchとQdrantを使う。
文字列で探す。
意味で探す。
結果を混ぜる。
前後の段落を足す。
そうして、ようやくLLMに渡す文脈ができる。
AIに答えさせる部分だけを見ると、RAGは魔法のように見えるかもしれない。
けれど、その手前には地味な検索がある。
本文を切り、IDを付け、索引を作り、検索し、候補を選ぶ。
その積み重ねがあって、ようやく引用付き回答が成立する。
第5章では、その引用付き回答の話に進む。
検索した本文を、LLMにどう読ませるのか。
本文以外を根拠にしないとはどういうことか。
そして、なぜ回答には出典IDが必要なのか。
検索で本文に近づいたら、次は、その本文を根拠に答える段階である。