Chat GPT API를 사용하여 iOS에서 Chat GPT를 사용할 수 있는 앱을 만들 수 있습니다

 

 

Chat GPT란

Chat GPT란 인터넷에 있는 셀 수 없이 많은 데이터들을 기반으로 학습된 모델로 사용자가 질문을 하면 그 질문에 대한 답을 해주는 AI로 머신러닝, 딥러닝을 기반으로 학습된 모델을 사용합니다.

 

오픈AI는 이렇게 학습된 모델을 서버에 올려 모든 사람들이 사용할 수 있게끔 만들어 두었으며 사용자는 https://platform.openai.com/settings/profile?tab=api-keys 링크에 접속하여 https://platform.openai.com/settings/profile?tab=api-keys에서 제공하는 인터페이스로 학습된 모델을 사용만 하면 됩니다.

 

Chat CPT SPM 불러오기

https://github.com/MacPaw/OpenAI 을 가지고 SPM을 추가해줍니다.

 

ChatGPT 사용 준비하기

다음으로는 SPM 을 사용하기 위해서 아래 처럼 OpenAISwift를 추가해줍니다.

import OpenAI

 

다음으로는 API를 사용하기 위한 API 키값을 발급 받아야 합니다. Create an OpenAI API key

간단한 요금제 설명

GPT의 Key를 발급받으면 무한으로 사용할 수 있는 것이 아닙니다. 처음 발급받았을 때는 무료 티어로 제공되며 추가로 5가지의 티어가 존재합니다. 설명 전에 RPM(분당 요청할 수 있는 건수), RPD(해당 날에 사용할 수 있는 토큰 수), TPM(분당 사용할 수 있는 토큰 수)

티어별 제공하는 제공 종류

https://platform.openai.com/docs/guides/rate-limits/usage-tiers?context=tier-free

 

토큰은 언어별로 다르기 때문에 링크를 통해서 확인할 수 있습니다.

  • 1 token ~= 4 알파벳 (ex. have)
  • 1 token ~= ¾ 단어
  • 100 tokens ~= 75 단어
  • 1-2 문장 ~= 30 tokens
  • 1 구문 ~= 100 tokens
  • 1,500 단어 ~= 2048 tokens

 

토큰 설정

만약 발급받았으면 아래 코드를 작성합니다. 위에서 제공되고 있는 openAISwift(authToken:)은 현재 업데이트에 반영되지 않은 코드입니다.

let openAI = OpenAI(apiToken: "발급받은키")

 

 

Chat GPT에 질문하기

이제 질문하고 싶은 것을 ChatQuery로 만들어 openAI 인스턴스의 chat 메서드에 query값을 넣어 요청하게 된다면 요청한 질문에 대한 값을 반환받을 수 있게 됩니다.

let query = ChatQuery(messages: [.init(role: .user, content: "who are you")!], model: .gpt3_5Turbo)
do {
    let result = try await openAI.chats(query: query)
    print(result.choices.description)
} catch {
    print(error.localizedDescription)
}

 

모델은 다음과 같이 여러 종류의 모델을 제공하고 있습니다

  • gpt4
  • gpt4_32k
  • gpt4_0613
  • gpt4_turbo
  • gpt4_32k_0613
  • gpt4_0125_preview
  • gpt4_vision_preview
  • gpt3-5Turbo_16k
  • gpt3_5Turbo_0125
  • gpt3_5Turbo_16k_0613

 

채팅방을 만들어 대화

채팅방을 만들어 질문을 할 수 있습니다, 아래 코드에서는 사용하는 방법만 다루기 때문에 대화 내용을 따로 저장하는 기능은 없습니다.

 

데이터 모델

대화방 모델

GPT와의 대화가 이루어질 대화방을 나타내는 모델입니다.

import Foundation
import OpenAI

struct Conversation: Hashable {
    init(id: String, messages: [Message] = []) {
        self.id = id
        self.messages = messages
    }
    
    typealias ID = String
    
    let id: String
    var messages: [Message]
}

extension Conversation: Equatable, Identifiable {}

 

메시지 모델

GPT와 주고받을 메시지를 나타내는 모델입니다.

import Foundation
import OpenAI

struct Message {
    var id: String
    var role: ChatQuery.ChatCompletionMessageParam.Role
    var content: String
    var createdAt: Date
}

extension Message: Equatable, Codable, Hashable, Identifiable {}

 

GPT를 사용하기 위한 클래스

