12
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftUIとOpenAI APIを使用したAIチャットアプリの作成

Posted at

概要

本記事では、AppleのUIフレームワークSwiftUIとOpenAIのGPT-4を使って以下のようなAIチャットアプリを作成する方法について説明します。
IMG_6729.jpeg

対象読者

  • SwiftUIの基礎ををある程度理解している人
  • Swiftの開発環境がすでに構築できている人
  • OpenAIのAPIを利用したIOS/Ipadアプリを作成したい人

開発環境

MacOS Ventura(v13.4)
Xcode(v14.3.1)

OpenAIでの各種キー取得

以下のリンクよりOpenAIのAPIキーを取得するページにアクセスし、ログインします。
もしOpenAIのアカウントがない場合はアカウントを作成してください。

その後以下のCreate new secret keyをクリックしAPIキーを作成します。
image.png
今回はAPIキーの他にOrganization IDも必要になるためそれも取得します。
以下のページにアクセスし、Organization IDを取得してください。

パッケージの導入

OpenAI APIをSwiftで使用するためにOpenAIKitというパッケージを導入します。
プロジェクトをまだ作成していない場合は、作成します。
その後、File→Add Packagesを選択し右上の検索バーのところに
https://github.com/marcodotio/OpenAIKit
と入力しAdd Packageをクリックします。
image.png
パッケージ依存関係の欄にOpenAIKitがされていれば完了です。

ソースコード解説

ソースコードを実行すると以下のようになります。
IMG_6730.png
今回作成したソースコードは以下の通りです。

ContentView.swift
import SwiftUI
import OpenAIKit

// チャット表示用のメインビュー
struct Chat: View {

    // 現在のチャットが完了しているかどうかを示す変数
    @State private var isCompleting: Bool = false
    
    // ユーザーが入力するテキストを保存する変数
    @State private var text: String = ""
    
    // チャットメッセージの配列
    @State private var chat: [ChatMessage] = [
        ChatMessage(role: .system, content: "あなたは、ユーザーの質問や会話に回答するロボットです。"),
        ChatMessage(role: .system, assistant:"こんにちは。何かお困りのことがあればおっしゃってください。")
    ]
    
