🦙

Ollamaを使ってLlamaIndexでRAGを構築する【Windows 11 + WSL 2 + JupyterLab】

2024/04/12に公開

はじめに

LlamaIndexとOllamaは、自然言語処理(NLP)の分野で注目を集めている2つのツールです。

LlamaIndexは、大量のテキストデータを効率的に管理し、検索やクエリに応答するためのライブラリです。PDFや文書ファイルから情報を抽出し、インデックスを作成することで、ユーザーが求める情報をすばやく見つけ出すことができます。

https://docs.llamaindex.ai/en/stable/

一方、Ollamaは、オープンソースの言語モデルであるLLaMA(ラマ)をベースに構築された、対話型のAIアシスタントです。ユーザーとの自然な会話を通じて、質問に答えたり、タスクを支援したりすることができます。

https://ollama.com/

本記事では、Windows 11環境にWSL 2(Ubuntu)とJupyterLabを設定し、LlamaIndexとOllamaを組み合わせてPDFファイルから情報を抽出し、クエリに応答する方法を初心者向けに解説します。コードの例示を交えながら、ステップバイステップで説明していきますので、プログラミングの経験が浅い方でも理解しやすい内容となっています。

本記事を通じて、LlamaIndexとOllamaを活用した文書管理と検索の基本的な流れをつかみ、自然言語処理の実践的なスキルを身につけることができるでしょう。それでは、早速始めていきましょう。

環境構築

LlamaIndexとOllamaを使用するには、まずWindows 11上に適切な環境を設定する必要があります。ここでは、WSL 2(Ubuntu)とminicondaを使って、仮想環境を構築する方法を説明します。

環境

  • Windows 11
  • WSL2:Ubuntu 22.04 LTS

LlamaIndexのバージョン(本記事で必要ないパッケージも含まれているかもしれません)

llama-cpp-python          0.2.61
llama-index               0.10.28
llama-index-cli           0.1.11
llama-index-core          0.10.28
llama-index-embeddings-huggingface 0.2.0
llama-index-embeddings-langchain 0.1.2
llama-index-indices-managed-llama-cloud 0.1.5
llama-index-legacy        0.9.48
llama-index-llms-llama-cpp 0.1.3
llama-index-llms-ollama   0.1.2
llama-index-readers-file  0.1.16
llama-index-readers-llama-parse 0.1.4
llama-parse               0.4.0
llamaindex-py-client      0.1.16

WSL 2 (Ubuntu)環境とminicondaのセットアップ

まず、Microsoftの公式ドキュメントに従って、WSL 2とUbuntuをインストールしてください。インストールが完了したら、Ubuntuを起動し、次のコマンドを実行して、システムを最新の状態に更新します。

sudo apt update && sudo apt upgrade -y

次に、minicondaをインストールします。公式サイトからインストーラーをダウンロードし、次のコマンドを実行します。

bash Miniconda3-latest-Linux-x86_64.sh

インストールが完了したら、新しい仮想環境を作成します。

conda create -n myenv python=3.9

仮想環境をアクティベートするには、次のコマンドを使用します。

conda activate myenv

JupyterLabのインストールと起動

最後に、JupyterLabをインストールします。

conda install -c conda-forge jupyterlab

インストールが完了したら、次のコマンドでJupyterLabを起動できます。

jupyter lab

ブラウザが自動的に開き、JupyterLabのインターフェースが表示されます。これで、LlamaIndexとOllamaを使用するための環境が整いました。

上記の手順に従うことで、初心者でもWindows 11上にWSL 2(Ubuntu)とminicondaを使った仮想環境を構築し、JupyterLabを起動することができます。

LlamaIndexとOllamaのインストール

前のセクションで構築した仮想環境内で、LlamaIndexとOllamaをインストールします。

llama-indexとllama-index-llms-ollamaのインストール手順

まず、次のコマンドを実行して、llama-indexとllama-index-llms-ollamaをインストールします。
※JupyterLab以外(コマンドプロンプトなど)で実行する場合は、先頭の!を外してください。

