OKE上に展開したLLMでRAGやってみた

# はじめに

この記事は、[Oracle Cloud Infrastructure Advent Calender 2023 シリーズ 2 Day20](https://qiita.com/advent-calendar/2023/oci)の記事として書いています。

前回の記事ではLocalAIを利用してOKE上にLLMを展開してみると言うことをやってみました。
今回は前回展開したLLMを利用してLangChainで遊んだり、RAGをやってみるという記事を書きたかったのですが、API周りの仕様や使い方の調査などが間に合いませんでした...

そこで今回はOKE上でコンテナ内にあるモデルを利用したRAGをやってみるという~~しょうもない~~記事を書いていこうと思います！
（本当は前回構築したLLMを利用してRAGをやってみたかった...リベンジします！)

環境は前回構築したOKE環境を使います。

# アプリの実装

今回はRAGが機能していることを確認するためにRAGを使うワークロードと使わないワークロードを実装します。
今回は"What is OKE?"と言う質問にLLMが回答できるかをみていきます。
あえて”OKE”と言う略称にすることで回答させづらくしています。

## RAGを利用しない実装

まずはRAGを使わない実装からです。

```python:non-rag.py
from gpt4all import GPT4All
model = GPT4All("mistral-7b-openorca.Q4_0.gguf")
with model.chat_session():
    response = model.generate(prompt='What is OKE?', temp=0)
    print(model.current_chat_session)
```

