PythonでRAGチャットボットを構築する
概要
OpenAI・Pinecone・Notionを組み合わせたRAG(Retrieval-Augmented Generation)チャットボット。
Notionをナレッジベースとして使用し、Pineconeでベクトル検索、OpenAIで回答生成を行う。
システム構成
Notion(ナレッジDB)
↓ ページ取得・テキスト抽出
Pinecone(ベクトルDB)
↓ 類似検索
OpenAI(回答生成)
↓
ユーザーへ返答 + スプレッドシートにログ記録
モジュール構成
以下の役割ごとにモジュールを分割して実装している。
- エントリーポイント(main.py) : 各モジュールを統合して回答を生成・ログ出力
- エラー通知 : 例外発生時にメールで通知
- スプレッドシート操作 : 行の追加・読み書き
- Notion連携 : ページリストの取得、ブロックAPIでのテキスト抽出
- Pinecone連携 : ベクトルデータの登録・更新・削除
- OpenAI連携 : テキストのベクトル化、回答・質問文の生成
- DBメンテナンスバッチ : Notionとの差分検出・Pinecone同期
- フロントエンド : StreamlitによるチャットUI
各モジュールの要点
エントリーポイント(main.py)
- 設定ファイルから各種APIキーや設定値を読み込む
- プロンプトファイルからシステムプロンプトを読み込む
- 回答生成後、質問・回答・参照ナレッジ・日時をスプレッドシートにログ記録
- 例外発生時はスタックトレースをメールで通知
# UTC→JSTに変換して記録
dt_now_utc = datetime.datetime.now(datetime.UTC)
dt_now_jst = dt_now_utc + datetime.timedelta(hours=9)
⚠️ APIキー等の機密情報は設定ファイルに外出しし、コードに直書きしない
エラー通知モジュール
SMTPでGmailを使ったエラー通知メール送信。
smtpobj = smtplib.SMTP('smtp.gmail.com', 587)
smtpobj.starttls()
smtpobj.login(sendAddress, password)
⚠️ パスワードはGmailのアプリパスワードを使用。コードへの直書きは避け、設定ファイルや環境変数で管理する
Notion連携モジュール
NotionブロックAPIでページ内テキストを再帰的に取得。
-
next_cursorを使ったページネーション対応 -
rich_textを持たないブロックは安全にスキップ - リスト系ブロックは先頭に
・を付与
Pinecone連携モジュール
- インデックスが存在しない場合は自動作成(次元数:1536、距離:euclidean)
-
index.update()でベクトルとメタデータ(ナレッジ本文)を更新
設定ファイルの構成例
[OPENAI]
OPENAI_API_KEY = your_api_key
[PINECONE]
PINECONE_API_KEY = your_api_key
PINECONE_EMVIRONMENT = us-east-1
PINECONE_INDEX = your_index_name
[NOTION]
NOTION_KEY = your_notion_key
NOTION_ID = your_database_id
[GSS]
GSS_TEMP_KEY = your_spreadsheet_key
GSS_NAME = your_spreadsheet_name
GSS_SHEET_NAME_LOG = log
KEY_FILE_NAME = credentials.json
DBメンテナンスバッチ
Notionのナレッジベースとの差分を検出し、Pineconeを自動で同期するバッチ処理。
処理フロー
スプレッドシートから最終実行日・ナレッジIDリスト取得
↓
Notionから全ページ情報を取得
↓
各ページの作成日・更新日と最終実行日を比較
↓ 新規 or 更新
テキスト取得 → 質問文生成(OpenAI)→ ベクトル化 → Pineconeに登録/更新
↓
スプレッドシートの最終実行日を更新
↓
差分チェック(追加:未登録IDを登録 / 削除:消去IDをPineconeから削除)
ポイント
| 処理 | 内容 |
|---|---|
| 差分検出 | スプレッドシートのIDリストとNotionのIDリストを set で差分比較 |
| ベクトル化 | ナレッジ本文 + OpenAIで生成した想定質問文を結合してベクトル化(検索精度向上) |
| 削除同期 | Notionで削除されたページはPineconeからも自動削除 |
| エラー通知 | 例外発生時はスタックトレースをメール通知 |
# 差分チェックのコア部分
InsertDiff = list(set(notionIds) - set(gsIds)) # Notionにあってシートにない → 新規
DeleteDiff = list(set(gsIds) - set(notionIds)) # シートにあってNotionにない → 削除
フロントエンド(Streamlit)
StreamlitでチャットボットのフロントエンドUIを構築。
画面構成
- チャット履歴をバブル形式で表示(ユーザー/ボットで色分け)
- ボットの回答はMarkdown→HTMLに変換してレンダリング
- 入力中は「考え中...」スピナーを表示
状態管理のポイント
Streamlitは毎回フルリロードされるため、st.session_state で状態を管理する。
# 回答の二重処理を防ぐフラグ制御
if last_msg["role"] == "user" and "answered" not in st.session_state:
# 回答生成
st.session_state.answered = True
st.rerun()
elif "answered" in st.session_state:
del st.session_state.answered # 次回入力に備えてフラグをクリア
st.rerun()でユーザー入力を即時反映してから回答を生成することで、自然なチャット体験を実現
全体アーキテクチャ
【バッチ】DBメンテナンス(定期実行)
Notion → テキスト抽出 → ベクトル化 → Pinecone登録/更新/削除
↓ IDリスト・実行日を管理
スプレッドシート
【フロント】Streamlit
ユーザー入力
↓
main.py → Pinecone検索 → OpenAI回答生成
↓
チャット画面に表示 + スプレッドシートにログ記録
ポイントまとめ
- APIキーは設定ファイルで一元管理し、コードに直書きしない
- エラーは必ずキャッチしてメール通知する運用にする
- Notionのページネーションに対応することで大量ナレッジにも対応可能
- スプレッドシートへのログ記録で運用後のQA分析が可能