!pip install llama-index
!pip install llama-index-llms-ollama
!pip install llama-index-embeddings-huggingface

Ollamaクライアントの初期化方法

インストールが完了したら、JupyterLabでノートブックを新規作成し、以下のコードを実行してOllamaクライアントを初期化します。

from llama_index.llms.ollama import Ollama
# 使用モデルを選択します。
llm = Ollama(model="llama2", request_timeout=30.0)

このコードでは、Ollamaクラスをインポートし、model引数で使用するモデル名(ここではllama2)を指定しています。request_timeout引数はリクエストのタイムアウト時間(秒)を設定します。

これで、LlamaIndexとOllamaが使用可能な状態になりました。

Ollamaクライアントを初期化する際、model引数で指定するモデル名は、Ollamaで提供されているモデルの中から選択します。また、request_timeout引数は、APIリクエストがタイムアウトするまでの時間を秒単位で指定します。デフォルト値は30秒ですが、必要に応じて長めに設定することができます。

LlamaIndexでのOllamaの基本的な使い方

LlamaIndexでOllamaを使用する際、主にcompleteメソッドとchatメソッドを使います。

completeメソッドの使用例

completeメソッドは、ユーザーの入力に対して、Ollamaが生成した回答を返します。以下は、completeメソッドの使用例です。

response = llm.complete("What is the capital of France?")
print(response)

このコードでは、"What is the capital of France?"という質問をcompleteメソッドに渡しています。Ollamaは、この質問に対する回答を生成し、response変数に格納します。最後に、print関数で回答を表示します。

chatメソッドの使用例

一方、chatメソッドは、ユーザーとOllamaの間で対話的なやり取りを行うために使用します。以下は、chatメソッドの使用例です。

from llama_index.core.llms import ChatMessage

messages = [
    ChatMessage(role="system", content="You are a helpful assistant."),
    ChatMessage(role="user", content="What is the weather like today?"),
]
response = llm.chat(messages)
print(response)

このコードでは、ChatMessageクラスを使って、システムメッセージとユーザーメッセージを定義しています。システムメッセージは、Ollamaの役割を指定し、ユーザーメッセージは、ユーザーからの質問を表します。これらのメッセージをchatメソッドに渡すことで、Ollamaとの対話が開始されます。

さらに、LlamaIndexではストリーミングAPIも提供しています。ストリーミングAPIを使うと、Ollamaの回答をリアルタイムで取得できます。stream_completeメソッドとstream_chatメソッドがそれぞれcompleteメソッドとchatメソッドに対応しています。

以下は、stream_completeメソッドの使用例です。

response = llm.stream_complete("What is the capital of France?")
for chunk in response:
    print(chunk.delta, end="", flush=True)

このコードでは、stream_completeメソッドを使って、Ollamaの回答をストリーミングで取得しています。回答は、チャンクごとにchunk.deltaに格納され、print関数で順次表示されます。

同様に、stream_chatメソッドを使ってストリーミングで対話的なやり取りを行うこともできます。

以上が、LlamaIndexでのOllamaの基本的な使い方です。completeメソッドとchatメソッドを使って、質問に回答したり、対話を行ったりできます。

PDFからの情報抽出とクエリ応答

LlamaIndexとOllamaを使って、PDFファイルから情報を抽出し、ユーザーのクエリに応答する方法を見ていきましょう。

PDFの準備

お好きなPDFを用意します。今回は、厚生労働省が公開しているモデル就業規則を使わせていただきました。

https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/koyou_roudou/roudoukijun/zigyonushi/model/index.html

# フォルダを作成します
!mkdir -p 'data/10k/'

# PDFをダウンロードします
!wget 'https://www.mhlw.go.jp/content/001018385.pdf' -O 'data/10k/001018385.pdf'

10kフォルダは特に意味はありません。省略可。
ダウンロード中でタイムアウトする場合は手動でダウンロードしてください。

HuggingFace Embeddingモデルの初期化

