๐คiOS ์ฑ์์ OpenAI api์ฌ์ฉํ๊ธฐ
๋ง์ ์ฑ ๊ฐ๋ฐ์๋ค์ iOS ํ๋ก์ ํธ์์ OpenAI API๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๊ณ ์ ๊ณ ๋ฏผํฉ๋๋ค. ํ์ง๋ง ์ด๋ ์ฝ์ง ์์ ์์ ์ผ ์ ์์ต๋๋ค. ํนํ ์ฑ์ ๋ฐ์ดํฐ ๋ ์ด์ด์ AI API๋ฅผ ํตํฉํ ๋ ์์ง๋์ด๋ง ์์น์ ์ ์ ์ฉํด์ผ ์์ ์ ์ด๊ณ ํจ์จ์ ์ธ ์ฑ์ ๋ง๋ค ์ ์์ต๋๋ค. ์ค๋์ Swift iOS์์ OpenAI API๋ฅผ ์ฌ์ฉํ์ฌ ํ๋กฌํํธ ์์ง๋์ด๋ง์ ํจ๊ณผ์ ์ผ๋ก ์ํํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ณด๋ ค๊ณ ํฉ๋๋ค. ### OpenAI API๋? ๋จผ์ OpenAI API์ ๋ํด ๊ฐ๋ตํ๊ฒ ์ค๋ช ํ๊ฒ ์ต๋๋ค. ์ด๋ ๊ฐ๋ฐ์๋ค์ด OpenAI์ ๊ฐ๋ ฅํ ์ธ๊ณต์ง๋ฅ ๋ชจ๋ธ๋ค์ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ RESTful API์ ๋๋ค. ์ด API๋ ํ ์คํธ ์์ฑ, ์ธ์ด ๋ฒ์ญ, ๊ฐ์ ๋ถ์ ๋ฑ ๋ค์ํ ์์ ์ ์ํํ ์ ์์ต๋๋ค. ### ํ๋กฌํํธ ์์ง๋์ด๋ง์ด๋? ํ๋กฌํํธ ์์ง๋์ด๋ง์ AI ๋ชจ๋ธ์๊ฒ ํน์ ์์ ์ ์ํํ๋๋ก ์ง์ํ๋ ๋ฐฉ๋ฒ์ ์ต์ ํํ๋ ๊ธฐ์ ์ ๋๋ค. ์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก AI์๊ฒ ์ ์ ํ ์ง๋ฌธ์ ๋์ง๊ฑฐ๋, ํน์ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋๋ก ์ค์ ํ ์ ์์ต๋๋ค. ### ํ๋ก์ ํธ์ OpenAI API ์ถ๊ฐํ๊ธฐ ํ๋ก์ ํธ์ OpenAI API๋ฅผ ํตํฉํ๊ธฐ ์ํด์๋ ๋ช ๊ฐ์ง ๋จ๊ณ๊ฐ ํ์ํฉ๋๋ค. ์ด๋ฅผ ํตํด JSON ์๋ต์ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ณ , API ์์ฒญ๊ณผ ์๋ต์ ๋น๋๊ธฐ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ต๋๋ค. #### 1. API ํด๋ผ์ด์ธํธ ์ค์ ์ฐ์ OpenAI API ํด๋ผ์ด์ธํธ๋ฅผ ์ค์ ํ๋ ์ฝ๋์ ๋๋ค. ์ด๋ ๋คํธ์ํฌ ์์ฒญ์ ์ํํ๊ณ , API ์๋ต์ ์ฒ๋ฆฌํ๋ HTTP ํด๋ผ์ด์ธํธ๋ฅผ ์ค์ ํ๋ ๊ณผ์ ์ ๋๋ค. ```swift public class OpenAIClient { ย ย private lazy var httpClient: HTTPClient = { ย ย ย ย URLSessionHTTPClient(session: URLSession(configuration: .ephemeral)) ย ย }() ย ย ย ย ย private lazy var baseURL = URL(string: "")! ย ย private let configuration = URLSessionConfiguration.default ย ย ย ย ย init() { ย ย ย ย self.configuration.timeoutIntervalForRequest = 10.0 ย ย ย ย self.configuration.timeoutIntervalForResource = 10.0 ย ย } ย ย ย ย ย public func sendRequest(endpoint: OpenAIEndpoint) -> AnyPublisher { ย ย ย ย guard let urlRequest = endpoint.url(baseURL: self.baseURL) else { ย ย ย ย ย ย return Empty(completeImmediately: false).eraseToAnyPublisher() ย ย ย ย } ย ย ย ย ย ย ย ย ย return httpClient.getPublisher(urlRequest: urlRequest) ย ย ย ย ย ย .tryMap(OpenAIMapper.map) ย ย ย ย ย ย .eraseToAnyPublisher() ย ย } } ์ด ํด๋์ค๋ OpenAI API์ ๊ธฐ๋ณธ URL๊ณผ ํจ๊ป HTTP ํด๋ผ์ด์ธํธ๋ฅผ ์ค์ ํฉ๋๋ค. API ์์ฒญ์ ๋ํ ํ์์์๋ ์ค์ ํฉ๋๋ค. sendRequest ๋ฉ์๋๋ API ํธ์ถ์ ์ํํ๊ณ , ์๋ต์ Publisher ํํ๋ก ๋ฐํํฉ๋๋ค. 2. ํ๋กฌํํธ ์์ง๋์ด๋ง ํจ์ ๊ตฌ์ฑ ๋ค์์ผ๋ก OpenAI API์ ํ๋กฌํํธ๋ฅผ ์ ์กํ๊ณ ์๋ต์ ์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. public func prompt(text: String, completion: @escaping (Result) -> Void) { ย ย let endpoint = OpenAIEndpoint.prompt(text: text) ย ย ย ย ย self.sendRequest(endpoint: endpoint).sink(receiveCompletion: { finish in ย ย ย ย if case let .failure(error) = finish { ย ย ย ย ย ย completion(.failure(error)) ย ย ย ย } ย ย }, receiveValue: { summary in ย ย ย ย let jsonString = summary.choices.first?.message.content ย ย ย ย guard let content = self.parseContent(jsonString) else { ย ย ย ย ย ย completion(.failure(MyError.invalidResponse)) ย ย ย ย ย ย return ย ย ย ย } ย ย ย ย ย ย ย ย ย let memo = Memo(content: content, createdAt: Date()) ย ย ย ย completion(.success(memo)) ย ย }).store(in: &self.cancellables) } private func parseContent(_ jsonString: String?) -> String? { ย ย guard let jsonString = jsonString, ย ย ย ย ย let data = jsonString.data(using: .utf8), ย ย ย ย ย let parsedContent = try? JSONDecoder().decode(String.self, from: data) else { ย ย ย ย return nil ย ย } ย ย return parsedContent } ์ด ํจ์๋ ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ํ๋กฌํํธ๋ฅผ OpenAI API๋ก ์ ์กํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ Memo๋ผ๋ ๊ฐ์ฒด๋ก ๋ณํํฉ๋๋ค. Memo๋ AI๊ฐ ์์ฑํ ๋ฐ์ดํฐ๋ฅผ ์ฑ์์ ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌํ๊ฒ ๊ตฌ์กฐํํ ๊ฒ์ ๋๋ค. ์ฝ๋ ์ค๋ช ๊ณผ ์ฃผ์ ์ฌํญ ์์ ์ฝ๋๋ค์ API ํธ์ถ๊ณผ JSON ์๋ต ์ฒ๋ฆฌ๋ฅผ ๋งค์ฐ ๊ฐ๋จํ๊ฒ ๋ง๋ค์ด ์ค๋๋ค. ํ์ง๋ง ์ค์ ํ๋ก์ ํธ์์๋ ๋ค์ํ ์์ธ ์ํฉ์ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋คํธ์ํฌ ์ค๋ฅ, ์๋ฒ ์ค๋ฅ, JSON ํ์ฑ ์ค๋ฅ ๋ฑ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. ์ด๋ฌํ ์์ธ ์ํฉ์ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ด ๊ฒฌ๊ณ ํ ์ฑ์ ๋ง๋๋ ๋ฐ ์ค์ํ ์์์ ๋๋ค. ๋ํ ๋น๋๊ธฐ์ ์ผ๋ก API ํธ์ถ์ ๊ด๋ฆฌํ๋ ๊ฒ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ด๋ ์ฃผ ์ค๋ ๋๋ฅผ ์ฐจ๋จํ์ง ์๊ณ , ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋คํธ์ํฌ ์์ฒญ์ ์ฒ๋ฆฌํ๋ฉฐ, ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์์ ๋ UI๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค. ๋ง๋ฌด๋ฆฌ ์ค๋์ Swift iOS ํ๋ก์ ํธ์์ OpenAI API๋ฅผ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ์ฌ ํ๋กฌํํธ ์์ง๋์ด๋ง์ ์ํํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์์ต๋๋ค. ์ฝ๋ ์์ ์ ํจ๊ป ๊ฐ ๋จ๊ณ๋ณ๋ก ์ธ๋ถ ์ฌํญ์ ์ค๋ช ํ์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์์๋ OpenAI API๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ ์ ์์ ๊ฒ์ ๋๋ค. ํ๋กฌํํธ ์์ง๋์ด๋ง๊ณผ OpenAI API์ ์กฐํฉ์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฉฐ, ์ด๋ฅผ ํตํด ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน๋ํํ ์ ์์ต๋๋ค. ์์ผ๋ก๋ ๋ ๋ง์ ๊ธฐ๋ฅ๋ค์ ์ถ๊ฐํ๊ณ , ์ต์ ํํ๋ ์์ ์ ์ง์์ ์ผ๋ก ์ํํด ๋๊ฐ์๊ธฐ ๋ฐ๋๋๋ค. ๋ง์ ์ฑ ๊ฐ๋ฐ์๋ค์ iOS ํ๋ก์ ํธ์์ OpenAI API๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๊ณ ์ ๊ณ ๋ฏผํฉ๋๋ค. ํ์ง๋ง ์ด๋ ์ฝ์ง ์์ ์์ ์ผ ์ ์์ต๋๋ค. ํนํ ์ฑ์ ๋ฐ์ดํฐ ๋ ์ด์ด์ AI API๋ฅผ ํตํฉํ ๋ ์์ง๋์ด๋ง ์์น์ ์ ์ ์ฉํด์ผ ์์ ์ ์ด๊ณ ํจ์จ์ ์ธ ์ฑ์ ๋ง๋ค ์ ์์ต๋๋ค. ์ค๋์ Swift iOS์์ OpenAI API๋ฅผ ์ฌ์ฉํ์ฌ ํ๋กฌํํธ ์์ง๋์ด๋ง์ ํจ๊ณผ์ ์ผ๋ก ์ํํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ณด๋ ค๊ณ ํฉ๋๋ค. ### OpenAI API๋? ๋จผ์ OpenAI API์ ๋ํด ๊ฐ๋ตํ๊ฒ ์ค๋ช ํ๊ฒ ์ต๋๋ค. ์ด๋ ๊ฐ๋ฐ์๋ค์ด OpenAI์ ๊ฐ๋ ฅํ ์ธ๊ณต์ง๋ฅ ๋ชจ๋ธ๋ค์ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ RESTful API์ ๋๋ค. ์ด API๋ ํ ์คํธ ์์ฑ, ์ธ์ด ๋ฒ์ญ, ๊ฐ์ ๋ถ์ ๋ฑ ๋ค์ํ ์์ ์ ์ํํ ์ ์์ต๋๋ค. ### ํ๋กฌํํธ ์์ง๋์ด๋ง์ด๋? ํ๋กฌํํธ ์์ง๋์ด๋ง์ AI ๋ชจ๋ธ์๊ฒ ํน์ ์์ ์ ์ํํ๋๋ก ์ง์ํ๋ ๋ฐฉ๋ฒ์ ์ต์ ํํ๋ ๊ธฐ์ ์ ๋๋ค. ์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก AI์๊ฒ ์ ์ ํ ์ง๋ฌธ์ ๋์ง๊ฑฐ๋, ํน์ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋๋ก ์ค์ ํ ์ ์์ต๋๋ค. ### ํ๋ก์ ํธ์ OpenAI API ์ถ๊ฐํ๊ธฐ ํ๋ก์ ํธ์ OpenAI API๋ฅผ ํตํฉํ๊ธฐ ์ํด์๋ ๋ช ๊ฐ์ง ๋จ๊ณ๊ฐ ํ์ํฉ๋๋ค. ์ด๋ฅผ ํตํด JSON ์๋ต์ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ณ , API ์์ฒญ๊ณผ ์๋ต์ ๋น๋๊ธฐ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ต๋๋ค. ### 1. API ํด๋ผ์ด์ธํธ ์ค์ ์ฐ์ OpenAI API ํด๋ผ์ด์ธํธ๋ฅผ ์ค์ ํ๋ ์ฝ๋์ ๋๋ค. ์ด๋ ๋คํธ์ํฌ ์์ฒญ์ ์ํํ๊ณ , API ์๋ต์ ์ฒ๋ฆฌํ๋ HTTP ํด๋ผ์ด์ธํธ๋ฅผ ์ค์ ํ๋ ๊ณผ์ ์ ๋๋ค. ```swift public class OpenAIClient { ย ย private lazy var httpClient: HTTPClient = { ย ย ย ย URLSessionHTTPClient(session: URLSession(configuration: .ephemeral)) ย ย }() ย ย private lazy var baseURL = URL(string: "")! ย ย private let configuration = URLSessionConfiguration.default ย ย init() { ย ย ย ย self.configuration.timeoutIntervalForRequest = 10.0 ย ย ย ย self.configuration.timeoutIntervalForResource = 10.0 ย ย } ย ย public func sendRequest(endpoint: OpenAIEndpoint) -> AnyPublisher { ย ย ย ย guard let urlRequest = endpoint.url(baseURL: self.baseURL) else { ย ย ย ย ย ย return Empty(completeImmediately: false).eraseToAnyPublisher() ย ย ย ย } ย ย ย ย return httpClient.getPublisher(urlRequest: urlRequest) ย ย ย ย ย ย .tryMap(OpenAIMapper.map) ย ย ย ย ย ย .eraseToAnyPublisher() ย ย } } ์ด ํด๋์ค๋ OpenAI API์ ๊ธฐ๋ณธ URL๊ณผ ํจ๊ป HTTP ํด๋ผ์ด์ธํธ๋ฅผ ์ค์ ํฉ๋๋ค. API ์์ฒญ์ ๋ํ ํ์์์๋ ์ค์ ํฉ๋๋ค. sendRequest ๋ฉ์๋๋ API ํธ์ถ์ ์ํํ๊ณ , ์๋ต์ Publisher ํํ๋ก ๋ฐํํฉ๋๋ค. 2. ํ๋กฌํํธ ์์ง๋์ด๋ง ํจ์ ๊ตฌ์ฑ ๋ค์์ผ๋ก OpenAI API์ ํ๋กฌํํธ๋ฅผ ์ ์กํ๊ณ ์๋ต์ ์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. public func prompt(text: String, completion: @escaping (Result) -> Void) { ย ย let endpoint = OpenAIEndpoint.prompt(text: text) ย ย self.sendRequest(endpoint: endpoint).sink(receiveCompletion: { finish in ย ย ย ย if case let .failure(error) = finish { ย ย ย ย ย ย completion(.failure(error)) ย ย ย ย } ย ย }, receiveValue: { summary in ย ย ย ย let jsonString = summary.choices.first?.message.content ย ย ย ย guard let content = self.parseContent(jsonString) else { ย ย ย ย ย ย completion(.failure(MyError.invalidResponse)) ย ย ย ย ย ย return ย ย ย ย } ย ย ย ย let memo = Memo(content: content, createdAt: Date()) ย ย ย ย completion(.success(memo)) ย ย }).store(in: &self.cancellables) } private func parseContent(_ jsonString: String?) -> String? { ย ย guard let jsonString = jsonString, ย ย ย ย ย let data = jsonString.data(using: .utf8), ย ย ย ย ย let parsedContent = try? JSONDecoder().decode(String.self, from: data) else { ย ย ย ย return nil ย ย } ย ย return parsedContent } ์ด ํจ์๋ ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ํ๋กฌํํธ๋ฅผ OpenAI API๋ก ์ ์กํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ Memo๋ผ๋ ๊ฐ์ฒด๋ก ๋ณํํฉ๋๋ค. Memo๋ AI๊ฐ ์์ฑํ ๋ฐ์ดํฐ๋ฅผ ์ฑ์์ ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌํ๊ฒ ๊ตฌ์กฐํํ ๊ฒ์ ๋๋ค. ์ฝ๋ ์ค๋ช ๊ณผ ์ฃผ์ ์ฌํญ ์์ ์ฝ๋๋ค์ API ํธ์ถ๊ณผ JSON ์๋ต ์ฒ๋ฆฌ๋ฅผ ๋งค์ฐ ๊ฐ๋จํ๊ฒ ๋ง๋ค์ด ์ค๋๋ค. ํ์ง๋ง ์ค์ ํ๋ก์ ํธ์์๋ ๋ค์ํ ์์ธ ์ํฉ์ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋คํธ์ํฌ ์ค๋ฅ, ์๋ฒ ์ค๋ฅ, JSON ํ์ฑ ์ค๋ฅ ๋ฑ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. ์ด๋ฌํ ์์ธ ์ํฉ์ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ด ๊ฒฌ๊ณ ํ ์ฑ์ ๋ง๋๋ ๋ฐ ์ค์ํ ์์์ ๋๋ค. ๋ํ ๋น๋๊ธฐ์ ์ผ๋ก API ํธ์ถ์ ๊ด๋ฆฌํ๋ ๊ฒ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ด๋ ์ฃผ ์ค๋ ๋๋ฅผ ์ฐจ๋จํ์ง ์๊ณ , ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋คํธ์ํฌ ์์ฒญ์ ์ฒ๋ฆฌํ๋ฉฐ, ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์์ ๋ UI๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค. ๋ง๋ฌด๋ฆฌ ์ค๋์ Swift iOS ํ๋ก์ ํธ์์ OpenAI API๋ฅผ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ์ฌ ํ๋กฌํํธ ์์ง๋์ด๋ง์ ์ํํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์์ต๋๋ค. ์ฝ๋ ์์ ์ ํจ๊ป ๊ฐ ๋จ๊ณ๋ณ๋ก ์ธ๋ถ ์ฌํญ์ ์ค๋ช ํ์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์์๋ OpenAI API๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ ์ ์์ ๊ฒ์ ๋๋ค. ํ๋กฌํํธ ์์ง๋์ด๋ง๊ณผ OpenAI API์ ์กฐํฉ์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฉฐ, ์ด๋ฅผ ํตํด ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน๋ํํ ์ ์์ต๋๋ค. ์์ผ๋ก๋ ๋ ๋ง์ ๊ธฐ๋ฅ๋ค์ ์ถ๊ฐํ๊ณ , ์ต์ ํํ๋ ์์ ์ ์ง์์ ์ผ๋ก ์ํํด ๋๊ฐ์๊ธฐ ๋ฐ๋๋๋ค.