RAGの基本

なぜRAGが必要なのか?

現代の大規模言語モデルは驚異的な能力を持っていますが、いくつかの重要な制約があります:

  1. 知識の鮮度の問題:大規模言語モデル(LLM)は膨大な情報を事前学習することによって作られます。そのため、この学習の時点で「凍結」された知識しか持ちません。例えば、2023年までのデータで学習したモデルは、2024年以降の出来事についての情報が含まれていません。
  2. ハルシネーション(幻覚):LLMは自信満々に間違った情報を生成することがあります。
  3. 専門知識の限界:特定の組織や分野の専門的な情報には弱いことがあります。これは事前学習のデータの範囲に依存しています。
  4. ソースの不透明性:LLMは回答の情報源を明示できないため、信頼性の検証が困難です。
  5. コンテキスト長の制限:現在ではLLMに情報を与えながら質問することもできます。しかし、一度に処理できる情報量には制限があります。

RAG(Retrieval-Augmented Generation)は、上記の制約に対する有効な解決策を提供します:

RAGの構造

RAGの処理を細かく見ると、以下の図ように分けることができます。

データベース化

  • ドキュメント: 社内文書、営業資料など様々なソースからデータを収集・整形する
  • チャンク分割: 大きなドキュメントを適切な大きさに分割
  • ベクトル化(エンベッディング): 各チャンクをベクトル表現に変換
  • メタデータ付与: 出典、日付、カテゴリなどの情報を追加
  • ベクトルデータベースへの格納: Chroma、Pinecone、Weaviateなどのベクトルデータベースに格納

検索(Retrieval)

  • ベクトル化(エンベッディング): ユーザークエリをDB作成時と同じエンベッディングモデルでベクトル化
  • フィルタリング: メタデータを使用して検索対象を絞り込み
  • 検索: ベクトル類似度等に基づいて関連性の高い情報を検索

生成(Generation)

  • プロンプトエンジニアリング: 検索結果を組み込んだ効果的なプロンプト設計
  • 回答生成: LLMによる回答の生成
  • 評価と改善: 生成された回答の評価と改善

RAGプロンプト

RAGシステムでは、効果的なプロンプト設計が重要です。以下は典型的なRAGプロンプトの構造です:

システム指示:
あなたは正確で有用な情報を提供するアシスタントです。以下のコンテキスト情報に基づいて質問に答えてください。
コンテキスト情報に含まれていない場合は、「その情報はコンテキストに含まれていません」と正直に答えてください。
回答ではコンテキスト情報の関連部分を引用し、ソースを明示してください。

コンテキスト情報:
[検索システムから取得した関連情報をここに挿入]

ユーザーの質問:
[ユーザーの質問をここに挿入]

プロンプト最適化テクニック

  1. 情報の階層化: 最も関連性の高い情報を先に配置する
  2. ソース明示: 各情報の出典を明確に示すように指示する
  3. 指示の明確化: AIに期待する回答形式を明示する
  4. Few-shotの活用: 期待する回答の例を示す
  5. ガイドラインの提示:回答を生成する上での手順、制約などを示す

最新のLLMモデルでは段階的な思考がモデルに組み込まれており、プロンプトの最適化手法は少し変わってきています。モデルの特性と全体の処理構造を理解した上で、モデルの選定とプロンプトの設定をする必要があります。

RAGサンプルコード

以下はLangChain-Pythonを使用したRAG実装の基本的なサンプルコードです:

Difyを使うとノーコードで実現できますが、RAGの内部処理を理解しておくことで、改善のポイントが見えてくることもあります。

import os
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.llms import OpenAI
from langchain.chains import RetrievalQA

# 環境変数の設定
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"

# 1. ドキュメントの読み込み
loader = TextLoader("./data/document.txt")
documents = loader.load()

# 2. ドキュメントのチャンク化
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    length_function=len,
)
chunks = text_splitter.split_documents(documents)

# 3. エンベッディングとベクトルDBの構築
embeddings = OpenAIEmbeddings()
vectordb = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)

# 4. 検索用リトリーバーの作成
retriever = vectordb.as_retriever(search_kwargs={"k": 3})

# 5. RAGシステムの構築
llm = OpenAI(temperature=0)
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    return_source_documents=True
)

# 6. クエリの実行
query = "この文書の主要なポイントは何ですか?"
result = qa_chain({"query": query})

# 7. 結果の表示
print("回答:", result["result"])
print("\n参照ソース:")
for i, doc in enumerate(result["source_documents"]):
    print(f"ソース {i+1}:\n{doc.page_content}\n")

まとめ

RAGの機能は強力ですが、それでも100%正しい答えが返ってくる保証はありません。 特に図やグラフの解釈や推論を必要とする回答は、まだ発展途上と言わざるを得ません。

しかし、適切なベクトルDBの構築、LLMの選定、プロンプトの改善などで正答率は上がってきます。過度な期待によって「使えない」と結論づけてしまうのではなく、現在のRAGの実力を正しく理解し、業務に適切に利用することが求められるかと思います。