LLMの幻覚をなくす!rag-from-scratchでRAGを徹底解剖


LLMの幻覚をなくす!rag-from-scratchでRAGを徹底解剖

langchain-ai/rag-from-scratch

2025-07-27

まず、この「rag-from-scratch」というツール、一体何者なのか? これを理解することが、事件解決の第一歩です。

これは、Retrieval Augmented Generation (RAG) という技術を「ゼロから」実装するためのリポジトリです。RAGというのは、大規模言語モデル(LLM)が、学習データにはない最新の情報や専門知識を基に、より正確で関連性の高い回答を生成するための強力な手法です。簡単に言うと、LLMに外部の知識ベース(例えば、会社のドキュメント、最新のニュース記事など)を参照させて、幻覚(Hallucination)を防ぎつつ、より賢い答えを出させるための仕組みです。

通常のLLMは、学習したデータに基づいて回答しますが、それだと最新情報に対応できなかったり、特定の専門分野の知識が不足したりします。そこでRAGの出番です。ユーザーの質問が来たら、まず関連する情報を外部のデータベースから「検索(Retrieval)」し、その検索結果をLLMに渡して「生成(Generation)」させることで、より的確な回答を引き出すことができるんです。

この「rag-from-scratch」は、そのRAGの基本的な要素を、外部ライブラリに頼りすぎずに、どのように構築するかを学ぶための教材であり、出発点となるリポジトリだと思ってください。まさに、RAGという仕組みの「設計図」と「組み立てキット」が一緒になったようなものです。

さて、この「設計図」と「組み立てキット」が、なぜ皆さんの仕事に役立つのか? 刑事の勘で言わせてもらうと、これは非常に強力な武器になります。

皆さんが開発しているLLMを使ったチャットボットやQ&Aシステムで、LLMが時々トンチンカンなことを言ったり、存在しない情報をでっち上げたりすることはありませんか? それが「幻覚」という現象です。RAGを導入すれば、この幻覚を大幅に減らすことができます。

例えば、

社内ナレッジベースの活用
会社の製品マニュアルや技術ドキュメントを外部知識としてLLMに参照させることで、社内問い合わせボットの回答精度を飛躍的に向上させられます。

最新情報の提供
ニュース記事やリアルタイムデータと連携させることで、LLMが常に最新の情報に基づいて回答できるようになります。

「rag-from-scratch」を参考にすれば、こうしたRAGの仕組みを理解し、皆さんのアプリケーションに組み込むための具体的な方法が見えてきます。

大規模なLLMをFine-tuning(追加学習)するには、膨大なデータと計算リソース、そして時間が必要です。しかしRAGであれば、Fine-tuningなしに外部知識を連携させることで、LLMの知識を拡張できます。これは開発コストと時間を大きく削減できる可能性を秘めています。

「rag-from-scratch」の最大の魅力は、RAGの裏側で何が起きているのかを「ゼロから」理解できる点です。多くのRAGライブラリは便利ですが、内部の動作が見えにくいことがあります。しかし、このリポジトリを読み解くことで、RAGの各コンポーネント(文書分割、埋め込み、ベクトル検索、プロンプト構築など)がどのように連携しているかを深く理解できます。これは、トラブルシューティングや、より高度なカスタマイズを行う上で非常に重要です。

さあ、事件現場に踏み込むように、具体的な導入方法を見ていきましょう。

「rag-from-scratch」は、特定のフレームワークやライブラリに依存せず、RAGのコンセプトをコードで示しています。そのため、「導入」というよりは「参考にし、自身のプロジェクトに組み込む」というイメージが近いでしょう。

まずは、証拠品を押収するかのごとく、リポジトリをクローンします。

git clone https://github.com/langchain-ai/rag-from-scratch.git
cd rag-from-scratch

ここが一番重要なフェーズです。中のコードをじっくりと分析し、RAGの各ステップがどのように実装されているかを理解しましょう。

主要なコンポーネントは以下の通りです。

Document Loading & Splitting (文書の読み込みと分割)
大量のテキストデータをLLMが処理しやすいように、小さな塊(チャンク)に分割します。

Embedding (埋め込み)
分割したテキストを、ベクトル(数値のリスト)に変換します。このベクトルは、テキストの意味的な内容を表します。

Vector Database / Vector Store (ベクトルデータベース)
埋め込みベクトルを保存し、高速に検索できるようにするデータベースです。

Retrieval (検索)
ユーザーの質問ベクトルと、データベースに保存された文書ベクトルを比較し、関連性の高い文書を特定します。

Prompt Construction (プロンプト構築)
検索で得られた関連文書とユーザーの質問を組み合わせて、LLMに渡すプロンプトを作成します。

