その他

【AIエージェント】RAG処理でファイル読解力を持たせてみた

その他
この記事は約10分で読めます。

前回のおさらい

こんにちは。

前回に引き続き、AIエージェントの開発を通して得られた知見を皆さんに共有したいと思います。

前回は、AIエージェントの推論力を高めるために「ReActパターン」と呼ばれる設計手法を導入し、さらに独自の工夫を加えることで推論の精度向上に取り組みました。

どのような内容だったかについては、下記添付の記事を再度ご覧いただければ幸いです!

今回やること

今回は、AIエージェントにより実用的な機能を持たせるための技術について紹介します

たとえば最近のAIツールでは、PowerPoint や Word(.docx)などのファイルをアップロードして、内容を要約・校正してもらう、といった使い方が一般的になってきましたよね。

そこで今回は、自作のAIエージェントにも同様にファイルをアップロードし、その内容に基づいて回答を得る機能を実装してみたいと思います。

RAG処理

AIエージェントにファイルの中身を理解させるためには、RAG(Retrieval-Augmented Generation)という仕組みが欠かせません。

RAG処理概要

名前を聞くと難しそうに思えるかもしれませんが、実は次の3ステップで構成されたシンプルな流れです。

1. アップロードしたファイルの内容を「ベクトルストア(検索インデックス)」に保存する
2. ユーザーのクエリに関連する内容を検索して、プロンプトを拡張する
3. その情報をもとに、AIが回答を生成する

一見すると、AIエージェントの処理全体は複雑に見えるかもしれません。
でも、こうして一つひとつ分解してみると、なんだか作れそうな気がしませんか?

埋め込みモデル(Embedding model)

AIエージェントにファイルを理解させるうえで欠かせないのが、埋め込みモデル(Embedding Model)です。

そもそも埋め込みとは、テキストや単語、フレーズなどの言語情報を数値ベクトルに変換する技術のことを指します。
この数値ベクトルは、意味の近さ(類似性)を計算するのに使われます。

自然言語処理の分野では、埋め込み技術は大規模言語モデルが登場する前から広く使われており、特に有名な手法として「word2vec」があります。

たとえば、「王様 − 男性 + 女性 = 女王」というように性別をベクトルとして計算することで導くことができます。

少し話が抽象的に感じたかもしれませんが、ここからが本題です。
埋め込みモデルを使えば、PDFやWordなどのファイルから抽出したテキストと、ユーザーが入力したクエリの両方を、同じベクトル空間にマッピングできます。

その結果、「意味的に近いテキスト」を高精度に検索することが可能になります。
こうして検索された情報が、プロンプトとしてLLMに渡され、自然な回答が生成される──これがRAGの核となる仕組みです。

RAG処理実装

ここまで、RAG処理の仕組みと、それを支える埋め込みモデルについて解説してきました。

では、いよいよ実際に自作のAIエージェントにRAG処理を組み込んでみましょう。

RAGは、その名の通り以下の3つのステップで構成されています。

  1. Retrieval(検索):ユーザーの質問に関連しそうな情報を、ベクトル検索で取り出す
  2. Augmentation(拡張):検索結果をプロンプトに追加して、AIに文脈を与える
  3. Generation(生成):プロンプトをもとに、LLMが自然な回答を生成する

それでは、これらの処理を順番に実装していきましょう。

Retrieval

このステップでは、ユーザーのクエリと意味的に近い内容をファイルから探し出します。

「意味的に近いかどうか」を測るためには埋め込みモデルを使用して、クエリとファイル内のテキストをベクトル空間にマッピングし、距離や類似度を使って検索する必要があります。

このとき使用するのが「埋め込みモデル(Embedding Model)」です。

埋め込みモデルにはさまざまなものがありますが、今回は OpenAI が提供している”text-embedding-3-small”を使用しています。

def process_uploaded_files(uploaded_files):
	text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
	    model_name="text-embedding-3-small",
	    chunk_size=500,
	    chunk_overlap=0,
	)

def get_file_context_for_query(query, k=3):
    """Get relevant context from uploaded files for a query"""
    if not st.session_state.file_vectorstore:
        return None
    
    try:
        retriever = st.session_state.file_vectorstore.as_retriever(
            search_type="similarity",
            search_kwargs={"k": k}
        )
        relevant_docs = retriever.get_relevant_documents(query)
        if relevant_docs:
            return "\n".join([doc.page_content for doc in relevant_docs])
    except Exception as e:
        st.error(f"ファイル検索エラー: {str(e)}")
    
    return None

Augmentation

ここでは、プロンプトの拡張を行います。

「プロンプトの拡張」とは、ユーザーのクエリだけでは不十分な場合に、アップロードされたファイルなどから取得した関連情報を付加して、LLMが正確に回答できるようにする処理のことです。

例えば、LLMが「このファイルの第3章に書かれている内容を説明してください」といったクエリに答えるためには、そのファイルの内容を一部でも「知っている必要」があります。そのため、検索ステップで抽出した関連文書をクエリに追加することで、回答の質を大きく向上させることが可能になります。