まず、HuggingFaceのEmbeddingモデルを初期化します。Embeddingモデルはsentence-transformers/all-MiniLM-L6-v2という比較的小さくて高速なモデルを使います。

from llama_index.embeddings.huggingface import HuggingFaceEmbedding

embed_model = HuggingFaceEmbedding(model_name="all-MiniLM-L6-v2")

PDFファイルの読み込みとインデックス作成

次に、PDFファイルを読み込み、インデックスを作成します。

from llama_index.core import SimpleDirectoryReader, VectorStoreIndex

reader = SimpleDirectoryReader(input_files=["./data/001018385.pdf"])
data = reader.load_data()
index = VectorStoreIndex.from_documents(data, embed_model=embed_model)

このコードでは、SimpleDirectoryReaderを使ってPDFファイルを読み込み、VectorStoreIndexを使ってインデックスを作成しています。

Ollamaを使ったクエリエンジンの初期化

続いて、Ollamaを使ったクエリエンジンを初期化します。

query_engine = index.as_query_engine(llm=llm, streaming=True, similarity_top_k=3)

ここでは、as_query_engineメソッドを使って、インデックスからクエリエンジンを初期化しています。llm引数でOllamaを指定し、streaming引数でストリーミングを有効にしています。また、similarity_top_k引数で、類似度の高い上位3つの結果を返すように設定しています。

ストリーミングでのレスポンス取得

クエリを実行し、ストリーミングでレスポンスを取得するには、以下のようにします。

response = query_engine.query("就業規則では休みは何日もらえますか?日本語で答えて。")
response.print_response_stream()

queryメソッドにクエリを渡し、print_response_streamメソッドでレスポンスをストリーミングで表示します。

ソースノードの確認と解析

最後に、ソースノードを確認し、解析します。

for node in response.source_nodes:
    print(f"Text: {node.node.text[:100]}...")
    print(f"Similarity score: {node.similarity_score}")

このコードでは、response.source_nodesからソースノードを取得し、各ノードのテキストと類似度スコアを表示しています。

出力結果

回答は以下になります。

Based on the provided context information, the answer to the query is:

就業規則では休みは3日もらえます。(65)

Just like how the law specifies that an employee is entitled to a maximum of 3 days of rest in a row, it also states that an employee must be given at least 24 hours of continuous rest time after each day of work. However, this rule does not apply in cases of natural disasters or other unavoidable circumstances. (36)-----
Text:	 なお、1か月60時間の算定には、法定休日に労働した時間数は含まれませんが、法 定外の休日に行った労働における時間外労働の時間数は含まれます。 ...
Metadata:	 {'page_label': '55', 'file_name': '001018385.pdf', 'file_path': 'data/10k/001018385.pdf', 'file_type': 'application/pdf', 'file_size': 1058782, 'creation_date': '2024-04-12', 'last_modified_date': '2023-07-07'}
Score:	 0.321
-----
Text:	 - 65 -   ません。しかし、賞与を支給する場合、就業規則に支給対象時期、賞与の算定基準、査 定期間、支払方法等を明確にしておくことが必要です。  2 就業規則に、賞与の支給対象者を一定の日(例えば、6月1日や12月1日、又は賞 与支給日)に在籍した者とする規定を設けることで、期間の途中で退職等し、その日に 在職しない者には支給しないこととすることも可能です。 ...
Metadata:	 {'page_label': '65', 'file_name': '001018385.pdf', 'file_path': 'data/10k/001018385.pdf', 'file_type': 'application/pdf', 'file_size': 1058782, 'creation_date': '2024-04-12', 'last_modified_date': '2023-07-07'}
Score:	 0.306
-----
Text:	 [例1] インターバル時間と翌日の所定労働時間が重複する部分を働いたものとみなす 場合    (勤務間インターバル)  第22条  いかなる場合も、従業員ごとに1日の勤務終了後、次の勤務の開始までに少 なくとも、○時間の継続した休息時間を与える。ただし、災害その他避けることがで きない場合は、この限りではない。 ...
Metadata:	 {'page_label': '36', 'file_name': '001018385.pdf', 'file_path': 'data/10k/001018385.pdf', 'file_type': 'application/pdf', 'file_size': 1058782, 'creation_date': '2024-04-12', 'last_modified_date': '2023-07-07'}
Score:	 0.306

