CAP (Node.js) からOpenAIに接続する方法2つ

## はじめに
この記事では、CAP (Node.js) のアプリケーションからOpenAIに接続する2つの方法について説明します。

1. [OpenAI Node API Library](https://github.com/openai/openai-node)を使う
1. OpenAIのREST APIを使う

作成したリポジトリは以下です。

https://github.com/miyasuta/cap-openai

※ SAP AI Coreを使用している場合は、[CAP LLM Plugin](https://www.npmjs.com/package/cap-llm-plugin?activeTab=readme)（ベータ）も選択肢になります。

## 前提
- OpenAIアカウントの作成
- APIキーの取得
- 課金情報の設定（今回使用しているgpt-3.5-turboモデルの場合、事前に課金が必要です）

## ステップ
### 0. プロジェクトの作成
#### 0.1. プロジェクトの初期化
`cds init <プロジェクト名>`でプロジェクトを作成し、以下のようにサービス定義を行います。

```srv/chat-service.cds
service ChatService {
    action chat_sdk(input: String) returns String; 
    action chat_api(input: String) returns String;
}
```

#### 0.2. 環境変数の設定
プロジェクトのルートに`.env`ファイルを作成し、APIキーを設定します。
```
OPENAI_API_KEY=<APIキー>
```

#### 0.3. イベントハンドラの作成
イベントハンドラを以下のように作成します。

```javascript:srv/chat-service.js
const cds = require('@sap/cds');
const OpenAI = require("openai");

module.exports = class ChatService extends cds.ApplicationService {
    async init () {
        this.on('chat_sdk', async(req) => {
        })

        this.on('chat_api', async(req) => {
        })
        return super.init();
    }
}
```

### 1. OpenAI Node API Libraryを使う
#### 1.1. dependencyのインストール
```
npm install openai
```

#### 1.2. イベントハンドラの実装
アクション"chat_sdk"の中で、OpenAI Node API Libraryを使用してOpenAIにアクセスします。

```javascript
        this.on('chat_sdk', async(req) => {
            const apiKey = process.env['OPENAI_API_KEY'];
            console.log(apiKey);
            const request = req.data.input;
            const openai = new OpenAI({
                apiKey: apiKey //省略可能
            });

            const response = await openai.chat.completions.create({
                messages: [
                    { role: "system", content: "You are a helpful assistant." },
                    { role: "user", content: request }
                ],
                model: "gpt-3.5-turbo",
              });

            console.log(response);
            return response.choices[0].message.content;

        })
```

.httpファイルを作成し、以下のリクエストを送信するとレスポンスが返ってきます。

```
@server=http://localhost:4004

###
POST {{server}}/odata/v4/chat/chat_sdk
Content-Type: application/json

{
    "input": "Hello, openAI"
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/fc08e09d-7f8a-059f-533e-144e5cd671d2.png)

### 2. OpenAIのREST APIを使う
CAP (Node.js) の場合、`service.send()`のAPIを使用してREST APIにアクセスすることができます。
https://cap.cloud.sap/docs/node.js/core-services#srv-send-request

#### 2.1. package.jsonに設定追加
package.jsonに以下の設定を追加します。本番環境で使う"openai"のDestinationは、デプロイ時に作成します。

```json
  "cds": {
    "requires": {
      "OpenAI.Chat.Completion.API": {
        "kind": "rest",
        "credentials": {
          "url": "https://api.openai.com",
          "path": "/v1"
        },
        "[production]": {
          "credentials": {
            "destination": "openai",
            "path": "/v1"
          }
        }
      }
    }
```

#### 2.2. dependencyのインストール
以下のライブラリを追加します。

```
npm add @sap-cloud-sdk/http-client@3.x @sap-cloud-sdk/connectivity@3.x @sap-cloud-sdk/resilience@3.x
```
参考：https://cap.cloud.sap/docs/guides/using-services#mock-remote-service-as-odata-service-node-js

#### 2.3. イベントハンドラの実装
アクション"chat_api"の中で、OpenAIのREST APIにアクセスします。

```javascript
        this.on('chat_api', async(req) => {
            const apiKey = process.env['OPENAI_API_KEY'];
            console.log(apiKey);
            const request = req.data.input;
            const openai = await cds.connect.to('OpenAI.Chat.Completion.API');
            const data = {
                "model": "gpt-3.5-turbo",
                "messages": [
                    { role: "system", content: "You are a helpful assistant." },
                    { role: "user", content: request }
                ]
            };
            const response = await openai.send({
                method: 'POST',
                path: '/chat/completions',
                headers: {
                    "Authorization": "Bearer " + apiKey,
                    "Content-Type": "application/json"
                },
                data: data
            });

            console.log(response);
            return response.choices[0].message.content;
        })
```
以下のリクエストを送信するとレスポンスが返ってきます。レスポンスの内容は1.と同じなため省略します。
```
###
POST {{server}}/odata/v4/chat/chat_api
Content-Type: application/json

{
    "input": "Hello, openAI"
}
```

## 3. デプロイ
### 3.1. 必要なサービスを宣言
以下のコマンドで必要なサービスを宣言します。

```
cds add xsuaa --for production
cds add destination // 2.の方法の場合
```

### 3.2. mta.yamlの追加

```
cds add mta
```
2.の方法で使うDestinationの設定を追加します。
```yaml
resources:
  # ...
  - name: cap-openai-destination
    type: org.cloudfoundry.managed-service
    parameters:
      service: destination
      service-plan: lite
      # 以下を追加
      config:
        init_data:
          instance:
            destinations:      
            - Authentication: NoAuthentication
              Name: openai
              ProxyType: Internet
              Type: HTTP
              URL: https://api.openai.com 
            existing_destinations_policy: update
```

### 3.3. デプロイ
以下のコマンドにより、Cloud Foundryにデプロイします。

```
mbt build -t gen --mtar mta.tar
cf deploy gen/mta.tar
```

### 3.4. 環境変数の設定
デプロイが完了したら、アプリケーションにAPIキーを環境変数として設定します。

- 以下のコマンドにより、ローカル環境で環境変数`OPENAI_API_KEY`を設定
```:linux
export OPENAI_API_KEY=<APIキー>
```

- 同じターミナルから、以下のコマンドによりアプリケーションに環境変数を設定
```
cf set-env <application> OPENAI_API_KEY $OPENAI_API_KEY 
cf restage <application>
```

以上で、Cloud Foundry環境でアプリケーションを動作させることができます。

## まとめ
OpenAI Node API Libraryを使った場合、宛先URLはライブラリ内で設定してくれるので、Destinationの作成が不要です。よって、OpenAIに接続する方法としてはこちらの方が簡単です。
一方、REST APIに接続する方法はOpenAIだけでなく他のAPIでも使えるので、知っておくと今後も役に立ちそうです。