【Swift】OpenAI APIを使ってChatBotを作ってみた！

OpenAI APIを用いてChatBotを作ってみました！この記事では、特にOpenAI APIの使用部分に焦点を当てて説明をしていきます。

![chatbot_sc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3812600/5ef9a7da-ec57-5db3-b8b7-c33cf2062c97.gif)



上記の実装をするための詳細なコードは以下をご覧ください。

https://github.com/nekoribocchi/ChatBot_OpenAI_API

## OpenAI API とは？

 OpenAI API とは OpenAI APIが提供するAPIで、ChatGPTに使用されている生成モデルにアクセスをすることができるAPIです。
このAPIを用いることで、簡単にチャットボットを作ることが出来ます！

## APIキーの取得

まず、OpenAI APIを利用するには、APIキーを取得する必要があります。
APIキーを取得するためには、最低5ドル(およそ770円)のクレジットを購入する必要があります。APIキーの取得方法については、こちらの動画がとても参考になりますので、ぜひご覧ください。

https://www.youtube.com/watch?v=NPMlzob_Rv4&t=743

## コードの解説

OpenAI API の処理を実装しているクラスはChatViewModelです。

このクラスを3ステップに分けて解説していきます。

### ステップ1: APIキーの設定

最初のコードでは主に、APIキーの取得を行っています。APIキーを直接コードに書くこともできますが、今回はセキュリティを考慮して、環境変数を使用する方法を紹介します。

#### APIキーの設定方法

1. Xcodeのプロジェクトを開く
2. 上部のメニューから `Product` > `Scheme` > `Edit Scheme...` を選択
3. 左側のメニューで `Run` を選択
4. 上部のタブで `Arguments` を選択
5. `Environment Variables` セクションに以下を追加
    - Name: `OPENAI_API_KEY`
    - Value: ご自身で取得したAPIキー

```swift
import Foundation
import OpenAI
import SwiftUI

class ChatViewModel: ObservableObject {
    // チャットメッセージのリスト
    @Published var messages: [ChatMessage] = [ChatMessage(role: .assistant, content: "こんにちは！")]
    
    // ユーザーが入力中の新しいメッセージ
    @Published var newMessage: String = ""
    
    // characterプロパティを初期化するためのイニシャライザ
    let character: CharacterType
    
    // OpenAIのインスタンスをプロパティとして保持
    private var openAI: OpenAI?
    
    init(character: CharacterType) {
        self.character = character
        
        // 環境変数からAPIキーを取得
        if let apiKey = ProcessInfo.processInfo.environment["OPENAI_API_KEY"] {
            // OpenAIのインスタンスを作成
            self.openAI = OpenAI(apiToken: apiKey)
            print("APIキーが設定されました。")
        } else {
            print("APIキーが設定されていません。環境変数の設定をしてください")
        }
    }
```

### ステップ2: メッセージ送信機能の実装

この関数では、`role` と `content` を引数として渡す `ChatMessage` オブジェクトを定義します。そのオブジェクトを `fetchOpenAIResponse` メソッドに渡して、返答を待ちます。

userMessageという定数を定義

```swift
func sendMessage() {
    guard !newMessage.isEmpty else { return }
    let userMessage = ChatMessage(role: .user, content: newMessage)
    messages.append(userMessage)
    newMessage = ""
    
    // fetchOpenAIResponseメソッドを非同期タスクで呼び出し、OpenAIからの応答を取得。
    Task {
        await fetchOpenAIResponse(for: userMessage)
    }
}

```

### ステップ3: OpenAIからの応答を取得するメソッドの実装

この関数は、OpenAI APIにユーザーメッセージを送信し、取得した応答をチャットメッセージリストに追加する非同期処理を実行します。

ここでは、ユーザーメッセージとシステムメッセージの設定を行います。それぞれのメッセージは role と content の引数を持ちます。

contentには、ユーザーメッセージの場合、ユーザーが入力した文章が設定され、システムメッセージの場合は、キャラクター設定のプロンプトが与えられます。キャラクター設定のプロンプトは、別のクラスで定義されています。

```swift
@MainActor
private func fetchOpenAIResponse(for userMessage: ChatMessage) async {
    guard let openAI = openAI else {
        let errorResponse = ChatMessage(role: .assistant, content: "APIキーが設定されていません")
        messages.append(errorResponse)
        return
    }
    
    // ユーザーのメッセージ
    guard let message = ChatQuery.ChatCompletionMessageParam(role: .user, content: userMessage.content),
          
          // システムメッセージ（キャラクターのプロンプト）
          let systemMessage = ChatQuery.ChatCompletionMessageParam(role: .system, content: character.prompt) else { return }

    // 使用するモデルを指定
    let query = ChatQuery(messages: [systemMessage, message], model: .gpt3_5Turbo)
    
    do {
        // クエリをOpenAIに送信し、応答を取得
        let result = try await openAI.chats(query: query)
        
        // 返された応答メッセージ
        if let firstChoice = result.choices.first {
            switch firstChoice.message {
            
            // 応答メッセージがアシスタントからのものである場合、その内容をassistantMessageとして取得
            case .assistant(let assistantMessage):
                
                // 取得したアシスタントのメッセージをChatMessageオブジェクトに変換し、内容が空の場合はデフォルトで「がんばって！」というメッセージを設定
                let assistantResponse = ChatMessage(role: .assistant, content: assistantMessage.content ?? "がんばって！")
                messages.append(assistantResponse)
                
            default:
                break
            }
        }
    } catch {
        let errorResponse = ChatMessage(role: .assistant, content: "エラー: \(error.localizedDescription)")
        messages.append(errorResponse)
    }
}

```
## 終わりに
初めて、OpenAI API を使ってみましたが、思ったよりも簡単に出来たことに驚きました！特にサーバーサイドのシンプルさが際立っていました。

複数のユーザー間のメッセージングアプリを作るとなると、データベース管理や認証などがとても大変ですが、今回のChatBotアプリでは、OpenAIのライブラリが用意されているおかげで、とてもシンプルな記述でコーディングできたのがよかったです！

## リンク
https://github.com/nekoribocchi/ChatBot_OpenAI_API