LlamaIndexのLlama Packsを試してみる
Llama Packsとは?
- コミュニティ主導のプリパッケージモジュールで、LLMアプリケーションを迅速に立ち上げることができる
- ユースケースに合わせて選択して使うだけもOK、中身を細かくカスタマイズするもOK
- LlamaHub経由で利用可能
Llama Packsが解決するもの
- LLMアプリの開発はいろいろな選択肢が多く大変。
- どのLLM・Embeddingsモデルを使うか?
- どのベクトルデータベースを使うか?
- どのチャンク分割やパース、検索アルゴリズムを採用するか?
これらをパッケージ提供することで容易にするのがLlama Packs
使い方
パッケージごとに使い方が異なる。2つの使い方があるのでそれぞれ見ていく。
Llama PacksのパッケージはLlama Hubで検索できる
基本的に各パッケージの使い方はそれぞれのUsageを見ることになる。
CLIで使う
まず、最初にCLIで使う場合の例としてStreamlitのLlama Packsを見てみる。
llamaindexのCLIコマンドを使ってpackをダウンロード。
$ llamaindex-cli download-llamapack StreamlitChatPack --download-dir ./streamlit_chatbot_pack
このとき、packの中にrequirements.txtが含まれているとどうやら自動的に解決される模様。streamlitもここでインストールされる。
(snip)
Installing collected packages: zipp, watchdog, validators, tzlocal, tornado, toolz, toml, soupsieve, smmap, rpds-py, retrying, PyYAML, pygments, pyarrow, psutil, protobuf, pillow, mdurl, MarkupSafe, html2text, cachetools, blinker, referencing, pyaml, markdown-it-py, jinja2, importlib-metadata, gitdb, beautifulsoup4, wikipedia, rich, pydeck, jsonschema-specifications, gitpython, jsonschema, llama-hub, altair, streamlit, streamlit-pills
Successfully installed MarkupSafe-2.1.4 PyYAML-6.0.1 altair-5.2.0 beautifulsoup4-4.12.3 blinker-1.7.0 cachetools-5.3.2 gitdb-4.0.11 gitpython-3.1.41 html2text-2020.1.16 importlib-metadata-7.0.1 jinja2-3.1.3 jsonschema-4.21.1 jsonschema-specifications-2023.12.1 llama-hub-0.0.77 markdown-it-py-3.0.0 mdurl-0.1.2 pillow-10.2.0 protobuf-4.25.2 psutil-5.9.8 pyaml-23.12.0 pyarrow-15.0.0 pydeck-0.8.1b0 pygments-2.17.2 referencing-0.33.0 retrying-1.3.4 rich-13.7.0 rpds-py-0.17.1 smmap-5.0.1 soupsieve-2.5 streamlit-1.30.0 streamlit-pills-0.3.0 toml-0.10.2 toolz-0.12.1 tornado-6.4 tzlocal-5.2 validators-0.22.0 watchdog-3.0.0 wikipedia-1.4.0 zipp-3.17.0
ダウンロードしたディレクトリはこんな感じ。
$ ls streamlit_chatbot_pack
__pycache__ base.py requirements.txt
中にあるスクリプトをstreamlitで起動する。OpenAI APIキーを環境変数にセットするのをお忘れなく。
$ export OPENAI_API_KEY=sk-XXXXXXXXXXXXXXX
$ streamlit run ./streamlit_chatbot_pack/base.py
サクッとstreamlitのチャットボットが使える。
Pythonコードで使う
Pythonコードで使う場合の例としてChroma AutoRetrieval Packを使ってみる。
download_llama_pack
を使ってパッケージのインストールを行う。依存関係も解決してくれるらしい。
from llama_index.llama_pack import download_llama_pack
ChromaAutoretrievalPack = download_llama_pack(
"ChromaAutoretrievalPack", "./chroma_pack"
)
あとはUsageに従ってコードから利用する。nodesは自分で用意したけど、それ以外はUsage通り。
from llama_index.vector_stores.types import MetadataInfo, VectorStoreInfo
from llama_index.schema import TextNode
# ここだけ自分で用意
nodes = [
TextNode(
text="大谷 翔平(おおたに しょうへい、1994年7月5日 - )は、岩手県水沢市(現:奥州市)出身のプロ野球選手(投手、指名打者、外野手)。右投左打。MLBのロサンゼルス・ドジャース所属。",
metadata={
"category": "Sports Entertainment",
},
),
TextNode(
text="ビートルズ(The Beatles)は、1960年代から1970年にかけて活動したイギリス・リヴァプール出身のロックバンド、および20世紀を代表する音楽グループである。音楽誌『ローリング・ストーン』による「ローリング・ストーンの選ぶ歴史上最も偉大な100組のアーティスト」において第1位にランクインしており[7]、経済紙ウォール・ストリート・ジャーナルの統計算出に基づく「史上最も人気のある100のロックバンド」でも1位となっている。グラミー賞を7回受賞し、23回ノミネートされている。",
metadata={
"category": "Music",
},
),
TextNode(
text="イーロン・リーヴ・マスク(Elon Reeve Musk, 1971年6月28日 - )は、南アフリカ共和国のプレトリア出身の、同国並びにカナダ、アメリカ合衆国国籍の起業家。PayPa、スペースX、テスラ、ボーリング・カンパニー、OpenAI、xAI等を共同設立。スペースX、テスラのCEO、X Corp.(旧:Twitter)の執行会長兼CTOを務めている。",
metadata={
"category": "Business",
},
),
]
vector_store_info = VectorStoreInfo(
content_info="brief biography of celebrities",
metadata_info=[
MetadataInfo(
name="category",
type="str",
description=(
"Category of the celebrity, one of [Sports Entertainment, Business, Music]"
),
),
],
)
import chromadb
client = chromadb.EphemeralClient()
chroma_pack = ChromaAutoretrievalPack(
collection_name="test",
vector_store_info=vector_store_index
nodes=nodes,
client=client
)
response = chroma_pack.run("音楽業界のセレブリティについて教えて")
print(response)
サクッと使えるようになっている。
音楽業界のセレブリティには、ビートルズ(The Beatles)のような有名なアーティストが含まれます。ビートルズは、1960年代から1970年にかけて活動し、イギリス・リヴァプール出身のロックバンドとして知られています。彼らは20世紀を代表する音楽グループであり、多くの賞を受賞し、人気を集めました。彼らは音楽史上でも最も偉大なアーティストの一組とされています。
こちらもダウンロードしたディレクトリの中身は同じ。requirements.txtのパッケージもインストールされている様子。
$ ls chroma_pack
base.py __pycache__ requirements.txt
$ cat chroma_pack/requirements.txt
chromadb
$ pip freeze | grep chromadb
chromadb==0.4.22
こんな感じで、かなり手早く使えるようになっている。
Llama Packsをもう少し深掘り
LlamaPackの仕組みをもう少し深掘りしていく。ドキュメントやブログに記載されている以下のパッケージを例に見ていく。
まずドキュメントを用意しておく。
from pathlib import Path
import requests
import re
def replace_heading(match):
level = len(match.group(1))
return '#' * level + ' ' + match.group(2).strip()
# Wikipediaからのデータ読み込み
wiki_titles = ["イクイノックス", "ドウデュース"]
for title in wiki_titles:
response = requests.get(
"https://ja.wikipedia.org/w/api.php",
params={
"action": "query",
"format": "json",
"titles": title,
"prop": "extracts",
# 'exintro': True,
"explaintext": True,
},
).json()
page = next(iter(response["query"]["pages"].values()))
wiki_text = f"# {title}\n\n"
wiki_text += page["extract"]
wiki_text = re.sub(r"(=+)([^=]+)\1", replace_heading, wiki_text)
wiki_text = re.sub(r"\t+", "", wiki_text)
wiki_text = re.sub(r"\n{3,}", "\n\n", wiki_text)
data_path = Path("data")
if not data_path.exists():
Path.mkdir(data_path)
with open(data_path / f"{title}.txt", "w") as fp:
fp.write(wiki_text)
パッケージをダウンロード。
from llama_index.llama_pack import download_llama_pack
VoyageQueryEnginePack = download_llama_pack(
"VoyageQueryEnginePack", "./voyage_pack"
)
ドキュメントを読み込んで、VoyageQueryEnginePackを初期化。
from llama_index import SimpleDirectoryReader
documents = SimpleDirectoryReader(input_dir="data").load_data()
# 事前にVOYAGE_API_KEYを環境変数に設定しておくこと
voyage_pack = VoyageQueryEnginePack(documents)
単純にpackをフル実行するにはrun()
を使う。
response = voyage_pack.run(
"ドウデュースの主な勝ち鞍を教えて。", similarity_top_k=2
)
print(response)
ドウデュースは日本ダービーと有馬記念を制覇しています。また、朝日杯フューチュリティステークスでも優勝しています。
読み込んだLlama Packsがどういうモジュールを使用しているかを確認する。get_modules()
を使う。
modules = voyage_pack.get_modules()
print(modules)
{
'llm': OpenAI(callback_manager=<llama_index.callbacks.base.CallbackManager object at 0x7e4bdfca9de0>, (snip)
),
'index': <llama_index.indices.vector_store.base.VectorStoreIndex object at 0x7e4be7823d00>}
モジュールを取り出して直接使うこともできる。
llm = modules["llm"]
index = modules["index"]
response = llm.complete("こんにちは。")
print(response)
こんにちは、お手伝いできることがあれば何でもお知らせください。
result = index.as_retriever().retrieve("ドウデュースの主な勝ち鞍を教えて")
for i in result:
print("Score: {}\nDocument: {}\nText: {}\n".format(i.get_score(), i.metadata["file_name"], i.get_text().replace("\n","")[:50]))
Score: 0.7525074236810217
Document: ドウデュース.txt
Text: 単勝オッズ2.2倍の1番人気に推された。道中は勝ち馬アスクビクターモアを見る形で追走。残り800メー
Score: 0.737808182089438
Document: ドウデュース.txt
Text: 馬番5番での優勝は、1970年のスピードシンボリ、1972年のイシノヒカルに次いで、51年ぶり3度目
パッケージ内のコードを見てみる。
!ls voyage_pack
base.py __pycache__ requirements.txt
!cat voyage_pack/requirements.txt
openai
voyageai
!cat voyage_pack/base.py
from typing import Any, Dict, List
from llama_index import ServiceContext, VectorStoreIndex
from llama_index.llms import OpenAI
from llama_index.embeddings import VoyageEmbedding
from llama_index.llama_pack.base import BaseLlamaPack
from llama_index.schema import Document
import os
class VoyageQueryEnginePack(BaseLlamaPack):
def __init__(self, documents: List[Document]) -> None:
llm = OpenAI(model="gpt-4")
embed_model = VoyageEmbedding(
model_name="voyage-01", voyage_api_key=os.environ["VOYAGE_API_KEY"]
)
service_context = ServiceContext.from_defaults(llm=llm, embed_model=embed_model)
self.llm = llm
self.index = VectorStoreIndex.from_documents(
documents, service_context=service_context
)
def get_modules(self) -> Dict[str, Any]:
"""Get modules."""
return {"llm": self.llm, "index": self.index}
def run(self, query_str: str, **kwargs: Any) -> Any:
"""Run the pipeline."""
query_engine = self.index.as_query_engine(**kwargs)
return query_engine.query(query_str)
llm/service_context/VectorStoreIndexなどがクラスでパッケージされていて、get_modulesとrunが生えている。カスタマイズしたければここをいじれば良い。
ダウンロードしたものをカスタマイズして使う、毎回download_llama_packでダウンロードせずにllamaindex-cliで一度ダウンロードものを読み込みたい、等の場合は単純にimportしてやればよい(requirements.txtなどは自分で解決してやる必要がある)
from voyage_pack_copy.base import VoyageQueryEnginePack
voyage_pack = VoyageQueryEnginePack(documents)
元々、LlamaIndexは抽象化が激しくてモジュール間の関係性もやや曖昧なところがあるので、Llama HubやLlama Packsとかみたいなのはあんまり使いたくないなぁと思っていたけども、Llama Packsを実際に見てみると色々なコンポーネントを単純にクラス化しただけの思いの外シンプルなものだった。
むしろ、自分のコードをLlama Packsのフォーマットに合わせて、使いまわしたり/組み合わせたり/配布したりというのがやりやすそうな気がする。