生成AIの登場以降、「マニュアルなどの文書を読み込ませてFAQ(よくある質問と回答)を自動生成すれば、問い合わせ対応のコストをゼロにできるのではないか」という期待が膨らみました。しかし、実際にPoC(概念実証)として検証を進めてみると、実務の現場では共通して次のような壁にぶつかります。
「もっともらしい嘘(ハルシネーション)が混ざってしまう」
「回答の細かさが、ユーザーの求めている意図とズレている」
「生成されたFAQが正しいか確認する作業に、手作業で作る以上の時間がかかる」
これらの課題の根本的な原因は、「生成すること」ばかりに気を取られ、「評価する仕組み」の設計が不足している点にあります。LLM(大規模言語モデル)は、確率に基づいて自然な言葉を紡ぎ出すエンジンであり、必ずしも真実を保証するデータベースではありません。そのため、私たちが設計すべきなのは「完璧な回答を出す魔法の指示(プロンプト)」ではなく、「出力された結果の品質をデータに基づいて定量的に監視し、継続的に改善していく仕組み(パイプライン)」です。
本記事では、LangChainという開発ツールを用いたRAG(検索拡張生成:外部データを取り込んで回答を生成する技術)ベースのFAQ生成システムの構築方法を解説します。さらに、Ragas(Retrieval Augmented Generation Assessment)という評価用のフレームワークを使い、生成されたFAQが「元の文書に忠実か」「質問に正しく答えているか」を自動で採点する実装の流れを分かりやすくお伝えします。
「精度が低い」という感覚的なフィードバックに対して、コードと実証データに基づいた論理的なアプローチで品質を保証する方法を見ていきましょう。
2. FAQ自動生成の技術的課題とアーキテクチャ設計
まず、システム全体の設計図(アーキテクチャ)に対する考え方を整理します。開発の初期段階で陥りやすいのが、ユーザーの質問に対してAIがその場で回答を作り、そのまま画面に表示する「完全自動のチャットボット」をいきなり目指してしまうことです。
静的生成(Batch)vs 動的生成(Real-time)のトレードオフ
社内向けのシステムであれば許容できるかもしれませんが、顧客向けのFAQシステムにおいて、リアルタイムに回答を生成させるのはリスクが高いアプローチです。悪意のある入力(プロンプトインジェクション)を防いだり、予期せぬ不適切な発言を完全にブロックする安全装置(ガードレール)を作るのは、技術的に非常に難しいためです。
そこで実用的で安全な構成として推奨したいのが、「人間が間に入る(Human-in-the-loop)」ことを前提とした、一括生成(バッチ生成)の仕組みです。
- Drafting(下書き生成): 社内のマニュアルや過去の問い合わせ履歴から、AIがFAQの候補(質問と回答のセット)を大量に作成します。
- Evaluation(自動評価): Ragasなどのツールを使い、作られたFAQが元の資料と矛盾していないかなどを自動で採点し、点数の低いものを除外します。
- Human Review(人間による確認): 高得点をクリアしたものだけを、カスタマーサポート(CS)の担当者が確認・修正し、承認します。
- Indexing(検索用の登録): 承認された確実なFAQだけをデータベース(Vector Store)に登録し、ユーザーにはその検索結果だけを表示します。
この方法なら、ユーザーの目に触れるのは「人間が内容を保証した確定情報」だけになり、リスクを最小限に抑えられます。AIはあくまで「CS担当者の記事作成アシスタント」として働くため、業務効率化という本来の目的もしっかりと達成できます。
RAGベースのFAQ生成パイプライン全体像
以下は、PythonとLangChainを使って実装する場合の、システム全体の流れを表した図です。
graph TD
A[非構造化データ<br>PDF/Slack/Notion] -->|Load & Split| B(Chunks)
B -->|Embedding| C[(Vector Store)]
B -->|Generate QA Pairs| D[LLM <br>ChatGPT/Claude 3.5]
D --> E[Candidate QA Pairs]
E -->|Automated Eval| F{Ragas Score}
F -->|Pass| G[Human Review Queue]
F -->|Fail| H[Discard / Retry]
G -->|Approve| I[(Official FAQ DB)]
I -->|Deploy| J[Web Search UI]
この設計の最大のポイントは、ステップFの「Automated Eval(自動評価)」です。ここを自動化できなければ、結局は人間がすべての生成結果をチェックすることになり、膨大な時間がかかってプロジェクトが立ち行かなくなってしまいます。
次の章からは、この仕組みを構成する各パーツの具体的な作り方を解説していきます。
3. データ前処理とVector Storeの構築
「Garbage In, Garbage Out(ゴミを入れればゴミが出る)」という言葉があるように、AI開発において入力データの質は非常に重要です。FAQの精度は、AIに読み込ませる文書の質と、それをどう適切なサイズに切り分けるか(チャンク分割)に大きく左右されます。
ノイズを除去するチャンク分割戦略
単純に「500文字ずつ」のように文字数だけで区切ってしまうと、文脈が途中で切れてしまい、AIにとって意味不明な文章の塊(チャンク)が大量にできてしまいます。LangChainの RecursiveCharacterTextSplitter という機能をベースにしながら、段落や見出しといった文書の構造を意識して分割することが大切です。
以下は、PDFを読み込み、ファイル名やページ番号といった付加情報(メタデータ)を残しながら分割を行うコードの例です。
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema import Document
def load_and_split_pdf(file_path: str) -> list[Document]:
# PDFの読み込み
loader = PyPDFLoader(file_path)
pages = loader.load()
# チャンク分割の設定
# chunk_size: 文脈を維持できる程度の長さ(トークン数ではなく文字数)
# chunk_overlap: 文脈の分断を防ぐための重複区間
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", "。", " ", ""]
)
# 分割実行
splits = text_splitter.split_documents(pages)
# メタデータのクリーニング(ファイル名やページ番号を保持)
for doc in splits:
source = doc.metadata.get('source', 'unknown')
page = doc.metadata.get('page', 0)
# 参照元として提示できるよう整形
doc.metadata['citation'] = f"{source} (p.{page+1})"
return splits
# 使用例
# docs = load_and_split_pdf("./manual_v1.pdf")
# print(f"Generated {len(docs)} chunks.")
メタデータ付与による検索精度の向上
FAQを作る上で欠かせないのが、「どの製品の」「どのバージョンの」情報なのかという前提条件(コンテキスト)です。これを単なる文章としてだけでなく、データベース(Vector Store)の検索用タグ(メタデータ)として明確に持たせることで、必要な情報を正確に引き出せるようになります。
例えば、PineconeやWeaviateといったベクトルデータベースを使う場合、次のようにタグによる絞り込み(フィルタリング)を活用します。
# メタデータにカテゴリ情報を追加する例
for doc in docs:
if "payment" in doc.page_content.lower():
doc.metadata["category"] = "billing"
elif "setup" in doc.page_content.lower():
doc.metadata["category"] = "technical"
# 後の検索フェーズでの利用イメージ
# retriever = vectorstore.as_retriever(
# search_kwargs={"filter": {"category": "technical"}}
# )
このように、データを準備する段階で情報を整理しておくことが、最終的な回答の正確さに直結します。
ハイブリッド検索(キーワード+ベクトル)の実装
意味の近さで探す「ベクトル検索」は、文脈を捉えるのは得意ですが、型番やエラーコード(例: "E-503")のように「一言一句同じ言葉」を探すのは少し苦手です。実用的なシステムにするためには、従来の「キーワード検索」と「ベクトル検索」を組み合わせたハイブリッド検索を取り入れるのが効果的です。
LangChainの EnsembleRetriever を使えば、この組み合わせを簡単に実装できます。
from langchain.retrievers import BM25Retriever, EnsembleRetriever
# vector_retrieverは既に定義されていると仮定
# vector_retriever = vectorstore.as_retriever()
# キーワード検索用Retriever
bm25_retriever = BM25Retriever.from_documents(docs)
# アンサンブル(重み付けは調整が必要)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6] # ベクトル検索をやや重視
)
この仕組みにより、ユーザーが「エラー 404」と検索しても、「ページが見つからない」と検索しても、適切なマニュアルの箇所を見つけ出せるようになります。
4. 質問・回答ペア(QA)生成プロンプトエンジニアリング
データの準備が整ったら、次はそのデータをもとにAIに「想定される質問」と「その回答」のセットを作らせます。ここで大切なのは、「資料にないことは勝手に答えない」というルールを徹底することと、「ユーザーが検索しそうな様々な言い回し」を用意することです。
Few-Shotプロンプティングによる回答スタイルの統一
単に「FAQを作って」と指示するだけでは、AIは長すぎる文章を書いたり、自社のブランドイメージに合わない言葉遣いをしたりすることがあります。そこで、具体的な良い例をいくつか提示する手法(Few-Shotプロンプティング)を使って、出力の形式やトーンをコントロールします。
from langchain.prompts import ChatPromptTemplate
qa_generation_system_prompt = """
あなたは企業のカスタマーサポート担当者です。
提供されたコンテキスト情報のみに基づいて、ユーザーから寄せられそうな質問と、それに対する簡潔で正確な回答のペアを作成してください。
制約事項:
- コンテキストに情報がない場合は、無理に生成せず「生成不可」と出力すること。
- 回答は「です・ます」調で、専門用語には簡単な補足を加えること。
- JSON形式で出力すること。
例:
{
"question": "パスワードを忘れた場合はどうすればいいですか?",
"answer": "ログイン画面の『パスワードをお忘れの方』リンクから再設定手続きを行ってください。登録メールアドレスにリセット用のURLが送信されます。"
}
"""
qa_generation_prompt = ChatPromptTemplate.from_messages([
("system", qa_generation_system_prompt),
("human", "コンテキスト: {context}")
])
想定される質問バリエーションの自動拡張
一つの回答に対して、ユーザーが検索する言葉は様々です。「パスワード変更」「ログインできない」「パスワード忘れた」など、色々なパターンが考えられます。これらをカバーするために、一つの情報から複数の質問パターンをAIに考えさせるのも非常に有効なアプローチです。
# 質問バリエーションを含めるよう指示を変更
variations_prompt = """
...前略...
出力形式:
{
"questions": [
"メインの質問",
"言い換えパターンの質問1",
"言い換えパターンの質問2"
],
"answer": "回答内容"
}
...後略...
"""
これにより、検索用のデータベースに登録されるキーワードの幅が広がり、ユーザーがどんな言葉で検索してもヒットしやすくなります。
5. Ragasを用いた回答精度の自動評価パイプライン
ここからが、システムを実用化するための核心部分です。AIが作ったFAQが本当に正しいのか、元のマニュアルに沿っているのかをどうやって判断するのでしょうか。人間がすべて目視で確認していてはキリがありません。そこで、Ragasという評価ツールを使った自動採点の仕組みを導入します。
Faithfulness(忠実性)とAnswer Relevance(関連性)の計測
Ragasは、外部データを使ったAIシステム(RAG)の評価に特化したツールです。FAQを自動生成する上で、特に注目すべき指標は次の2つです。
- Faithfulness(忠実性): 生成された回答が、元の資料(コンテキスト)の内容と矛盾していないか。AIの知ったかぶり(ハルシネーション)を見つけ出します。
- Answer Relevance(回答の関連性): 生成された回答が、質問の意図に対して的確に答えているか。
Ragasによる評価コードの実装
まずは必要なライブラリをインストールします。
pip install ragas langchain openai
次に、生成したFAQのセットと、その元になった資料のテキストを使って、評価を実行します。
import pandas as pd
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
)
# LLMが生成したデータをリスト形式で準備
# contextsはリストのリストである点に注意(1つのQAに対して複数のコンテキストがあり得るため)
data_samples = {
'question': ['パスワードリセットの方法は?', '退会方法は?'],
'answer': ['ログイン画面から再設定してください。', '設定画面の最下部から退会可能です。'],
'contexts': [
['パスワードリセットはログイン画面のリンクから...'],
['退会は設定メニューの「アカウント削除」から...']
],
# ground_truth(正解)があれば精度評価も可能だが、
# 生成フェーズでは「コンテキストと矛盾がないか」を見るため必須ではない場合も
}
dataset = Dataset.from_dict(data_samples)
# 評価の実行
results = evaluate(
dataset,
metrics=[
faithfulness,
answer_relevancy,
],
)
# 結果の確認
df_results = results.to_pandas()
print(df_results[['question', 'faithfulness', 'answer_relevancy']])
CI/CDパイプラインへの評価組み込み
この評価プログラムは、単発で動かすのではなく、システムの一連の流れの中に組み込みます。
例えば、「忠実性(faithfulness)」のスコアが 0.8未満 のFAQは自動的に捨てるか、「要確認」のマークをつけて人間の担当者に回す、といったルールを設定します。
def filter_high_quality_qa(df_results, threshold=0.8):
"""高品質なQAペアのみを抽出する"""
# 忠実性と関連性の両方が閾値を超えているものを採用
qualified_df = df_results[
(df_results['faithfulness'] >= threshold) &
(df_results['answer_relevancy'] >= threshold)
]
return qualified_df
# フィルタリング後のデータをDBへ保存
qualified_qa = filter_high_quality_qa(df_results)
save_to_db(qualified_qa)
このように「AIを審査員として使う(LLM-as-a-Judge)」仕組みを配置することで、品質の低いデータが人間の目に触れるのを防ぎ、確認作業の負担を大幅に減らすことができます。
5. 本番運用と継続的な改善ループ
システムは公開してからが本番です。社内のマニュアルは日々更新され、ユーザーからの新しい問い合わせの傾向も変化していきます。この変化に合わせて継続的に改善を繰り返す仕組み(ループ)を作れるかどうかが、FAQシステムの価値を決定づけます。
CS担当者からのフィードバックループ実装
AIが下書きしたFAQをCS担当者が確認し、もし修正を加えた場合は、その修正後のデータを「AIを賢くするための正解データ(教師データ)」として蓄積していくことが理想的です。
SlackやTeamsなどの普段使っているチャットツールと連携し、担当者が業務の中で自然に評価できる仕組みを作ります。
- Goodボタン: 内容が完璧でそのまま採用。データベースに登録し、今後の検索精度を上げるための良い例として活用します。
- Editボタン: 少し修正して採用。どこを直したかの履歴を残し、次回のAIへの指示(プロンプト)の改善や、良い回答例として活用します。
- Badボタン: 使えないため破棄。なぜダメだったのか(「情報が古い」「事実と違う」「AIの作り話」など)の理由をタグ付けし、評価用のデータセットに追加します。
低評価回答の再生成とナレッジベース更新
Ragasの評価スコアが低かったり、CS担当者から何度も修正が入ったりする場合、AIの性能だけでなく「元のマニュアル自体が分かりにくい」「必要な情報が書かれていない」という原因も考えられます。
システムを最適化する視点からは、単にAIの指示文をいじるだけでなく、「社内文書の品質」そのものを見直すよう組織に働きかけることも重要です。FAQ生成時のエラー記録は、社内文書の不備を見つけるセンサーとしても役立ちます。文書の修正プロセスとAIシステムを連動させることで、結果的に組織全体の情報の質が高まっていきます。
コスト管理とトークン最適化
Ragasを使った自動評価は、評価そのものにもAIモデルを使用するため、APIの利用料金が発生します。特に、論理的な矛盾を正確に見抜くためには高性能なモデル(ChatGPTの最新版など)を使うことが推奨されるため、すべてのデータを評価しようとするとコストが膨らむリスクがあります。そのため、一部のデータを抜き出して評価する(サンプリング)手法や、一度計算した結果を保存して再利用する(キャッシュ)工夫が欠かせません。
from langchain.cache import InMemoryCache
import langchain
# メモリ内キャッシュを有効化(同じ問い合わせに対する再生成コストを削減)
langchain.llm_cache = InMemoryCache()
また、作業の難易度に合わせてAIモデルを使い分ける(Model Routing)ことも、費用対効果を最大化する実践的なテクニックです。
複雑な推論や評価には「高性能なモデル」を使い、大量のシンプルな回答作成には「軽量で安価なモデル(Miniシリーズなど)」を使うといった構成が、実務の現場ではよく採用されています。最近の軽量モデルは性能が飛躍的に向上しているため、コストを抑えながらも十分な品質を確保できるようになっています。
まとめ:技術は「信頼」を作るためにある
FAQ自動生成システムにおいて、最も重視すべき指標は「どれだけ多くのFAQを作れたか」ではなく、「ユーザーが自力で疑問を解決できた割合(自己解決率)」です。そして、それを支える根幹は「情報に対する信頼性」に他なりません。
今回解説したRagasを用いた評価の仕組みは、AIの不確実な出力に対して、データと論理に基づいたエンジニアリングのアプローチで「品質の保証」を与えるものです。
- Human-in-the-loopアーキテクチャで、不適切な回答が表に出るリスクを回避する。
- 適切なチャンク分割とメタデータで、必要な情報を正確に引き出す検索精度を高める。
- Ragasによる自動評価で、品質の低い回答をデータに基づいて客観的に除外する。
この3つのポイントを実践することで、サポートチームからもユーザーからも信頼される、実用的なナレッジベースを構築できると考えられます。
生成AIの技術は日々進化しています。Ragasのような評価ツールも頻繁にアップデートされ、新しい評価の基準も次々と登場しています。常に最新の動向を検証しながら、ビジネスの課題解決に直結する効率的で信頼性の高いAIシステムを構築していきましょう。
コメント