🍎

初心者向け!ChatGPTのAPIを使ったアプリ開発入門

2025/02/08に公開

🚀 本記事について

こんにちは!この記事では iOS初心者向けに「生成AI APIを使ったiOSアプリ開発」 について解説します。
API通信とは何か? から ChatGPT APIを使ったアプリの実装 まで、実際に動かしながら学べます!

🎯 この記事で学べること

✅ APIとは何か、どう使うのか
✅ iOSアプリにAPI通信を実装する方法
ChatGPT API を使ってAIと会話できるアプリを作る

🔹 API通信とは?

APIって何?

API(Application Programming Interface)は、アプリと外部のサービスをつなぐ仕組み です。

身近なAPIの例

  • 天気アプリ → 天気APIを使って、現在の天気情報を取得
  • YouTubeアプリ → YouTube APIを使って、動画リストを取得
  • ChatGPTアプリ → OpenAI APIを使って、AIと会話できる!

「iOSアプリでAPIを使うと、外部の便利な機能を簡単に組み込める!」ということです。

APIのざっくりとした仕組み

APIを使うには 「エンドポイント」 という URL にリクエストを送ります。
リクエスト内容に応じて、レスポンスが返ってきます。

  • リクエスト: 「この質問に答えて!」
  • レスポンス: 「ChatGPTの回答を返す!」

🔹 APIキーとは?

APIを使うには、まずAPIキーを発行する必要があります。

APIキーとは、APIを使うために発行される「パスワード」ようなものです。

ChatGPTのAPIでは、以下の手順で発行する必要があります。

事前にAPIの料金を支払う

以下のURLにアクセスします。(ChatGPTのログインが必須です

https://platform.openai.com/settings/organization/billing/overview

次に以下のページを開いたら、赤字のタブをクリックします。

課金額と支払い方法を選択します。
まずは試してみたい程度であれば、5〜10ドル程度の少額で十分 です。

APIキーの取得

右上の「+ Create new secret key」をクリックします。

APIキーの名前、project、permission(権限)の3つを選択します。
特にこだわりがなければ、以下の設定にしておきましょう。

入力が完了したら、右下の「Create secret key」を押します。

最後に以下のポップアップが表示されたら、APIキーの発行は完了です。
sk-〜〜から始まる文字列は後で使うので、忘れずに「Copy」を押してメモ帳などに保存しておきましょう。

🔹 iOSアプリのプロジェクトを作成

Xcodeを開いてプロジェクトを作成しましょう。
プロジェクトの作成方法がわからない方は以下の記事から確認してください!

https://zenn.dev/fuya777/books/67a40f438c9c56/viewer/4a5bf1

ベースの実装を追加

以下は、ChatGPT APIを使って、回答内容を受け取るためのベースの実装です。

struct ContentView: View {
    @State private var inputText: String = ""
    @State private var chatResponse: String = "ここにAIの返答が表示されます"
    
    var body: some View {
        VStack(spacing: 24) {
            Text(chatResponse)

            TextField("質問を入力...", text: $inputText)
                .textFieldStyle(.roundedBorder)

            Button("送信") {
                // TODO: タップ後の処理
            }
            .buttonStyle(.bordered)
        }
        .padding(20)
    }
}

#Preview {
    ContentView()
}

以下のように表示できたらOKです。

🔹 ChatGPT APIと接続

今回の肝となるChatGPTのAPIと接続する方法を解説します。

fetchAIResponseメソッドの追加

まずは以下の「⭐️追加」の位置にfetchAIResponseメソッドを追加します。

struct ContentView: View {
    @State private var inputText: String = ""
    @State private var chatResponse: String = "ここにAIの返答が表示されます"
    
    var body: some View {
      // 省略しています。
    }

    // ⭐️追加
    func fetchAIResponse() async {
       // 以降で具体的にコードを追加します。
    }
}

fetchAIResponse()の後にasyncというキーワードをつけています。
これはasyncを付けることで非同期処理を行う(ネットワーク通信は時間がかかるため、アプリが止まらないようにする)という意味を指します。

APIキー & エンドポイントの設定

次にfetchAIResponse内に以下のコードを追加します。

func fetchAIResponse() async {
    // ⭐️追加:先ほど設定したAPIキーを設定
    // TODO: ⚠️ GitHubや本番で公開する場合は、直接コードに書かずに適切方法で管理する
    let apiKey = "sk-〜〜〜〜"

    // ⭐️追加:エンドポイントの設定
    let url = URL(string: "https://api.openai.com/v1/chat/completions")!
}

OpenAIのサイトで取得したAPIキーを指定しています。
通常、APIキーはコードに直接書かず、安全に管理する必要があります。
今回は動作確認のために一時的に記述していますが、誤って残さないように コメントを追加して注意 しましょう🙋

// TODO: ⚠️ GitHubや本番で公開する場合は、直接コードに書かずに適切方法で管理する
let apiKey = "sk-〜〜〜〜"

また、urlプロパティの方では、エンドポイントのURL を指定(chat/completions エンドポイントを使用)しています。

送信するリクエストのデータを準備

次に以下のコードを追加します。

func fetchAIResponse() async {
    // TODO: ⚠️ GitHubや本番で公開する場合は、直接コードに書かずに適切方法で管理する
    let apiKey = "sk-〜〜〜〜"
    let url = URL(string: "https://api.openai.com/v1/chat/completions")!

    // ⭐️追加
    let body: [String: Any] = [
        "model": "gpt-3.5-turbo",
        "messages": [["role": "user", "content": inputText]]
    ]
}

"model": "gpt-3.5-turbo"

GPTのバージョンとして gpt-3.5-turbo を指定しています。
他にもさまざまなモデルを選択できるので、以下のサイトを参考に、お好みのモデルを指定してください。

