LlamaIndexでは、外部から情報を抽出して質問に回答するRAGを構築できます。
この記事では、『HUNTER×HUNTER』のWikipediaを読み込んで質問に回答するRAGを作る方法を紹介します。
ざっくり言うと
- LlamaIndexでは外部から情報を抽出して質問に回答するRAGを構築できる
- 『HUNTER×HUNTER』のWikipediaを読み込んで質問に回答するRAGを作成する
- 日本語のローカルLLM「Llama-3-ELYZA-JP-8B」を使用
LlamaIndexで『HUNTER×HUNTER』を回答するRAGを構築
LlamaIndexでは、外部から情報を抽出して質問に回答するRAGを構築できます。
この記事では、『HUNTER×HUNTER』のWikipediaを読み込んで質問に回答するRAGを作る方法を紹介します。
RAGの構築手順は以下のとおりです。
- LLMの設定
- 埋め込みモデルの設定
- Wikipediaページからデータを抽出
- テキストをチャンクに分割
- ベクトルインデックスの作成
LlamaIndex RAGの実行環境
この記事で用意した実行環境は以下のとおりです。
- GPU:NVIDIA A100 80GB
- GPUメモリ(VRAM):80GB
- OS :Ubuntu 22.04
- Docker
Dockerで環境構築
Dockerを使用してLlamaIndexの環境構築をします
Dockerの使い方は以下の記事をご覧ください。
Ubuntuのコマンドラインで、Dockerfileを作成します。
mkdir llamaindex
cd llamaindex
nano Dockerfile
Dockerfileに以下の記述を貼り付けます。
# ベースイメージ(CUDA)の指定
FROM nvidia/cuda:12.1.0-cudnn8-devel-ubuntu22.04
# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y python3-pip python3-venv git nano curl
RUN curl -fsSL https://ollama.com/install.sh | sh
# 作業ディレクトリを設定
WORKDIR /app
# アプリケーションコードをコピー
COPY . /app
# Python仮想環境の作成
RUN python3 -m venv /app/.venv
# 仮想環境をアクティベートするコマンドを.bashrcに追加
RUN echo "source /app/.venv/bin/activate" >> /root/.bashrc
# JupyterLabのインストール
RUN /app/.venv/bin/pip install Jupyter jupyterlab
# LlamaIndex関連のインストール
RUN /app/.venv/bin/pip install ollama llama-index llama-index-core llama-index-readers-file llama-index-llms-ollama llama-index-embeddings-huggingface llama-index-readers-web
# コンテナの起動時にbashを実行
CMD ["/bin/bash"]
FROM nvidia/cuda:12.1.0-cudnn8-devel-ubuntu22.04
CUDA12.1のベースイメージを指定しています。
RUN apt-get update && apt-get install -y python3-pip python3-venv git nano curl
必要なパッケージをインストールしています。
RUN curl -fsSL https://ollama.com/install.sh | sh
Linux版のOllamaをインストールしています。PythonでOllamaを動かす際にもLinux版Ollamaのインストールが必要になりますのでご注意ください。
RUN /app/.venv/bin/pip install Jupyter jupyterlab
JupyterLabをインストールしています。
RUN /app/.venv/bin/pip install ollama llama-index llama-index-core llama-index-readers-file llama-index-llms-ollama llama-index-embeddings-huggingface llama-index-readers-web
LlamaIndexとOllama関連のパッケージをインストールしています。
LLMはOllamaのライブラリを使って動かしますので、PyTorchやTransformerは別途インストール不要です。
docker-compose.ymlでDockerコンテナの設定をします。
docker-compose.ymlのYAMLファイルを作成して開きます。
nano docker-compose.yml
以下のコードをコピーして、YAMLファイルに貼り付けます。
services:
llamaindex:
build:
context: .
dockerfile: Dockerfile
image: llamaindex
runtime: nvidia
container_name: llamaindex
ports:
- "8888:8888"
volumes:
- .:/app/llamaindex
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
bash -c '/usr/local/bin/ollama serve & /app/.venv/bin/jupyter lab --ip="*" --port=8888 --NotebookApp.token="" --NotebookApp.password="" --no-browser --allow-root'
bash -c ‘/usr/local/bin/ollama serve & /app/.venv/bin/jupyter lab –ip=”*” –port=8888 –NotebookApp.token=”” –NotebookApp.password=”” –no-browser –allow-root’
bash -c '/usr/local/bin/ollama serve
Ollama Serverを起動しています。PythonのOllamaを使用する際に、Ollama Serverを起動しておく必要がありますので、ご注意ください。
& /app/.venv/bin/jupyter lab --ip="*" --port=8888 --NotebookApp.token="" --NotebookApp.password="" --no-browser --allow-root'
JupyterLabを8888番ポートで起動しています。
Dockerfileからビルドしてコンテナを起動します。
docker compose up
Dockerの起動後にブラウザの検索窓に”localhost:8888″を入力すると、Jupyter Labをブラウザで表示できます。
localhost:8888
LlamaIndex RAGの実装
Dockerコンテナで起動したJupyter Lab上でRAGの実装をします。
日本語LLMモデル「tokyotech-llm-Llama-3.1-Swallow-8B-Instruct-v0.1-Q4_K_M.gguf」をダウンロードします。
!curl -L -o tokyotech-llm-Llama-3.1-Swallow-8B-Instruct-v0.1-Q4_K_M.gguf "https://huggingface.co/mmnga/tokyotech-llm-Llama-3.1-Swallow-8B-Instruct-v0.1-gguf/resolve/main/tokyotech-llm-Llama-3.1-Swallow-8B-Instruct-v0.1-Q4_K_M.gguf?download=true"
tokyotech-llm-Llama-3.1-Swallowについては、別記事で詳しく解説しています。
LLMの実行にはOllamaを使用します。
LLMのモデルがOllama使えるようにプロンプトテンプレートを指定して、モデルを作成します。
import ollama
from llama_index.core import Settings
from llama_index.llms.ollama import Ollama
modelfile='''
FROM ./tokyotech-llm-Llama-3.1-Swallow-8B-Instruct-v0.1-Q4_K_M.gguf
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
{{ .Response }}<|eot_id|>"""
PARAMETER stop "<|start_header_id|>"
PARAMETER stop "<|end_header_id|>"
PARAMETER stop "<|eot_id|>"
PARAMETER stop "<|reserved_special_token"
'''
ollama.create(model='swallow3_1', modelfile=modelfile)
Settings.llm = Ollama(model="swallow3_1", request_timeout=360.0)
FROM ./tokyotech-llm-Llama-3.1-Swallow-8B-Instruct-v0.1-Q4_K_M.gguf
ダウンロードしたモデルのパスが入ります。
TEMPLATE “””{{ if .System }}<|start_header_id|>system<|end_header_id|>…
モデルで使用するプロンプトテンプレートが入ります。
ollama.create(model=’elyza8b’, modelfile=modelfile)
モデルとプロンプトテンプレートを使ってOllama用のモデルを作成します。model
にはOllamaで呼び出す際に使用する名前をつけられます。
Settings.llm = Ollama(model=”elyza8b”, request_timeout=360.0)
Settings.llm
は、LlamaIndexにおけるLLMをグローバルに設定しています。LlamaIndexでLLMを使用する際に、自動的にグローバルに設定したLLMが使用されます。
Ollama(model="elyza8b", request_timeout=360.0)
Ollamaのモデルを指定しています。またリクエストのタイムアウト時間を360秒に設定しています。
Ollamaの詳しい使い方は、別の記事で解説しています。
テキストをベクトル表現に変換する埋め込みモデルを読み込みます。
日本語性能が高く、無料で使える埋め込みモデル「intfloat/multilingual-e5-large」を指定しています。
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
Settings.embed_model = HuggingFaceEmbedding(model_name="intfloat/multilingual-e5-large")
Settings.embed_model
埋め込みモデルをグローバルに設定しています。LlamaIndexでインデックス作成時に埋め込みモデルが自動的に使用されます。
Wikipediaからテキストデータを抽出して、読み込みます。
Wikipediaの『HUNTER×HUNTER』に関するページを指定しています。
from llama_index.readers.web import SimpleWebPageReader
documents = SimpleWebPageReader(html_to_text=True).load_data(
["https://ja.wikipedia.org/wiki/HUNTER%C3%97HUNTER",
"https://ja.wikipedia.org/wiki/HUNTER%C3%97HUNTER%E3%81%AE%E7%99%BB%E5%A0%B4%E4%BA%BA%E7%89%A9",
"https://ja.wikipedia.org/wiki/%E5%B9%BB%E5%BD%B1%E6%97%85%E5%9B%A3",
"https://ja.wikipedia.org/wiki/%E3%82%BE%E3%83%AB%E3%83%87%E3%82%A3%E3%83%83%E3%82%AF%E5%AE%B6"]
)
SimpleWebPageReader(html_to_text=True).load_data()
指定したWebページから取得したHTMLデータをテキスト形式で読み込みます。
読み込んだデータをチャンクに分割し、各チャンクをノードとして処理します。
ノードは効率的な検索やクエリ処理をサポートするため、メタデータや関係情報を含む単位です。
from llama_index.core import Settings
from llama_index.core.node_parser import SentenceSplitter
import tiktoken
splitter = SentenceSplitter(
separator=" ",
chunk_size=1000,
chunk_overlap=100,
paragraph_separator="\n\n\n",
secondary_chunking_regex="[^,.;。]+[,.;。]?")
nodes = splitter.get_nodes_from_documents(documents)
nodes[60:63]
SentenceSplitter()
separator=" "
: 文を分割する際に使用するセパレーターです。ここではスペースが指定されており、スペースによって文を分けます。
chunk_size=1000
: 各チャンクの最大トークン数を指定しています。
chunk_overlap=100
: チャンク間でオーバーラップさせるトークン数です。オーバーラップを持たせることで、テキスト間の文脈を保ちながら次のチャンクに続けることができます。
paragraph_separator="\n\n\n"
: パラグラフ(段落)を分割する際に使用するセパレーターです。ここでは3つの改行\n\n\n
で段落を区切ります。
secondary_chunking_regex="[^,.;。]+[,.;。]?"
: この正規表現は、チャンクのさらに細かい分割方法を定義します。文の終わりを示す句読点.,;。
に基づいてテキストを分割します。
確認のため、分割したノードを3つ表示しています。
[TextNode(id_='c95e0c9e-2488-4fec-998e-b08bc2b06dba', embedding=None, metadata={}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={<NodeRelationship.SOURCE: '1'>: RelatedNodeInfo(node_id='https://ja.wikipedia.org/wiki/HUNTER%C3%97HUNTER', node_type=<ObjectType.DOCUMENT: '4'>, metadata={}, hash='a752d2b45c2d98384dd4ad7313bc97adce015daf92e499b8c00686ea06fa20ef'), <NodeRelationship.PREVIOUS: '2'>: RelatedNodeInfo(node_id='709998bb-1b4f-46be-abe2-0e7e3fb11ed7', node_type=<ObjectType.TEXT: '1'>, metadata={}, hash='731646b9d91214fcb53d6f8878251e3eea9680d8fe225c399e1800178da6a820'), <NodeRelationship.NEXT: '3'>: RelatedNodeInfo(node_id='7705eec0-1aeb-47dd-b877-1291f5e5edcf', node_type=<ObjectType.TEXT: '1'>, metadata={}, hash='58bb5808feb884e1b680111f569ae201f3f3d4e689e384a5699fd021d85534a1')}, text='発(ハツ)\n\n 自分のオーラを自在に操る技術。念能力の集大成。いわゆる[必殺技](/wiki/%E5%BF%85%E6%AE%BA%E6%8A%80 "必殺技")といわれるもの。後述の個別能力とも重複する。\n\n念を教えてはならない相手には、精神論の建前のネン=「**燃** 」心を燃やす意志の強さとして説明し、念能力のことは隠す。テン=「**点**\n」で精神を集中して目標を定め、ゼツ=「**舌** 」でその目標を口頭または頭の中で言葉にし、レン=「**錬** 」でその意志を高め、ハツ=「**発**\n」で実際の行動に移すと説明される[12]。これはこれで念技術を支える心構えとして重要であり、ゴンは念禁止の療養中に「燃」の修行を行うことで、後の念を強化している。\n\n#### 念の応用技\n\n応用技は四大行と比べ疲労が激しい。\n\n周(シュウ)\n\n 「纏」の応用技。自分の体ではなく物にオーラを纏わせる技術。刃物の切れ味を強化するなど、対象物の持つ能力を強化する。しばしば「硬」と併用される。武器の「強化」「放出」「操作」に直結する。\n隠(イン)\n\n 「絶」の応用技。自分のオーラを見えにくくする技術。\n 基本的に「凝」を用いれば見破る事ができるが、力量差によっては全力を用いた「凝」でも「部分的にしか見破れない場合」「全く見えない場合」もある。\n凝(ギョウ)\n\n 「練」の応用技。オーラを体の一部に集め、増幅する技術。\n 限られたオーラをどう配分するかなので、集中させた箇所は攻防力が上がり、反面オーラが薄くなった箇所の攻防力は下がる。打撃の威力を増したり、急所に溜めて致命傷を逃れたりと様々な局面で使われる技術であるが、通常ただ「凝」という場合は、目に集めてオーラを見ることを意味する。熟練者は「隠」で隠されたオーラをも見ることができる。', mimetype='text/plain', start_char_idx=31151, end_char_idx=31939, text_template='{metadata_str}\n\n{content}', metadata_template='{key}: {value}', metadata_seperator='\n'),
TextNode(id_='7705eec0-1aeb-47dd-b877-1291f5e5edcf', embedding=None, metadata={}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={<NodeRelationship.SOURCE: '1'>: RelatedNodeInfo(node_id='https://ja.wikipedia.org/wiki/HUNTER%C3%97HUNTER', node_type=<ObjectType.DOCUMENT: '4'>, metadata={}, hash='a752d2b45c2d98384dd4ad7313bc97adce015daf92e499b8c00686ea06fa20ef'), <NodeRelationship.PREVIOUS: '2'>: RelatedNodeInfo(node_id='c95e0c9e-2488-4fec-998e-b08bc2b06dba', node_type=<ObjectType.TEXT: '1'>, metadata={}, hash='801af902ef6e289c797caaa5ee36239ec151e4eaf80e6b93447409f195f3e545'), <NodeRelationship.NEXT: '3'>: RelatedNodeInfo(node_id='a9fc4709-a7f9-4680-be1f-f575cfa5a2b4', node_type=<ObjectType.TEXT: '1'>, metadata={}, hash='122ae9d82e6add240917904f61e209dde47b7b8e19ab1030b09dd18730dfc0d6')}, text='熟練者は「隠」で隠されたオーラをも見ることができる。\n 副読本『ハンターズ・ガイド』の解説によると、「凝」は目に集中させ視力を強化することだけを意味し、身体能力向上のために他の部位に集中させることはすべて「硬」と呼ぶ、という記述があるが、作中では例えばゲンスルーが手や足にオーラを集中させることも「凝」と呼んでいること、「硬」はあくまで「絶」をも併用した技術であることを踏まえ、本項では目以外に集中させることも「凝」と分類する。\n堅(ケン)\n\n 「纏」「練」の応用技。「練」で増幅したオーラを維持する技術。\n 全身に平均的に平常時以上のオーラを纏っている状態のこと。戦闘時は主に「堅」を維持したまま闘うことになる。「堅」が解けると(解くと)防御力が著しく落ちるため、相手との実力差が大きいと一瞬で敗北という状況にもなりうる。\n 維持できるオーラ量が基礎的な戦闘力を大きく左右する。修行して持続時間を10分間伸ばすだけでも1か月かかると言われている。\n円(エン)\n\n 「纏」「練」の応用技。体の周囲を覆っているオーラを自分を中心に半径2m以上広げ、1分以上維持する技術。肌ではなくオーラで触れることによる感知技。その広さは個々人によって異なり、達人になると50m以上に達する。通常は本人を中心とした円形にオーラが広がる。薄く広げているだけなので、真の臨戦態勢時は「円」を解いて己のもとにオーラを圧縮させる。\n 得意不得意が顕著な技術であり、念使いの中でも一部の者しか使用した描写がない。例えば、念の才能そのものは1000万人に1人と呼ばれるほどのキルアはこれが極端に苦手であり、やろうとしても身体から50cm程度までしか広げられない(なので「円」ができていると言わない)。逆にネフェルピトーは異常に長けており、アメーバ状に任意の形で「円」を伸ばす事ができ、2km以上先の標的すら補足可能な範囲を誇る上、「円」の内部に穴をあけるなどの応用さえ可能。\n硬(コウ)\n\n 「纏」「絶」「練」「発」「凝」を複合した応用技。', mimetype='text/plain', start_char_idx=31913, end_char_idx=32789, text_template='{metadata_str}\n\n{content}', metadata_template='{key}: {value}', metadata_seperator='\n'),
TextNode(id_='a9fc4709-a7f9-4680-be1f-f575cfa5a2b4', embedding=None, metadata={}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={<NodeRelationship.SOURCE: '1'>: RelatedNodeInfo(node_id='https://ja.wikipedia.org/wiki/HUNTER%C3%97HUNTER', node_type=<ObjectType.DOCUMENT: '4'>, metadata={}, hash='a752d2b45c2d98384dd4ad7313bc97adce015daf92e499b8c00686ea06fa20ef'), <NodeRelationship.PREVIOUS: '2'>: RelatedNodeInfo(node_id='7705eec0-1aeb-47dd-b877-1291f5e5edcf', node_type=<ObjectType.TEXT: '1'>, metadata={}, hash='58bb5808feb884e1b680111f569ae201f3f3d4e689e384a5699fd021d85534a1'), <NodeRelationship.NEXT: '3'>: RelatedNodeInfo(node_id='c2aefcdd-75f6-4be6-848a-b8c904c6a893', node_type=<ObjectType.TEXT: '1'>, metadata={}, hash='99f46b47dfb3f3b190fa74c4a4f11660b683ced206c376a57843cb8d15813411')}, text='硬(コウ)\n\n 「纏」「絶」「練」「発」「凝」を複合した応用技。\n 「凝」と同じく特定の部位をオーラを集中させる事で強化する技術であるが、「凝」との違いは「絶」を併用する事で集中させる部位以外の「余分なオーラの漏れ」を締める事で、集中量を飛躍的に増大させる事である。デメリット部分も「凝」に比べて極端化され、集中部位以外は「絶」状態であるため、いくばくかの攻防力が残る「凝」と違い、完全なゼロの生身になる。\n 熟練者は更なる応用技として「周」とも併用し、刀の切っ先に全オーラを集中させる等の芸当も可能。\n ゴンの強化系の「発(ジャジャン拳)」の基礎。ゴンはウィングの「(教えたことの)全てを同時に見せなさい」というアドバイスを元に自らこの応用技にたどり着いたため、特に思い入れが強い様子。\n流(リュウ)\n\n 応用技である「凝」を、さらに応用する高等技術。「凝」の「オーラを移動させて集中する」行為を素早く行う、集中する量を意識的にコントロールする技術。\n 「凝」そのものはある程度オーラのコントロールに慣れれば大半の者ができる技術であるが、未熟な使い手だと「体術でフェイントをかけても本命の方にオーラが集中しているのでフェイントの意味を成さない」「素早い打撃の応酬にオーラの集中が間に合わずオーラが集中できる前に攻撃を当ててしまう」「相手の攻撃を食らうまでにオーラの集中が間に合わない」などと言った弱点が存在する。オーラの移動を素早くする事で、フェイントをかける時点では実際にオーラの集中は行わず本命の打撃が当たる直前に瞬間的にオーラを集中させる、相手の打撃を察知してから当たるまでにとっさにオーラを集中させるといった芸当を可能にする。\n また、毎回何も考えずに全力でオーラを集中していると、「硬」ほどではないにせよ非集中部位の攻防力が下がりすぎてしまう。そうする必要がある場面はともかく、少し集中させる程度の攻防力で事足りる場面でそうする事はデメリットしかないため、状況をよく見極めて必要な分だけ集中させる技術も必要になってくる。また、複数個所に別々に振り分けて同時に「凝」を行う事も含まれる。', mimetype='text/plain', start_char_idx=32754, end_char_idx=33673, text_template='{metadata_str}\n\n{content}', metadata_template='{key}: {value}', metadata_seperator='\n')]
ノードからベクトル表現が使えるインデックスを作成し、類似検索をするクエリエンジンを構築しています。
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex(nodes)
query_engine = index.as_query_engine(similarity_top_k=3)
index = VectorStoreIndex(nodes)
渡されたノードをもとに ベクトル表現が使えるインデックス を作成しています。グローバルで設定された埋め込みモデルSettings.embed_model
が自動的に使用されます。
index.as_query_engine(similarity_top_k=3)
作成したインデックスからクエリエンジンを生成し、類似度の高い上位3つのノードを返す設定をしています。グローバルで設定したLLMSettings.llm
が自動的に使用されます。
LlamaIndex RAGに『HUNTER×HUNTER』について聞いてみる
LlamaIndexで構築したRAGに『HUNTER×HUNTER』について質問してみます。
response = query_engine.query("キルアの癖は?")
print(response)
キルアの癖は?
キルアの癖は暗歩です。
response = query_engine.query("ノブナガの円の大きさは?")
print(response)
ノブナガの円の大きさは?
ノブナガの円の大きさは半径4mです。
response = query_engine.query("カストロの敗因は何ですか?")
print(response)
カストロの敗因は何ですか?
カストロの敗因は、容量(メモリ)のムダ使いです。彼は本来の能力である強化系と相性の悪い超高度な具現化系・操作系の複合能力である分身(ダブル)を使うことを批判されました。
response = query_engine.query("ゲンスルーの能力を解除する方法は?")
print(response)
ゲンスルーの能力を解除する方法は?
ゲンスルーの「命の音(カウントダウン)」を解除するには、触れながら「ボマー捕まえた」と言えばよい。また、「解放(リリース)」と言うことで、作動中の爆弾を強制的に起爆させることができる。ただし、この方法は仲間のサブ、バラと三人で互いの親指をくっつけ合う必要がある。
response = query_engine.query("ハンターハンターはいつ頃から休載していますか?")
print(response)
ハンターハンターはいつ頃から休載していますか?
1999年以降、毎年10回以上休載しています。
生成AI・LLMのコストでお困りなら
GPUのスペック不足で生成AIの開発が思うように進まないことはありませんか?
そんなときには、高性能なGPUをリーズナブルな価格で使えるGPUクラウドサービスがおすすめです!
GPUSOROBANは、生成AI・LLM向けの高速GPUを業界最安級の料金で使用することができます。
インターネット環境さえあれば、クラウド環境のGPUサーバーをすぐに利用可能です。
大規模な設備投資の必要がなく、煩雑なサーバー管理からも解放されます。