SAP BTP, Kyma Runtime 上で動作する ChatGPT クローンから Kyma について聞いてみた

# はじめに

この記事は [SAP Advent Calendar 2023](https://adventar.org/calendars/8870) の 12 月 1 日分の記事として執筆しています。

最近、ChatGPT に代表される生成 AI のビジネス活用が活況です。そのフロントエンドとして Chatbot UI を活用する機会がありましたので、SAP BTP, Kyma Runtime にホスティングしてみようと思い立ちました。

タイトルは伝わりやすさ最優先で ChatGPT と記載しましたが、ChatGPT は OpenAI 社が提供するチャットボットサービスであり、今回扱うのは ChatGPT そのものではなく、あくまでクローンです。フロントエンドには ChatGPT UI クローンである Chatbot UI（Next.js ベースの Web アプリ）を、バックエンド API には OpenAI 社との提携により提供されている Azure OpenAI Service（モデルとして GPT-3.5 を指定）を利用しています。

### 前提条件

- macOS を利用しています
- Git および Docker がインストールされていること
- GitHub アカウントを持っていること
- SAP BTP トライアル環境を持っており、SAP BTP, Kyma Runtime が有効化済みであること
- Azure OpenAI サービスを有効化済みであること（要申請）
- Azure OpenAI サービスにモデルをデプロイ済みであること

Azure OpenAI サービスの利用申請およびモデルのデプロイについては、以下記事の 2 章をご参照ください。

https://qiita.com/AGV_morinaga/items/04c1b3f505efca8b1511

# アーキテクチャ図

ざっくりとした全体アーキテクチャはこちら。利用している主要なコンポーネントやサービスは以下の通りです。手順 1-4 はおおまかな作業の流れを示しています。

- Chatbot UI：フロントエンドとして採用（ChatGPT の UI クローン）
- GitHub Container Registry：コンテナレジストリとして利用
- SAP Kyma：コンテナプラットフォームとして利用
- Azure OpenAI サービス：生成 AI サービスとして利用

![chatbot-ui-on-kyma.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/809bd687-7abc-0ec0-37c8-4031b5ec5b10.png)

# Kyma への Chatbot UI デプロイ

アーキテクチャ図における手順 1-4 を主軸として、幾つかの手順を実施していきます。

1. 事前準備
1. Chatbot UI イメージ準備（手順 1-2）
1. Kyma への Chatbot UI デプロイ（手順 3-4）

## 事前準備

### GitHub Personal Access Token

GitHub にコンソールログインし、Personal Access Token を取得します。取得したトークンを環境変数にセットしておきます。

```
export CR_PAT=<your personal access token>
```

Personal Access Token を払い出す手順については、GitHub 公式ドキュメントをご参照ください。現在、より厳密なスコープ管理が可能な Fine-grained Personal Access Token の利用が推奨されていますが、今回は ``docker push`` する際にも利用するため、``write:packages`` スコープを持つ Personal Access Token (Classic) を払い出しました。

https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens

### kubeconfig Files

Kyma ダッシュボードの Overview > Kyma Environment > KubeconfigURL リンクをクリックし、 kubeconfig ファイルをダウンロードします。ダウンロードした ``kubeconfig.yaml`` ファイルを ``~/.kube/config`` ファイルとしてリネーム保存します。
![screenshot-cockpit.hanatrial.ondemand.com-2023.10.04-16_44_26_masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/9901b4bb-9937-a343-ead5-f9a6f358cb80.png)
上記手順により、デフォルトの接続先として Kyma が指定されます。この方法の他に都度 kubectl コマンドのパラメータとして指定する方法や環境変数（``KUBECONFIG``）として設定する方法があります。詳細は Kubernetes 公式ドキュメントをご参照ください。

https://kubernetes.io/ja/docs/concepts/configuration/organize-cluster-access-kubeconfig/

## Chatbot UI イメージ準備

まず、SAP Kyma から参照される Chatbot UI の Docker イメージを準備します。手順は以下の通りです。

1. Git リポジトリをクローンする
1. Docker イメージをビルドする
1. Docker イメージにタグを付ける
1. Docker レジストリにログインする
1. Docker イメージをレジストリにプッシュする

### Git リポジトリをクローンする

Chatbot UI をクローンします。

```
git clone https://github.com/mckaywrigley/chatbot-ui.git
```

### Docker イメージをビルドする

クローンしたリポジトリディレクトリに移動し、Docker イメージをビルドします。

```
cd chatbot-ui
docker build -t chatbot-ui .
```

### Docker イメージにタグを付ける

ビルドしたイメージにタグ付けします。

```
docker tag chatbot-ui ghcr.io/<your username>/chatbot-ui:latest
```

### Docker レジストリにログインする

レジストリにログインします。今回は、GitHub Container Registry を利用しました。パスワードに指定する Personal Access Token は、先ほど環境変数にセットしたものです。

```
echo ${CR_PAT} | docker login ghcr.io -u <your username> --password-stdin
```


### Docker イメージをレジストリにプッシュする

タグ付けしたイメージをレジストリにプッシュします。

```
docker push ghcr.io/<your username>/chatbot-ui:latest
```

以上の手順にて、SAP Kyma から参照される Chatbot UI の Docker イメージ準備が整いました。

## Kyma への Chatbot UI デプロイ

続いて、SAP Kyma への Chatbot UI アプリのデプロイ手順となります。大まかな流れは以下の通り。

1. ネームスペースを用意する
1. Docker レジストリ向け Secret を作成する
1. Azure OpenAI サービス向け Secret を作成する
1. マニフェストファイルを適用する
1. Kyma ダッシュボードから適用結果を確認する

### ネームスペースを用意する

安直ですが、``chatbot-ui`` というネームスペースを用意します。以降のリソースは、``chatbot-ui`` ネームスペース内に作成していきます。

```
kubectl create namespace chatbot-ui
```

### Docker レジストリ向け Secret を作成する

Docker レジストリ向けの Secret を作成します。今回は、Docker Hub ではなく GitHub Container Registry（``ghcr.io``） を利用しました。シークレット名（``github-container-registry``）やネームスペース名（``chatbot-ui``）等は適宜読み替えてください。

```
kubectl create secret docker-registry github-container-registry \
    --docker-server=ghcr.io \
    --docker-username=<your username> \
    --docker-password=${CR_PAT} \
    --namespace chatbot-ui
```

### Azure OpenAI 向け Secret を作成する

Azure OpenAI サービスの API キーを Secret として登録します。

```
kubectl create secret generic openai-api-key \
    --from-literal=OPENAI_API_KEY=<your api key> \
    --namespace chatbot-ui
```

API キーは、 Azure OpenAI サービスの Key and Enpoint から確認可能です。

- ``OPENAI_API_KEY`` : KEY 1 の値
- ``OPENAI_API_HOST`` : Endpoint の値（マニフェストファイル内で指定します）

![screenshot-portal.azure.com-2023.09.28-11_40_33_masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/3fd3b629-0b74-3a6e-09ee-98ad5e7fd8f2.png)

### マニフェストファイルを適用する

必要な修正を施したマニフェストファイルを Kyma 環境に適用します。

```
kubectl apply -f k8s/chatbot-ui.yaml
```

マニフェストファイルには以下のリソースが記述されています。

- Namespace
- Deployment
- Service
- APIRule

Git クローンした Chatbot UI のオリジナル ``k8s/chatbot-ui.yaml`` ファイルからの変更点は以下の通りです。

- Secret（``OPENAI_API_KEY``）をソースコードから削除
- Deployment に以下の環境変数を追加
  - ``OPENAI_API_HOST``
  - ``OPENAI_API_TYPE``
  - ``AZURE_DEPLOYMENT_ID``
- APIRule を追加（APIRule は Kyma による Custom Resource）

環境変数の定義については、Chatbot UI 公式ドキュメントをご参照ください。

https://github.com/mckaywrigley/chatbot-ui/#configuration

``AZURE_DEPLOYMENT_ID`` に指定する値を確認するには、まず Azure OpenAI サービスの Model deployments を開き、Manage Deployments ボタンをクリックします。すると、Azure OpenAI Studio が起動しますので、Deployments メニューを開き、Deployment name の値を控えておきます。
![screenshot-portal.azure.com-2023.10.04-14_43_47_masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/fcbf73fe-2b0b-ca92-2515-e1cee4afa039.png)
![screenshot-oai.azure.com-2023.10.04-14_43_17_masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/7d99a1ea-c503-2d86-eb14-67f658291725.png)

#### 具体的なマニフェストファイルの内容はこちら

以下のプレースホルダは適宜読み替えてください。
- Chatbot UI イメージパス（``ghcr.io/<your username>/chatbot-ui:latest``）
- Azure OpenAI エンドポイント URL（``https://<your endpoint>.openai.azure.com``）
- Deployment ID（``<your deployment name>``）
- フロントエンド URL（``chatbot-ui.<your cluster name>.kyma.ondemand.com``）

```yaml:chatbot-ui.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: chatbot-ui
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: chatbot-ui
  name: chatbot-ui
  labels:
    app: chatbot-ui
spec:
  replicas: 1
  selector:
    matchLabels:
      app: chatbot-ui
  template:
    metadata:
      labels:
        app: chatbot-ui
    spec:
      containers:
        - name: chatbot-ui
          image: ghcr.io/<your username>/chatbot-ui:latest
          resources: {}
          ports:
            - containerPort: 3000
          env:
            - name: OPENAI_API_KEY
              valueFrom:
                secretKeyRef:
                  name: openai-api-key
                  key: OPENAI_API_KEY
            - name: OPENAI_API_HOST
              value: https://<your openai endpoint>.openai.azure.com
            - name: OPENAI_API_TYPE
              value: azure
            - name: AZURE_DEPLOYMENT_ID
              value: <your deployment name>
      imagePullSecrets:
        - name: github-container-registry
---
kind: Service
apiVersion: v1
metadata:
  namespace: chatbot-ui
  name: chatbot-ui
  labels:
    app: chatbot-ui
spec:
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 3000
  selector:
    app: chatbot-ui
  type: ClusterIP
---
apiVersion: gateway.kyma-project.io/v1beta1
kind: APIRule
metadata:
  namespace: chatbot-ui
  name: chatbot-ui
  labels:
    app.kubernetes.io/name: chatbot-ui
spec:
  gateway: kyma-gateway.kyma-system.svc.cluster.local
  host: chatbot-ui.<your cluster name>.kyma.ondemand.com
  rules:
    - accessStrategies:
        - handler: allow
      methods:
        - GET
        - POST
        - PUT
        - DELETE
        - PATCH
        - HEAD
      path: /.*
  service:
    name: chatbot-ui
    port: 80
```

### Kyma ダッシュボードから適用結果を確認する

Kyma ダッシュボードにアクセスし、``chatbot-ui`` ネームスペース内の Pod が正常に稼働していることが確認できました。例えば、Docker イメージ取得用のシークレットが誤っていると起動エラーとなり、Pod のカウントが 0/1 としてエラー表示されます。そのときは、ログからエラーメッセージを確認し、トラブルシューティングすることになります。
![screenshot-dashboard.kyma.cloud.sap-2023.10.04-15_02_29_masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/da36b95b-dc8a-2015-ab47-300307e3b00f.png)

# SAP Kyma の特長（生成 AI への質問）

### Chatbot UI on SAP Kyma にアクセスする

Kyma ダッシュボードの Discovery and Network > API Rules から ``chatbot-ui`` アプリのエンドポイントを開きます（Host 列）。
![screenshot-dashboard.kyma.cloud.sap-2023.10.04-15_06_32_masked.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/de2bbffb-9acd-1a8e-d8eb-3e326beeed02.png)

### SAP Kyma について聞いてみる

API Rule エンドポイントに認証を設定していないため、（URL さえ知っていれば）世界中からパブリックアクセス出来てしまいます。わたしの Azure サブスクリプションの OpenAI サービスが、世界中から呼ばれ放題というわけにもいきませんので、アプリ側に MSAL (Microsoft Authentication Library) を利用して Microsoft Entra ID（旧 Azure AD）との認証を組み込みました。
![screenshot-login.microsoftonline.com-2023.10.16-14_09_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/12c50d49-9aa6-d046-3956-e1c33ef82025.png)
本題（Kyma）から外れますので、ソースコードは省略しますが、以下を参考にしました。

https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-react-samples/nextjs-sample

chatbot-ui アプリが起動したら、「SAP Kyma の特長を400文字以内に要約してください」とお願いしてみます。システムプロンプトはデフォルトのままです。
![screenshot-chatbot-ui.c-6940146.kyma.ondemand.com-2023.10.04-15_15_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/d57cd860-4ce9-0505-a406-fde93281cd51.png)

数秒で以下のような回答が得られました。なるほど、よくまとまっていると思います。
![screenshot-chatbot-ui.c-6940146.kyma.ondemand.com-2023.10.04-15_14_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/b8bf2d44-933d-46a4-e471-048c4fe7ca40.png)

実は、「ちょっと違うな、、、」と思う回答が複数回返されたため、「もう一度回答する」ボタンをクリックし、ハルシネーションが含まれていない回答をキャプチャしました。このあたり、プロンプトエンジニアリングで精度の高い回答を得ればよいのですが、本題とは関係ないので、本稿としてはここまでとします。

# さいごに

ここ数回、UI5 Web Components for React を利用して自分のアプリを作ってみた系の投稿が続いておりましたが、今回は既にあるフロントエンドを動かす形となりました。Chatbot UI は React 向けフレームワークのデファクトスタンダードと言っても過言ではない Next.js ベースで作成されており、カスタマイズ性にも優れています。

仕事では狭義のサーバーレス構成を採用することが多いのですが、久しぶりの Kyma で「コンテナもいいな」と思い返しています。特にエンタープライズ用途では、究極のコスト極小化を目指さなくても成立しますし、、、。

また、AWS Amplify や Azure Static Web Apps も Next.js の静的 Web ホスティングをサポートしているものの、レスポンスストリーミング（＝ちょっとずつ回答が返却される）に対応していないなど一部制限があり、UX 低下が否めないことも、「コンテナもいいな」と思った一因です。

なお、今年の 4 月には、Lambda のレスポンスストリーミングがサポートされたようですので、Amplify Hosting にお任せせず、API Router を Lambda@Edge にマニュアル実装すれば、対応可能かも知れません。こちらは、時間があるときに検証してみたいと思います。

https://aws.amazon.com/jp/blogs/compute/introducing-aws-lambda-response-streaming/

さいごに「SAP Advent Calendar等のコミュニティ活動に参加する意義を300文字以内に要約してください」と聞いてみた回答をご紹介します（こちらはダークモード）。以下、5 点が挙げられましたが、優等生的回答で全く異論ありません。

- 情報共有と学習の機会
- ネットワーキングとつながりの形成
- 自己成長とスキル向上
- 影響力を持つ機会
- SAP コミュニティの拡大と貢献

![screenshot-chatbot-ui.c-6940146.kyma.ondemand.com-2023.10.04-15_53_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/4a4758fa-f394-88df-9675-3f45e8389a55.png)

上記は、参加する意義ですが、とにもかくにも「SAP Advent Calendar」という場を毎年提供くださっている運営の方々に感謝しながら（「いつもありがとうございます！」）、次の方にバトンを渡したいと思います！

### 参考リンク

- [ChatGPT (OpenAI)](https://chat.openai.com/)
- [Chatbot UI (GitHub)](https://github.com/mckaywrigley/chatbot-ui/)
- [Azure OpenAI Service (Microsoft)](https://azure.microsoft.com/ja-jp/products/ai-services/openai-service/)
- [Kyma Project](https://kyma-project.io/)
- [SAP BTP, Kyma Runtime (SAP)](https://discovery-center.cloud.sap/serviceCatalog/kyma-runtime?region=all)