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 |