「上司がつらい」と書くと、
なぜこのコーチが出てくるのか
RAGの費用や構成の話をこれまで何本か書いてきました。ですが、検討中の方から一番よく返ってくるのは「結局、どう動くんですか」という一言です。図で説明するより、実際に動いているものをそのままお見せするほうが早いと思いました。
題材は、当社が運営している worry.team の「あなたに合うコーチを探す」ページです。相談者がチャットに悩みを書くと、その内容に合いそうなコーチをAIが探して紹介します。この中身が、まさに社内文書検索と同じRAGの構造をしています。
この記事では、実際に検索を流したときのスコアをそのまま載せます。数字は開発環境で実測したもので、丸めたり作ったりはしていません。
そもそも何をしているのか
処理は3段階です。
- ① 検索語を作る:相談者が書いた文章から、検索に使うキーワードをAIに抽出させる。
- ② 探す:コーチ一人ひとりのプロフィール(紹介文・得意な相談分野・学んだ場所・保有ライセンス・経歴・相談してきた人数)を1件のテキストにまとめ、あらかじめ「意味のベクトル」に変換して保存しておく。①のキーワードも同じようにベクトルに変換して、近いものを上から並べる。
- ③ 書く:上位に出たコーチの情報だけをAIに渡し、「この中から選んで、なぜ合うのかを書いて」と指示する。
ポイントは、誰を候補にするかを決めているのは③のAIではなく、②の検索だということです。ここを分けておくのが、業務でRAGを使うときの一番大事な設計だと考えています。理由は後半で書きます。
こう検索すると、こう出ます
開発環境に入っている7名分のコーチデータ(本番はいまコーチを募集中のため、これはデモ用のプロフィールです)に対して、実際の相談文らしき文章を6つ投げました。数字はコサイン類似度で、1.0に近いほど「意味が近い」ことを表します。
「上司に毎日きつい言い方をされて、朝、会社に行こうとすると動悸がする」
0.700 山田はるか 職場の人間関係、上司との関係、ハラスメント、休職と復職 0.654 佐藤みさき 上司とのコミュニケーション、転職の迷い、職場の孤立感 0.652 高村けい 言いにくいことを角を立てずに伝える/上司との面談を…
ここが、キーワード検索との分かれ目です。相談文には「ハラスメント」も「休職」も一言も書かれていません。書いてあるのは「きつい言い方」「動悸」だけです。それでも、ハラスメントと休職を得意とするコーチが1位に来ます。単語が一致したからではなく、文章全体の意味が近いからです。
「子どもが中学生になってから口をきいてくれない」
0.711 森下ちひろ 思春期の子どもとの会話を再開する/責め合いにならない夫婦の話し合い方… 0.637 佐藤みさき 上司とのコミュニケーション、転職の迷い、職場の孤立感 0.632 望月あかり 断り方を身につける/パートナーに本音を伝える/…
これも同じです。「口をきいてくれない」と「会話を再開する」に共通する単語はありません。1位と2位の差が0.07以上あり、迷いなく1人に決まっているのが分かります。
「父を亡くしてから半年、まだ何も手につきません」
0.747 里見たかし 大切な存在を見送ったあとの暮らしを組み立て直す/思い出との付き合い方… 0.642 佐藤みさき 上司とのコミュニケーション、転職の迷い、職場の孤立感 0.640 望月あかり 断り方を身につける/…
「亡くす」という言葉は、里見さんのプロフィールには入っていません。入っているのは「見送ったあと」です。それでもきちんと1位に来ます。しかも2位を0.1以上引き離しています。
「いまの会社に残るか、転職するかを決められない」
0.747 大津ゆうすけ 残るか動くかを自分で決められるようにする/これまでの仕事を棚卸しする… 0.709 山田はるか 職場の人間関係、上司との関係、ハラスメント、休職と復職 0.708 佐藤みさき 上司とのコミュニケーション、転職の迷い、職場の孤立感
いちばん大事なのは「出さない」こと
ここまでは、うまくいく例です。RAGを業務で使うときに本当に難しいのは、その逆です。
「歯が痛いので歯医者を探しています」
0.561 森下ちひろ 0.560 佐藤みさき 0.557 望月あかり
見ていただきたいのは、まったく無関係なのに、スコアが0になっていないことです。0.56前後の値が並んでいます。ベクトルの類似度とはそういうもので、日本語の文章同士であれば、内容が無関係でもある程度の数字は必ず出ます。
ですから「上から3件を出す」という作りにすると、歯が痛い人に人間関係のコーチを勧めてしまうことになります。これは、社内RAGで「その規程には書かれていないこと」を堂々と答えてしまう事故と、まったく同じ原因です。
そこで当社の実装では、しきい値を 0.60 に置いています。この設定で「歯が痛い」を検索すると、結果は0件になります。
この0.60という数字に、理論的な根拠はありません。自社のデータで実際に測って決めた値です。RAGを導入するとき、この「しきい値を決める作業」は必ず発生しますし、データが変われば決め直しになります。見積書に載りにくいのに手間がかかる工程は、だいたいこういうところです。
相談文を、そのまま検索に使ってはいけない
冒頭で「①検索語を作る」という段を挟んでいると書きました。これは飾りではありません。効き目を測ったので、そのまま載せます。
たとえば最初の相談文からは、AIがこういう検索語を作りました。
相談文: 上司に毎日きつい言い方をされて、朝、会社に行こうとすると動悸がする
↓
検索語: パワハラ, 職場ストレス, 適応障害, メンタルヘルス, 動悸,
うつ傾向, 対人関係の悩み, 職場環境, ストレスケア, 心療内科
相談者が書かなかった「パワハラ」という語を、こちらで補っています。コーチのプロフィール側は、この語彙で書かれているからです。話し言葉と、プロフィールの書き言葉のあいだを埋める工程だと考えてください。
この一段があるとないとで、スコアがどう変わるか。
以下は1回ずつの測定です。ばらつきについては、この直後で扱います。
| 相談内容 | 相談文のまま | 検索語を作ってから | 差 |
|---|---|---|---|
| 上司の言い方がつらい | 0.650 | 0.700 | +0.050 |
| 断れない | 0.645 | 0.693 | +0.048 |
| 子どもが口をきかない | 0.654 | 0.711 | +0.057 |
| 父を亡くした | 0.623 | 0.747 | +0.124 |
| 転職を決められない | 0.672 | 0.747 | +0.075 |
| 歯が痛い(無関係) | 0.554 | 0.561 | +0.007 |
当たっているものは大きく伸び、無関係なものはあまり伸びていません。ただし検索語を作るのはAIなので、同じ相談文でも毎回わずかに違う語が出ます。1回の測定で結論を出すわけにいかないので、代表的な2問を5回ずつ測り直しました。
| 相談内容 | 相談文のまま | 検索語を作ってから(5回平均) | ばらつき |
|---|---|---|---|
| 上司の言い方がつらい(当たり) | 0.652 | 0.735(+0.083) | 0.729〜0.741 |
| 歯が痛い(外れ) | 0.553 | 0.578(+0.025) | 0.561〜0.587 |
当たりが+0.083、外れが+0.025。差は広がりますが、外れも一緒に上がります。実際、別の実行では「歯が痛い」が0.599まで上がりました。しきい値0.60まで、残り0.001です。
1位のコーチが入れ替わったケースは6件中0件でした。変わったのは順位ではなく、確信の度合いのほうです。
この差は、たまたま見つかりました
正直に書きます。この記事を書くために動かすまで、当社ではこの検索語を作る処理が、ずっと失敗していました。ライブラリの読み込みが1行足りず、呼ぶたびにエラーになっていたのです。
ところが、画面はまったく正常でした。エラーは内部で握り潰され、「検索語が取れなかったら相談文をそのまま使う」という予備の経路に静かに落ちていたからです。紹介は出る。それらしい文章も返る。ただ、精度を上げるための工程だけが丸ごと空振りしていた。
動いているように見えることと、設計どおりに動いていることは、別である。
これはRAGに限った話ではありませんが、AIを使ったシステムでは特に起きやすい失敗です。出力がそれらしいので、壊れていても気づけません。各段が「取れなかったら次善の策で進む」作りになっていると、なおさらです。ログに何が出ているかを、定期的に見るしかありません。
AIには「検索結果の外」を書かせない
③でAIに渡しているプロンプトには、こう書いてあります。
- 検索結果に存在しないコーチを絶対に作り出さないでください。 - 紹介するのは最大2名までです。 - 診断や治療の断定はせず、「こういう方が力になれそうです」に留めてください。
さらに、プロフィールへのリンクを書かせるときは、URLの後半(slug)を検索結果の中に明記して、それを写させています。AIにURLを考えさせると、それらしく存在しないURLを作るからです。実際に出てきた回答が、こちらです。
毎日、上司の方からの厳しい言葉を受け続け、朝になると身体に不調が出るほどの 状況、本当にお辛いこととお察しします。ご自身の心身を守ることを最優先に、 まずは無理をせず、今の気持ちを誰かに話すことから始めてみませんか。 ご相談内容を踏まえ、力になれそうなコーチを2名ご紹介します。 コーチ名: 山田はるか 得意分野: 職場の人間関係、上司との関係、ハラスメント、休職と復職 なぜこの方が合うか: 企業の相談窓口で12年という長いキャリアを持ち、特に 「上司との関係」や「ハラスメント」に関する相談実績が豊富です。身体に症状が 出ている現状に対し、休職や復職を含めた専門的な知見から、心身のケアを優先して サポートしてくれると考えられます。 プロフィールURL: https://www.worry.team/accounts/demo-haruka
「経験12年」という部分は、AIが盛ったわけではありません。検索結果としてAIに渡したプロフィールに書いてある情報です。根拠になる文章を先に渡し、その中だけで書かせる。これがRAGの本質で、逆に言えば、渡す文章がいい加減だと出力もいい加減になります。
速度と費用:遅いのは検索ではありません
この処理にかかった時間を、分けて測りました。
- 類似度の計算:0.004ミリ秒(100回の平均)。7件×768次元の行列とベクトルの掛け算です。
- 文章をベクトルに変換するAPI呼び出し:1,209ミリ秒。
- 3段すべて含めた全体:約6.2秒。
つまり、時間のほぼ全部は外部APIの往復で、検索そのものは一瞬です。「ベクトル検索は重いのでは」という心配をよくいただきますが、数千件から数万件の規模なら、心配する場所はそこではありません。
なお、当社のこの機能はpgvectorのようなDB拡張を使っていません。ベクトルをそのままバイト列として保存し、必要なときにメモリ上で行列に戻して計算しています。本番環境がPostgreSQL 12で拡張を追加できない、という制約から始めた作りですが、実測では1,000件で1.2ミリ秒、10,000件で23ミリ秒。当面これで足ります。
もう一つ、コストを下げている工夫があります。使っている埋め込みモデルは既定で3,072次元のベクトルを返しますが、当社は先頭768次元だけを切り取って使っています。このモデルは「先頭から順に重要な情報が入る」ように学習されているので、切っても意味が壊れません。保存量も計算量も4分の1になります。
権限と鮮度:ここが実務の本番
最後が、社内RAGでもっとも事故が起きる部分です。検索の対象にするコーチを、次の3条件をすべて満たす人に限定しています。
- 運営が承認済みであること(審査中の申請者を紹介しない)
- アカウントが有効であること
- 退会申請が出ていないこと
この条件が1か所でも緩いと、まだ審査していない人を「この方が合います」と相談者に紹介してしまいます。社内文書でいえば、他部署しか見てはいけない資料が検索結果に出てくるのと同じです。実際、この3条件が揃うまでには一度作り直しています。
鮮度のほうは、プロフィールの文面からハッシュ値を作って持たせています。コーチが自己紹介を書き換えるとハッシュが変わり、そこではじめてベクトルを作り直します。中身が変わっていない保存では、APIを呼びません。更新のたびに全件作り直す作りにすると、費用も時間も無駄になります。
まとめ:御社の資料でも、同じことができます
- 単語が一致しなくても見つかる。「動悸がする」からハラスメント相談のコーチに辿り着けます。
- 質問文は、そのまま検索に使わない。検索語を作り直す一段で、当たりだけが伸び、ハズレとの差が広がります。
- 関係ないときは黙らせる。しきい値を実測で決め、候補ゼロならAIを呼ばない。ここが品質の分かれ目です。
- AIには渡した文章の中だけを書かせる。候補選びはAIの仕事ではありません。
- 検索は速い。遅いのはAPI。数万件までは大がかりな基盤は要りません。
- 権限フィルタと更新の設計に、いちばん手間がかかる。ここを省くと事故になります。
- そして、動いているように見えても壊れていることがある。ログを見る習慣まで含めて、運用です。
コーチのプロフィールを、社内規程・過去の提案書・問い合わせ履歴・製品マニュアルに置き換えれば、そのまま社内RAGになります。当社は自社サービスでこれを動かしているので、「うまくいく例」だけでなく「うまくいかない場合にどう黙らせるか」までお見せできます。
実際の動きは worry.team のコーチ紹介ページ でご覧いただけます。自社の資料で試してみたい、という段階のご相談も歓迎です。お問い合わせからどうぞ。