見出し画像

AzureからGPTを使ってみる | RAG(Retriever編)



LLMに独自データから回答してもらうために、
・独自データとして利用したいドキュメントをチャンクに分割

・分割したドキュメントをベクトル化して、データベースへ格納、類似度から検索

については以前に記事にしたので、今回は、LangChainのRetrieverを使って、LLMに独自ドキュメントから回答をしてもらうことにしましょう。

Retrieverとは?

簡単に言えば、Retrieverは「最適な情報を見つけ出すための検索エンジン」と考えるとわかりやすいです。
LangChainの「Retriever」は、外部のデータや情報を効率的に取得するためのモジュールです。具体的には、検索機能のような役割を果たします。

Retrieverの主な役割


質問に関連する情報の検索:ユーザーが質問をしたときに、データベースやドキュメント、Webサイトなどの外部情報源から関連する情報を取得します。
検索アルゴリズムの利用:Retrieverは、キーワード検索やベクトル検索など、さまざまな検索アルゴリズムを使って、関連性の高い情報を効率的に抽出します。
他のLangChainコンポーネントとの連携:RetrieverはLangChainの他のモジュール(例えば、チェーンやエージェント)と連携し、AIの応答を強化します。Retrieverが取得した情報を元に、LLMが質問に答える際に正確で具体的な回答を生成します。

Retrieverを実装する

「ジョジョの奇妙な冒険」についてのwikipediaを、markdown記法で成形したドキュメントを使ってみます。

#langchain-chroma=0.1.3   
#langchain-openai=0.1.15
#langchain-text-splitters=0.2.2  

from langchain_chroma import Chroma
from langchain_openai import AzureOpenAIEmbeddings
from langchain_text_splitters import MarkdownHeaderTextSplitter

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough

from dotenv import load_dotenv
import os

# OpenAI APIキーの設定
dotenv_path = ".env"
load_dotenv(dotenv_path)

OPENAI_API_BASE = os.getenv('OPENAI_API_BASE')
OPENAI_API_VERSION = os.getenv('OPENAI_API_VERSION')
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')

os.environ["AZURE_OPENAI_API_KEY"] = OPENAI_API_KEY
os.environ["AZURE_OPENAI_ENDPOINT"] = OPENAI_API_BASE

# Load example document
with open("jojo.md") as f:
    state_of_the_union = f.read()

# Markdown見出しで文章を分割します    
headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
    ("####", "Header 4"),
]    

markdown_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=headers_to_split_on,
    strip_headers = False #コンテンツび分割されるヘッダーを含めるかどうか
    )

md_header_splits = markdown_splitter.split_text(state_of_the_union)

#embeddingには、Azureからtext-embedding-ada-002を使用します。
embed = AzureOpenAIEmbeddings(
    model = 'text-embedding-ada-002',
)
documents = md_header_splits

# VectorStoreの準備
vectorstore = Chroma.from_documents(
    documents,
    embedding=embed,
    #collection_name="example_collection",
    #persist_directory="./chroma_langchain_db",  # Where to save data locally, remove if not neccesary
)   

# Retrieverの準備
retriever = vectorstore.as_retriever()

# プロンプトテンプレートの準備
message = """
提供されたコンテキストのみを使用して、この質問に答えてください。

{question}

Context:
{context}
"""

prompt = ChatPromptTemplate.from_messages([("human", message)]) 

まずは、promptまでをchainでつないで出力を見てみます。

# ユーザー入力
user_input="カーズとは?"
# RAGチェーンの準備
rag_chain = {"context": retriever, "question": RunnablePassthrough()} | prompt

# chainの実行
result = rag_chain.invoke(user_input)
print(result)

(*出力は一部省略して、見やすいように加工しています。)

