「社内ドキュメントを検索できるRAGを作ったけれど、AIが平気な顔で嘘をつく」という課題は、実務の現場で頻繁に直面する問題です。
PoC(概念実証)段階では期待を集めても、いざ実務で使い始めると、存在しない社内規定をでっち上げたり、異なる部門やプロジェクトの売上データを混同して回答したりする「もっともらしい嘘(ハルシネーション)」が発覚し、システムへの信頼が失墜してしまうケースが少なくありません。
プロンプトに「嘘をつくな」「ソースにないことは答えるな」と指示を追加するだけでは、根本的な解決には限界があります。
今回は、精神論的なプロンプト調整から一歩進んで、「システムとして嘘を検知・抑制する仕組み」をコードレベルで実装する方法を解説します。具体的には、LangChainを使って回答に引用元を強制的に紐付け、さらにAI自身に回答の整合性をチェックさせる「自己検証(Self-Correction)ループ」を構築します。
AIはあくまでビジネス課題を解決するための手段です。実務で活用できる信頼性を、エンジニアリングの力で担保していくアプローチを紹介します。
1. なぜRAGでもAIは嘘をつくのか?グラウンディングの技術的アプローチ
そもそも、なぜ参照データを与えているのにAIは間違えるのでしょうか?最新のLLM(大規模言語モデル)は推論能力が飛躍的に向上していますが、RAG(Retrieval-Augmented Generation)における「もっともらしい嘘」は依然として解決すべき重要な課題です。
検索してもハルシネーションが起きるメカニズム
RAGにおけるハルシネーションの主な原因は、「コンテキスト無視」と「知識の混同」です。
LLMは、学習済みの膨大な知識を持っています。RAGで渡された社内ドキュメント(コンテキスト)よりも、自身の学習データにある「一般的な知識」を優先してしまうことがあります。これを専門的には「パラメトリックメモリ(学習済み知識)がノンパラメトリックメモリ(検索結果)に干渉する」と表現します。
例えば、「2025年の最新就業規則について」聞いているのに、検索結果の読み込みが甘く、LLMが一般常識としての「一般的な就業規則」を答えてしまう現象です。
さらに最近のトレンドとして、進化型RAG(GraphRAGなど)やマルチモーダルRAGが注目されています。これは、単一のテキスト検索だけでなく、情報の構造(グラフ)や画像・図表を含めた多角的な情報を統合して回答精度を高めるアプローチですが、情報源が複雑になる分、どの情報を優先すべきかの判断(重み付け)がより重要になっています。
グラウンディング(根拠付け)の3つのレベル
この問題に対処する技術概念が「グラウンディング(Grounding)」です。AIの回答を特定の情報源にしっかりと「着地」させることを指します。実務的には以下の3レベルで対策します。
- Citation(引用明示): 回答のどの部分がどのドキュメントに基づいているかを明示させる。GraphRAGのような構造化された検索を用いることで、引用の精度は向上します。
- Attribution(帰属確認): 生成された回答が、検索されたドキュメントの内容だけで構成されているか確認する。Ragasなどの最新の評価フレームワークでは、この帰属確認を自動化する機能が強化されています。
- Verification(事実検証): 外部ツールや確定情報と突き合わせて真偽を判定する。
本記事で実装する「検証ループ」のアーキテクチャ
今回は、特に効果が高い「2. Attribution」を自動化するアーキテクチャを実装します。
従来の一方通行(検索 → 生成 → 回答)ではなく、「検索 → 生成 → 検証(AIによるチェック) → 合格なら回答 / 不合格なら再生成」というループ構造を作ります。
これは、最新のRAG開発において推奨されている「Agentic RAG(エージェント型RAG)」の考え方を取り入れたものです。単に検索して終わりではなく、AI自身が「この回答はソースに基づいているか?」を自己評価し、必要であればクエリを書き直したり(Query Rewriting)、再検索を行ったりする自律的なプロセスを組み込みます。これにより、ユーザーの目に触れる前に「嘘」をフィルタリングすることが可能になります。
2. 実装準備:検証用パイプラインのセットアップ
まずは、検証コードを動かすための土台を作ります。ここではPythonとLangChainを使用します。
必要なライブラリと環境
以下のライブラリを使用します。AI開発のツールチェーンは進化が速く、特にLangChainは頻繁に更新されています。
セキュリティに関する重要な注意:
LangChain Coreの古いバージョンにはセキュリティ上の脆弱性が報告されるケースがあります。本番環境で利用する際は、必ずlangchain-coreを含む関連ライブラリを最新の安定版にアップデートして使用してください。
# langchain-communityも必要になります
pip install langchain langchain-community langchain-openai chromadb pydantic
ベースとなるRAGチェーンの構築
ここで最も重要なのは、チェーンの設定で return_source_documents=True(またはそれに準ずる設定)にすることです。回答だけでなく、「AIが何を見て答えたか」という証拠データを後続のプロセスに渡す必要があるからです。
以下は、検証用の最小構成コードです。
import os
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.schema import Document
# APIキーの設定(本番環境では.envファイル等での管理を強く推奨します)
os.environ["OPENAI_API_KEY"] = "sk-..."
# 1. モデルとVectorStoreの準備
# モデル名は利用時点の最新版(例: ChatGPTなど)を指定してください
llm = ChatOpenAI(model_name="ChatGPT", temperature=0)
embeddings = OpenAIEmbeddings()
# サンプルデータの作成(意図的に紛らわしいデータを含めるとテストしやすい)
docs = [
Document(page_content="プロジェクトAの予算は1000万円です。担当は佐藤です。", metadata={"source": "doc_1"}),
Document(page_content="プロジェクトBの予算は500万円です。担当は鈴木です。", metadata={"source": "doc_2"}),
]
# VectorStoreの構築(メモリ内)
vectorstore = Chroma.from_documents(docs, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# 2. 基本的なRAGチェーンの定義
# return_source_documents=True が重要!
# ※最新のLangChainではcreate_retrieval_chainの使用も検討されますが、
# ここでは検証ロジックの分かりやすさを優先してRetrievalQAを使用しています。
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True
)
これで、「回答」と「根拠文書」の両方を取得できる準備が整いました。
3. 【基本実装】回答に「引用元」を強制的に紐付ける
次に、AIに対して「どのドキュメントを使ったのか」を回答時に宣言させます。これはユーザーにとっても信頼性の指標になります。
プロンプトエンジニアリングによる引用指示
System Promptを工夫し、回答フォーマットを厳格に指定します。
from langchain.prompts import PromptTemplate
template = """
あなたは誠実なアシスタントです。以下のコンテキストのみを使用して質問に答えてください。
もしコンテキストに答えがない場合は、「情報がありません」と答えてください。推測してはいけません。
回答する際は、必ず情報の出所となるソースID(source_id)を角括弧 [] で文末に付記してください。
例: プロジェクトAの予算は1000万円です。[doc_1]
コンテキスト:
{context}
質問:
{question}
回答:
"""
QA_CHAIN_PROMPT = PromptTemplate(
input_variables=["context", "question"],
template=template,
)
# チェーンの再構築
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={"prompt": QA_CHAIN_PROMPT}
)
回答に含まれる情報とソースドキュメントの照合ロジック
回答が得られたら、Python側でその引用が正しいか簡易チェックを行います。
def verify_citation(result):
answer = result['result']
source_docs = result['source_documents']
# 実際に検索でヒットしたソースIDのリスト
actual_sources = [doc.metadata['source'] for doc in source_docs]
print(f"生成された回答: {answer}")
print(f"検索されたソース: {actual_sources}")
# 簡易チェック: 回答に含まれる[doc_x]がactual_sourcesに含まれているか
# (本番では正規表現で抽出してマッチングします)
for source in actual_sources:
if f"[{source}]" in answer:
print(f"✅ 引用確認: {source} が参照されています。")
else:
print(f"⚠️ 注意: 検索された {source} は回答に引用されていません(情報の取捨選択が行われた可能性があります)。")
# 実行テスト
query = "プロジェクトAの予算は?"
result = qa_chain.invoke({"query": query})
verify_citation(result)
これだけでも「ユーザーに見える引用」は実装できますが、AIが「[doc_1]」と書きながら嘘の内容を書く可能性は排除できません。そこで次のステップが必要です。
4. 【応用実装】「自己検証(Self-Correction)」ループの構築
ここが本記事のハイライトです。回答生成後に、別のAI人格(Verifier)を呼び出し、「その回答は本当にコンテキストから導き出せるか?」を客観的に審査させます。
Verifier(検証者)としてのLLMチェーン実装
検証用のプロンプトを定義します。ここでは「NLI(自然言語推論)」のタスクとして扱います。
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
# 検証結果の構造定義
class VerificationResult(BaseModel):
is_grounded: bool = Field(description="回答がコンテキストに基づいている場合はTrue、そうでない場合はFalse")
reason: str = Field(description="判定の理由")
parser = PydanticOutputParser(pydantic_object=VerificationResult)
verify_template = """
あなたは厳格なファクトチェッカーです。
以下の「コンテキスト」と、それに基づいて生成された「回答」を確認し、
回答がコンテキスト内の情報だけで裏付けられているか判定してください。
コンテキスト:
{context}
回答:
{answer}
{format_instructions}
"""
verify_prompt = PromptTemplate(
template=verify_template,
input_variables=["context", "answer"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
# 検証用チェーン
verify_chain = verify_prompt | llm | parser
「回答はコンテキストに基づいているか?」を判定させるコード
実際にRAGの回答を審査し、不合格なら再生成させるループ関数を作ります。
def robust_rag_query(query, max_retries=3):
for attempt in range(max_retries):
# 1. 通常のRAG回答生成
result = qa_chain.invoke({"query": query})
answer = result['result']
source_docs = result['source_documents']
# コンテキストテキストの結合
context_text = "\n".join([doc.page_content for doc in source_docs])
# 2. 検証実行
verification = verify_chain.invoke({
"context": context_text,
"answer": answer
})
print(f"--- 試行 {attempt + 1} 回目 ---")
print(f"回答: {answer}")
print(f"判定: {'合格' if verification.is_grounded else '不合格'}")
print(f"理由: {verification.reason}")
if verification.is_grounded:
return result # 合格なら結果を返す
# 不合格の場合、次のループへ(LLMのTemperatureが0より高い場合、結果が変わる可能性がある)
# ※より高度な実装では、前の判定理由を次のプロンプトにフィードバックして修正を促します
return {"result": "申し訳ありません。正確な回答を生成できませんでした。", "source_documents": []}
# 実行
# わざと嘘をつかせにくいですが、複雑な質問で試すと効果が出ます
final_result = robust_rag_query("プロジェクトAとBの予算の合計と、それぞれの担当者の関係性は?")
このコードにより、AIが勝手に外部知識で補完したり、幻覚を見たりした場合、Verifierが「コンテキストに記述がありません」と弾いてくれるようになります。
5. 実用化に向けたチューニングとコスト管理
この検証ループは強力ですが、API呼び出し回数が増えるため、コストとレイテンシ(待ち時間)が増加します。実用化においては、以下のバランス調整が重要です。
サンプリング検証によるコスト削減
すべてのクエリで検証を行う必要はありません。以下のようなルールでフィルタリングすることをお勧めします。
- 重要度ベース: 「契約」「予算」「規定」などのキーワードが含まれる質問のみ検証する。
- ランダムサンプリング: 品質のモニタリング目的であれば、全リクエストの10%だけを検証し、ログに残す。
ユーザーへの「不確実性」の提示方法
技術的に完全にハルシネーションをゼロにすることは困難です。システム側での工夫として、フロントエンドで以下のような表示を行うことが有効です。
- 引用元のハイライト: ユーザーが回答内のリンクをクリックすると、原文の該当箇所がハイライトされるUI。
- 信頼度スコアの表示: Verifierが出した確信度(Confidence Score)をアイコンの色などで表示する。
まとめ
RAGにおけるハルシネーション対策は、プロンプトという「お願い」だけでなく、コードによる「検閲」プロセスを組み込むことで飛躍的に向上します。
- Citation: どこを見たかを言わせる。
- Verification: 本当にそこに書いてあるかを別のAIにチェックさせる。
- Loop: 間違っていたら直させる。
この3ステップを実装することで、RAGシステムは単なる対話ツールから、実務に耐えうる信頼性の高い業務パートナーへと進化します。
自社での実装やメンテナンスにリソースを割くのが難しい場合は、すでにこれらのグラウンディング技術が組み込まれた専門的なプラットフォームを活用することも、ROIを最大化する上で有効な選択肢となります。
コメント