Llama2-70b-chat on watsonxとおしゃべりする

こんにちは。ChatGPTは最高に楽しいのですが、知能レベルが変わってしまったり不適切な会話をしているとBANされてしまったりと、特に最近はLLMのセルフホスト熱が高まっています。個人的に。

みんなColab使ったりローカルマシンで動かしたりしているようですが、IBMのwatsonxというサービスを使うと環境構築周りの知識ゼロでもブラウザーやAPI経由で触れるようになります。

ということで、この記事ではwatsonxのAPIを使って簡単なチャットプログラムを作る方法をご紹介します。公式クライアントモジュールを使用しませんので、この記事はPythonベースですが別の言語で試す場合にも読み替えていただけると思います。

# watsonxの準備

ここが結構ややこしいんですけど、こちらの記事に手順が書いてありましたのでこの通りやってみてください。「まずはアクセスしてみよう」まででOKです。ブラウザで使えることを確認してください。なお事前にIBM Cloudへのサインアップが必要です。

https://qiita.com/katahiro/items/3258cd42226ed82268ac#ibm-watsonxへまずアクセスしてみよう

# APIキーの取得

IBM Cloudのコンソール画面の上にあるメニュー「管理」から「アクセス(IAM)」を選択します。開いた画面の左側メニューで「APIキー」から作成しましょう。![スクリーンショット 2023-09-27 19.31.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53296/5d034715-1c53-819e-9560-b7749a0cfaa6.png)

# Pythonの環境準備

HTTP通信用にrequestsを使用しますので、事前にインストールしましょう。

```sh
$ pip install requests
```

# アクセストークンの取得

ここからはPythonのコードでの作業になります。watsonxのAPIを呼び出すためのアクセストークンを、APIキーを使って取得するコードは以下の通りです。

```python
def get_access_token(api_key: str):
    url = "https://iam.cloud.ibm.com/identity/token"
    data = {
        "grant_type": "urn:ibm:params:oauth:grant-type:apikey",
        "apikey": api_key
    }
    resp = requests.post(url, data=data)
    access_token = resp.json()["access_token"]
    return access_token

print(get_access_token("YOUR_API_KEY"))
```

これを実行すると、以下のようにアクセストークンが表示されます。

```sh
eyJraWQiOiI（略）...
```

レスポンスの中には有効期限などの情報が含まれていますが、今回はトークン失効管理を行わないので全部無視しています。


# Llama2 on watsonxをAPI経由で呼び出す

この記事の本丸です。そもそもAPIドキュメントを見つけられなかったのですが、watsonxのブラウザー上でAPI呼び出し方法を確認することができました。また、`project_id`なるものをあとで使いますので、値を控えておいてください。
![スクリーンショット 2023-09-27 19.52.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53296/ce9438d6-6237-8efe-c837-428d2a4a97b9.png)

この画面のcurlコマンドをPythonのrequestsベースのものに焼きなおすと以下の通りです。parametersのところは各自調整してみてください。おそらくChatGPTのAPIとほぼ同じ意味です。

```python
def get_response(access_token: str, input_content: str, model_id: str, stop_words: list):
    url = "https://us-south.ml.cloud.ibm.com/ml/v1-beta/generation/text?version=2023-05-29"
    headers = {"Authorization": "Bearer " + access_token}
    data = {
        "model_id": model_id,
        "input": input_content,
        "parameters": {
            "decoding_method": "sample", 
            "max_new_tokens": 500,
            "min_new_tokens": 0,
            "stop_sequences": stop_words,
            "temperature": 0.6,
            "repetition_penalty": 1.05,
            "top_k": 50,
            "top_p": 1
        },
        "project_id": "YOUR_PROJECT_ID"
    }

    resp = requests.post(url, headers=headers, json=data)
    return resp.json()

access_token = get_access_token("YOUR_API_KEY")
print(get_response(access_token, "こんにちは", "meta-llama/llama-2-70b-chat", []))
```

これを実行すると、何か応答が返ってくると思います。

# 文脈を含めたマルチターンの会話ができるようにする

仕上げです。これまで作った2つの関数を使って以下の会話ループ処理を作成します。

```python
access_token = get_access_token("YOUR_API_KEY")
input_content = "あなたは優秀なアシスタントです。\n\n"

while True:
    user_content = input(f"user: ")
    if not user_content:
        break
    input_content += f"user: {user_content}\n\nassistant:"
    resp = get_response(access_token, input_content, "meta-llama/llama-2-70b-chat", ["user:"])
    if "results" in resp:
        assistant_content = resp["results"][0]["generated_text"].replace("user:", "")
        print(f"assistant: {assistant_content}")
        input_content += f" {assistant_content}\n\n"
    else:
        print(resp)
        break
```

実行すると、以下の通りマルチターンの会話ができるようになります。

```sh
user: こんにちは
assistant: こんにちは。おまたせ！
user: 
```

Llama2-70b-chatと言えど、そのまま使うと会話が成り立たなかったり突然英語になったりとChatGPTには程遠い状況です。watsonx上でチューニングすることができるのかとか、プロンプトで追い込みをかければとかわかっていないのですが、グラウンディングして照会対応みたいなのは結構よい感触が得られました。仕事で使うとかにはデータ保護の観点からも結構良いかもしれません。

watsonx詳しい方はぜひコメント欄でいろいろ教えていただけると嬉しいです✨🙏✨

ではでは〜👋

# コード全体

途中にデバッグコード入れたりもしたので、最後にコードの全体を掲載しますね。

```python
import requests

def get_access_token(api_key: str):
    url = "https://iam.cloud.ibm.com/identity/token"
    data = {
        "grant_type": "urn:ibm:params:oauth:grant-type:apikey",
        "apikey": api_key
    }
    resp = requests.post(url, data=data)
    access_token = resp.json()["access_token"]
    print(f"access_token: {access_token}")
    return access_token

def get_response(access_token: str, input_content: str, model_id: str, stop_words: list):
    url = "https://us-south.ml.cloud.ibm.com/ml/v1-beta/generation/text?version=2023-05-29"
    headers = {"Authorization": "Bearer " + access_token}
    data = {
        "model_id": model_id,
        "input": input_content,
        "parameters": {
            "decoding_method": "sample", 
            "max_new_tokens": 500,
            "min_new_tokens": 0,
            "stop_sequences": stop_words,
            "temperature": 0.6,
            "repetition_penalty": 1.05,
            "top_k": 50,
            "top_p": 1
        },
        "project_id": "13b436c4-ceba-4813-bb1d-1fb597ff447a"
    }

    resp = requests.post(url, headers=headers, json=data)
    return resp.json()

access_token = get_access_token("YOUR_API_KEY")
input_content = "あなたは優秀なアシスタントです。\n\n"

while True:
    user_content = input(f"user: ")
    if not user_content:
        break
    input_content += f"user: {user_content}\n\nassistant:"
    resp = get_response(access_token, input_content, "meta-llama/llama-2-70b-chat", ["user:"])
    if "results" in resp:
        assistant_content = resp["results"][0]["generated_text"].replace("user:", "")
        print(f"assistant: {assistant_content}")
        input_content += f" {assistant_content}\n\n"
    else:
        print(resp)
        break
```