messages=[HumanMessage(content=
'\n提供されたコンテキストのみを使用して、この質問に答えてください。\n\nカーズとは?\n\n
Context:\n

[Document(metadata={\'Header 1\': \'1 シリーズ構成\', \'Header 2\': \'Part 2 『戦闘潮流』\', \'Header 3\': \'登場人物\', \'Header 4\': \'カーズ\'},

page_content=\'#### カーズ\\n「柱の男」のリーダー格。柱の男たちの中では最も知能が高く、「究極生命体(アルティメット・シイング)」となることを望み、「石仮面」を作り出した天才(究極生命体となった時点ではIQ400)。身体から生やす刃を煌めせる光の流法「輝彩滑刀(きさいかっとう)」を武器として用いる。刃が光るからくりは、刃の表面をチェーンソーのように走る無数のキバ状の突起が不規則に起こす乱反射によるもので、金属も容易に切断する切れ味を持つ。刃は腕以外にも脚などからも生やせる。\\n弱点なき究極の生命体の追求を信念としている。
~略~。
\'),

Document(metadata={\'Header 1\': \'1 シリーズ構成\', \'Header 2\': \'Part 2 『戦闘潮流』\', \'Header 3\': \'登場人物\', \'Header 4\': \'エシディシ\'},

page_content=\'#### エシディシ\\n「柱の男」の1人でカーズの同志。ダイナマイトを飲み込み、腹の中で爆発させても平然としているほど強靭な肉体を持つ。血液を摂氏500℃にまで加温し、この熱を様々に利用する、「炎の流法」(テレビアニメ版では「熱の流法」)の使い手。切れた血管を針のように伸ばし、敵に突き刺し過熱血液を流し込む「怪焔王の流法」(かいえんのうのモード)、血管針を全身から突き出させて回転攻撃を行う「怪焔王大車獄(かいえんのうだいしゃごく)」でジョセフを苦しめた。性格はカーズやワムウと比べると荒っぽく直情的であるが、自身もそれを自覚しており、頭に血が上った時は大泣きして落ち着くことで感情を制御する。
~略~。
。\'),

Document(metadata={\'Header 1\': \'1 シリーズ構成\', \'Header 2\': \'Part 2 『戦闘潮流』\', \'Header 3\': \'登場人物\', \'Header 4\': \'サンタナ\'},

page_content=\'#### サンタナ\\n「柱の男」の1人で、彼ら4人の内では最下位の階級にあたる。カーズたちが旅を始めた頃は赤ん坊であり、カーズいわく「(自分たちの)十分の一しか生きていない若僧」「青っちろいガキ」。カーズたち3人と離れメキシコの遺跡で眠っていた。鉱物化して眠る柱をスピードワゴン財団が発見し、さらにそれをナチス・ドイツが将来人類の存亡に関わると考え、「柱の男」を倒す研究のために奪取し、研究の過程で多数の囚人の生き血を吸わせることによって目覚めさせた。後述の3人とは異なって特定の流法(モード)は見せておらず、「憎き肉片(ミート・インベイド)」「露骨な肋骨(リブス・ブレード)」などの自らの肉体の一部を操作して攻撃を行う。
~略~。
\'),

Document(metadata={\'Header 1\': \'1 シリーズ構成\', \'Header 2\': \'Part 3 『スターダストクルセイダース』\', \'Header 3\': \'中盤\'},

page_content=\'### 中盤\\n一方でDIOはジョースター家との因縁に決着をつけるため、タロットカードの暗示を受けた刺客のスタンド使いたちを承太郎たちに差し向ける。敵は旅客機を墜落させ、無関係の一般人を犠牲にすることも躊躇しないため、承太郎たちは少人数で海路・陸路を行くことを選択する。\\n香港島、インド、パキスタン、紅海を経由して、エジプトに入った彼らは財団が連れてきたスタンド使いの犬・イギーを新たに仲間へ加えて戦力を増強するが、送り込まれる刺客もまたエジプト9栄神の名のカードを冠する強力なスタンド使いとなっていく。それらを撃退しながらアスワン、ルクソール、ギザを経由して北上していき、ついにカイロへと辿りつく。\')]\n')]

しっかりカーズについてのドキュメントを検索できていますが、最後のスターダストクルセダーズ編のドキュメントはカーズとは関係ないと思われます。

LLMに独自ドキュメントから回答してもらう

LLMまでchainでつなげてみましょう。

 #langchain  -openai=0.1.15 #langchain  =0.2.7  
from langchain_openai import AzureChatOpenAI

llm = AzureChatOpenAI(
    api_version=OPENAI_API_VERSION, 
    azure_deployment="gpt4o",# azure_deployment = "deployment_name"
    verbose=True
    )

# RAGチェーンの準備
rag_chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | llm

# ユーザー入力
user_input="カーズとは?"

# chainの実行
response = rag_chain.invoke(user_input)
print(response.content)

カーズは「柱の男」のリーダー格で、高い知能を持ち、「究極生命体」となることを目指して「石仮面」を作り出した天才です。彼は「輝彩滑刀」という光る刃を武器とし、究極の生命体になるための研究を行っていました。カーズは過去に同族を滅ぼし、仲間のエシディシ、ワムウ、サンタナと共に旅に出ました。最終的には「スーパーエイジャ」と石仮面の力で究極生物となり、太陽光や波紋を克服しましたが、ジョセフとの戦いで宇宙空間へ放逐され、永遠に宇宙を漂うことになりました。

うまく出力できました!
一応、ブラウザからGPT4oの回答を見てみます。

「ジョジョの奇妙な冒険」に登場する”カーズ”とは?

「ジョジョの奇妙な冒険」に登場する**カーズ**(Kars)は、シリーズ第2部「戦闘潮流」における主要な敵キャラクターで、ジョジョシリーズ全体でも非常に印象的な存在です。カーズは、**柱の男**と呼ばれる古代の人間に似た種族の一員で、その中でも最も野心的で危険な人物として描かれています。

### カーズの特徴
1. **柱の男のリーダー**:
カーズは柱の男たちのリーダーであり、他のメンバーにはエシディシ、ワムウなどがいます。彼らは通常の人間よりも遥かに強靭で、吸血鬼をも凌駕する身体能力を持っています。

2. **究極生物への執着**:
カーズの最大の目的は、自らを「究極生物」へと進化させることです。彼は石仮面を開発し、吸血鬼の能力を得るだけでなく、太陽の光すら克服できる究極の存在になることを夢見ています。そのために、ジョジョたちと戦い、エイジャの赤石(太陽光を無限に増幅することができる宝石)を狙います。

3. **カーズの能力**:
- **石仮面を使うことで吸血鬼化**: 石仮面を用いることで、吸血鬼を生み出すことができる。さらに、彼はこの技術を利用して自分を究極生物へと進化させようとします。
- **波紋を凌駕する力**: 柱の男として、並外れた戦闘能力を持っており、波紋の技をも超越した力でジョセフ・ジョースターらを苦しめます。

4. **究極生物への進化**:
カーズは物語のクライマックスでついに究極生物へと進化します。究極生物となったカーズはあらゆる生命体の能力を自在に操ることができ、体の一部を動植物に変えたり、どんな傷でも即座に治癒する驚異的な再生能力を持ちます。また、太陽の光に対しても完全に耐性を持ちます。

5. **運命の結末**:
究極生物となったカーズは圧倒的な力を手に入れましたが、ジョセフ・ジョースターの機転により、地球から宇宙へと飛ばされ、帰還する手段もないまま宇宙を漂い続けるという運命を迎えます。この状態でカーズは不老不死であり、意識も失っていないが、何もすることができないまま永遠に宇宙を彷徨うことになります。

### カーズのキャラクター性
カーズは、知性と残虐性を兼ね備えたキャラクターで、仲間の柱の男たちに対しても支配的で冷徹です。また、ただ単に力を追い求めるだけでなく、知的好奇心と科学的な探求心も持ち合わせており、究極生物になることを目的に自ら石仮面を作り出すなど、非常に計画的で賢いキャラクターとして描かれています。

その結果、カーズは「ジョジョの奇妙な冒険」の中でも非常に象徴的なヴィランとして、多くのファンに記憶されています。

すでにジョジョについては、相当くわしいようでした。

おまけ:webからの情報をRetrieverを使ってLLMに回答させる

wikipediaのドラゴンボールから回答してもらいます。

#pip install unstructured

import requests
import re

from langchain.document_loaders import UnstructuredHTMLLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import AzureChatOpenAI

# Download the content
response = requests.get("https://ja.wikipedia.org/wiki/ドラゴンボール")
# Write it to a file
with open("DB.html", "w", encoding="utf-8") as f:
    f.write(response.text)
# Load it with an HTML parser
doc = UnstructuredHTMLLoader("DB.html").load()

document = doc[0]
document.page_content = re.sub("\n\n+", "\n", document.page_content)

all_documents = []
# HTML内のデータを分割する用のSplitterを用意
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=0)
all_documents += text_splitter.create_documents([document.page_content])

embed = AzureOpenAIEmbeddings(
    model = 'text-embedding-ada-002',
)

# VectorStoreの準備
vectorstore = Chroma.from_documents(
    all_documents,
    embedding=embed,
    #collection_name="example_collection",
    #persist_directory="./chroma_langchain_db",  # Where to save data locally, remove if not neccesary
)    
# Retrieverの準備
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 2},
)
# プロンプトテンプレートの準備
message = """
提供されたコンテキストのみを使用して、この質問に答えてください。

{question}

Context:
{context}
"""

prompt = ChatPromptTemplate.from_messages([("human", message)])

llm = AzureChatOpenAI(
    api_version=OPENAI_API_VERSION, 
    azure_deployment="gpt4o",# azure_deployment = "deployment_name"
    verbose=True
    )

# RAGチェーンの準備
rag_chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | llm

# 質問応答
response = rag_chain.invoke("孫悟空について教えてください")
print(response.content)

孫悟空は、地球の人里離れた山奥に住む尻尾の生えた少年です。ある日、西の都から来た少女ブルマと出会い、ドラゴンボールを探す旅に出ます。ドラゴンボールは7つ集めると神龍が現れて、どんな願いでも叶えてくれるとされています。悟空は育ての親である孫悟飯の形見の「四星球」を大切に持っており、それがドラゴンボールの1つであることを知ります。旅の中で修行を重ね、天下一武道会でその成果を発揮し、人気が急上昇します。

この記事が気に入ったらサポートをしてみませんか?