LlamaindexとLlama3でローカルRAGを実行してみる

# はじめに

こんにちは 某地方国立大学で AI の研究してます。ゆーいちです！
今回は Llama3 と研究室の Slack を連携させて RAG をしてみた！ということで、
備忘録的に失礼します!

# 作成したもの

今回はローカル環境で llamaindex を用いた LLM による RAG を作ってみました!

# なぜ書いたか

研究室では LLM を用いた研究をしており、
非 AI エンジニアに研究内容を紹介しやすいように趣味の範囲で作りました！

また、本記事の対象読者は Python である程度の環境構築ができている方に向けてます！
具体的には、
・Pytorch を使ったことがある
・LLM などの AI 技術について知識がある(RAG や Llama など)

また、llamaindex の詳細な説明は省きます!

# 本題

### 開発環境

PC:Linux
仮想環境:Anaconda
言語:Python(3.9.19)
GPU:NVIDIA RTX A6000
開発環境:VScode
使用したライブラリ:Pytorch, llamaindex, Huggingface, Slack API

:::note warn
#### Llama3の利用について
Hugging faceでの利用申請が必要です。
記事下部の参考資料をご一読ください。
:::

### ライブラリのインポート

まずは、ライブラリのインポートをお願いします!
とりあえず呪文のようにお願いします

コマンドラインで

```
$pip install llama_index
$pip install torch (CUDAのバージョンに合わせてインストールしてください)
```

```python:RAG.py
import torch
import datetime
from transformers import AutoTokenizer
from llama_index.core import SimpleDirectoryReader
from llama_index.core import Settings
from llama_index.core import SummaryIndex
from llama_index.core import VectorStoreIndex
from llama_index.core.llms import ChatMessage
from llama_index.core.tools import BaseTool, FunctionTool
from llama_index.core.agent import ReActAgent
from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.readers.slack import SlackReader
from llama_index.llms.huggingface import HuggingFaceLLM
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
```

### ちょこっとした工夫

Slack を検索しますが、検索相手が膨大だとメモリが足りないので、ある程度の期間で絞ります。

```python:RAG.py
dt_now = datetime.datetime.now()
latest_date = dt_now
earliest_date = latest_date - datetime.timedelta(weeks=8)
```

### 必要な API key の発行

今回は、Slack と Hugging face の API を発行します。
発行の仕方は参考資料を参照お願いいたします！
なお今回は general チャンネルを RAG することにします！

また、Llama3 を使う場合は Hugging face での申請が必要です!

```python:RAG.py
HF_TOKEN = "hf_xxxxxxxxxxxxxxxxxxxxxx"

#slack関係
SLACK_API = "xxxxx-000000000000000-0000000000000-0000000000000-00000000000000000000"
channel_ids = ["チャンネルのID"]
```

### Llama3 のセットアップ

Hugging face を使いなれている方も多いと思うで、呪文のように（笑）
温度パラメータとかはお好みでカスタマイズしてね！

```python:RAG.py
tokenizer = AutoTokenizer.from_pretrained(
    "meta-llama/Meta-Llama-3-8B-Instruct",
    token=HF_TOKEN,
)

stopping_ids = [
    tokenizer.eos_token_id,
    tokenizer.convert_tokens_to_ids("
"),
]


llm = HuggingFaceLLM(
    model_name="meta-llama/Meta-Llama-3-8B-Instruct",
    model_kwargs={
        "token": HF_TOKEN,
        "torch_dtype": torch.bfloat16,
    },
    generate_kwargs={
        "do_sample": True,
        "temperature": 0.6,
        "top_p": 0.9,
    },
    tokenizer_name="meta-llama/Meta-Llama-3-8B-Instruct",
    tokenizer_kwargs={"token": HF_TOKEN},
    stopping_ids=stopping_ids,
)

```

### 埋め込むモデルの定義

Slack からとってきた記事をベクトルに変換するモデルを定義します!

```python:RAG.py
embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")
```

### Slack API でのクローリング設定と、LLM のセッティング

```python:RAG.py
slack_reader = SlackReader(slack_token=SLACK_API, earliest_date=earliest_date, latest_date=latest_date)
documents = slack_reader.load_data(channel_ids=channel_ids)
index = SummaryIndex.from_documents(documents)

Settings.embed_model = embed_model
Settings.llm = llm
```

### クエリーエンジンの定義

検索エンジンの定義をします