あえ~。3日ですか…。

テキストファイルからの情報抽出とクエリ応答

PDFだけではなく、テキストファイルからも情報を抽出して回答してくれます。今回は、npaka先生のやり方を使わせていただきました。

https://mangapedia.com/ぼっち・ざ・ろっく!-3iy4p4ji1#:~:text=友達のいない引っ込み思案,コミカルに描いている。

変更した箇所は、以下になります。bocchi.txtというファイルを新たに用意しました。文化祭ライブという単語だけで回答できるのでしょうか?

# PDF(テキスト)の読み込み
reader = SimpleDirectoryReader(input_files=["./data/10k/bocchi.txt"])
data = reader.load_data()

# ストリーミングでレスポンスを取得
response = query_engine.query(
    "文化祭ライブはどうだったのか**日本語**で簡潔に説明してください。 "
    " page reference after each statement."
)
response.print_response_stream()

質問に対する回答は以下になります。

文化祭ライブは、後藤ひとりが大きな不安を抱えつつも、周囲の励ましと協力によって成功を収めた。ひとりは、喜多郁代の後押しもあり、ライブをすることを決めたが、ライブ中に機材トラブルが発生し、ひとりはとっさにボトルネック奏法のアドリブを行い、仲間たちはそのフォローに回る。演奏をみんなから拍手喝采されるひとりだったが、コミュ症のひとりはその晴れ舞台に混乱し、観客席にダイブするという暴挙に出てしまう。結果としては成功を収めたものの、ひとりの奇行によって、結束バンドの名はフリーライターの佐藤愛子の耳に入ることとなる。---

In summary, the cultural festival live was successful for later Hiro, but his comedic behavior caused him to accidentally dive into the audience seats. Despite this, the performance was well-received by the audience and marked a successful debut for the band.-----
 夏休みに入り、後藤ひとりは知り合いも増えていたが、自分から遊びに誘うことができずに夏休みも終わり、新学期を迎えていた。相も変わらずコミュ症なひとりは文化祭で盛り上がるクラスメイトをよそに、一人黄昏ていた。 ...ギターの腕前はかなりのものとなっていたが、気がつけば中学は卒業。結局、友達を作るという当初の目標は達成することはできずじまいだった。そして高校に入学したひとりはギター演奏を動画配信し、「ギターヒーロー」としてネットでそこそこの人気を集めていたが、現実では変わらず友達を作れずにいた。ひとりはギターをこれみよがしに持ち、ギタリストだということをアピールするものの効果はなし。黄昏(たそがれ)ていたところ、ギタリストを探していた伊地知虹夏に誘われ、バンド活動をすることとなる。ひとりは虹夏、山田リョウらと共に「結束バンド」として活動を始めるが、友達は欲しいけどコミュ症独りぼっちのひとりは問題行動ばかり起こしていた。結束バンドなのに、結束力ゼロな凸凹バンド活動は幕開けするのだった。そしてそんなひとりに、クラスメイトの喜多郁代が話しかけてきて、彼女が結束バンドの元メンバーだったことを知る。紆余(うよ)曲折の末、郁代はひとりとの交流で結束バンドに戻ることになり、ひとりたちはライブに向けて準備をすることとなる。ひとりはライブチケットのノルマをこなすため、方々を回ってチケットを売ることになるが、人見知りのひとりには難題ですぐに壁にぶち当たってしまう。絶望するひとりは、そこで行き倒れた廣井きくりと遭遇。彼女の計らいで突発的な路上ライブを行なって、観客を魅了する。きくりのお陰でライブチケットのノルマもこなし、結束バンドはライブに臨むのだった。