GPT를 사용하기 위한 질문 요청, 채팅방 만들기 등등을 포함하고 있는 클래스입니다.

import Foundation
import OpenAI
import SwiftUI



public final class ChatGPT: ObservableObject {
    let openAIClient = OpenAI(apiToken: "")


    @Published var conversations: [Conversation] = []
    @Published var conversationErrors: [Conversation.ID: Error] = [:]
    @Published var selectedConversationID: Conversation.ID?

    var selectedConversation: Conversation? {
        selectedConversationID.flatMap { id in
            conversations.first { $0.id == id }
        }
    }

    init(selectedConversationID: Conversation.ID? = nil) {
        self.selectedConversationID = selectedConversationID
    }

    // MARK: - Events
    func createConversation() {
        let conversation = Conversation(id: UUID().uuidString, messages: [])
        conversations.append(conversation)
    }
    
    func selectConversation(_ conversationId: Conversation.ID?) {
        selectedConversationID = conversationId
    }
    
    func deleteConversation(_ conversationId: Conversation.ID) {
        conversations.removeAll(where: { $0.id == conversationId })
    }
    
    @MainActor
    func sendMessage(
        _ message: Message,
        conversationId: Conversation.ID,
        model: Model
    ) async {
        guard let conversationIndex = conversations.firstIndex(where: { $0.id == conversationId }) else {
            print("채팅방의 아이디가 존재하지 않는다")
            return
        }
        conversations[conversationIndex].messages.append(message)

        await completeChat(conversationId: conversationId, model: model)
    }
    
    
    @MainActor
    func completeChat(conversationId: Conversation.ID, model: Model) async {
        guard let conversation = conversations.first(where: { $0.id == conversationId }) else { // 만약 conversationId 대화의 아이디가 존재하는 채팅방들의 id에서 존재하지 않는다면 return
            return
        }
                
        conversationErrors[conversationId] = nil // 문제가 없으면 에러를 nil로 표기

        do {
            guard let conversationIndex = conversations.firstIndex(where: { $0.id == conversationId }) else { // 마지막의 채팅 index 가져오기
                return
            }
            
            let chatsStream: AsyncThrowingStream<ChatStreamResult, Error> = openAIClient.chatsStream(
                query: ChatQuery(
                    messages: conversation.messages.map { message in
                        ChatQuery.ChatCompletionMessageParam(role: message.role, content: message.content)!
                    }, model: model
                )
            )

            for try await partialChatResult in chatsStream {
                for choice in partialChatResult.choices { // 대화 내용(content)을 담고 있는 choices
                    let existingMessages = conversations[conversationIndex].messages

                    let messageText = choice.delta.content ?? ""

                    let message = Message(
                        id: partialChatResult.id,
                        role: choice.delta.role ?? .assistant,
                        content: messageText,
                        createdAt: Date(timeIntervalSince1970: TimeInterval(partialChatResult.created))
                    )
                    // 메시지의 id가 이전거랑 같다면 덮어 씌우기, 왜냐면 단어는 끊어서 여러개로 나눠 전송되기 때문에.
                    if let existingMessageIndex = existingMessages.firstIndex(where: { $0.id == partialChatResult.id }) { 
                        // Meld into previous message
                        let previousMessage = existingMessages[existingMessageIndex]
                        let combinedMessage = Message( // GPT로부터 받은 메시지를 Message로 구조체에 맞춰서 인스턴스 생성
                            id: message.id, // id stays the same for different deltas
                            role: message.role,
                            content: previousMessage.content + message.content,
                            createdAt: message.createdAt
                        )
                        conversations[conversationIndex].messages[existingMessageIndex] = combinedMessage
                    } else {
                        conversations[conversationIndex].messages.append(message) // 같은 메시지 id가 아니라면 새로 추가하기
                    }
                }
            }
        } catch {
            conversationErrors[conversationId] = error
        }
    }
}

 

결과로는 이렇게 대화를 진행할 수 있게 됩니다, 추가로 데이터를 저장하기 위해서는 CoreData 또는 SwiftData를 이용하는 방법도 있겠습니다.

 

'SwiftUI' 카테고리의 다른 글

Subscripts  (0) 2024.05.17
async let 으로 비동기 작업들을 동시에 수행하기 (1)  (0) 2024.05.17
공공 데이터 지도에 띄우기 (with 네이버 지도)  (0) 2024.05.13
custom error  (0) 2024.05.11
custom binding  (0) 2024.05.10
ytw_developer