こんにちは、たねやつです。
前回、私たちはRAGシステムの「検索(Retrieval)」部分を完成させました。ユーザーの質問に対して、我々の知識の宝庫であるベクトルデータベースから、最も関連性の高い情報(チャンク)を瞬時に取り出すことができるようになりました。
しかし、取り出した情報をそのまま見せるだけでは、無愛想な検索結果リストに過ぎません。私たちの目標は、まるで人間と対話しているかのような、自然で賢い「AIアシスタント」です。
そこで今回は、いよいよRAG (Retrieval-Augmented Generation) の最後のピース、"G" すなわち「生成(Generation)」のフェーズを実装します。検索してきた情報を元に、LLM (大規模言語モデル) がユーザーの質問に直接、かつ自然な言葉で回答を生成する部分です。この最終工程では、LLMに的確な指示を与える「プロンプトエンジニアリング」が鍵を握ります。
前の記事
この記事でできること
- RAGにおける生成(Generation)フェーズの役割を理解できる。
- LLMに的確な指示を与える「プロンプトエンジニアリング」の基本がわかる。
- GoogleのLLMである Gemini API を利用し、検索結果(コンテキスト)を基に回答を生成するPythonコードを実装できる。
事前に必要なもの
- Pythonの実行環境
- Google Gemini APIキー:
- Google AI for Developers にアクセスし、Googleアカウントでログインします。
- 「Get API key in Google AI Studio」をクリックしてAPIキーを生成します。
- 生成されたAPIキーは、後でコード内で使用するので、安全な場所に保管してください。
- 関連ライブラリ: GoogleのAPIをPythonから簡単に利用するためのライブラリをインストールします。
bash pip install google-generativeai - 前回の成果物:
rag_retrieval.pyで作成したSimpleRAGクラス。
LLMとプロンプトエンジニアリング
LLMは非常に賢いですが、万能の魔法使いではありません。最高のパフォーマンスを引き出すには、「何を」「どのような形式で」 やってほしいのかを、明確に指示する必要があります。この指示書こそが「プロンプト」です。
RAGにおける生成フェーズのプロンプトは、一般的に以下の要素で構成されます。
- 役割設定(Instruction): LLMにどのような役割を演じてほしいかを指示します。「あなたは優秀な育児アシスタントです」のように。
- 参考資料(Context): 前回、ベクトルデータベースから検索してきた関連チャンクをここに埋め込みます。「以下の情報を参考にしてください:...」
- 質問(Question): ユーザーが入力した元の質問をそのまま含めます。「質問:...」
- 出力形式の指定(Output Format): 回答の形式を指示します。「上記の情報を基に、質問に対して日本語で簡潔に答えてください」のように。
このプロンプトをテンプレートとして用意し、実行時に関連情報と質問を動的に埋め込むことで、LLMは「参考資料の中から答えを探し、アシスタントとして振る舞い、質問に答える」という一連のタスクを正確に実行できるようになります。
実装:Gemini APIとの連携
それでは、前回の SimpleRAG クラスを拡張して、Gemini APIと連携する generate_answer メソッドを追加しましょう。
# rag_system.py (rag_retrieval.pyを改名・拡張) import os import chromadb from sentence_transformers import SentenceTransformer import google.generativeai as genai # ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ # 自身のAPIキーを設定してください # ※コードに直接書き込むのは危険です。環境変数からの読み込みを推奨します。 os.environ['GOOGLE_API_KEY'] = "YOUR_API_KEY" # ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ genai.configure(api_key=os.environ["GOOGLE_API_KEY"]) class PiyologRAG: def __init__(self, model_name='intfloat/multilingual-e5-large', db_path="./chroma_db", collection_name="piyolog_rag_collection"): print("モデルとデータベースを初期化しています...") self.retrieval_model = SentenceTransformer(model_name) self.db_client = chromadb.PersistentClient(path=db_path) self.collection = self.db_client.get_collection(name=collection_name) # LLMモデルの初期化 self.generation_model = genai.GenerativeModel('gemini-2.5-flash') print("初期化が完了しました。") def search(self, query, k=3): # (前回のsearchメソッドと全く同じ) query_embedding = self.retrieval_model.encode(query, normalize_embeddings=True) results = self.collection.query( query_embeddings=[query_embedding.tolist()], n_results=k ) return results['documents'][0] def generate_answer(self, query, context): """ コンテキストと質問を基に、LLMで回答を生成する """ # プロンプトテンプレートの定義 prompt_template = """ あなたは、提供された育児記録データにのみ基づいて質問に答える、誠実なAIアシスタントです。 以下の情報を参考にしてください。 --- 参考情報 --- {context} --- 上記の参考情報に基づいて、以下の質問に日本語で回答してください。 参考情報に答えがない場合は、「分かりません」とだけ答えてください。 質問: {question} """ # テンプレートにコンテキストと質問を埋め込む context_str = "\n\n".join(context) prompt = prompt_template.format(context=context_str, question=query) print("\n--- LLMへの入力プロンプト ---") print(prompt) print("---------------------------\n") # LLMによる回答生成 response = self.generation_model.generate_content(prompt) return response.text def ask(self, query): """ 質問応答のパイプライン全体を実行する """ # 1. 検索 (Retrieval) retrieved_context = self.search(query, k=2) # 2. 生成 (Generation) answer = self.generate_answer(query, retrieved_context) return answer # --- 実行部分 --- if __name__ == '__main__': # RAGシステムをインスタンス化 rag_system = PiyologRAG() # 質問応答を実行 test_query = "9月1日のミルクの合計量は?" final_answer = rag_system.ask(test_query) # 最終的な回答の表示 print(f"--- 最終的な回答 ---\n{final_answer}")
PiyologRAG クラスに generate_answer と ask メソッドを追加しました。
generate_answer: プロンプトテンプレートを定義し、searchで得られたcontextとユーザーのqueryを埋め込んで最終的なプロンプトを作成します。そしてgeneration_model.generate_content(prompt)を呼び出して、Gemini APIに回答生成を依頼します。ask: これまで作ってきたsearchとgenerate_answerを順番に呼び出す、一連の処理の流れ(パイプライン)を定義したメソッドです。ユーザーは、このaskメソッドを呼び出すだけで、質問から回答生成までの一連の流れを実行できます。
実行結果の確認
YOUR_API_KEY の部分を自身のものに書き換えて(注意:環境変数からの読み込みを強く推奨します)、実行してみましょう。
--- LLMへの入力プロンプト --- あなたは、提供された育児記録データにのみ基づいて質問に答える、誠実なAIアシスタントです。 以下の情報を参考にしてください。 --- 参考情報 --- 日付: 2022年06月17日 -------------------- 時刻: 01:50, イベント: 起きる (睡眠時間: 205分) 時刻: 01:50, イベント: おしっこ 時刻: 01:50, イベント: 母乳 (授乳時間: 左8分, 右7分) 時刻: 02:15, イベント: うんち 時刻: 02:20, イベント: 母乳 (授乳時間: 左0分, 右15分) 時刻: 02:35, イベント: 吐く 時刻: 03:00, イベント: 寝る 時刻: 04:00, イベント: 起きる (睡眠時間: 60分) 時刻: 04:00, イベント: おしっこ 時刻: 04:30, イベント: 母乳 (授乳時間: 左10分, 右8分) 時刻: 05:15, イベント: 寝る 時刻: 07:30, イベント: 起きる (睡眠時間: 135分) 時刻: 07:35, イベント: おしっこ 時刻: 07:35, イベント: 母乳 (授乳時間: 左5分, 右6分) 時刻: 07:35, イベント: うんち 時刻: 07:45, イベント: 吐く 時刻: 08:00, イベント: おしっこ 時刻: 08:00, イベント: うんち 時刻: 08:40, イベント: 母乳 (授乳時間: 左8分, 右6分) 時刻: 09:25, イベント: 寝る 時刻: 10:00, イベント: 起きる (睡眠時間: 35分) 時刻: 10:10, イベント: おしっこ 時刻: 10:15, イベント: 母乳 (授乳時間: 左7分, 右5分) 時刻: 11:05, イベント: おしっこ 時刻: 11:05, イベント: うんち 時刻: 11:40, イベント: 寝る 時刻: 12:00, イベント: 起きる (睡眠時間: 20分) 時刻: 12:15, イベント: 母乳 (授乳時間: 左5分, 右7分) 時刻: 12:30, イベント: 寝る 時刻: 13:10, イベント: おしっこ 時刻: 13:10, イベント: 起きる (睡眠時間: 40分) 時刻: 13:15, イベント: 母乳 (授乳時間: 左5分, 右5分) 時刻: 13:45, イベント: 病院 時刻: 13:45, イベント: 体温 時刻: 14:00, イベント: 体重 時刻: 14:00, イベント: 身長 時刻: 14:00, イベント: 頭囲 時刻: 16:10, イベント: おしっこ 時刻: 16:15, イベント: 母乳 (授乳時間: 左7分, 右9分) 時刻: 16:35, イベント: 寝る 時刻: 18:30, イベント: 起きる (睡眠時間: 115分) 時刻: 18:30, イベント: 母乳 (授乳時間: 左7分, 右7分) 時刻: 19:10, イベント: おしっこ 時刻: 19:15, イベント: 母乳 (授乳時間: 左11分, 右0分) 時刻: 19:30, イベント: 寝る 時刻: 20:30, イベント: 起きる (睡眠時間: 60分) 時刻: 21:00, イベント: おしっこ 時刻: 21:00, イベント: お風呂 時刻: 21:15, イベント: 母乳 (授乳時間: 左8分, 右8分) 時刻: 21:35, イベント: 母乳 (授乳時間: 左0分, 右15分) 時刻: 22:10, イベント: 洗浄液作成 時刻: 23:05, イベント: おしっこ 時刻: 23:05, イベント: うんち 時刻: 23:10, イベント: 母乳 (授乳時間: 左8分, 右9分) 時刻: 23:40, イベント: 寝る -------------------- 1日のサマリー: - 合計睡眠時間: 670分 - ミルク合計: 0ml - 母乳合計: 左89分, 右107分 - おしっこ: 11回 - うんち: 5回 日付: 2022年06月15日 -------------------- 時刻: 00:00, イベント: おしっこ 時刻: 00:00, イベント: 起きる (睡眠時間: 120分) 時刻: 00:00, イベント: 母乳 (授乳時間: 左8分, 右12分) 時刻: 00:40, イベント: 寝る 時刻: 03:20, イベント: おしっこ 時刻: 03:20, イベント: 起きる (睡眠時間: 160分) 時刻: 03:25, イベント: 母乳 (授乳時間: 左9分, 右13分) 時刻: 04:00, イベント: うんち 時刻: 04:30, イベント: 寝る 時刻: 06:20, イベント: 起きる (睡眠時間: 110分) 時刻: 06:20, イベント: おしっこ 時刻: 06:20, イベント: うんち 時刻: 06:25, イベント: 母乳 (授乳時間: 左8分, 右10分) 時刻: 06:45, イベント: 寝る 時刻: 10:00, イベント: 母乳 (授乳時間: 左8分, 右10分) 時刻: 10:00, イベント: 起きる (睡眠時間: 195分) 時刻: 10:00, イベント: おしっこ 時刻: 10:55, イベント: おしっこ 時刻: 10:55, イベント: うんち 時刻: 12:30, イベント: 母乳 (授乳時間: 左7分, 右6分) 時刻: 13:00, イベント: 寝る 時刻: 13:45, イベント: 起きる (睡眠時間: 45分) 時刻: 13:50, イベント: 母乳 (授乳時間: 左7分, 右7分) 時刻: 14:15, イベント: 寝る 時刻: 14:45, イベント: 起きる (睡眠時間: 30分) 時刻: 15:00, イベント: おしっこ 時刻: 15:35, イベント: 母乳 (授乳時間: 左7分, 右9分) 時刻: 16:30, イベント: 寝る 時刻: 17:30, イベント: 起きる (睡眠時間: 60分) 時刻: 17:40, イベント: おしっこ 時刻: 17:45, イベント: お風呂 時刻: 18:00, イベント: 母乳 (授乳時間: 左10分, 右10分) 時刻: 19:25, イベント: おしっこ 時刻: 19:25, イベント: うんち 時刻: 20:30, イベント: 母乳 (授乳時間: 左11分, 右11分) 時刻: 21:20, イベント: 寝る 時刻: 22:20, イベント: 起きる (睡眠時間: 60分) 時刻: 22:25, イベント: おしっこ 時刻: 22:25, イベント: 母乳 (授乳時間: 左8分, 右5分) 時刻: 22:45, イベント: うんち 時刻: 23:10, イベント: 寝る -------------------- 1日のサマリー: - 合計睡眠時間: 780分 - ミルク合計: 0ml - 母乳合計: 左83分, 右93分 - おしっこ: 9回 - うんち: 5回 --- 上記の参考情報に基づいて、以下の質問に日本語で回答してください。 参考情報に答えがない場合は、「分かりません」とだけ答えてください。 質問: 2022年06月17日の合計睡眠時間は? --------------------------- --- 最終的な回答 --- 2022年06月17日の合計睡眠時間は670分です。
(※合計量などは皆さんのデータによって変わります)
素晴らしい!検索結果のテキストをただ並べるのではなく、LLMがその内容を解釈し質問の意図に沿った形で、かつ自然な文章で回答を生成してくれました。
最後に
ついに、私たちの育児AIアシスタントが完成しました!
- プロンプトエンジニアリングでLLMに的確な指示を与える方法を学んだ。
- Google Gemini APIを使い、検索結果を基に回答を生成する機能を実装した。
- 検索(R)→生成(G)という、RAGの一連のパイプラインを完成させた。
これにて、ぴよログデータでつくる育児AIアシスタントシリーズは一旦完結です!
当初の非構造化データから始まり、正規表現でのデータ抽出、チャンク化、ベクトル化、そしてLLMとの連携と、RAGシステムを一から構築する長い道のりでした。 最終的に、育児記録に関する質問へ自然言語で応答できるAIアシスタントが完成しました。
この連載を通じて、RAGの基本的な仕組みや実装の流れを掴んでいただけたなら幸いです。 ここからさらに、
- StreamlitなどでUIを作成して、より対話的に使えるようにする
- 異なるエンベディングモデルやLLMを試して、回答精度を比較する
- チャンキング戦略をさらに洗練させて、検索精度を向上させる
など、多くの発展が考えられます。ぜひご自身のプロジェクトとして育ててみてください。
全9回にわたり、お付き合いいただき本当にありがとうございました!