https://platform.openai.com/docs/pricing

"messages": [["role": "user", "content": inputText]]

messagesは会話のやり取りをリスト形式で指定するキーです。

  • role: "user" → ユーザー(アプリ)側で入力した内容であることを明示するもの
  • content: inputText → ユーザーが入力したテキストをAPIに送る

APIリクエストの送信

リクエストの作成

ChatGPTの回答を得るためにAPI通信でリクエストを送る準備をします。

func fetchAIResponse() async {
    // TODO: ⚠️ GitHubや本番で公開する場合は、直接コードに書かずに適切方法で管理する
    let apiKey = "sk-〜〜〜〜"
    let url = URL(string: "https://api.openai.com/v1/chat/completions")!
    let body: [String: Any] = [
        "model": "gpt-3.5-turbo",
        "messages": [["role": "user", "content": inputText]]
    ]

    // ⭐️追加
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try? JSONSerialization.data(withJSONObject: body)
}
  • httpMethod = "POST" → POSTリクエストを送る(データをAPIに送信)
  • Authorization ヘッダーに APIキーをセット(認証)
  • Content-Type を "application/json" に指定(JSONデータを送信するため)
  • リクエストのボディ(データ)をJSON形式に変換してセット

レスポンスの型を作成

API通信を行うと、以下のJSONという形でレスポンスを受け取ることができます。

{
  "choices": [
    {
      "message": {
        "content": "こんにちは!"
      }
    }
  ]
}

このレスポンスをアプリ側で上手く受け取るために、以下のレスポンスの型を定義します。
ContentViewの下に追加しましょう。

struct ContentView: View {
  // 省略
}

struct ChatResponse: Codable {
    let choices: [Choice]

    struct Choice: Codable {
        let message: Message

        struct Message: Codable {
            let content: String
        }
    }
}

非同期でAPIにリクエストを送信

以下の「⭐️追加」部分にコードを追加します。

func fetchAIResponse() async {
    // TODO: ⚠️ GitHubや本番で公開する場合は、直接コードに書かずに適切方法で管理する
    let apiKey = "sk-〜〜〜〜"
    let url = URL(string: "https://api.openai.com/v1/chat/completions")!
    let body: [String: Any] = [
        "model": "gpt-3.5-turbo",
        "messages": [["role": "user", "content": inputText]]
    ]

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try? JSONSerialization.data(withJSONObject: body)

    // ⭐️追加
    do {
        let (data, _) = try await URLSession.shared.data(for: request)
        if let response = try? JSONDecoder().decode(ChatResponse.self, from: data) {
            chatResponse = response.choices.first?.message.content ?? "エラー"
        }
    } catch {
        chatResponse = "通信エラー"
    }
}

順に説明します。

let (data, _) = try await URLSession.shared.data(for: request)
  • URLSession.shared.data(for: request) → 非同期でAPIにリクエストを送信
  • await を使いレスポンスを待つ(非同期処理)
  • (data, _) → APIから返ってきたデータ(JSON)

次にレスポンスをデコードしています。
デコード(Decode) とは、JSONなどのデータ形式をSwiftの構造体(Struct)やクラス(Class)に変換する処理 のことです。

if let response = try? JSONDecoder().decode(ChatResponse.self, from: data) {
    chatResponse = response.choices.first?.message.content ?? "エラー"
}
  • JSONDecoder を使い、APIからのレスポンス(JSON)を Swiftの型に変換
  • 取得した ChatGPT の返答 (choices.first?.message.content) を chatResponse に保存

最後にエラーが発生した時の処理を追加しています。

} catch {
    chatResponse = "通信エラー"
}
  • APIの呼び出しに失敗した場合、エラーメッセージを表示

最後にButtonのTODOコメントの箇所にfetchAIResponseメソッドを追加します。

Button("送信") {
    Task {
        await fetchAIResponse()
    }
}
.buttonStyle(.bordered)

Task {} は Swiftの非同期処理(async処理)を開始するためのブロック です。
SwiftUIのボタン処理など、非同期処理を直接呼び出せない場面で使います。


以下のように動作したら完成です!

回答内容はモデルや指示の仕方によって変わります。

全体のコード
import SwiftUI

struct ContentView: View {
    @State private var inputText: String = ""
    @State private var chatResponse: String = "ここにAIの返答が表示されます"
    
    var body: some View {
        VStack(spacing: 24) {
            Text(chatResponse)

            TextField("質問を入力...", text: $inputText)
                .textFieldStyle(.roundedBorder)

            Button("送信") {
                Task {
                    await fetchAIResponse()
                }
            }
            .buttonStyle(.bordered)
        }
        .padding(20)
    }
    
    func fetchAIResponse() async {
        // TODO: ⚠️ GitHubや本番で公開する場合は、直接コードに書かずに適切方法で管理する
        let apiKey = "sk-〜〜〜〜"
        let url = URL(string: "https://api.openai.com/v1/chat/completions")!
        let body: [String: Any] = [
            "model": "gpt-3.5-turbo",
            "messages": [["role": "user", "content": inputText]]
        ]
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try? JSONSerialization.data(withJSONObject: body)

        do {
            let (data, _) = try await URLSession.shared.data(for: request)
            if let response = try? JSONDecoder().decode(ChatResponse.self, from: data) {
                chatResponse = response.choices.first?.message.content ?? "エラー"
            }
        } catch {
            chatResponse = "通信エラー"
        }
    }
}

struct ChatResponse: Codable {
    let choices: [Choice]

    struct Choice: Codable {
        let message: Message
        
        struct Message: Codable {
            let content: String
        }
    }
}


#Preview {
    ContentView()
}

Discussion