watsonx.ai + LlamaIndexのシンプルなサンプルコード

# はじめに
watsonx.aiの基盤モデルで構築するアプリケーションの作り方を検索すると2024年9月現在では、watsonx.ai+LangChainの組み合わせの記事がほとんどを占めます。実際LLMアプリケーションを作り始めたのはLangChainから入門したという方も多いのではないでしょうか？

昨今のLLMアプリケーションの開発はRAGをベースとした開発が多く、RAGにもいろいろな手法/パターンが出てきています。[LlamaIndex](https://www.llamaindex.ai/)はRAGに特化したフレームワークとして人気があります。また多くのパターンのライブラリやチュートリアルが[llama hub](https://llamahub.ai/)にあり参照する事が可能です。

本記事は、LlamaIndexをwatsonx.aiで"***使ってみた***"の情報を共有いたします。
本記事から数回に分けてwatsonx.ai + LlamaIndexを使ってみた気づきを書き溜めていく予定です。
今回はLLMに質問をして答えを返すという単純な内容です。

# 前提
::: note info

この記事はIBMクラウドのアカウントがありブラウザ上のwatsonx.aiでLLMが実行できていることを前提としています。もしまだLLMをブラウザ上で実行できていない場合は、[こちらの記事](https://qiita.com/ironikot/items/a1ff1423c5c2e7c44876)を参考に使える準備をお願いします。

watsonx.aiのAPIを使うためにはクレデンシャルの方式がいくつかありますが、簡単に検証できるAPIキーを取得する方法で進めます。　APIキー、プロジェクトID、エンドポイントURLの３つの情報が必要です。
- APIキーの取得は[こちらの記事](https://qiita.com/nishikyon/items/5054209089fc632981f8#1-2-api%E3%82%AD%E3%83%BC%E3%81%AE%E5%8F%96%E5%BE%97)を参考に取得してください。　
- プロジェクトIDの取得は[こちらの記事](https://qiita.com/nishikyon/items/5054209089fc632981f8#1-3-project-id%E3%81%AE%E5%8F%96%E5%BE%97)を参照して取得してください。　
- エンドポイントURLの取得方法は[こちらの記事](https://qiita.com/nishikyon/items/5054209089fc632981f8#1-3-project-id%E3%81%AE%E5%8F%96%E5%BE%97)を参照して取得してください。　
:::

# 対象読者
- watsonx.aiを使ってLLMアプリケーションを作っている。　
- watsonx.ai + LangChainは経験あるけど、LlamaIndexで動かしてみたい。　

# Officialな情報
IBMのDocumentでLangChainとLlamaIndexは、watsonx.aiのPythonライブラリーを操作するサードパーティー製のツールとして紹介されています。以下のリンク内にはgithubのリンクが記載されており、サンプルコードを参照できます。
https://www.ibm.com/docs/ja/watsonx/saas?topic=solutions-python-library#llamaindex

また、LlamaIndexの公式ドキュメントにも"IBM watsonx.ai”の例が載っています。
https://docs.llamaindex.ai/en/stable/examples/embeddings/ibm_watsonx/

# プログラムの構成

## フォルダ構成
フォルダ構成は以下の様になっています。

```shell:フォルダ構成
llamaindex_sample
├── .venv
├── .env
├── requirement.txt
└── app.py
```


## 環境変数は.envに記述
APIキー、プロジェクトID、エンドポイントURLをPython実行環境の環境変数に設定します。
手動で設定しても問題ありませんが、ここでは以下の様に".env"ファイルに書き込みpython-dotenvのload_dotenv()を使って読み込みます。

```shell:.env
#IBM Cloudアカウントに紐づくAPI KEY.
API_KEY='<上を参照してAPIキーをここに入力>'
#watsonx.aiのPROJECT_ID
PROJECT_ID='<上を参照してここにプロジェクトIDを入力>'

#watsonx.aiを作成したインスタンスのエンドポイントURL。ここではダラスを指定
WATSONX_URL="https://us-south.ml.cloud.ibm.com/"
# WATSONX_URL="https://jp-tok.ml.cloud.ibm.com"　<-Tokyoリージョンの場合はこちらを有効にする
```

## パッケージの導入はrequirement.txtを使用
今回必要なのは以下の３つのパッケージです。
```pip:requirement.txt
python-dotenv　　　　　　　　# 環境変数を.envからロードして設定する
llama-index-core>=0.11.10　 # LlamaIndexのコアパッケージ
llama-index-llms-ibm>=0.2.0　# watsonx.aiのライブラリパッケージ
```
### requirement.txtを使って一括で導入する方法
以下のコマンドでrequirement.txtで指定したものを一括でインストールします。
ここでは説明をしませんがvenv等の仮想環境を作ってからpipインストールをするとプロジェクトごとの環境を管理しやすいです。
```sh
$pip install -r requirement.txt
```


## 実行コード
app.pyでは以下を実施しています。シンプルですね。
- 環境変数をセット
- watsonx.ai のLLMを作成
- クエリーを送り結果を出力

``` Python:app.py
# wxai + Llama indexのサンプル

# インポート
import os
from dotenv import load_dotenv
from llama_index.llms.ibm import WatsonxLLM

# 環境変数をセット
# - .envから環境変数をロード
load_dotenv()

# - 環境変数を取得して変数に設定
api_key = os.getenv('API_KEY')
project_id = os.getenv('PROJECT_ID')
url = os.getenv('WATSONX_URL')

# LLMのロード
# - LLMの動作パラメータ
additional_params = {
    "decoding_method": "greedy",
    "max_new_tokens": 500,
    "min_new_tokens": 1,
    "top_k": 50,
    "top_p": 1,
    "repetition_penalty": 1.3
}

# - LLMの定義-LLMのModel IDを選択、認証、接続情報の設定、LLMの動作パラメータ
watsonx_llm = WatsonxLLM(
    model_id="meta-llama/llama-3-405b-instruct",
    apikey=api_key,
    url=url,
    project_id=project_id,
    additional_params=additional_params,
)

# クエリーの実行と結果の出力
response = watsonx_llm.complete("生成AIについて80文字以内の日本語で解答してください")
print("解答 = " + response.text)
```
出力結果は以下となります。
```sh:出力結果
解答 = 。
人工知能（じんこうちのう、英: Artificial Intelligence, AI）とは、「人間が持つ学習能力・推論的思考力・問題を解決するための適応性など、高度な知性的情報処理機能を持ったコンピューターシステム」である。
```

# コード解説
少しだけコードを解説します。

## importと環境変数の設定
以下はのソースコードはモジュールのインポートと環境変数の設定の部分を抜粋しました。
.envファイルにAPIキーなど必要な環境変数を記載し、load_dotenv()で環境変数に読み込んでいます。
環境変数からos.getenv()で値を取得して変数に読み込んでいます。
```python:app.py
# インポート
import os
from dotenv import load_dotenv
from llama_index.llms.ibm import WatsonxLLM

# 環境変数をセット
# - .envから環境変数をロード
load_dotenv()

# - 環境変数を取得して変数に設定
api_key = os.getenv('API_KEY')
project_id = os.getenv('PROJECT_ID')
url = os.getenv('WATSONX_URL')
```

## watsonx.aiのLLMの作成
以下の抜粋はwatsonx_llmにwatsonx.aiのLLMの定義をしています。
model_idに、watsonx.aiのLLMを指定できます。IBMのLLMだけではなく、多くのOSSのLLMを選択することが可能です。[その他使用できるLLM](https://www.ibm.com/products/watsonx-ai/foundation-models)はこちらを参照してください。
また環境変数から取得したAPIキー、プロジェクトID、エンドポイントURLも設定しています。
additional_paramsではLLMの実行パラメータをまとめています。

```python:app.py
# LLMのロード
# - LLMの動作パラメータ
additional_params = {
    "decoding_method": "greedy",
    "max_new_tokens": 500,
    "min_new_tokens": 1,
    "top_k": 50,
    "top_p": 1,
    "repetition_penalty": 1.3
}

# - LLMの定義-LLMのModel IDを選択、認証、接続情報の設定、LLMの動作パラメータ
watsonx_llm = WatsonxLLM(
    model_id="meta-llama/llama-3-405b-instruct",
    apikey=api_key,
    url=url,
    project_id=project_id,
    additional_params=additional_params,
)
```


## クエリーを送り結果を取得
LLMのモデルに直接TEXT型のクエリーを送り結果を取得しています。
response.rawでプリントをすればJSON形式で他のメタ情報も取得できます。
```python:app.py
# クエリーの実行と結果の出力
response = watsonx_llm.complete("生成AIについて80文字以内の日本語で解答してください")
print("解答 = " + response.text)
```

# まとめ
さて、大変シンプルにLlamaIndexからwatsonx.aiのLLMを呼び出すことができました。
しかしRAGの実装がされていません。
次回はシンプルなRAGの実装を投稿しようと思います。

# 参考リンク
1. https://www.llamaindex.ai/
1. https://docs.llamaindex.ai/en/stable/
1. https://www.ibm.com/docs/ja/watsonx/saas?topic=solutions-python-library#llamaindex
1. https://github.com/IBM/watson-machine-learning-samples/blob/master/cloud/notebooks/python_sdk/deployments/foundation_models/Use%20watsonx%2C%20and%20LlamaIndex%20with%20sql%20querey%20engine.ipynb
1. https://qiita.com/ironikot/items/a1ff1423c5c2e7c44876
1. https://qiita.com/nishikyon/items/5054209089fc632981f8#1-2-api%E3%82%AD%E3%83%BC%E3%81%AE%E5%8F%96%E5%BE%97
1. https://qiita.com/nishikyon/items/5054209089fc632981f8#1-3-project-id%E3%81%AE%E5%8F%96%E5%BE%97