Metadata:	 {'file_path': 'data/10k/bocchi.txt', 'file_name': 'bocchi.txt', 'file_type': 'text/plain', 'file_size': 31713, 'creation_date': '2024-04-11', 'last_modified_date': '2024-04-04'}
Score:	 0.095
-----
 人気メタルバンド「SIDEROS(シデロス)」のリーダーを務める少女。ギター兼ボーカルを担当している。ライブハウス「FOLT」で活動しており、廣井きくりを純粋に慕っている数少ない人物の一人。小心者なのに意地っ張りという難儀な性格をしており、弱い自分を見せまいとつい強がりをしてしまう。コミュニケーションを取るのも下手で敵をつくりがちであるため、友達もおらず、バンドメンバーも入れ替わりが激しい。きくりはそんな自分にも構ってくれるために慕っているが、飯をたかられたり、お金を貸したり、便利に使われていることに気づいていない。 ...ては憤りを感じており、彼女が一時期FOLTから姿を消した際には平和を満喫していた。
Metadata:	 {'file_path': 'data/10k/bocchi.txt', 'file_name': 'bocchi.txt', 'file_type': 'text/plain', 'file_size': 31713, 'creation_date': '2024-04-11', 'last_modified_date': '2024-04-04'}
Score:	 0.076
-----
 未確認ライオットに参加するためには、デモ審査、ウェブ投票、ライブ審査を通過して、最終審査に合格する必要があった。そして結束バンドは、デモ審査のためのMV(ミュージックビデオ)作成を始める。そんな中、結束バンドの面々は、フェスではライバルとなる人気メタルバンド「SIDEROS」に所属する大槻ヨヨコと出会う。ひねくれ者なヨヨコは、結束バンドをライバル視しながらも、彼女たちにアドバイスを送り、ファンたちの助けもあってMVを完成させる。デモ審査の結果が出るのを待つばかりとなり、新たな春を迎える。ひとりたちは無事進級するものの、相も変わらずコミュ症のひとりは奇行が目立っていた。 ...拍手喝采されるひとりだったが、コミュ症のひとりはその晴れ舞台に混乱し、観客席にダイブするという暴挙に出てしまう。ひとまずライブは成功を収めるものの、ひとりの奇行により、結束バンドの名はフリーライターの佐藤愛子の耳に入ることとなる。そして愛子が結束バンドを取材した際に、ひとりが「ギターヒーロー」であることがバレてしまう。愛子はひとりに強い関心を抱くが、結束バンドの演奏に対しては辛らつで、彼女たちを酷評する。そしてひとりたちは、愛子を見返すため、10代限定のロックフェス「未確認ライオット」に参加し、グランプリを目指すことを決める。
Metadata:	 {'file_path': 'data/10k/bocchi.txt', 'file_name': 'bocchi.txt', 'file_type': 'text/plain', 'file_size': 31713, 'creation_date': '2024-04-11', 'last_modified_date': '2024-04-04'}
Score:	 0.073

回答するためにテキストのどこを参照したのかも出力されているようです。

まとめ

LlamaIndexとOllamaを組み合わせることで、PDFファイルから情報を効率的に抽出し、ユーザーのクエリに対して適切な回答を提供できます。この手法は、大量の文書を扱う必要がある場合に特に有効です。

LlamaIndexとOllamaは、PDFファイル以外にも、様々なテキストデータに応用できます。例えば、ウェブページ、ニュース記事、レポートなどから情報を抽出し、ユーザーの質問に答えるシステムを構築することができます。また、会話型のインターフェースを提供することで、ユーザーとのインタラクションを向上させることも可能です。

今後、LlamaIndexとOllamaのようなツールは、自然言語処理の分野でますます重要な役割を果たすことが期待されます。大規模な言語モデルと効率的なインデックス作成技術を組み合わせることで、より高度な情報検索システムの開発が可能になるでしょう。また、ユーザーとのインタラクションをより自然でスムーズなものにするために、対話システムの改善にも注目が集まるかもしれません。

参考資料

https://qiita.com/ShuntaIto/items/bb6793c0bbf2ed4e53de#fn1
https://note.com/npaka/n/nbe5f9849b723

Discussion