概要
本記事では、AppleのUIフレームワークSwiftUIとOpenAIのGPT-4を使って以下のようなAIチャットアプリを作成する方法について説明します。
対象読者
- SwiftUIの基礎ををある程度理解している人
- Swiftの開発環境がすでに構築できている人
- OpenAIのAPIを利用したIOS/Ipadアプリを作成したい人
開発環境
MacOS Ventura(v13.4)
Xcode(v14.3.1)
OpenAIでの各種キー取得
以下のリンクよりOpenAIのAPIキーを取得するページにアクセスし、ログインします。
もしOpenAIのアカウントがない場合はアカウントを作成してください。
その後以下のCreate new secret keyをクリックしAPIキーを作成します。
今回はAPIキーの他にOrganization IDも必要になるためそれも取得します。
以下のページにアクセスし、Organization IDを取得してください。
パッケージの導入
OpenAI APIをSwiftで使用するためにOpenAIKitというパッケージを導入します。
プロジェクトをまだ作成していない場合は、作成します。
その後、File→Add Packagesを選択し右上の検索バーのところに
https://github.com/marcodotio/OpenAIKit
と入力しAdd Packageをクリックします。
パッケージ依存関係の欄にOpenAIKitがされていれば完了です。
ソースコード解説
ソースコードを実行すると以下のようになります。
今回作成したソースコードは以下の通りです。
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
送信ボタンをクリックしたときに発火するアクションを定義しています。まず、isCompleting
をtrue
に設定してテキストフィールドを無効化し、次にユーザーのメッセージを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を用いた開発を行う方々にとって、本記事が参考になれば幸いです。