```python: RAG.py
query_engine = index.as_query_engine(similarity_top_k=3)

# カスタムツールを定義するか、作成したQueryEngineを使用したQueryEngineToolを使用します。
query_engine_tool = QueryEngineTool(
    query_engine=query_engine,
    metadata=ToolMetadata(
        name="my_query_engine",
        description="インデックスされたドキュメントから情報を提供します。",
    ),
)
```

### Chat エージェントの設定と生成結果の表示

ここまでお疲れ様でした！
ラストのコーデイングです!

```python: RAG.py
# ツールとLLMを使用してReActAgentインスタンスを作成します。
agent = ReActAgent.from_tools([query_engine_tool], llm=llm, verbose=True)

# エージェントをクエリします。
response = agent.chat("10月からのゼミはいつありますか?")
#てきとーにプロンプト作りました！
print(str(response))
```

### 検索結果

個人情報の漏洩が怖いので、一部伏せて書いてます！
何とかできました！

```検索結果
月曜○限と金曜○限です
```
### コード全文
```python: RAG.py
import torch
import datetime
from transformers import AutoTokenizer
from llama_index.core import SimpleDirectoryReader
from llama_index.core import Settings
from llama_index.core import SummaryIndex
from llama_index.core import VectorStoreIndex
from llama_index.core.llms import ChatMessage
from llama_index.core.tools import BaseTool, FunctionTool
from llama_index.core.agent import ReActAgent
from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.readers.slack import SlackReader
from llama_index.llms.huggingface import HuggingFaceLLM
from llama_index.embeddings.huggingface import HuggingFaceEmbedding


dt_now = datetime.datetime.now()
latest_date = dt_now
earliest_date = latest_date - datetime.timedelta(weeks=8)

HF_TOKEN = "hf_YOURHUGGINGFACETOKEN"

#slack関係
SLACK_API = "YOURSLACKAPITOKEN"
channel_ids = ["CHANNELID"]

tokenizer = AutoTokenizer.from_pretrained(
    "meta-llama/Meta-Llama-3-8B-Instruct",
    token=HF_TOKEN,
)

stopping_ids = [
    tokenizer.eos_token_id,
    tokenizer.convert_tokens_to_ids("
"),
]


llm = HuggingFaceLLM(
    model_name="meta-llama/Meta-Llama-3-8B-Instruct",
    model_kwargs={
        "token": HF_TOKEN,
        "torch_dtype": torch.bfloat16,
    },
    generate_kwargs={
        "do_sample": True,
        "temperature": 0.6,
        "top_p": 0.9,
    },
    tokenizer_name="meta-llama/Meta-Llama-3-8B-Instruct",
    tokenizer_kwargs={"token": HF_TOKEN},
    stopping_ids=stopping_ids,
)

embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")

slack_reader = SlackReader(slack_token=SLACK_API, earliest_date=earliest_date, latest_date=latest_date)
documents = slack_reader.load_data(channel_ids=channel_ids)
index = SummaryIndex.from_documents(documents)
#index = VectorStoreIndex.from_documents(documents)

Settings.embed_model = embed_model
Settings.llm = llm

query_engine = index.as_query_engine(similarity_top_k=3)


# カスタムツールを定義するか、作成したQueryEngineを使用したQueryEngineToolを使用します。
query_engine_tool = QueryEngineTool(
    query_engine=query_engine,
    metadata=ToolMetadata(
        name="my_query_engine",
        description="インデックスされたドキュメントから情報を提供します。",
    ),
)

# ツールとLLMを使用してReActAgentインスタンスを作成します。
agent = ReActAgent.from_tools([query_engine_tool], llm=llm, verbose=True)

# エージェントをクエリします。
response = agent.chat("院試合格したのは誰ですか。")
print(str(response))
```
### まとめ

今回は、slack を RAG して検索結果に反映できるようにしました！
llamaindex は、PDF やパワーポイントなどにも対応しているので、今度はそちらも試してみたいですね!

# 参考文献
[ローカルの LLM RAG チュートリアル：Llama 3 と LlamaIndex を使用したリトリーバル拡張生成システムの構築](https://anakin.ai/ja-jp/blog/local-rag-llm-tutorial/)
[LlamaIndex でサクッと Slack の情報をベースに回答させてみる](https://developer.so-tech.co.jp/entry/2023/10/11/120000)
[Hugging Face にある Llama3 を動かしてみた](https://zenn.dev/shibayan/articles/b8a91a1c175685)

# 最後に

最後まで読んでいただきありがとうございます！
Qiita 初投稿で緊張します！
どうか生暖かい目で見てください！