LLM Invocation (LLM呼び出し)
構築したプロンプトをLLMに渡し、回答を生成させます。

これらのステップが、どのようなPythonコードで表現されているかを、丁寧に読み解いてください。特に、どのようにテキストが処理され、ベクトルに変換され、検索されるのかがポイントです。

理解が深まったら、いよいよ皆さんのプロジェクトに応用する番です。

データソースの変更
リポジトリではサンプルのテキストが使われていますが、皆さんのプロジェクトでは、データベース、API、ファイルシステムなど、さまざまなデータソースから情報を取得する必要があります。

埋め込みモデルの選択
どの埋め込みモデルを使うか、どのLLMを使うかは、プロジェクトの要件や予算によって変わってきます。OpenAIの埋め込みモデルを使うこともできますし、Hugging Faceのオープンソースモデルを使うこともできます。

ベクトルデータベースの選択
Faiss, Chroma, Pinecone, Weaviateなど、様々なベクトルデータベースがあります。プロジェクトの規模や要件に合わせて最適なものを選択しましょう。

LLMの統合
OpenAI APIやGoogle Cloud Vertex AIなど、利用するLLMサービスへの接続方法を実装します。

「rag-from-scratch」自体がサンプルコードの塊ですが、ここではその概念を汲み取った、よりシンプルな「RAGの心臓部」のイメージをお見せします。

# 以下のコードは、rag-from-scratchの概念を理解するための簡略化された例です。
# 実際のrag-from-scratchリポジトリには、より詳細で完全な実装が含まれています。

import openai
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

# --- 1. ドキュメントの準備と埋め込み生成 ---
documents = [
    "製品Aは高性能で、最新のAIチップを搭載しています。",
    "製品Bは軽量で持ち運びが簡単です。バッテリー寿命が長いです。",
    "製品Aの価格は500ドルです。",
    "製品Bは2024年1月にリリースされました。"
]

# 埋め込みモデルの初期化(例: OpenAIのtext-embedding-ada-002)
# 実際のアプリケーションではAPIキーの設定が必要です
# openai.api_key = "YOUR_OPENAI_API_KEY"

def get_embedding(text):
    # これは例です。実際にはOpenAI APIを呼び出します
    # response = openai.embeddings.create(input=[text], model="text-embedding-ada-002")
    # return response.data[0].embedding
    
    # ダミーの埋め込みを生成 (実際は意味的なベクトルが返ります)
    return np.random.rand(1536) # 仮に1536次元のベクトルとします

doc_embeddings = [get_embedding(doc) for doc in documents]
print("ドキュメントの埋め込みが完了しました。")

# --- 2. ユーザーからの質問と検索 ---
query = "製品Aの値段はいくらですか?"
query_embedding = get_embedding(query)
print(f"ユーザーの質問: '{query}' の埋め込みが完了しました。")

# コサイン類似度を使って最も関連性の高いドキュメントを検索
similarities = [cosine_similarity([query_embedding], [doc_emb])[0][0] for doc_emb in doc_embeddings]
most_relevant_doc_index = np.argmax(similarities)
retrieved_doc = documents[most_relevant_doc_index]

print(f"\n検索された最も関連性の高いドキュメント:\n'{retrieved_doc}'")

# --- 3. プロンプトの構築とLLM呼び出し ---
# 実際のLLM呼び出し (例: OpenAIのChatCompletion)
# 実際のアプリケーションではAPIキーの設定が必要です
# openai.api_key = "YOUR_OPENAI_API_KEY"

def ask_llm(prompt_text):
    # これは例です。実際にはOpenAI APIなどを呼び出します
    # response = openai.chat.completions.create(
    #     model="gpt-3.5-turbo",
    #     messages=[
    #         {"role": "system", "content": "あなたは親切なAIアシスタントです。"},
    #         {"role": "user", "content": prompt_text}
    #     ]
    # )
    # return response.choices[0].message.content
    
    # ダミーの回答を返す
    return f"ダミー回答: '{prompt_text}'に基づいて、製品Aの価格は500ドルです。"

# LLMに渡すプロンプトを構築
prompt = f"""
以下の情報に基づいて、質問に答えてください。
情報: {retrieved_doc}
質問: {query}
"""

print(f"\nLLMに送るプロンプト:\n{prompt}")
response = ask_llm(prompt)

print(f"\nLLMの回答:\n{response}")

「rag-from-scratch」は、RAGの「手口」を学ぶための最高の教材です。このリポジトリを徹底的に分析し、ご自身のプロジェクトに応用することで、皆さんのLLMアプリケーションは、より賢く、より信頼性の高いものへと進化するでしょう。


langchain-ai/rag-from-scratch