余計なクラウド環境などを使わずに、とりあえずローカルからPython
とOpenAIのAPI
だけでRAGする。
そもそもRAGとはなんだったか
LLMは何でも知っている訳ではない。例えば特定の会社の規則などの、社外秘情報は知らない。
なので、まずはそれらを検索できるデータベースなど使って、ユーザーの質問
から、質問に関する情報
を取得する。
そして質問
と質問に関する情報
を一緒にLLMに渡してやることで、与えた情報に基づいて回答を生成させる。

必要なライブラリを入れる
pip install openai
pip install numpy
データベースを作成する
データベースから検索したい文章について、ベクトルを生成する。
検索させたい単位毎にベクトルを生成する。
ここでは、コード中の配列に設定した文字列データを入力として、embed.json
という名称のファイルに、ベクトル化したデータを書き出す。これをデータベースとする。
from openai import OpenAI
import json
OPEN_AI_API_KEY = 'xxxx'
data = {
'日本国憲法 第1章 天皇 第1条': {
'body': '天皇は、日本国の象徴であり日本国民統合の象徴であつて、この地位は、主権の存する日本国民の総意に基く。'
},
'日本国憲法 第1章 天皇 第2条': {
'body': '皇位は、世襲のものであつて、国会の議決した皇室典範の定めるところにより、これを継承する。'
}
}
openai_client = OpenAI(api_key=OPEN_AI_API_KEY)
# ベクトルを保存する配列
index = []
# 全データに対して繰り返し
for title, item in data.items():
body = item['body']
input = title + '\n' + body
# ベクトル化
response = openai_client.embeddings.create(
model="text-embedding-3-small",
input=input
)
embedding = response.data[0].embedding
# ベクトルを配列に追加
index.append({
'title': title,
'body': body,
'embedding': embedding
})
# ファイルに書き出し
with open('./embed.json', 'w') as f:
json.dump(index, f)
ユーザーの質問からデータベースを検索する
ユーザーの質問もベクトル化する。
そしてembed.json
から、質問ベクトルに近いベクトルを検索する。
ベクトルの近さはコサイン類似度
によって計算する。
import json
from openai import OpenAI
from numpy import dot
from numpy.linalg import norm
OPEN_AI_API_KEY = 'xxxx'
openai_client = OpenAI(api_key=OPEN_AI_API_KEY)
# 準備したベクトルデータベースを読み込み
with open('./embed.json') as f:
index = json.load(f)
# コサイン類似度を計算する関数
def cosine_similarity(lhs, rhs):
return dot(lhs, rhs) / (norm(lhs) * norm(rhs))
# 準備したベクトルデータベースから、近いものを検索する関数
def search(vec, k=3):
results = map(
lambda i: {
'title': i['title'],
'body': i['body'],
'similarity': cosine_similarity(i['embedding'], vec)
},
index
)
# 類似度の大きいもの順にソート
results = sorted(results, key=lambda i: i['similarity'], reverse=True)
# 類似度の大きいものからk件を返す
return results[:k]
# --------------------------------
# LLMへの質問
input = '皇位は世襲ですか?'
# 質問をベクトル化
response = openai_client.embeddings.create(
model='text-embedding-3-small',
input=input
)
vec = response.data[0].embedding
# 近いデータを検索
data = search(vec)
print(data)
実行結果
[{'title': '日本国憲法 第1章 天皇 第2条', 'body': '皇位は、世襲のものであつて、国会の議決した皇室典範の定めるところにより、これを継承する。', 'similarity': np.float64(0.5653466695377146)}, {'title': '日本国憲法 第1章 天皇 第1条', 'body': '天皇は、日本国の象徴であり日本国民統合の象徴であつて、この地位は、主権の存する日本国民の総意に基く。', 'similarity': np.float64(0.37133550037315366)}]
プロンプトを生成する
検索したデータから、プロンプトを準備する。
データからLLMに渡す文字列を作成するだけ。
# プロンプトのテンプレート
prompt_template = '''
ユーザーの質問に対する回答を作成してください。
回答に必要であれば、以下のinformationを参照してください。
informationに示す文章には、0から1の範囲の数値が、ユーザーの発言に対する類似度として付与されています。類似度は1が最もユーザーの発言に近いという意味です。
# information
{information}
'''
# プロンプトに埋め込むテキストを検索したデータから作成
information = '\n\n'.join(map(lambda e: f"=====\n類似度 {e['similarity']}\n\n{e['body']}", data))
# プロンプト生成
prompt = prompt_template.format(
information=information,
).strip()
print(prompt)
実行結果
ユーザーの質問に対する回答を作成してください。
回答に必要であれば、以下のinformationを参照してください。
informationに示す文章には、0から1の範囲の数値が、ユーザーの発言に対する類似度として付与されています。類似度は1が最もユーザーの発言に近いという意味です。
# information
=====
類似度 0.5653466695377146
皇位は、世襲のものであつて、国会の議決した皇室典範の定めるところにより、これを継承する。
=====
類似度 0.37133550037315366
天皇は、日本国の象徴であり日本国民統合の象徴であつて、この地位は、主権の存する日本国民の総意に基く。
LLMに回答を生成させる
準備したプロンプトとユーザーの質問をLLMに渡して、回答を生成させる。
response = openai_client.chat.completions.create(
messages=[
{
"role": "system",
"content": prompt, # プロンプトを渡す
},
{
"role": "user",
"content": input, # 質問を渡す
}
],
model="gpt-4o",
n=1,
temperature=1.0,
frequency_penalty=0.5,
)
content = response.choices[0].message.content
print(content)
実行結果
はい、皇位は世襲であり、国会の議決した皇室典範の定めるところにより継承されます。
おまけ
Python
とAWS
を使用して、Slack
からのチャットによる問い合わせにRAG
で回答するボットの、サンプルコードを作成しました。
よろしければ、ご自由にご利用ください🤗