def create_initial_prompt(user_query: str) -> str:
    """
    初期モデル出力用のプロンプトを作成
    """
    base_prompt = f"""
ユーザーからの質問に対して、まず初期的な回答を生成してください。
この回答は後でReActパターンによってより詳細で正確な回答に改善される可能性があります。

ユーザーの質問: {user_query}
"""
    
    if st.session_state.file_vectorstore and st.session_state.uploaded_file_info:
        file_context = ta.get_file_context_for_query(user_query)
        if file_context:
            base_prompt += f"""

アップロードされたファイルからの関連情報:
{file_context}

上記のファイル情報も参考にして回答してください。
"""
    
    base_prompt += "\n初期回答を生成してください:"
    return base_prompt

Generation

この段階では、これまでの検索とプロンプト拡張で準備した情報をもとに、モデルが最終的な回答を生成します。

特別にRAG固有の処理があるわけではありませんが、検索で得た関連情報を含めたプロンプトを使うことで、より正確かつ具体的な回答を作り出すことが可能になります。

with st.spinner("初期回答を生成中..."):
	try:
      initial_prompt = create_initial_prompt(user_input)
      initial_response = st.session_state.llm.invoke(initial_prompt)
                
        if hasattr(initial_response, 'content'):
        	initial_output = initial_response.content
        else:
        	initial_output = str(initial_response)
                
        st.markdown("### 🔄 初期モデル出力")
        st.markdown(initial_output)

完成

ファイルアップロード

自作AIエージェントの利点として、ChatGPTの無料プランでは1日のファイルアップロード数が3件程度と限られていますが、自分で作ってしまえば気にしなくて良いのはとてもありがたいですね。

余談はさておき、AIエージェントの開発のために作成した要件定義書(エクセルファイル)をアップロードして、下記のプロンプトを入力してみましょう。

添付したエクセルファイルを読み込んでください。 そして、スプレッドシートごとの内容にいついてまとめ、何を開発しようとしているかについて教えてください。

Redirecting

生成結果

生成結果を見てみると、要件定義書にある内容を漏れなくまとめてくれていることを確認できました。

また、スプレッドシートが複数あっても問題なく動作していることが確認できています。

このプロジェクトは、高度なAIを活用したインタラクティブなテキスト生成システムの開発を目指しています。主な特徴と目的は以下の通りです:

システム構成:

Python言語とStreamlitフレームワークを使用したユーザーインターフェース
LangChainを用いたシステム設計
ANTHROPIC APIによるテキスト生成
DuckDuckGO APIによるWeb検索機能

主要機能:
ユーザーがプロンプトを入力できるチャットインターフェース
4つのClaudeモデルから選択可能
モデルパラメータのカスタマイズ機能
チャット履歴の保存と管理
ReActパターン(Reason + Act)の適用オプション
コスト表示機能

ReActパターンの実装:
プロンプトを段階的に深掘りし、より的確な出力を目指す構造パターン
ユーザーが任意でReActパターンを適用できるボタンを実装
出力されたテキストに対してもReActパターンを適用することで、擬似的なデバッグ機能を提供

想定されるユースケース:
高度な質問応答システム
インタラクティブな情報探索ツール
AIアシスタントとしての利用
テキスト生成の品質向上のための実験的プラットフォーム

プロジェクトの目的:
高度なAIテキスト生成能力と柔軟なユーザー操作を組み合わせたシステムの開発
ReActパターンを活用した、より精度の高い回答生成
Web検索APIの統合による、モデル単独では回答できない質問への対応
ユーザーフレンドリーなインターフェースの提供
テキスト生成のコスト管理と透明性の確保

このシステムは、AIの能力を最大限に活用しつつ、ユーザーが柔軟にカスタマイズできる環境を提供することで、様々な用途に適応可能な高度なテキスト生成プラットフォームの実現を目指しています。ReActパターンの革新的な適用方法により、AIの回答精度向上と、ユーザーによる出力の理解促進を図っています。

おわりに

いかがでしたでしょうか。

今回は自作のAIエージェントにRAG処理を実装することでファイルの読解力を持たせてみました。

作成したソースコードを現状では共有できませんが、もしツールを使ってみたいという方がいましたら、ぜひお昼休憩の時などお声がけいただけると嬉しいです。

今後の課題

現時点ではファイルをアップロードすることはできましたが、任意の形式でファイルを出力することができていません。

そのためLLMが回答したテキストの一部をユーザーが求める形でファイルとして出力できるよう実装していくことを目指します。

参考URL

【生成AI入門】「RAG」をできるだけわかりやすく解説してみる - Qiita
はじめに 現在、IT業界では生成AIが非常に盛り上がっており、エンジニアとして避けて通れない技術だと考えます。そのため、生成AIのことをいろいろと調べて学んでいきたいと思います。 今回は、「 RAG(Retrieval-Augmented Generation)」について...
OpenAIの埋め込みモデルを使ってみる(基本から応用まで) - Qiita
はじめに この記事では、OpenAIの埋め込みモデルの基礎を解説し、実際にコードを使って類似度計算や応用例を試してみます。 埋め込み(embedding)とは? 「埋め込み (embedding)」 とは、単語や文章などの特徴をベクトル(数値)に変換する手法で、文脈によ...