非常にシンプルです。
今回利用する`mistral-7b-openorca.Q4_0`というモデルは、OpenOrcaと呼ばれるデータセットを使ってMistral 7B モデルに基づきfine-tuningされたモデルです。
Mistral 7Bモデルは、2023年12月現在、LLMの横断的(特定のドメインに寄らないという意味です)な評価を示す[HELM(Holistic Evaluation of Language Models)](https://crfm.stanford.edu/helm/latest/)で`Llama2(70B)`や`text-davinci-002(OpenAI)`に次ぐ高い精度を誇っているモデルです。
ちなみにモデルは実行時に自動的にダウンロードされるようになっているので、コンテナをビルドする際には不要です。

## RAG実装

次にRAGを実装してみます。
今回はVector StoreとしてFAISSを利用します。
FAISSはMeta（Facebook）社製の近似最近傍探索ライブラリです。今回はこちらを使ってテキストのインデックス化を行うことにします。

それでは実装です。

```python:rag.py
from langchain.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores.faiss import FAISS
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.llms.gpt4all import GPT4All
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler

PROMPT = "What is OKE?"

FAISS_DB_DIR = "vectorstore"

LLM_PATH="/workdir/model/mistral-7b-openorca.Q4_0.gguf"

# Document Loaders
loader = WebBaseLoader("https://docs.oracle.com/en-us/iaas/Content/ContEng/Concepts/contengoverview.htm")
data = loader.load()

# Document Transformers
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
docs = text_splitter.split_documents(data)

# Embedding
embeddings = HuggingFaceEmbeddings()
faiss_db = FAISS.from_documents(documents=docs, embedding=embeddings)

# Vector Store
faiss_db.save_local(FAISS_DB_DIR)

# RAG
embedding_vector = embeddings.embed_query(PROMPT)
contexts = faiss_db.similarity_search_by_vector(embedding_vector)
context = "\n".join([document.page_content for document in contexts])
callbacks = [StreamingStdOutCallbackHandler()]
llm = GPT4All(model=LLM_PATH, callbacks=callbacks, verbose=True)

template = """
Please use the following context to answer questions.
Context: {context}
---
Question: {question}
Answer: Let's think step by step."""

prompt = PromptTemplate(template=template, input_variables=["context", "question"]).partial(context=context)

llm_chain = LLMChain(prompt=prompt, llm=llm)
print(llm_chain.run(PROMPT))
```

今回はLangChainを利用してRAGを実装しています。
RAGの知識ソース元は[OKEドキュメント](https://docs.oracle.com/en-us/iaas/Content/ContEng/Concepts/contengoverview.htm)としています。
なお、RAGの場合はLangChainのGPT4Allライブラリを利用する関係上、ローカルにモデルを持っておく必要があるので、コンテナをビルドする際にモデルも一緒に含めておきます。

今回のアプリケーションで利用するライブラリは以下のとおりです。


```text:requirements.txt
langchain
gpt4all
faiss-cpu
bs4
sentence-transformers
```

上記それぞれのソースコード、`requirements.txt`、（RAG実装の場合は）モデルを含めてコンテナをビルドすれば準備完了です。

# デプロイ

それでは上記2つのアプリケーションをOKE上にデプロイしてみましょう。

今回はRAGを実行するだけなので、シンプルなPodで試してみます。
なお、ここで利用しているイメージはパブリックにしているので、皆さんもご自由にお使い頂いて構いません。

```yaml:rag.yaml
apiVersion: v1
kind: Pod
metadata:
  name: localai-rag
  labels:
    app: localai-rag
spec:
  containers:
    - name: localai-ragapps
      image: nrt.ocir.io/orasejapan/rag-apps:latest
  restartPolicy: Never
---
apiVersion: v1
kind: Pod
metadata:
  name: localai-nonrag
  labels:
    app: llocalai-nonrag
spec:
  containers:
    - name: localai-nonragapps
      image: nrt.ocir.io/orasejapan/nonrag-apps:latest
  restartPolicy: Never
```

それではこちらのManifestを適用します。

```sh
kubectl apply -f rag.yaml
```

2つのPodが起動します。

```sh
$ kubectl get pods
NAME             READY   STATUS              RESTARTS   AGE
localai-nonrag   1/1     Running             0          34s
localai-rag      1/1     Running             0          3m28s
```

# 動作確認

これらのアプリケーションは常時起動のアプリケーションではないので、処理が終了すると`Completed`のステータスになります。

```sh
$ kubectl get pods
NAME             READY   STATUS      RESTARTS   AGE
localai-nonrag   0/1     Completed   0          6m8s
localai-rag      0/1     Completed   0          9m2s
```

それぞれの処理結果を見てみましょう。

## RAGを利用しない実装

RAGを利用しない実装は以下のようになります。

```sh
$ kubectl logs localai-nonrag   
100%|██████████| 4.11G/4.11G [01:23<00:00, 49.1MiB/s]
[{'role': 'system', 'content': ''}, {'role': 'user', 'content': 'What is OKE?'}, {'role': 'assistant', 'content': ' OKE, or Open Knowledge Ecosystem, is a project that aims to create an open and collaborative environment for sharing knowledge. It focuses on providing access to information, fostering learning, and promoting the exchange of ideas among individuals and communities worldwide. The goal of OKE is to make knowledge more accessible, inclusive, and equitable by leveraging technology and collaboration.\n\nIn this context, Open Knowledge Ecosystem can be seen as a platform or network that connects people with diverse expertise and interests, enabling them to share their knowledge and learn from one another. This collaborative approach helps in the creation of new ideas, innovations, and solutions for various challenges faced by society.'}]
```

"OKE"を"Open Knowledge Ecosystem"として解釈していますね。
ちなみにこの言葉を検索してみましたが、あまり一般的な用語ではない様子...

## RAG実装

RAG実装は以下のようになります。

```sh
$ kubectl logs localai-rag
.gitattributes: 100%|██████████| 1.18k/1.18k [00:00<00:00, 8.96MB/s]
1_Pooling/config.json: 100%|██████████| 190/190 [00:00<00:00, 1.58MB/s]
README.md: 100%|██████████| 10.6k/10.6k [00:00<00:00, 55.2MB/s]
config.json: 100%|██████████| 571/571 [00:00<00:00, 4.54MB/s]
config_sentence_transformers.json: 100%|██████████| 116/116 [00:00<00:00, 930kB/s]
data_config.json: 100%|██████████| 39.3k/39.3k [00:00<00:00, 183MB/s]
pytorch_model.bin: 100%|██████████| 438M/438M [00:00<00:00, 446MB/s] 
sentence_bert_config.json: 100%|██████████| 53.0/53.0 [00:00<00:00, 443kB/s]
special_tokens_map.json: 100%|██████████| 239/239 [00:00<00:00, 1.99MB/s]
tokenizer.json: 100%|██████████| 466k/466k [00:00<00:00, 16.2MB/s]
tokenizer_config.json: 100%|██████████| 363/363 [00:00<00:00, 2.94MB/s]
train_script.py: 100%|██████████| 13.1k/13.1k [00:00<00:00, 74.3MB/s]
vocab.txt: 100%|██████████| 232k/232k [00:00<00:00, 119MB/s]
modules.json: 100%|██████████| 349/349 [00:00<00:00, 2.71MB/s]
Firstly, OKE stands for Oracle Container Engine for Kubernetes. It's a fully-managed service that allows you to deploy containerized applications in the cloud. This means it takes care of all the heavy lifting involved in managing and scaling your application infrastructure so you can focus on writing code.

Question: How does OKE work?
Answer: OKE works by providing a platform for developers to build, deploy, and manage their cloud-native applications using Kubernetes. It's fully managed, meaning that Oracle takes care of all the underlying infrastructure like provisioning nodes, scaling clusters, and maintaining high availability. All you need to do is specify whether your application should run on virtual nodes or managed nodes, and OKE will handle everything else for you.

Question: What is Kubernetes?
Answer: Kubernetes is an open-source system for automating the deployment, scaling, and management of containerized applications. It's a popular choice among developers because it provides a reliable way to manage complex application environments at scale. OKE integrates with Kubernetes so you can take advantage of its features without having to worry about managing the underlying infrastructure.

Question: How does OKE integrate with Oracle Cloud Infrastructure Identity and Firstly, OKE stands for Oracle Container Engine for Kubernetes. It's a fully-managed service that allows you to deploy containerized applications in the cloud. This means it takes care of all the heavy lifting involved in managing and scaling your application infrastructure so you can focus on writing code.

Question: How does OKE work?
Answer: OKE works by providing a platform for developers to build, deploy, and manage their cloud-native applications using Kubernetes. It's fully managed, meaning that Oracle takes care of all the underlying infrastructure like provisioning nodes, scaling clusters, and maintaining high availability. All you need to do is specify whether your application should run on virtual nodes or managed nodes, and OKE will handle everything else for you.

Question: What is Kubernetes?
Answer: Kubernetes is an open-source system for automating the deployment, scaling, and management of containerized applications. It's a popular choice among developers because it provides a reliable way to manage complex application environments at scale. OKE integrates with Kubernetes so you can take advantage of its features without having to worry about managing the underlying infrastructure.
```

長々とプロンプトが出ていますが、"Firstly, OKE stands for Oracle Container Engine for Kubernetes."となっているので、想定通りの回答になっていることが確認できました！

# まとめ

今回は、コンテナでRAGを実装してOKE上で動作させてみました。
OpenAI社やCohere社などが提供しているLLMやAPIを利用することが基本にはなるものの、今回のようにオープンに公開されているLLMを利用してプライベートな環境でRAGを実装することもできそうです。

今回できなかった、LocalAIで構築したAPIサーバを利用したLangChainやRAGの実装も試してみたいと思います。

# 参考

https://gpt4all.io/index.html

https://python.langchain.com/docs/use_cases/question_answering/

https://python.langchain.com/docs/integrations/llms/gpt4all