はじめに
初めまして、bonjunと申します。
私はエンジニア経験がありませんが、前職で次のような課題を感じていました。
マニュアルが増えすぎて、必要な情報がどこに書かれているのか分からない
そこで今回は、Notionに作成した社内マニュアルを検索し、質問に回答してくれるRAGチャットボットを作ってみました。
まだ学習中のため、説明や実装に誤りがありましたら、コメントで教えていただけると嬉しいです!
作ったもの
Notionに作成したダミーの社内マニュアルについて質問すると、関連するページを検索し、参照元と一緒に回答してくれるチャットボットです。
例えば「経費精算はどうやるの?」と質問すると、Notion内の関連ページを検索し、経費精算の手順と参照元を表示します。
- GitHubリポジトリ:https://github.com/botbotM/manual-ai
ソースコード
今回作成したチャットボットのソースコードは、GitHubで公開しています。
エンジニア未経験から学習しながら作成したため、改善できる点もあると思います。コードや設計について気になる点がありましたら、コメントやGitHubのIssueで教えていただけると嬉しいです。
使用した技術
今回使用した主な技術は以下のとおりです。
| 技術 | 役割 |
|---|---|
| Next.js | チャット画面とAPIの作成 |
| TypeScript | アプリケーションの処理を記述 |
| Notion API | Notionにあるマニュアルページの取得 |
| Ollama | Mac上でAIモデルを実行 |
| Qwen3 4B | 検索したマニュアルをもとに回答を生成 |
| EmbeddingGemma | 質問とマニュアルの文章をベクトル化 |
| Node.js Test Runner | 検索処理や安全機能の自動テスト |
| GitHub Actions | テスト・Lint・ビルドの自動実行 |
RAGとは
RAGは「Retrieval-Augmented Generation」の略で、日本語では「検索拡張生成」と呼ばれます。
簡単に説明すると、AIが回答を作る前に関連資料を検索し、その内容を参考にして回答する仕組みです。
通常の生成AIに質問しただけでは、AIが社内マニュアルの内容を知らなかったり、事実と異なる回答をしたりする可能性があります。
そこで今回は、質問に近い内容をNotionのマニュアルから検索し、見つかった文章をQwen3へ渡して回答を生成するようにしました。
処理の流れは次のとおりです。
ユーザーが質問を入力
↓
質問をベクトル化
↓
保存済みのマニュアルから
意味の近い文章を検索
↓
関連する文章をQwen3へ渡す
↓
回答と参照元を表示
システムの構成
大まかな構成は次のようになっています。
Notion
│
│ Notion APIでページを取得
↓
文章を適切な長さに分割
│
↓
EmbeddingGemmaでベクトル化
│
↓
検索用データをMac内に保存
│
│
ユーザーの質問
│
↓
質問をベクトル化
│
↓
意味の近い文章を検索
│
↓
検索結果をQwen3へ渡す
│
↓
回答と参照元を画面に表示
生成AIの処理をローカル化した理由
当初は、外部の生成AI APIやベクトルデータベースを利用する方法も検討しました。
しかし、次の点が気になりました。
- APIの利用料金
- 社内情報を外部の生成AIサービスへ送ることへの不安
- 学習中に何度も実行した場合のコスト
- できるだけ手元で仕組みを確認したい
そこで、回答生成とベクトル化にはOllamaを使用し、検索用のデータもMac内に保存する構成にしました。
この構成では、質問文や検索したマニュアルの内容を、回答生成のために外部の生成AI APIへ送信しません。
ただし、Notionからページを取得する際にはNotion APIとの通信が発生します。そのため、システム全体が完全にオフラインで動作するわけではありません。
また、今回はあくまで学習用のMVPです。実際の社内システムとして利用する場合は、少なくとも次のような対策が必要だと考えています。
- ユーザー認証
- Notionのページ権限と回答範囲の連動
- ユーザーごとのアクセス制御
- 操作履歴や監査ログの保存
- 保存データの暗号化
- トークンや秘密情報の安全な管理
- プロンプトインジェクションへの対策
- 回答内容と参照元の検証
- バックアップと障害対応
実装で意識したこと
参照元を表示する
生成された回答だけでは、その内容が正しいか判断しづらいため、回答と一緒に参照元のNotionページを表示するようにしました。
利用者が元のマニュアルを確認できるため、回答をそのまま信じるのではなく、必要に応じて一次情報へ戻れます。
関連情報がない場合は無理に回答しない
検索結果に十分な情報がない場合、AIが一般的な知識から回答を作ってしまう可能性があります。
そのため、関連する文章が見つからなかった場合は、推測で答えず「関連する情報が見つかりませんでした」と表示するようにしました。
秘密情報をGitへ登録しない
Notion APIのトークンなどは、ソースコードへ直接書かず、環境変数で管理しました。
また、環境変数を保存するファイルがGitHubへpushされないよう、.gitignoreの設定も確認しました。
自動テストとCIを用意する
検索処理や安全機能が、変更によって壊れていないか確認できるように自動テストを作成しました。
さらにGitHub Actionsを設定し、コードをGitHubへpushしたときに、次の処理を自動実行するようにしました。
- テスト
- Lint
- ビルド
これにより、手元での確認を忘れた場合でも、GitHub上で問題を発見できます。
苦労したこと
最も苦労したのは、技術用語と各処理の役割を理解することでした。
最初は、Embeddingやベクトル検索、チャンクといった言葉を見ても、それぞれが何のために必要なのか分かりませんでした。
分からない用語が出てくるたびにCodexへ質問し、次のような点を一つずつ確認しながら組み立てました。
- この処理は何のために必要なのか
- 入力と出力は何か
- どこでデータが外部へ送信されるのか
- エラーが発生するとどうなるのか
- 秘密情報をどこに保存すべきか
また、X(旧Twitter)でエンジニアの方が発信している情報なども参考にしました。
コードを動かすことだけでなく、「なぜこの処理が必要なのか」を理解することに時間がかかりました。
今回学んだこと
今回の開発を通して、主に次のことを学びました。
- RAGの基本的な流れ
- Embeddingとベクトル検索の役割
- Notion APIからページを取得する方法
- ローカルLLMと外部AI APIの違い
- エラーが発生した場合の処理
- GitとGitHubで秘密情報を扱う際の注意点
- 自動テストとCIの役割
- 参照元を表示することの重要性
- 実装だけでなく、安全性や運用も設計に含まれること
まだコードをすべて一人で書けるわけではありません。
それでも、「なぜこの仕組みが必要なのか」「どのような流れで動いているのか」を少しずつ説明できるようになりました。
今後改善したいこと
今後は、次の改善にも取り組みたいと考えています。
- 回答できなかった質問の記録
- 検索精度を評価するテスト
- 検索結果が適切だったか確認する仕組み
- スマートフォン表示の確認と改善
- エラー表示に対する自動テスト
- チャット履歴の保存
- Notionの権限を考慮したアクセス制御
- 回答速度の改善
- SQLを使ったデータ検索アプリの作成
- Pythonの学習
特に、RAGは「回答が表示されたら完成」ではなく、検索結果が本当に適切だったかを評価することが重要だと感じています。
今後は、あらかじめ質問と期待する参照ページの組み合わせを用意し、検索精度を継続的に確認できるようにしたいです。
まとめ
今回は、Notionに作成したダミーの社内マニュアルをRAGで検索できる、ローカルLLMを利用したチャットボットを作りました。
開発を通して、画面や機能を作るだけでなく、次のような点も考える必要があると学びました。
- 情報をどこへ保存するか
- データがどこへ送信されるか
- 回答の根拠をどう示すか
- エラーが発生した場合にどうするか
- 変更によって機能が壊れていないか
- 実際の運用でどのような安全対策が必要か
まだ分からないことは多いですが、今後も実際に手を動かしながら、SQLやPythonを含めて知識を深めていきたいと思います。
最後まで読んでいただき、ありがとうございました!
説明や実装に改善できる点がありましたら、コメントで教えていただけると嬉しいです!
