Claude APIとFastAPIで作るRAGチャットボット入門!PDFをナレッジベース化してAIに質問する方法
「PDFの内容をAIに質問できたら、めちゃくちゃ便利なのに…」
そう思ったことはありませんか?分厚いマニュアルや議事録、技術資料を毎回読み返すのは正直しんどいですよね。そんな悩みを解決してくれるのが、RAG(Retrieval-Augmented Generation)という仕組みです。
今回は Claude API × FastAPI × Python を使って、PDFをナレッジベース化してAIに質問できるRAGチャットボットをゼロから作っていきます。「RAGって難しそう…」という方でも、ざっくりとした流れがつかめるはずです!
対象読者:Python中級者(FastAPIの基本を触ったことがある方)
難易度:★★★☆☆
RAGってそもそも何?

まず「RAG」という言葉からおさえておきましょう。
RAG(Retrieval-Augmented Generation)は、日本語にすると「検索拡張生成」です。イメージとしてはこんな感じです。
- 📄 あなたの資料(PDF)を細かく分割して保存しておく
- 🔍 質問が来たら「関連しそうな部分」を検索して取り出す
- 🤖 取り出した情報をClaude APIに渡して、回答を生成してもらう
つまり、AIに「あなたの資料を読ませた上で」質問に答えてもらう仕組みです。Claude自身の学習データにない情報でも、資料の内容をもとに正確に答えてもらえるのが最大のメリットです。
今回作るシステムの全体像
今回構築するRAGチャットボットの構成はこちらです。
- FastAPI:APIサーバー(フロントからの質問を受け取る)
- PyMuPDF(fitz):PDFをテキストに変換
- sentence-transformers:テキストをベクトルに変換
- FAISS:ベクトル検索エンジン(関連テキストを高速に取り出す)
- Claude API(anthropic):取り出したテキストをもとに回答生成
まずは必要なライブラリをインストールしておきましょう。
pip install fastapi uvicorn anthropic pymupdf sentence-transformers faiss-cpu python-multipart
STEP 1|PDFをチャンクに分割してベクトル化する
最初のステップは、PDFを読み込んでテキストを細かいかたまり(チャンク)に分割し、ベクトルとして保存する処理です。
ここが重要なポイントです。
- チャンクサイズが大きすぎると検索精度が下がる
- 小さすぎると文脈が失われる
- 今回は 500文字ごと・50文字のオーバーラップで分割します
# knowledge_builder.py
import fitz # PyMuPDF
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
import pickle
# ベクトル化モデルの読み込み(多言語対応モデル)
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
def extract_text_from_pdf(pdf_path: str) -> str:
"""PDFからテキストを抽出する"""
doc = fitz.open(pdf_path)
full_text = ""
for page in doc:
full_text += page.get_text()
return full_text
def split_into_chunks(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]:
"""テキストをオーバーラップ付きで分割する"""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunks.append(text[start:end])
start += chunk_size - overlap # オーバーラップ分だけ戻る
return chunks
def build_knowledge_base(pdf_path: str):
"""PDFをベクトルDBとして保存する"""
# テキスト抽出 → チャンク分割
text = extract_text_from_pdf(pdf_path)
chunks = split_into_chunks(text)
print(f"✅ {len(chunks)}件のチャンクを生成しました")
# ベクトル化
embeddings = model.encode(chunks, show_progress_bar=True)
embeddings = np.array(embeddings).astype("float32")
# FAISSインデックスの作成と保存
index = faiss.IndexFlatL2(embeddings.shape[1])
index.add(embeddings)
faiss.write_index(index, "knowledge.index")
# チャンクのテキストも保存しておく
with open("chunks.pkl", "wb") as f:
pickle.dump(chunks, f)
print("✅ ナレッジベースを保存しました!")
if __name__ == "__main__":
build_knowledge_base("sample.pdf") # 対象のPDFを指定
まずこのスクリプトを実行して、手元のPDFをナレッジベース化しておいてください。
STEP 2|関連チャンクを検索する関数を作る
次に、質問に対して関連性の高いチャンクを取り出す「ベクトル検索」の関数を用意します。
# retriever.py
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
import pickle
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
# 保存済みのインデックスとチャンクを読み込む
index = faiss.read_index("knowledge.index")
with open("chunks.pkl", "rb") as f:
chunks = pickle.load(f)
def search_relevant_chunks(query: str, top_k: int = 3) -> list[str]:
"""質問に関連するチャンクをtop_k件返す"""
# 質問をベクトル化
query_vec = model.encode([query]).astype("float32")
# FAISSで類似ベクトルを検索
distances, indices = index.search(query_vec, top_k)
# 対応するテキストを返す
results = [chunks[i] for i in indices[0] if i < len(chunks)]
return results
ここがRAGの「R(Retrieval=検索)」にあたる部分です。ベクトル同士の距離を計算して、意味的に近いチャンクを取り出しています。
STEP 3|FastAPIとClaude APIで質問応答エンドポイントを作る
いよいよ本体です。FastAPIで質問を受け取り、Claude APIに「取得したチャンク+質問」をセットで送って回答を生成します。
# main.py
from fastapi import FastAPI
from pydantic import BaseModel
import anthropic
from retriever import search_relevant_chunks
app = FastAPI()
client = anthropic.Anthropic() # ANTHROPIC_API_KEY は環境変数から自動読み込み
class QuestionRequest(BaseModel):
question: str
class AnswerResponse(BaseModel):
answer: str
context_used: list[str] # 参照したチャンクも返す(デバッグ用)
@app.post("/ask", response_model=AnswerResponse)
def ask_question(request: QuestionRequest):
# 関連チャンクを検索
relevant_chunks = search_relevant_chunks(request.question, top_k=3)
context = "\n\n---\n\n".join(relevant_chunks)
# Claude APIへ送るプロンプトを組み立てる
system_prompt = (
"あなたは親切なアシスタントです。"
"以下の【参照情報】だけをもとに質問に答えてください。"
"参照情報に載っていないことは『その情報は資料に含まれていません』と答えてください。"
)
user_message = f"""【参照情報】
{context}
【質問】
{request.question}"""
# Claude APIを呼び出す
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system=system_prompt,
messages=[
{"role": "user", "content": user_message}
]
)
answer = response.content[0].text
return AnswerResponse(
answer=answer,
context_used=relevant_chunks
)
@app.get("/health")
def health_check():
return {"status": "ok"}
ポイントをまとめるとこんな感じです。
- 🔍 search_relevant_chunks()で関連テキストを取得
- 📝 取得したテキストをsystemプロンプト+userメッセージに組み込む
- 🤖 Claude APIが「資料の範囲内」で回答を生成する
- 💡
context_usedを返すことで、どのチャンクを参照したか確認できる
STEP 4|サーバーを起動して動かしてみよう
準備ができたら、実際に動かしてみましょう。まず環境変数を設定してからサーバーを起動します。
# 環境変数の設定(Mac/Linux)
export ANTHROPIC_API_KEY="sk-ant-xxxxxx"
# サーバー起動
uvicorn main:app --reload
サーバーが起動したら、別のターミナルから curl で試してみましょう。
curl -X POST http://localhost:8000/ask \
-H "Content-Type: application/json" \
-d '{"question": "このマニュアルの返品ポリシーを教えてください"}'
FastAPIの自動生成ドキュメント(http://localhost:8000/docs)からGUIで試すこともできますよ。
精度を上げるための3つのTips
基本的なRAGが動いたら、次は精度を高めていきましょう。
① チャンクサイズを調整する
資料の種類によって最適なサイズが変わります。
- 技術文書・仕様書 → 300〜500文字
- FAQ・箇条書き資料 → 100〜200文字
- 長文の報告書 → 800〜1000文字
② top_kの数を増やす
デフォルトの3件では情報が足りないと感じたら、top_k=5やtop_k=7に増やすと回答の精度が上がることがあります。ただし、トークン消費量も増えるので注意してください。
③ 「資料外の質問」をClaudeに明示的に断らせる
systemプロンプトの指示が重要です。「参照情報に載っていない場合はその旨を伝えるよう」明示することで、AIのハルシネーション(でたらめな回答)を防げます。今回のサンプルにも既に含めてあります。
まとめ
今回は Claude API + FastAPI + FAISSを使ったRAGチャットボットの基本的な実装を解説しました。
- ✅ PDFをチャンクに分割してFAISSでベクトル化する
- ✅ 質問に対して関連チャンクを検索して取り出す
- ✅ 取り出したチャンクをClaude APIのプロンプトに埋め込んで回答を得る
この仕組みを応用すれば、社内マニュアル検索ツールや、複数PDFをまとめて参照できる調査アシスタントなど、実務に直結するツールを作れます。
「むずかしそう」と思っていたRAGも、ステップを分解してみると意外とシンプルですよね。ぜひ手元の資料で試してみてください!一緒に学んでいきましょう 🚀
こちらも読まれています
📚 関連商品・おすすめ書籍
※本記事にはアフィリエイトリンクが含まれます。





