導入
理屈はここまでにして、まず動く最小のRAGを作ってみましょう。「検索して、その結果をもとに答える」という一直線の流れを、実際にコードで体験します。
説明
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# 小さな知識ベース(英語の文書。日本語だとTF-IDFがうまく分割できない)
docs = [
"The Eiffel Tower is located in Paris, France.",
"Mount Fuji is the highest mountain in Japan, at 3776 meters.",
"Python is a popular programming language for data science.",
"The Great Wall of China is more than 20000 kilometers long.",
]
# ① 文書をベクトル化しておく(インデックス作成)
vectorizer = TfidfVectorizer()
doc_vectors = vectorizer.fit_transform(docs)
def retrieve(query, k=1):
# ② 質問をベクトル化し、コサイン類似度が高い文書を取ってくる
q = vectorizer.transform([query])
scores = cosine_similarity(q, doc_vectors)[0]
top = scores.argsort()[::-1][:k]
return [docs[i] for i in top]
def answer(query):
context = retrieve(query, k=1)[0]
# ③ 本物のLLMの代わりに、取ってきた文をそのまま答えに使う(第4章で本物APIに置換)
return f"Q: {query}\nRetrieved context: {context}\nAnswer: {context}"
print(answer("How tall is Mount Fuji?"))
print()
print(answer("Where is the Eiffel Tower?"))
最初の実行は少し待ちます:scikit-learn を使う回は、初回だけライブラリの読み込みに数十秒かかることがあります(2回目以降は速くなります)。「読み込み中…」と出たら、そのまま待ってください。
コードは3つの部分に分かれています。
vectorizer.fit_transform(docs):知識ベースの全文書をベクトル化し、あとで検索できるように索引を作っています。これがレッスン2でいう「インデックス作成フェーズ」です。retrieve(query, k=1):質問を同じ方法でベクトル化し(vectorizer.transform)、cosine_similarityでベクトル同士の近さを計算して、最も近い文書を返します。これが「検索」です。answer(query):検索で見つけた文書をそのまま答えとして返しています。まだ本物の生成ではありません。テンプレートに埋め込んでいるだけです。第4章で、この部分を本物のLLM呼び出しに置き換えていきます。
print(answer("How tall is Mount Fuji?")) を実行すると、富士山についての文書が検索で見つかり、それが「答え」として使われている様子が分かります。
やってみよう
answer("What is Python used for?") を試して、どの文書が検索されるか確かめてみましょう。docs に自分で1文足して、その内容について質問してみるのもおすすめです。
演習
retrieve 関数を k=2 で呼び出し、上位2件の文書を1行ずつ表示するコードを書いてください。
ヒント1を見る
for d in retrieve("...", k=2): print(d) のように、上位2件をループで表示します。
ヒント2を見る
k を2にするだけで、関連度の高い順に2件のリストが返ってきます。
まとめ
- 最小のRAGは「①索引作成(fit_transform)→ ②検索(transform + cosine_similarity)→ ③答えを組み立てる」の3ステップ。
- 今回の③はまだ「検索結果をそのまま返す」だけの仮の生成。本物のLLM呼び出しへの置き換えは第4章で行う。
- 検索の仕組みそのものは、これで一通り動かせるようになった。