    // チャット画面のビューレイアウト
    var body: some View {
        VStack {
            // スクロール可能なメッセージリストの表示
            ScrollView {
                VStack(alignment: .leading) {
                    ForEach(chat.indices, id: \.self) { index in
                        // 最初のメッセージ以外を表示
                        if index > 1 {
                            MessageView(message: chat[index])
                        }
                    }
                }
            }
            .padding(.top)
            // 画面をタップしたときにキーボードを閉じる
            .onTapGesture {
                UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
            }
            
            // テキスト入力フィールドと送信ボタンの表示
            HStack {
                // テキスト入力フィールド
                TextField("メッセージを入力", text: $text)
                    .disabled(isCompleting) // チャットが完了するまで入力を無効化
                    .font(.system(size: 15)) // フォントサイズを調整
                    .padding(8)
                    .padding(.horizontal, 10)
                    .background(Color.white) // 入力フィールドの背景色を白に設定
                    .overlay(
                        RoundedRectangle(cornerRadius: 20)
                            .stroke(Color.gray.opacity(0.5), lineWidth: 1.5)

                    )
                
                // 送信ボタン
                Button(action: {
                    isCompleting = true
                    // ユーザーのメッセージをチャットに追加
                    chat.append(ChatMessage(role: .user, content: text))
                    text = "" // テキストフィールドをクリア
                    
                    Task {
                        do {
                            // OpenAIの設定
                            let config = Configuration(
                                organizationId: "Organization IDを入力",
                                apiKey: "APIキーを入力"
                            )
                            let openAI = OpenAI(config)
                            let chatParameters = ChatParameters(model: "gpt-4", messages: chat)
                            
                            // チャットの生成
                            let chatCompletion = try await openAI.generateChatCompletion(
                                parameters: chatParameters
                            )
                            
                            isCompleting = false
                            // AIのレスポンスをチャットに追加
                            chat.append(ChatMessage(role: .assistant, content: chatCompletion.choices[0].message.content))
                        } catch {
                            print("ERROR DETAILS - \(error)")
                        }
                    }
                }) {
                    // 送信ボタンのデザイン
                    Image(systemName: "arrow.up.circle.fill")
                        .font(.system(size: 30))
                        .foregroundColor(self.text == "" ? Color(#colorLiteral(red: 0.75, green: 0.95, blue: 0.8, alpha: 1)) : Color(#colorLiteral(red: 0.2078431373, green: 0.7647058824, blue: 0.3450980392, alpha: 1)))
                }
                // テキストが空またはチャットが完了していない場合はボタンを無効化
                .disabled(self.text == "" || isCompleting)
            }
            .padding(.horizontal)
            .padding(.bottom, 8) // 下部のパディングを調整
        }
    }
}

// メッセージのビュー
struct MessageView: View {
    var message: ChatMessage
    
    var body: some View {
        HStack {
            if message.role.rawValue == "user" {
                Spacer()
            } else {
                // ユーザーでない場合はアバターを表示
                AvatarView(imageName: "avatar")
                    .padding(.trailing, 8)
            }
            VStack(alignment: .leading, spacing: 4) {
                // メッセージのテキストを表示
                Text(message.content)
                    .font(.system(size: 14)) // フォントサイズを調整
                    .foregroundColor(message.role.rawValue == "user" ? .white : .black)
                    .padding(10)
                    // ユーザーとAIのメッセージで背景色を変更
                    .background(message.role.rawValue == "user" ? Color(#colorLiteral(red: 0.2078431373, green: 0.7647058824, blue: 0.3450980392, alpha: 1)) : Color(#colorLiteral(red: 0.9098039216, green: 0.9098039216, blue: 0.9176470588, alpha: 1)))
                    .cornerRadius(20) // 角を丸くする
            }
            .padding(.vertical, 5)
            // ユーザーのメッセージの場合は右側にスペースを追加
            if message.role.rawValue != "user" {
                Spacer()
            }
        }
        .padding(.horizontal)
    }
}

// アバタービュー
struct AvatarView: View {
    var imageName: String
    
    var body: some View {
        VStack {
            // アバター画像を円形に表示
            Image(systemName: "person.crop.circle")
                .resizable()
                .frame(width: 30, height: 30)
                .clipShape(Circle())
            
            // AIの名前を表示
            Text("AI")
                .font(.caption) // フォントサイズを小さくするためのオプションです。
                .foregroundColor(.black) // テキストの色を黒に設定します。
        }
    }
}


// プレビュー
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Chat()
    }
}

以下に各部分の説明を書きます。

メインビュー(Chat)

ここで作成されるメインのViewはチャットの画面全体を制御します。それぞれのメッセージ(ユーザーのメッセージとAIからのレスポンス)はchat配列に追加され、これらはMessageViewで表示されます。

ScrollView

ScrollViewを用いてメッセージリストを表示しています。ForEachを使用してchat配列内の各メッセージをループしています。そして、各メッセージについてMessageViewを作成しています。

TextField

ここではユーザーからの入力を受け取るテキストフィールドを設定しています。テキストフィールドはisCompletingの値によって無効化されます。これは、AIがレスポンスを生成している間はユーザーからの新たな入力を受け付けないようにするためです。

Button

送信ボタンをクリックしたときに発火するアクションを定義しています。まず、isCompletingtrueに設定してテキストフィールドを無効化し、次にユーザーのメッセージをchat配列に追加します。その後、入力フィールドをクリアし、非同期タスクを開始してAIからのレスポンスを待ちます。

この非同期タスクでは、OpenAIの設定を行い、チャットパラメータを設定しています。これにはモデル名(gpt-4)とchat配列が含まれます。最後に、AIからのレスポンスを取得し、それをchat配列に追加します。

メッセージのビュー(MessageView)

メッセージ送信者がユーザーである場合、メッセージは右寄せになります。テキストは白色で、背景は緑色です。
メッセージ送信者がユーザーでない場合(つまり、AIからのレスポンス)、メッセージは左寄せになります。この場合、テキストは黒色で、背景は灰色です。
このビューはHStackを使用してアバターとメッセージテキストを配置し、送信者がユーザーかどうかによってメッセージの位置を調整します。これにより、ユーザーとAIからのメッセージを視覚的に区別することが可能です。

アバタービュー(AvatarView)

非ユーザーメッセージの隣に表示されるアバター画像を表示するためのビューです。システムイメージ名person.crop.circleを用いてシンプルなアバターを作成しています。
システムイメージはSF Symbolsにて一覧を確認することができます。

OpenAIの設定

今回は以下の部分でAIの設定をしました。

    @State private var chat: [ChatMessage] = [
        ChatMessage(role: .system, content: "あなたは、ユーザーの質問や会話に回答するロボットです。"),
        ChatMessage(role: .assistant, content:"こんにちは。何かお困りのことがあればおっしゃってください。")
    ]

OpenAIのAPIにはroleが必要でsystem,assistant,userがあります。
systemが最初に性格の設定で使用する部分、assistantがAIの返答、userがユーザーの入力したものです。
今回は、メッセージの初めにrole:systemの後のcontentを入れるによりAIの性格を変え、systemでAIの口調を決めています。

また、

let chatParameters = ChatParameters(model: "gpt-4", messages: chat)

にて、言語モデルをgpt-4に指定していますが、gpt-3.5-turboにすることも可能です。
gpt-4は出力の精度は良いものの、ランニングコストがgpt-3.5-turboの約10倍となっているため。、用途により変更する必要があります。

まとめ

この投稿では、SwiftUIとOpenAIのGPT-4を使用してAIチャットアプリを作成する方法について説明しました。このアプリは簡単ながらも、ユーザーとAIとの対話を可能にします。UIはカスタマイズ可能なので、自身のプロジェクトに合わせて必要に応じて修正を加えることができます。

なお、今回のソースコードは基本的なものです。更なる改善や拡張(例えば、エラーハンドリングの追加、UIの改善、ユーザー体験の向上など)を行うことで、より良いアプリケーションを作成することができます。
これからSwiftUIやOpenAIを用いた開発を行う方々にとって、本記事が参考になれば幸いです。

12
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?