From 5ddc42d69d32531fc1bcc67f0beceb304f12bde1 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Tue, 9 Apr 2024 20:47:09 +0200 Subject: [PATCH 01/46] refactor: remove dependencies --- {Documentation => Documentations}/CHAT.md | 0 .../COMPLETION.md | 0 .../CORRECTION.md | 0 {Documentation => Documentations}/IMAGE.md | 0 .../SENTIMENT.md | 0 {Documentation => Documentations}/SUMMARY.md | 0 .../TRANSLATION.md | 0 Package.resolved | 23 ------------------- Package.swift | 21 +++++++++-------- 9 files changed, 12 insertions(+), 32 deletions(-) rename {Documentation => Documentations}/CHAT.md (100%) rename {Documentation => Documentations}/COMPLETION.md (100%) rename {Documentation => Documentations}/CORRECTION.md (100%) rename {Documentation => Documentations}/IMAGE.md (100%) rename {Documentation => Documentations}/SENTIMENT.md (100%) rename {Documentation => Documentations}/SUMMARY.md (100%) rename {Documentation => Documentations}/TRANSLATION.md (100%) delete mode 100644 Package.resolved diff --git a/Documentation/CHAT.md b/Documentations/CHAT.md similarity index 100% rename from Documentation/CHAT.md rename to Documentations/CHAT.md diff --git a/Documentation/COMPLETION.md b/Documentations/COMPLETION.md similarity index 100% rename from Documentation/COMPLETION.md rename to Documentations/COMPLETION.md diff --git a/Documentation/CORRECTION.md b/Documentations/CORRECTION.md similarity index 100% rename from Documentation/CORRECTION.md rename to Documentations/CORRECTION.md diff --git a/Documentation/IMAGE.md b/Documentations/IMAGE.md similarity index 100% rename from Documentation/IMAGE.md rename to Documentations/IMAGE.md diff --git a/Documentation/SENTIMENT.md b/Documentations/SENTIMENT.md similarity index 100% rename from Documentation/SENTIMENT.md rename to Documentations/SENTIMENT.md diff --git a/Documentation/SUMMARY.md b/Documentations/SUMMARY.md similarity index 100% rename from Documentation/SUMMARY.md rename to Documentations/SUMMARY.md diff --git a/Documentation/TRANSLATION.md b/Documentations/TRANSLATION.md similarity index 100% rename from Documentation/TRANSLATION.md rename to Documentations/TRANSLATION.md diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 13e3017..0000000 --- a/Package.resolved +++ /dev/null @@ -1,23 +0,0 @@ -{ - "pins" : [ - { - "identity" : "swiftyhttp", - "kind" : "remoteSourceControl", - "location" : "https://github.com/antonio-war/SwiftyHTTP", - "state" : { - "revision" : "a342df5ff99456b38543ad5147c539ed3198cb44", - "version" : "0.1.5" - } - }, - { - "identity" : "swiftyranged", - "kind" : "remoteSourceControl", - "location" : "https://github.com/antonio-war/SwiftyRanged", - "state" : { - "revision" : "8ee2f1b071fe5b06334aeab7555df560e08ae555", - "version" : "1.0.0" - } - } - ], - "version" : 2 -} diff --git a/Package.swift b/Package.swift index ca191ac..f7039ab 100644 --- a/Package.swift +++ b/Package.swift @@ -5,25 +5,28 @@ import PackageDescription let package = Package( name: "SwiftyGPT", - platforms: [.macOS(.v13), .iOS(.v13)], + platforms: [ + .macOS(.v13), + .iOS(.v13) + ], products: [ .library( name: "SwiftyGPT", - targets: ["SwiftyGPT"]), + targets: ["SwiftyGPT"] + ), ], dependencies: [ - .package(url: "https://github.com/antonio-war/SwiftyHTTP", from: "0.1.5"), - .package(url: "https://github.com/antonio-war/SwiftyRanged", from: "1.0.0") ], targets: [ .target( name: "SwiftyGPT", - dependencies: ["SwiftyHTTP", "SwiftyRanged"]), + dependencies: [ + ] + ), .testTarget( name: "SwiftyGPTTests", - dependencies: ["SwiftyGPT", "SwiftyHTTP"], - resources: [ - .copy("Utils/OpenAI-Info.plist") - ]) + dependencies: [ + ] + ) ] ) From 8a1a1cc86655ffe53274fc5de4e4db3259072119 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Tue, 9 Apr 2024 21:01:46 +0200 Subject: [PATCH 02/46] refactor: remove deprecated code --- Sources/SwiftyGPT/Chat/SwiftyGPT+Chat.swift | 88 ----------- .../SwiftyGPT/Chat/SwiftyGPTChatMessage.swift | 30 ---- .../SwiftyGPT/Chat/SwiftyGPTChatModel.swift | 38 ----- .../SwiftyGPT/Chat/SwiftyGPTChatRequest.swift | 33 ---- .../Chat/SwiftyGPTChatResponse.swift | 30 ---- .../SwiftyGPT/Chat/SwiftyGPTChatRole.swift | 14 -- .../Completion/SwiftyGPT+Completion.swift | 55 ------- .../Completion/SwiftyGPTCompletionModel.swift | 51 ------ .../SwiftyGPTCompletionRequest.swift | 39 ----- .../SwiftyGPTCompletionResponse.swift | 32 ---- .../Correction/SwiftyGPT+Correction.swift | 42 ----- Sources/SwiftyGPT/Image/SwiftyGPT+Image.swift | 145 ----------------- .../Image/SwiftyGPTImageRequest.swift | 38 ----- .../Image/SwiftyGPTImageResponse.swift | 28 ---- .../Networking/SwiftyGPTRouter.swift | 67 -------- .../SwiftyGPT+SentimentAnalysis.swift | 42 ----- .../Sentiment/SwiftyGPTSentiment.swift | 25 --- .../SwiftyGPT/Summary/SwiftyGPT+Summary.swift | 42 ----- Sources/SwiftyGPT/SwiftyGPT.swift | 19 --- .../Translation/SwiftyGPT+Translation.swift | 43 ----- Sources/SwiftyGPT/Utils/SwiftyGPTError.swift | 31 ---- .../SwiftyGPT/Utils/SwiftyGPTLanguage.swift | 147 ------------------ Sources/SwiftyGPT/Utils/SwiftyGPTModel.swift | 14 -- .../SwiftyGPT/Utils/SwiftyGPTRequest.swift | 22 --- .../SwiftyGPT/Utils/SwiftyGPTResponse.swift | 46 ------ .../SwiftyGPT/Utils/SwiftyGPTTokenizer.swift | 27 ---- Tests/SwiftyGPTTests/SwiftyGPTChatTests.swift | 135 ---------------- .../SwiftyGPTCompletionTests.swift | 45 ------ .../SwiftyGPTTests/SwiftyGPTErrorTests.swift | 40 ----- .../SwiftyGPTTests/SwiftyGPTImageTests.swift | 78 ---------- .../SwiftyGPTSentimentTests.swift | 44 ------ .../SwiftyGPTSummaryTests.swift | 54 ------- .../SwiftyGPTTokenizerTest.swift | 33 ---- .../SwiftyGPTTranslationTests.swift | 44 ------ .../SwiftyGTPCorrectionTests.swift | 44 ------ Tests/SwiftyGPTTests/Utils/OpenAI-Info.plist | 8 - .../Utils/SwiftyGPTSecureTest.swift | 23 --- .../Utils/SwiftyGPTTestCase.swift | 24 --- 38 files changed, 1760 deletions(-) delete mode 100644 Sources/SwiftyGPT/Chat/SwiftyGPT+Chat.swift delete mode 100644 Sources/SwiftyGPT/Chat/SwiftyGPTChatMessage.swift delete mode 100644 Sources/SwiftyGPT/Chat/SwiftyGPTChatModel.swift delete mode 100644 Sources/SwiftyGPT/Chat/SwiftyGPTChatRequest.swift delete mode 100644 Sources/SwiftyGPT/Chat/SwiftyGPTChatResponse.swift delete mode 100644 Sources/SwiftyGPT/Chat/SwiftyGPTChatRole.swift delete mode 100644 Sources/SwiftyGPT/Completion/SwiftyGPT+Completion.swift delete mode 100644 Sources/SwiftyGPT/Completion/SwiftyGPTCompletionModel.swift delete mode 100644 Sources/SwiftyGPT/Completion/SwiftyGPTCompletionRequest.swift delete mode 100644 Sources/SwiftyGPT/Completion/SwiftyGPTCompletionResponse.swift delete mode 100644 Sources/SwiftyGPT/Correction/SwiftyGPT+Correction.swift delete mode 100644 Sources/SwiftyGPT/Image/SwiftyGPT+Image.swift delete mode 100644 Sources/SwiftyGPT/Image/SwiftyGPTImageRequest.swift delete mode 100644 Sources/SwiftyGPT/Image/SwiftyGPTImageResponse.swift delete mode 100644 Sources/SwiftyGPT/Networking/SwiftyGPTRouter.swift delete mode 100644 Sources/SwiftyGPT/Sentiment/SwiftyGPT+SentimentAnalysis.swift delete mode 100644 Sources/SwiftyGPT/Sentiment/SwiftyGPTSentiment.swift delete mode 100644 Sources/SwiftyGPT/Summary/SwiftyGPT+Summary.swift delete mode 100644 Sources/SwiftyGPT/SwiftyGPT.swift delete mode 100644 Sources/SwiftyGPT/Translation/SwiftyGPT+Translation.swift delete mode 100644 Sources/SwiftyGPT/Utils/SwiftyGPTError.swift delete mode 100644 Sources/SwiftyGPT/Utils/SwiftyGPTLanguage.swift delete mode 100644 Sources/SwiftyGPT/Utils/SwiftyGPTModel.swift delete mode 100644 Sources/SwiftyGPT/Utils/SwiftyGPTRequest.swift delete mode 100644 Sources/SwiftyGPT/Utils/SwiftyGPTResponse.swift delete mode 100644 Sources/SwiftyGPT/Utils/SwiftyGPTTokenizer.swift delete mode 100644 Tests/SwiftyGPTTests/SwiftyGPTChatTests.swift delete mode 100644 Tests/SwiftyGPTTests/SwiftyGPTCompletionTests.swift delete mode 100644 Tests/SwiftyGPTTests/SwiftyGPTErrorTests.swift delete mode 100644 Tests/SwiftyGPTTests/SwiftyGPTImageTests.swift delete mode 100644 Tests/SwiftyGPTTests/SwiftyGPTSentimentTests.swift delete mode 100644 Tests/SwiftyGPTTests/SwiftyGPTSummaryTests.swift delete mode 100644 Tests/SwiftyGPTTests/SwiftyGPTTokenizerTest.swift delete mode 100644 Tests/SwiftyGPTTests/SwiftyGPTTranslationTests.swift delete mode 100644 Tests/SwiftyGPTTests/SwiftyGTPCorrectionTests.swift delete mode 100644 Tests/SwiftyGPTTests/Utils/OpenAI-Info.plist delete mode 100644 Tests/SwiftyGPTTests/Utils/SwiftyGPTSecureTest.swift delete mode 100644 Tests/SwiftyGPTTests/Utils/SwiftyGPTTestCase.swift diff --git a/Sources/SwiftyGPT/Chat/SwiftyGPT+Chat.swift b/Sources/SwiftyGPT/Chat/SwiftyGPT+Chat.swift deleted file mode 100644 index 4dcb3d5..0000000 --- a/Sources/SwiftyGPT/Chat/SwiftyGPT+Chat.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// SwiftyGPT+Chat.swift -// -// -// Created by Antonio Guerra on 30/03/23. -// - -import Foundation -import SwiftyHTTP -import SwiftyRanged - -// MARK: - Chat -extension SwiftyGPT { - - public func chat(messages: [SwiftyGPTChatMessage], model: SwiftyGPTChatModel = .stable, @SwiftyOptionalRanged(0...2) temperature: Float? = nil, choices: Int? = nil, maxTokens: Int? = nil, @SwiftyOptionalRanged(-2...2) presencePenalty: Float? = nil, @SwiftyOptionalRanged(-2...2) frequencyPenalty: Float? = nil, user: String? = nil, completion: @escaping (Result) -> ()) { - - let request = SwiftyGPTChatRequest(messages: messages, model: model, temperature: temperature, choices: choices, stream: false, maxTokens: maxTokens, presencePenalty: presencePenalty, frequencyPenalty: frequencyPenalty, user: user) - SwiftyHTTP.request(SwiftyGPTRouter.chat(apiKey, request)) { result in - switch result { - case .success(let response): - if response.statusCode == 200 { - guard let body = try? JSONDecoder().decode(SwiftyGPTChatResponse.self, from: response.body) else { - completion(.failure(URLError(.badServerResponse))) - return - } - completion(.success(body)) - } else { - guard let error = try? JSONDecoder().decode(SwiftyGPTError.self, from: response.body) else { - completion(.failure(URLError(.badServerResponse))) - return - } - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func chat(messages: [SwiftyGPTChatMessage], model: SwiftyGPTChatModel = .stable, @SwiftyOptionalRanged(0...2) temperature: Float? = nil, choices: Int? = nil, maxTokens: Int? = nil, @SwiftyOptionalRanged(-2...2) presencePenalty: Float? = nil, @SwiftyOptionalRanged(-2...2) frequencyPenalty: Float? = nil, user: String? = nil) async -> Result { - - return await withCheckedContinuation { continuation in - chat(messages: messages, model: model, temperature: temperature, choices: choices, maxTokens: maxTokens, presencePenalty: presencePenalty, frequencyPenalty: frequencyPenalty, user: user) { result in - continuation.resume(returning: result) - } - } - } - - public func chat(message: SwiftyGPTChatMessage, model: SwiftyGPTChatModel = .stable, @SwiftyOptionalRanged(0...2) temperature: Float? = nil, choices: Int? = nil, maxTokens: Int? = nil, @SwiftyOptionalRanged(-2...2) presencePenalty: Float? = nil, @SwiftyOptionalRanged(-2...2) frequencyPenalty: Float? = nil, user: String? = nil, completion: @escaping (Result) -> ()) { - chat(messages: [message], model: model, temperature: temperature, choices: choices, maxTokens: maxTokens, presencePenalty: presencePenalty, frequencyPenalty: frequencyPenalty, user: user, completion: completion) - } - - public func chat(message: SwiftyGPTChatMessage, model: SwiftyGPTChatModel = .stable, @SwiftyOptionalRanged(0...2) temperature: Float? = nil, choices: Int? = nil, maxTokens: Int? = nil, @SwiftyOptionalRanged(-2...2) presencePenalty: Float? = nil, @SwiftyOptionalRanged(-2...2) frequencyPenalty: Float? = nil, user: String? = nil) async -> Result { - - await chat(messages: [message], model: model, temperature: temperature, choices: choices, maxTokens: maxTokens, presencePenalty: presencePenalty, frequencyPenalty: frequencyPenalty, user: user) - } - - public func chat(messages: [String], model: SwiftyGPTChatModel = .stable, user: String? = nil, completion: @escaping (Result) -> ()) { - chat(messages: messages.map({SwiftyGPTChatMessage(content: $0)}), model: model, user: user) { result in - switch result { - case .success(let response): - guard let message = response.choices.first?.message else { - completion(.failure(URLError(.badServerResponse))) - return - } - completion(.success(message.content)) - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func chat(messages: [String], model: SwiftyGPTChatModel = .stable, user: String? = nil) async -> Result { - return await withCheckedContinuation { continuation in - chat(messages: messages, model: model, user: user) { result in - continuation.resume(returning: result) - } - } - } - - public func chat(message: String, model: SwiftyGPTChatModel = .stable, user: String? = nil, completion: @escaping (Result) -> ()) { - chat(messages: [message], model: model, user: user, completion: completion) - } - - public func chat(message: String, model: SwiftyGPTChatModel = .stable, user: String? = nil) async -> Result { - await chat(messages: [message], model: model, user: user) - } -} diff --git a/Sources/SwiftyGPT/Chat/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPT/Chat/SwiftyGPTChatMessage.swift deleted file mode 100644 index ce03e47..0000000 --- a/Sources/SwiftyGPT/Chat/SwiftyGPTChatMessage.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// SwiftyGPTChatMessage.swift -// -// -// Created by Antonio Guerra on 27/03/23. -// - -import Foundation - -public struct SwiftyGPTChatMessage: Codable, Identifiable { - public let id: UUID = UUID() - public let date: Date = Date() - public let role: SwiftyGPTChatRole - public let content: String - - enum CodingKeys: String, CodingKey { - case role - case content - } - - public init(role: SwiftyGPTChatRole, content: String) { - self.role = role - self.content = content - } - - public init(content: String) { - self.role = .user - self.content = content - } -} diff --git a/Sources/SwiftyGPT/Chat/SwiftyGPTChatModel.swift b/Sources/SwiftyGPT/Chat/SwiftyGPTChatModel.swift deleted file mode 100644 index cf4b9eb..0000000 --- a/Sources/SwiftyGPT/Chat/SwiftyGPTChatModel.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// SwiftyGPTChatModel.swift -// -// -// Created by Antonio Guerra on 27/03/23. -// - -import Foundation - -public enum SwiftyGPTChatModel: String, SwiftyGPTModel { - case gpt4 = "gpt-4" - case gpt4_0314 = "gpt-4-0314" - case gpt4_32k = "gpt-4-32k" - case gpt4_32k_0314 = "gpt-4-32k-0314" - case gpt3_5_turbo = "gpt-3.5-turbo" - case gpt3_5_turbo_0301 = "gpt-3.5-turbo-0301" - - public static var stable: SwiftyGPTChatModel { - gpt3_5_turbo - } - - var maxTokens: Int { - switch self { - case .gpt4: - return 8_192 - case .gpt4_0314: - return 8_192 - case .gpt4_32k: - return 8_192 - case .gpt4_32k_0314: - return 8_192 - case .gpt3_5_turbo: - return 4_096 - case .gpt3_5_turbo_0301: - return 4_096 - } - } -} diff --git a/Sources/SwiftyGPT/Chat/SwiftyGPTChatRequest.swift b/Sources/SwiftyGPT/Chat/SwiftyGPTChatRequest.swift deleted file mode 100644 index 2de2c8b..0000000 --- a/Sources/SwiftyGPT/Chat/SwiftyGPTChatRequest.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// SwiftyGPTChatRequest.swift -// -// -// Created by Antonio Guerra on 27/03/23. -// - -import Foundation -import SwiftyHTTP - -public struct SwiftyGPTChatRequest: SwiftyGPTRequest { - public let messages: [SwiftyGPTChatMessage] - public let model: SwiftyGPTChatModel - public let temperature: Float? - public let choices: Int? - public let stream: Bool? - public let maxTokens: Int? - public let presencePenalty: Float? - public let frequencyPenalty: Float? - public let user: String? - - enum CodingKeys: String, CodingKey { - case model - case messages - case temperature - case choices = "n" - case stream - case maxTokens = "max_tokens" - case presencePenalty = "presence_penalty" - case frequencyPenalty = "frequency_penalty" - case user - } -} diff --git a/Sources/SwiftyGPT/Chat/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPT/Chat/SwiftyGPTChatResponse.swift deleted file mode 100644 index c2dd251..0000000 --- a/Sources/SwiftyGPT/Chat/SwiftyGPTChatResponse.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// SwiftyGPTChatResponse.swift -// -// -// Created by Antonio Guerra on 27/03/23. -// - -import Foundation -import SwiftyHTTP - -public struct SwiftyGPTChatResponse: SwiftyGPTResponse { - public let id, object: String - public let created: TimeInterval - public let model: SwiftyGPTChatModel - public let usage: SwiftyGPTUsage - public let choices: [SwiftyGPTChatChoice] -} - -// MARK: - Choice -public struct SwiftyGPTChatChoice: SwiftyGPTChoice { - public let finishReason: SwiftyGPTFinishReason - public let index: Int - public let message: SwiftyGPTChatMessage - - enum CodingKeys: String, CodingKey { - case message - case finishReason = "finish_reason" - case index - } -} diff --git a/Sources/SwiftyGPT/Chat/SwiftyGPTChatRole.swift b/Sources/SwiftyGPT/Chat/SwiftyGPTChatRole.swift deleted file mode 100644 index aa869ec..0000000 --- a/Sources/SwiftyGPT/Chat/SwiftyGPTChatRole.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// SwiftyGPTChatRole.swift -// -// -// Created by Antonio Guerra on 11/04/23. -// - -import Foundation - -public enum SwiftyGPTChatRole: String, Codable { - case system - case user - case assistant -} diff --git a/Sources/SwiftyGPT/Completion/SwiftyGPT+Completion.swift b/Sources/SwiftyGPT/Completion/SwiftyGPT+Completion.swift deleted file mode 100644 index 40ab522..0000000 --- a/Sources/SwiftyGPT/Completion/SwiftyGPT+Completion.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// SwiftyGPT+Completion.swift -// -// -// Created by Antonio Guerra on 11/04/23. -// - -import Foundation -import SwiftyHTTP -import SwiftyRanged - -// MARK: - Completion -extension SwiftyGPT { - - public func completion(prompt: String, model: SwiftyGPTCompletionModel = .stable, suffix: String? = nil, maxTokens: Int? = nil, @SwiftyOptionalRanged(0...2) temperature: Float? = nil, choices: Int? = nil, @SwiftyOptionalRanged(1...5) logprobs: Int? = nil, echo: Bool? = nil, @SwiftyOptionalRanged(-2...2) presencePenalty: Float? = nil, @SwiftyOptionalRanged(-2...2) frequencyPenalty: Float? = nil, user: String? = nil, completion: @escaping (Result) -> ()) { - - let lowerBound = SwiftyGPTTokenizer().tokenize(prompt) - let upperBound = model.maxTokens-lowerBound - @SwiftyRanged(lowerBound...upperBound) var maxTokens = maxTokens ?? upperBound - - let request = SwiftyGPTCompletionRequest(prompt: prompt, model: model, suffix: suffix, maxTokens: maxTokens, temperature: temperature, choices: choices, stream: false, logprobs: logprobs, echo: echo, presencePenalty: presencePenalty, frequencyPenalty: frequencyPenalty, user: user) - SwiftyHTTP.request(SwiftyGPTRouter.completion(apiKey, request)) { result in - switch result { - case .success(let response): - if response.statusCode == 200 { - guard let body = try? JSONDecoder().decode(SwiftyGPTCompletionResponse.self, from: response.body) else { - completion(.failure(URLError(.badServerResponse))) - return - } - - let formattedBody = SwiftyGPTCompletionResponse(id: body.id, object: body.object, created: body.created, usage: body.usage, model: body.model, choices: body.choices.map { SwiftyGPTCompletionChoice(finishReason: $0.finishReason, index: $0.index, text: $0.text.trimmingCharacters(in: .whitespacesAndNewlines), logprobs: $0.logprobs)}) - - completion(.success(formattedBody)) - } else { - guard let error = try? JSONDecoder().decode(SwiftyGPTError.self, from: response.body) else { - completion(.failure(URLError(.badServerResponse))) - return - } - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func completion(prompt: String, model: SwiftyGPTCompletionModel = .stable, suffix: String? = nil, maxTokens: Int? = nil, @SwiftyOptionalRanged(0...2) temperature: Float? = nil, choices: Int? = nil, @SwiftyOptionalRanged(1...5) logprobs: Int? = nil, echo: Bool? = nil, @SwiftyOptionalRanged(-2...2) presencePenalty: Float? = nil, @SwiftyOptionalRanged(-2...2) frequencyPenalty: Float? = nil, user: String? = nil) async -> Result { - - return await withCheckedContinuation { continuation in - completion(prompt: prompt, model: model, suffix: suffix, maxTokens: maxTokens, temperature: temperature, choices: choices, logprobs: logprobs, echo: echo, presencePenalty: presencePenalty, frequencyPenalty: frequencyPenalty, user: user) { result in - continuation.resume(returning: result) - } - } - } -} diff --git a/Sources/SwiftyGPT/Completion/SwiftyGPTCompletionModel.swift b/Sources/SwiftyGPT/Completion/SwiftyGPTCompletionModel.swift deleted file mode 100644 index 7b126cd..0000000 --- a/Sources/SwiftyGPT/Completion/SwiftyGPTCompletionModel.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// SwiftyGPTCompletionModel.swift -// -// -// Created by Antonio Guerra on 11/04/23. -// - -import Foundation - -public enum SwiftyGPTCompletionModel: String, SwiftyGPTModel { - - case ada - case text_ada_001 = "text-ada-001" - - case babbage - case text_babbage_001 = "text-babbage-001" - - case curie - case text_curie_001 = "text-curie-001" - - case davinci - case text_davinci_002 = "text-davinci-002" - case text_davinci_003 = "text-davinci-003" - - public static var stable: SwiftyGPTCompletionModel { - .text_davinci_003 - } - - var maxTokens: Int { - switch self { - case .ada: - return 2_048 - case .text_ada_001: - return 2_048 - case .babbage: - return 2_048 - case .text_babbage_001: - return 2_048 - case .curie: - return 2_048 - case .text_curie_001: - return 2_048 - case .davinci: - return 2_048 - case .text_davinci_002: - return 4_096 - case .text_davinci_003: - return 4_094 - } - } -} diff --git a/Sources/SwiftyGPT/Completion/SwiftyGPTCompletionRequest.swift b/Sources/SwiftyGPT/Completion/SwiftyGPTCompletionRequest.swift deleted file mode 100644 index c763a90..0000000 --- a/Sources/SwiftyGPT/Completion/SwiftyGPTCompletionRequest.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// SwiftyGPTCompletionRequest.swift -// -// -// Created by Antonio Guerra on 11/04/23. -// - -import Foundation -import SwiftyHTTP - -public struct SwiftyGPTCompletionRequest: SwiftyGPTRequest { - public let prompt: String - public let model: SwiftyGPTCompletionModel - public let suffix: String? - public let maxTokens: Int? - public let temperature: Float? - public let choices: Int? - public let stream: Bool? - public let logprobs: Int? - public let echo: Bool? - public let presencePenalty: Float? - public let frequencyPenalty: Float? - public let user: String? - - enum CodingKeys: String, CodingKey { - case prompt - case model - case suffix - case maxTokens = "max_tokens" - case temperature - case choices = "n" - case stream - case logprobs - case echo - case presencePenalty = "presence_penalty" - case frequencyPenalty = "frequency_penalty" - case user - } -} diff --git a/Sources/SwiftyGPT/Completion/SwiftyGPTCompletionResponse.swift b/Sources/SwiftyGPT/Completion/SwiftyGPTCompletionResponse.swift deleted file mode 100644 index 31682f6..0000000 --- a/Sources/SwiftyGPT/Completion/SwiftyGPTCompletionResponse.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// SwiftyGPTCompletionResponse.swift -// -// -// Created by Antonio Guerra on 11/04/23. -// - -import Foundation - -public struct SwiftyGPTCompletionResponse: SwiftyGPTResponse { - public let id: String - public let object: String - public let created: TimeInterval - public let usage: SwiftyGPTUsage - public let model: SwiftyGPTCompletionModel - public let choices: [SwiftyGPTCompletionChoice] -} - -// MARK: - Choice -public struct SwiftyGPTCompletionChoice: SwiftyGPTChoice { - public let finishReason: SwiftyGPTFinishReason - public let index: Int - public let text: String - public let logprobs: Int? - - enum CodingKeys: String, CodingKey { - case text - case finishReason = "finish_reason" - case index - case logprobs - } -} diff --git a/Sources/SwiftyGPT/Correction/SwiftyGPT+Correction.swift b/Sources/SwiftyGPT/Correction/SwiftyGPT+Correction.swift deleted file mode 100644 index f33cc10..0000000 --- a/Sources/SwiftyGPT/Correction/SwiftyGPT+Correction.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// SwiftyGPT+Correction.swift -// -// -// Created by Antonio Guerra on 13/04/23. -// - -import Foundation - -extension SwiftyGPT { - - public func correction(text: String, language: SwiftyGPTLanguage? = nil, user: String? = nil, completion: @escaping (Result) -> ()) { - self.completion(prompt: prompt(text: text, language: language), model: .text_davinci_003, temperature: 0.0, presencePenalty: 0.0, frequencyPenalty: 0.0, user: user) { result in - switch result { - case .success(let response): - if let text = response.choices.first?.text { - completion(.success(text)) - } else { - completion(.failure(URLError(.badServerResponse))) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func correction(text: String, language: SwiftyGPTLanguage? = nil, user: String? = nil) async -> Result { - return await withCheckedContinuation { continuation in - correction(text: text, language: language, user: user) { result in - continuation.resume(returning: result) - } - } - } - - private func prompt(text: String, language: SwiftyGPTLanguage? = nil) -> String { - let text = text.trimmingCharacters(in: .whitespacesAndNewlines) - guard let language = language?.rawValue.capitalized else { - return "Correct this text: \n\(text)" - } - return "Correct this text to standard \(language): \n\(text)" - } -} diff --git a/Sources/SwiftyGPT/Image/SwiftyGPT+Image.swift b/Sources/SwiftyGPT/Image/SwiftyGPT+Image.swift deleted file mode 100644 index 6803c4e..0000000 --- a/Sources/SwiftyGPT/Image/SwiftyGPT+Image.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// SwiftyGPT+Image.swift -// -// -// Created by Antonio Guerra on 30/03/23. -// - -import Foundation -import SwiftyHTTP -import SwiftyRanged - -// MARK: - Image with UIKit - -#if canImport(UIKit) - -import UIKit - -extension SwiftyGPT { - - public func image(prompt: String, @SwiftyRanged(1...10) choices: Int, size: SwiftyGPTImageSize? = nil, user: String? = nil, completion: @escaping (Result<[UIImage], Error>) -> ()) { - - let request = SwiftyGPTImageRequest(prompt: prompt, choices: choices, size: size, responseFormat: .b64, user: user) - SwiftyHTTP.request(SwiftyGPTRouter.image(apiKey, request)) { result in - switch result { - case .success(let response): - if response.statusCode == 200 { - guard let body = try? JSONDecoder().decode(SwiftyGPTImageResponse.self, from: response.body) else { - completion(.failure(URLError(.badServerResponse))) - return - } - completion(.success(body.data.compactMap({ Data(base64Encoded: $0.b64) }).compactMap({ UIImage(data: $0)}))) - } else { - guard let error = try? JSONDecoder().decode(SwiftyGPTError.self, from: response.body) else { - completion(.failure(URLError(.badServerResponse))) - return - } - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func image(prompt: String, @SwiftyRanged(1...10) choices: Int, size: SwiftyGPTImageSize? = nil, user: String? = nil) async -> Result<[UIImage], Error> { - - return await withCheckedContinuation { continuation in - image(prompt: prompt, choices: choices, size: size, user: user) { result in - continuation.resume(returning: result) - } - } - } - - public func image(prompt: String, size: SwiftyGPTImageSize? = nil, user: String? = nil, completion: @escaping (Result) -> ()) { - image(prompt: prompt, choices: 1, size: size, user: user) { result in - switch result { - case .success(let images): - if let image = images.first { - completion(.success(image)) - } else { - completion(.failure(URLError(.badURL))) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func image(prompt: String, size: SwiftyGPTImageSize? = nil, user: String? = nil) async -> Result { - return await withCheckedContinuation { continuation in - image(prompt: prompt, size: size, user: user) { result in - continuation.resume(returning: result) - } - } - } - -} - -// MARK: - Image with AppKit - -#elseif canImport(AppKit) - -import AppKit - -extension SwiftyGPT { - - public func image(prompt: String, @SwiftyRanged(1...10) choices: Int, size: SwiftyGPTImageSize? = nil, user: String? = nil, completion: @escaping (Result<[NSImage], Error>) -> ()) { - - let request = SwiftyGPTImageRequest(prompt: prompt, choices: choices, size: size, responseFormat: .b64, user: user) - SwiftyHTTP.request(SwiftyGPTRouter.image(apiKey, request)) { result in - switch result { - case .success(let response): - if response.statusCode == 200 { - guard let body = try? JSONDecoder().decode(SwiftyGPTImageResponse.self, from: response.body) else { - completion(.failure(URLError(.badServerResponse))) - return - } - completion(.success(body.data.compactMap({ Data(base64Encoded: $0.b64) }).compactMap({ NSImage(data: $0) }))) - } else { - guard let error = try? JSONDecoder().decode(SwiftyGPTError.self, from: response.body) else { - completion(.failure(URLError(.badServerResponse))) - return - } - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func image(prompt: String, @SwiftyRanged(1...10) choices: Int, size: SwiftyGPTImageSize? = nil, user: String? = nil) async -> Result<[NSImage], Error> { - - return await withCheckedContinuation { continuation in - image(prompt: prompt, choices: choices, size: size, user: user) { result in - continuation.resume(returning: result) - } - } - } - - public func image(prompt: String, size: SwiftyGPTImageSize? = nil, user: String? = nil, completion: @escaping (Result) -> ()) { - image(prompt: prompt, choices: 1, size: size, user: user) { result in - switch result { - case .success(let images): - if let image = images.first { - completion(.success(image)) - } else { - completion(.failure(URLError(.badURL))) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func image(prompt: String, size: SwiftyGPTImageSize? = nil, user: String? = nil) async -> Result { - return await withCheckedContinuation { continuation in - image(prompt: prompt, size: size, user: user) { result in - continuation.resume(returning: result) - } - } - } -} - -#endif diff --git a/Sources/SwiftyGPT/Image/SwiftyGPTImageRequest.swift b/Sources/SwiftyGPT/Image/SwiftyGPTImageRequest.swift deleted file mode 100644 index 67b83a9..0000000 --- a/Sources/SwiftyGPT/Image/SwiftyGPTImageRequest.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// SwiftyGPTImageRequest.swift -// -// -// Created by Antonio Guerra on 30/03/23. -// - -import Foundation -import SwiftyHTTP - -public struct SwiftyGPTImageRequest: SwiftyHTTPRequestBody { - public let prompt: String - public let choices: Int? - public let size: SwiftyGPTImageSize? - public let responseFormat: SwiftyGPTImageResponseFormat? - public let user: String? - - enum CodingKeys: String, CodingKey { - case prompt - case choices = "n" - case size - case responseFormat = "response_format" - case user - } -} - -// MARK: - ImageSize -public enum SwiftyGPTImageSize: String, Encodable { - case x256 = "256x256" - case x512 = "512x512" - case x1024 = "1024x1024" -} - -// MARK: - ResponseFormat -public enum SwiftyGPTImageResponseFormat: String, Codable { - case url = "url" - case b64 = "b64_json" -} diff --git a/Sources/SwiftyGPT/Image/SwiftyGPTImageResponse.swift b/Sources/SwiftyGPT/Image/SwiftyGPTImageResponse.swift deleted file mode 100644 index 3bea4a6..0000000 --- a/Sources/SwiftyGPT/Image/SwiftyGPTImageResponse.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// SwiftyGPTImageResponse.swift -// -// -// Created by Antonio Guerra on 30/03/23. -// - -import Foundation -import SwiftyHTTP - -struct SwiftyGPTImageResponse: SwiftyHTTPResponseBody { - let created: TimeInterval - let data: [ResponseData] -} - -protocol SwiftyGPTImageResponseData: Decodable {} - -struct SwiftyGTPImageURLResponseData: SwiftyGPTImageResponseData { - let url: String -} - -struct SwiftyGPTB64ResponseData: SwiftyGPTImageResponseData { - let b64: String - - enum CodingKeys: String, CodingKey { - case b64 = "b64_json" - } -} diff --git a/Sources/SwiftyGPT/Networking/SwiftyGPTRouter.swift b/Sources/SwiftyGPT/Networking/SwiftyGPTRouter.swift deleted file mode 100644 index 6f9b9ed..0000000 --- a/Sources/SwiftyGPT/Networking/SwiftyGPTRouter.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// SwiftyGPTRouter.swift -// -// -// Created by Antonio Guerra on 27/03/23. -// - -import Foundation -import SwiftyHTTP - -enum SwiftyGPTRouter: SwiftyHTTPRequest { - - case chat(String, SwiftyGPTChatRequest) - case image(String, SwiftyGPTImageRequest) - case completion(String, SwiftyGPTCompletionRequest) - - var baseURL: URL? { - return URL(string: "https://api.openai.com") - } - - var path: String { - switch self { - case .chat: - return "/v1/chat/completions" - case .image: - return "/v1/images/generations" - case .completion: - return "/v1/completions" - } - } - - var method: SwiftyHTTPMethod { - switch self { - case .chat: - return .post - case .image: - return .post - case .completion: - return .post - } - } - - var headers: [SwiftyHTTPHeader] { - switch self { - case .chat(let apiKey, _), .image(let apiKey, _), .completion(let apiKey, _): - return [ - SwiftyHTTPHeader.contentType(.application(.json)), - SwiftyHTTPHeader.authorization(.bearer(apiKey)) - ] - } - } - - var parameters: [SwiftyHTTPQueryParameter] { - [] - } - - var body: SwiftyHTTPRequestBody? { - switch self { - case .chat(_, let request): - return request - case .image(_, let request): - return request - case .completion(_, let request): - return request - } - } -} diff --git a/Sources/SwiftyGPT/Sentiment/SwiftyGPT+SentimentAnalysis.swift b/Sources/SwiftyGPT/Sentiment/SwiftyGPT+SentimentAnalysis.swift deleted file mode 100644 index cc54b43..0000000 --- a/Sources/SwiftyGPT/Sentiment/SwiftyGPT+SentimentAnalysis.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// SwiftyGPT+Sentiment.swift -// -// -// Created by Antonio Guerra on 13/04/23. -// - -import Foundation - -extension SwiftyGPT { - - public func sentiment(text: String, language: SwiftyGPTLanguage? = nil, user: String? = nil, completion: @escaping (Result) -> ()) { - self.completion(prompt: prompt(text: text, language: language), model: .text_davinci_003, temperature: 0.0, presencePenalty: 0.0, frequencyPenalty: 0.5, user: user) { result in - switch result { - case .success(let response): - if let text = response.choices.first?.text { - completion(.success(SwiftyGPTSentiment(rawValue: text.lowercased()) ?? .neutral)) - } else { - completion(.failure(URLError(.badServerResponse))) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func sentiment(text: String, language: SwiftyGPTLanguage? = nil, user: String? = nil) async -> Result { - return await withCheckedContinuation { continuation in - sentiment(text: text, language: language, user: user) { result in - continuation.resume(returning: result) - } - } - } - - private func prompt(text: String, language: SwiftyGPTLanguage? = nil) -> String { - let text = text.trimmingCharacters(in: .whitespacesAndNewlines) - guard let language = language?.rawValue.capitalized else { - return "Decide whether this text sentiment is positive, neutral, or negative: \n\(text)" - } - return "Decide whether this \(language) text sentiment is positive, neutral, or negative: \n\(text)" - } -} diff --git a/Sources/SwiftyGPT/Sentiment/SwiftyGPTSentiment.swift b/Sources/SwiftyGPT/Sentiment/SwiftyGPTSentiment.swift deleted file mode 100644 index 7289b53..0000000 --- a/Sources/SwiftyGPT/Sentiment/SwiftyGPTSentiment.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// SwiftyGPTSentiment.swift -// -// -// Created by Antonio Guerra on 13/04/23. -// - -import Foundation - -public enum SwiftyGPTSentiment: String, Codable { - case positive - case neutral - case negative - - var intValue: Int { - switch self { - case .positive: - return 1 - case .neutral: - return 0 - case .negative: - return -1 - } - } -} diff --git a/Sources/SwiftyGPT/Summary/SwiftyGPT+Summary.swift b/Sources/SwiftyGPT/Summary/SwiftyGPT+Summary.swift deleted file mode 100644 index 0fbf232..0000000 --- a/Sources/SwiftyGPT/Summary/SwiftyGPT+Summary.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// SwiftyGPT+Summary.swift -// -// -// Created by Antonio Guerra on 12/04/23. -// - -import Foundation - -extension SwiftyGPT { - - public func summary(text: String, language: SwiftyGPTLanguage? = nil, user: String? = nil, completion: @escaping (Result) -> ()) { - self.completion(prompt: prompt(text: text, language: language), model: .text_davinci_003, temperature: 0.3, presencePenalty: 0.0, frequencyPenalty: 0.0, user: user) { result in - switch result { - case .success(let response): - if let text = response.choices.first?.text { - completion(.success(text)) - } else { - completion(.failure(URLError(.badServerResponse))) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func summary(text: String, language: SwiftyGPTLanguage? = nil, user: String? = nil) async -> Result { - return await withCheckedContinuation { continuation in - summary(text: text, language: language, user: user) { result in - continuation.resume(returning: result) - } - } - } - - private func prompt(text: String, language: SwiftyGPTLanguage? = nil) -> String { - let text = text.trimmingCharacters(in: .whitespacesAndNewlines) - guard let language = language?.rawValue.capitalized else { - return "Summarize this text for a second-grade student: \n\(text)" - } - return "Summarize this \(language) text for a second-grade student: \n\(text)" - } -} diff --git a/Sources/SwiftyGPT/SwiftyGPT.swift b/Sources/SwiftyGPT/SwiftyGPT.swift deleted file mode 100644 index 54c87d0..0000000 --- a/Sources/SwiftyGPT/SwiftyGPT.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// SwiftyGPT.swift -// -// -// Created by Antonio Guerra on 27/03/23. -// - -import Foundation -import SwiftyHTTP -import SwiftyRanged - -public struct SwiftyGPT { - - let apiKey: String - - public init(apiKey: String) { - self.apiKey = apiKey - } -} diff --git a/Sources/SwiftyGPT/Translation/SwiftyGPT+Translation.swift b/Sources/SwiftyGPT/Translation/SwiftyGPT+Translation.swift deleted file mode 100644 index 03dffb9..0000000 --- a/Sources/SwiftyGPT/Translation/SwiftyGPT+Translation.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// SwiftyGPT+Translation.swift -// -// -// Created by Antonio Guerra on 12/04/23. -// - -import Foundation - -extension SwiftyGPT { - - public func translation(text: String, from: SwiftyGPTLanguage? = nil, to: SwiftyGPTLanguage, user: String? = nil, completion: @escaping (Result) -> ()) { - self.completion(prompt: prompt(text: text, from: from, to: to), model: .text_davinci_003, temperature: 0.3, presencePenalty: 0.0, frequencyPenalty: 0.0, user: user) { result in - switch result { - case .success(let response): - if let text = response.choices.first?.text { - completion(.success(text)) - } else { - completion(.failure(URLError(.badServerResponse))) - } - case .failure(let error): - completion(.failure(error)) - } - } - } - - public func translation(text: String, from: SwiftyGPTLanguage? = nil, to: SwiftyGPTLanguage, user: String? = nil) async -> Result { - return await withCheckedContinuation { continuation in - translation(text: text, from: from, to: to, user: user) { result in - continuation.resume(returning: result) - } - } - } - - private func prompt(text: String, from: SwiftyGPTLanguage? = nil, to: SwiftyGPTLanguage) -> String { - let text = text.trimmingCharacters(in: .whitespacesAndNewlines) - let to = to.rawValue.capitalized - guard let from = from?.rawValue.capitalized else { - return "Translate this into \(to): \n\(text)" - } - return "Translate this from \(from) into \(to): \n\(text)" - } -} diff --git a/Sources/SwiftyGPT/Utils/SwiftyGPTError.swift b/Sources/SwiftyGPT/Utils/SwiftyGPTError.swift deleted file mode 100644 index b613b9a..0000000 --- a/Sources/SwiftyGPT/Utils/SwiftyGPTError.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// SwiftyGPTError.swift -// -// -// Created by Antonio Guerra on 29/03/23. -// - -import Foundation - -public struct SwiftyGPTError: Error, Decodable { - let message, type: String - let code: String? - - enum WrapperCodingKeys: CodingKey { - case error - } - - enum CodingKeys: CodingKey { - case message - case type - case code - } - - public init(from decoder: Decoder) throws { - let wrapper = try decoder.container(keyedBy: WrapperCodingKeys.self) - let container = try wrapper.nestedContainer(keyedBy: CodingKeys.self, forKey: .error) - self.message = try container.decode(String.self, forKey: .message) - self.type = try container.decode(String.self, forKey: .type) - self.code = try container.decode(String?.self, forKey: .code) - } -} diff --git a/Sources/SwiftyGPT/Utils/SwiftyGPTLanguage.swift b/Sources/SwiftyGPT/Utils/SwiftyGPTLanguage.swift deleted file mode 100644 index 5000e4a..0000000 --- a/Sources/SwiftyGPT/Utils/SwiftyGPTLanguage.swift +++ /dev/null @@ -1,147 +0,0 @@ -// -// SwiftyGPTLanguage.swift -// -// -// Created by Antonio Guerra on 12/04/23. -// - -import Foundation -import NaturalLanguage - -public enum SwiftyGPTLanguage: String { - case afrikaans - case arabic - case armenian - case azerbaijani - case belarusian - case bosnian - case bulgarian - case catalan - case chinese - case croatian - case czech - case danish - case dutch - case english - case estonian - case finnish - case french - case galician - case german - case greek - case hebrew - case hindi - case hungarian - case icelandic - case indonesian - case italian - case japanese - case kannada - case kazakh - case korean - case latvian - case spanish - case swahili - case swedish - case tagalog - case tamil - case thai - case turkish - case ukrainian - case urdu - case vietnamese - case welsh - - var naturalLanguage: NLLanguage? { - switch self { - case .afrikaans: - return nil - case .arabic: - return .arabic - case .armenian: - return .armenian - case .azerbaijani: - return nil - case .belarusian: - return nil - case .bosnian: - return nil - case .bulgarian: - return .bulgarian - case .catalan: - return .catalan - case .chinese: - return .simplifiedChinese - case .croatian: - return .croatian - case .czech: - return .czech - case .danish: - return .danish - case .dutch: - return .dutch - case .english: - return .english - case .estonian: - return nil - case .finnish: - return .finnish - case .french: - return .french - case .galician: - return nil - case .german: - return .german - case .greek: - return .greek - case .hebrew: - return .hebrew - case .hindi: - return .hindi - case .hungarian: - return .hungarian - case .icelandic: - return .icelandic - case .indonesian: - return .indonesian - case .italian: - return .italian - case .japanese: - return .japanese - case .kannada: - return .kannada - case .kazakh: - if #available(iOS 16.0, *) { - return .kazakh - } else { - return nil - } - case .korean: - return .korean - case .latvian: - return nil - case .spanish: - return .spanish - case .swahili: - return nil - case .swedish: - return .swedish - case .tagalog: - return nil - case .tamil: - return .tamil - case .thai: - return .thai - case .turkish: - return .turkish - case .ukrainian: - return .ukrainian - case .urdu: - return .urdu - case .vietnamese: - return .vietnamese - case .welsh: - return nil - } - } -} diff --git a/Sources/SwiftyGPT/Utils/SwiftyGPTModel.swift b/Sources/SwiftyGPT/Utils/SwiftyGPTModel.swift deleted file mode 100644 index 60e0731..0000000 --- a/Sources/SwiftyGPT/Utils/SwiftyGPTModel.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// SwiftyGPTModel.swift -// -// -// Created by Antonio Guerra on 11/04/23. -// - -import Foundation - -protocol SwiftyGPTModel: Codable { - static var stable: Self { get } - - var maxTokens: Int { get } -} diff --git a/Sources/SwiftyGPT/Utils/SwiftyGPTRequest.swift b/Sources/SwiftyGPT/Utils/SwiftyGPTRequest.swift deleted file mode 100644 index a58acba..0000000 --- a/Sources/SwiftyGPT/Utils/SwiftyGPTRequest.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// SwiftyGPTRequest.swift -// -// -// Created by Antonio Guerra on 11/04/23. -// - -import Foundation -import SwiftyHTTP - -protocol SwiftyGPTRequest: SwiftyHTTPRequestBody { - associatedtype GPTModel: SwiftyGPTModel - - var model: GPTModel { get } - var user: String? { get } - var choices: Int? { get } - var maxTokens: Int? { get } - var temperature: Float? { get } - var stream: Bool? { get } - var presencePenalty: Float? { get } - var frequencyPenalty: Float? { get } -} diff --git a/Sources/SwiftyGPT/Utils/SwiftyGPTResponse.swift b/Sources/SwiftyGPT/Utils/SwiftyGPTResponse.swift deleted file mode 100644 index b7ef271..0000000 --- a/Sources/SwiftyGPT/Utils/SwiftyGPTResponse.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// SwiftyGPTResponse.swift -// -// -// Created by Antonio Guerra on 11/04/23. -// - -import Foundation -import SwiftyHTTP - -protocol SwiftyGPTResponse: SwiftyHTTPResponseBody { - associatedtype GPTModel: SwiftyGPTModel - associatedtype GPTChoice: SwiftyGPTChoice - - var id: String { get } - var object: String { get } - var created: TimeInterval { get } - var model: GPTModel { get } - var usage: SwiftyGPTUsage { get } - var choices: [GPTChoice] { get } -} - -// MARK: - Choice -protocol SwiftyGPTChoice: Codable { - var finishReason: SwiftyGPTFinishReason { get } - var index: Int { get } -} - -// MARK: - Usage -public struct SwiftyGPTUsage: Codable { - public let promptTokens, completionTokens, totalTokens: Int - - enum CodingKeys: String, CodingKey { - case promptTokens = "prompt_tokens" - case completionTokens = "completion_tokens" - case totalTokens = "total_tokens" - } -} - -// MARK: - FinishReason -public enum SwiftyGPTFinishReason: String, Codable { - case stop - case length - case contentFilter = "content_filter" - case null -} diff --git a/Sources/SwiftyGPT/Utils/SwiftyGPTTokenizer.swift b/Sources/SwiftyGPT/Utils/SwiftyGPTTokenizer.swift deleted file mode 100644 index b2515fe..0000000 --- a/Sources/SwiftyGPT/Utils/SwiftyGPTTokenizer.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// SwiftyGPTTokenizer.swift -// -// -// Created by Antonio Guerra on 13/04/23. -// - -import Foundation -import NaturalLanguage - -public struct SwiftyGPTTokenizer { - private let wrapped: NLTokenizer - - init() { - wrapped = NLTokenizer(unit: .word) - } - - func tokenize(_ text: String, language: SwiftyGPTLanguage? = nil) -> Int { - wrapped.string = text - - if let language = language?.naturalLanguage { - wrapped.setLanguage(language) - } - - return Int(Float(wrapped.tokens(for: text.startIndex.. = await swiftyGPT.chat(messages: [SwiftyGPTChatMessage(role: .user, content: "Hi, how are you?")]) - switch result { - case .success(let response): - XCTAssertGreaterThanOrEqual(response.choices.count, 1) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } - - func testSingleMessageCompletion() { - let expectation = expectation(description: "SingleMessageCompletion") - swiftyGPT.chat(message: SwiftyGPTChatMessage(role: .user, content: "Hi, how are you?")) { result in - switch result { - case .success(let response): - XCTAssertEqual(response.choices.count, 1) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - - waitForExpectations(timeout: 30, handler: nil) - } - - func testSingleMessageAsync() async { - let result: Result = await swiftyGPT.chat(message: SwiftyGPTChatMessage(role: .user, content: "Hi, how are you?")) - switch result { - case .success(let response): - XCTAssertEqual(response.choices.count, 1) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } - - func testStringCompletion() { - let expectation = expectation(description: "StringCompletion") - swiftyGPT.chat(messages: ["Hi how are you ?", "I'm SwiftyGPT"]) { result in - switch result { - case .success(let response): - XCTAssertNotEqual(response, "") - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - waitForExpectations(timeout: 30, handler: nil) - } - - func testStringAsync() async { - let result: Result = await swiftyGPT.chat(messages: ["Hi how are you ?", "I'm SwiftyGPT"]) - switch result { - case .success(let response): - XCTAssertNotEqual(response, "") - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } - - func testSingleStringCompletion() { - let expectation = expectation(description: "SingleStringCompletion") - swiftyGPT.chat(message: "Hi how are you ?") { result in - switch result { - case .success(let response): - XCTAssertNotEqual(response, "") - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - waitForExpectations(timeout: 30, handler: nil) - } - - func testSingleStringAsync() async { - let result: Result = await swiftyGPT.chat(message: "Hi how are you ?") - switch result { - case .success(let response): - XCTAssertNotEqual(response, "") - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } -} diff --git a/Tests/SwiftyGPTTests/SwiftyGPTCompletionTests.swift b/Tests/SwiftyGPTTests/SwiftyGPTCompletionTests.swift deleted file mode 100644 index 9682033..0000000 --- a/Tests/SwiftyGPTTests/SwiftyGPTCompletionTests.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// SwiftyGPTCompletionTests.swift -// -// -// Created by Antonio Guerra on 12/04/23. -// - -import XCTest -@testable import SwiftyGPT - -final class SwiftyGPTCompletionTests: SwiftyGPTTestCase { - - func testDefaultCompletion() throws { - let expectation = expectation(description: "DefaultCompletion") - swiftyGPT.completion(prompt: "Say \"Hello\" in italian") { result in - switch result { - case .success(let response): - XCTAssertGreaterThanOrEqual(response.choices.count, 1) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - - waitForExpectations(timeout: 30, handler: nil) - } - - func testDefaultAsync() async throws { - let result: Result = await swiftyGPT.completion(prompt: "Say \"Hello\" in italian") - switch result { - case .success(let response): - XCTAssertGreaterThanOrEqual(response.choices.count, 1) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } -} diff --git a/Tests/SwiftyGPTTests/SwiftyGPTErrorTests.swift b/Tests/SwiftyGPTTests/SwiftyGPTErrorTests.swift deleted file mode 100644 index d897560..0000000 --- a/Tests/SwiftyGPTTests/SwiftyGPTErrorTests.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// SwiftyGPTErrorTests.swift -// -// -// Created by Antonio Guerra on 29/03/23. -// - -import XCTest -@testable import SwiftyGPT - -final class SwiftyGPTErrorTests: XCTestCase { - - var jsonString: String! - - override func setUpWithError() throws { - try super.setUpWithError() - jsonString = """ - { - "error": { - "message": "This model's maximum context length is 4097 tokens, however you requested 4451 tokens (357 in your prompt; 4094 for the completion). Please reduce your prompt; or completion length.", - "type": "invalid_request_error", - "param": null, - "code": null - } - } - """ - } - - override func tearDownWithError() throws { - try super.tearDownWithError() - jsonString = nil - } - - func testDecode() throws { - let data = jsonString.data(using: .utf8) - XCTAssertNotNil(data) - let error = try JSONDecoder().decode(SwiftyGPTError.self, from: data!) - XCTAssertNotNil(error) - } -} diff --git a/Tests/SwiftyGPTTests/SwiftyGPTImageTests.swift b/Tests/SwiftyGPTTests/SwiftyGPTImageTests.swift deleted file mode 100644 index f618483..0000000 --- a/Tests/SwiftyGPTTests/SwiftyGPTImageTests.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// SwiftyGPTImageTests.swift -// -// -// Created by Antonio Guerra on 30/03/23. -// - -import XCTest -@testable import SwiftyGPT - -final class SwiftyGPTImageTests: SwiftyGPTTestCase { - - #if canImport(UIKit) - func testDefaultCompletion() throws { - let expectation = expectation(description: "DefaultImageCompletion") - swiftyGPT.image(prompt: "Draw an unicorn", choices: 2, size: .x256) { result in - switch result { - case .success(let images): - XCTAssertEqual(images.count, 2) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - waitForExpectations(timeout: 30, handler: nil) - } - - func testDefaultAsync() async throws { - let result: Result<[UIImage], Error> = await swiftyGPT.image(prompt: "Draw an unicorn", choices: 2, size: .x256) - switch result { - case .success(let images): - XCTAssertEqual(images.count, 2) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } - - func testSingleCompletion() throws { - let expectation = expectation(description: "SingleImageCompletion") - swiftyGPT.image(prompt: "Draw an unicorn", size: .x256) { result in - switch result { - case .success(let image): - XCTAssertNotNil(image) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - waitForExpectations(timeout: 30, handler: nil) - } - - func testSingleAsync() async throws { - let result: Result = await swiftyGPT.image(prompt: "Draw an unicorn", size: .x256) - switch result { - case .success(let image): - XCTAssertNotNil(image) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } - #endif -} diff --git a/Tests/SwiftyGPTTests/SwiftyGPTSentimentTests.swift b/Tests/SwiftyGPTTests/SwiftyGPTSentimentTests.swift deleted file mode 100644 index 760a3b3..0000000 --- a/Tests/SwiftyGPTTests/SwiftyGPTSentimentTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// SwiftyGPTSentimentTests.swift -// -// -// Created by Antonio Guerra on 13/04/23. -// - -import XCTest -@testable import SwiftyGPT - -final class SwiftyGPTSentimentTests: SwiftyGPTTestCase { - - func testDefaultCompletion() throws { - let expectation = expectation(description: "DefaultCompletion") - swiftyGPT.sentiment(text: "I loved the new Batman movie!", language: .english) { result in - switch result { - case .success(let response): - XCTAssertEqual(response, .positive) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - waitForExpectations(timeout: 30, handler: nil) - } - - func testDefaultAsync() async throws { - let result: Result = await swiftyGPT.sentiment(text: "I loved the new Batman movie!", language: .english) - switch result { - case .success(let response): - XCTAssertEqual(response, .positive) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } -} diff --git a/Tests/SwiftyGPTTests/SwiftyGPTSummaryTests.swift b/Tests/SwiftyGPTTests/SwiftyGPTSummaryTests.swift deleted file mode 100644 index b98735e..0000000 --- a/Tests/SwiftyGPTTests/SwiftyGPTSummaryTests.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// SwiftyGPTSummaryTests.swift -// -// -// Created by Antonio Guerra on 12/04/23. -// - -import XCTest -@testable import SwiftyGPT - -final class SwiftyGPTSummaryTests: SwiftyGPTTestCase { - - func testDefaultCompletion() throws { - let expectation = expectation(description: "DefaultCompletion") - - let text = """ - La mia istruttrice di nuoto Elke W. aveva un cane barbone bianco, piuttosto grande per la sua razza, di nome Martino. Lei stessa ammetteva che era un cane stupido e fifone, pieno di difetti che lei amava elencare anche a interlocutori occasionali o a perfetti sconosciuti che incontrava per strada. Martino aveva paura, tra le altre cose, degli oggetti appuntiti, delle oche (un cane barbone che aveva paura della oche!) e di entrare nei furgoni dalla parte posteriore. In compenso appena poteva si buttava nell’acqua a nuotare e non si faceva scoraggiare dalla stagione avversa e dalla veemenza dei flutti, talché un giorno di marzo fu proprio la padrona a dover entrare nell’acqua gelata fino alla vita e trascinarlo fuori dal mare per il collare, dopo che il povero Martino era stato sul punto di soccombere. Lo coperse di contumelie, un po’ in italiano e un po’ in dialetto, mentre lui uggiolava sotto un massaggio vigoroso a colpi di plaid. - """ - - swiftyGPT.summary(text: text, language: .italian) { result in - switch result { - case .success(let response): - XCTAssertLessThan(response.count, text.count) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - - waitForExpectations(timeout: 30, handler: nil) - } - - func testDefaultAsync() async throws { - let text = """ - La mia istruttrice di nuoto Elke W. aveva un cane barbone bianco, piuttosto grande per la sua razza, di nome Martino. Lei stessa ammetteva che era un cane stupido e fifone, pieno di difetti che lei amava elencare anche a interlocutori occasionali o a perfetti sconosciuti che incontrava per strada. Martino aveva paura, tra le altre cose, degli oggetti appuntiti, delle oche (un cane barbone che aveva paura della oche!) e di entrare nei furgoni dalla parte posteriore. In compenso appena poteva si buttava nell’acqua a nuotare e non si faceva scoraggiare dalla stagione avversa e dalla veemenza dei flutti, talché un giorno di marzo fu proprio la padrona a dover entrare nell’acqua gelata fino alla vita e trascinarlo fuori dal mare per il collare, dopo che il povero Martino era stato sul punto di soccombere. Lo coperse di contumelie, un po’ in italiano e un po’ in dialetto, mentre lui uggiolava sotto un massaggio vigoroso a colpi di plaid. - """ - - let result: Result = await swiftyGPT.summary(text: text, language: .italian) - switch result { - case .success(let response): - XCTAssertLessThan(response.count, text.count) - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } -} diff --git a/Tests/SwiftyGPTTests/SwiftyGPTTokenizerTest.swift b/Tests/SwiftyGPTTests/SwiftyGPTTokenizerTest.swift deleted file mode 100644 index 5f41630..0000000 --- a/Tests/SwiftyGPTTests/SwiftyGPTTokenizerTest.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// SwiftyGPTTokenizerTest.swift -// -// -// Created by Antonio Guerra on 13/04/23. -// - -import XCTest -@testable import SwiftyGPT - -final class SwiftyGPTTokenizerTest: XCTestCase { - var tokenizer: SwiftyGPTTokenizer! - - override func setUpWithError() throws { - try super.setUpWithError() - tokenizer = SwiftyGPTTokenizer() - } - - override func tearDownWithError() throws { - try super.tearDownWithError() - tokenizer = nil - } - - func testTokenize() { - let text = """ - La mia istruttrice di nuoto Elke W. aveva un cane barbone bianco, piuttosto grande per la sua razza, di nome Martino. Lei stessa ammetteva che era un cane stupido e fifone, pieno di difetti che lei amava elencare anche a interlocutori occasionali o a perfetti sconosciuti che incontrava per strada. Martino aveva paura, tra le altre cose, degli oggetti appuntiti, delle oche (un cane barbone che aveva paura della oche!) e di entrare nei furgoni dalla parte posteriore. In compenso appena poteva si buttava nell’acqua a nuotare e non si faceva scoraggiare dalla stagione avversa e dalla veemenza dei flutti, talché un giorno di marzo fu proprio la padrona a dover entrare nell’acqua gelata fino alla vita e trascinarlo fuori dal mare per il collare, dopo che il povero Martino era stato sul punto di soccombere. Lo coperse di contumelie, un po’ in italiano e un po’ in dialetto, mentre lui uggiolava sotto un massaggio vigoroso a colpi di plaid. - """ - - let count = tokenizer.tokenize(text, language: .italian) - print(count) - XCTAssertGreaterThan(count, 160) - } -} diff --git a/Tests/SwiftyGPTTests/SwiftyGPTTranslationTests.swift b/Tests/SwiftyGPTTests/SwiftyGPTTranslationTests.swift deleted file mode 100644 index 1f3bf3f..0000000 --- a/Tests/SwiftyGPTTests/SwiftyGPTTranslationTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// SwiftyGPTTranslationTests.swift -// -// -// Created by Antonio Guerra on 12/04/23. -// - -import XCTest -@testable import SwiftyGPT - -final class SwiftyGPTTranslationTests: SwiftyGPTTestCase { - - func testDefaultCompletion() throws { - let expectation = expectation(description: "DefaultCompletion") - swiftyGPT.translation(text: "Hi, how are you ?", from: .english, to: .italian) { result in - switch result { - case .success(let response): - XCTAssertEqual(response.lowercased(), "ciao, come stai?") - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - waitForExpectations(timeout: 30, handler: nil) - } - - func testDefaultAsync() async throws { - let result: Result = await swiftyGPT.translation(text: "Hello", from: .english, to: .italian) - switch result { - case .success(let response): - XCTAssertEqual(response.lowercased(), "ciao") - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } -} diff --git a/Tests/SwiftyGPTTests/SwiftyGTPCorrectionTests.swift b/Tests/SwiftyGPTTests/SwiftyGTPCorrectionTests.swift deleted file mode 100644 index 3975fd1..0000000 --- a/Tests/SwiftyGPTTests/SwiftyGTPCorrectionTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// SwiftyGTPCorrectionTests.swift -// -// -// Created by Antonio Guerra on 13/04/23. -// - -import XCTest -@testable import SwiftyGPT - -final class SwiftyGTPCorrectionTests: SwiftyGPTTestCase { - - func testDefaultCompletion() throws { - let expectation = expectation(description: "DefaultCompletion") - swiftyGPT.correction(text: "She no went to the market", language: .english) { result in - switch result { - case .success(let response): - XCTAssertEqual(response.lowercased(), "she did not go to the market.") - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - expectation.fulfill() - } - waitForExpectations(timeout: 30, handler: nil) - } - - func testDefaultAsync() async throws { - let result: Result = await swiftyGPT.correction(text: "She no went to the market", language: .english) - switch result { - case .success(let response): - XCTAssertEqual(response.lowercased(), "she did not go to the market.") - case .failure(let error): - if let error = error as? SwiftyGPTError { - XCTFail(error.message) - } else { - XCTFail(error.localizedDescription) - } - } - } -} diff --git a/Tests/SwiftyGPTTests/Utils/OpenAI-Info.plist b/Tests/SwiftyGPTTests/Utils/OpenAI-Info.plist deleted file mode 100644 index 8decbec..0000000 --- a/Tests/SwiftyGPTTests/Utils/OpenAI-Info.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - API_KEY - - - diff --git a/Tests/SwiftyGPTTests/Utils/SwiftyGPTSecureTest.swift b/Tests/SwiftyGPTTests/Utils/SwiftyGPTSecureTest.swift deleted file mode 100644 index a55bb1e..0000000 --- a/Tests/SwiftyGPTTests/Utils/SwiftyGPTSecureTest.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// SwiftyGPTSecureTest.swift -// -// -// Created by Antonio Guerra on 30/03/23. -// - -import Foundation - -protocol SwiftyGPTSecureTest { - var apiKey: String { get } -} - -extension SwiftyGPTSecureTest { - var apiKey: String { - get { - guard let filePath = Bundle.module.path(forResource: "OpenAI-Info", ofType: "plist"), let plist = NSDictionary(contentsOfFile: filePath), let apiKey = plist.value(forKey: "API_KEY") as? String else { - return "" - } - return apiKey - } - } -} diff --git a/Tests/SwiftyGPTTests/Utils/SwiftyGPTTestCase.swift b/Tests/SwiftyGPTTests/Utils/SwiftyGPTTestCase.swift deleted file mode 100644 index 4311999..0000000 --- a/Tests/SwiftyGPTTests/Utils/SwiftyGPTTestCase.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// SwiftyGPTTestCase.swift -// -// -// Created by Antonio Guerra on 12/04/23. -// - -import XCTest -@testable import SwiftyGPT - -class SwiftyGPTTestCase: XCTestCase, SwiftyGPTSecureTest { - - var swiftyGPT: SwiftyGPT! - - override func setUpWithError() throws { - try super.setUpWithError() - swiftyGPT = SwiftyGPT(apiKey: apiKey) - } - - override func tearDownWithError() throws { - try super.tearDownWithError() - swiftyGPT = nil - } -} From d6a949d10ead1b28033edab820980909617ff4d0 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Tue, 9 Apr 2024 21:04:08 +0200 Subject: [PATCH 03/46] feat: remove deprecated documentation --- Documentations/CHAT.md | 104 ---------------------------------- Documentations/COMPLETION.md | 36 ------------ Documentations/CORRECTION.md | 32 ----------- Documentations/IMAGE.md | 50 ---------------- Documentations/SENTIMENT.md | 33 ----------- Documentations/SUMMARY.md | 37 ------------ Documentations/TRANSLATION.md | 33 ----------- 7 files changed, 325 deletions(-) delete mode 100644 Documentations/CHAT.md delete mode 100644 Documentations/COMPLETION.md delete mode 100644 Documentations/CORRECTION.md delete mode 100644 Documentations/IMAGE.md delete mode 100644 Documentations/SENTIMENT.md delete mode 100644 Documentations/SUMMARY.md delete mode 100644 Documentations/TRANSLATION.md diff --git a/Documentations/CHAT.md b/Documentations/CHAT.md deleted file mode 100644 index b8add31..0000000 --- a/Documentations/CHAT.md +++ /dev/null @@ -1,104 +0,0 @@ -# Chat - -Chat is the first and main feature of SwiftyGPT, it consists of creating a conversation with **ChatGPT**. -The goal is to provide all the features of this AI Chat Assistant through a very simple interface, therefore SwiftyGPT provides multiple methods for achieving the same purpose. We will therefore make a difference between low-level methods and high-level methods. - -## Low Level - -It allow you maximum control over request creation. The main element of a request is a **SwiftyGPTChatMessage**. - -```swift -let message = SwiftyGPTChatMessage(role: .user, content: "Hi, how are you?") -``` - -You can use **role** to instruct the model precisely as explained by the ChatGPT documentation and get the control you want. - -```swift -swiftyGPT.chat(message: message) { result in - switch result { - case .success(let response): - print(response) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } -} -``` -Alternatively if you need to send multiple messages in one request you can use the multiple input method, that accept an array as input. - -```swift -swiftyGPT.chat(messages: messages) { result in - switch result { - case .success(let response): - print(response) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } -} -``` -In both method you can specify some optional parameters like temperature, maxTokens and others established by OpenAI. Of course, you can also select the model to use. - -```swift -swiftyGPT.chat(message: SwiftyGPTChatMessage(role: .user, content: "Hi, how are you?"), temperature: 5, user: "Test") { result in - switch result { - case .success(let response): - print(response) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } -} -``` - -In case of success methods return a **SwiftyGPTChatResponse** object which is the entire transcript of ChatGPT HTTP response. -To access the received message or messages you have to check the content of the 'choices' attribute. By default choices array size is one, so you can get the message in this way and read its content or other attributes. - -```swift -let message = response.choices.first?.message -``` - -However, if you have requested a different number of choices, the array will have a larger size and you will have to manage the response in a custom way. - - -## High Level - -If you don't need a lot of control on your requests these methods works with simple Strings. Obviously this brings some limitations : - -- **Role:** all messages are sent using the 'user' role, you can't send messages as 'system'. -- **Parameters:** these methods allow you to specify only the model to use and, if necessary, a user. -- **Single Choice:** each request corresponds to a single response message. - -```swift -swiftyGPT.chat(message: "Hi how are you ?") { response in - switch result { - case .success(let response): - print(response) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } -} -``` - -In this case the method directly returns the message of the single choice in string format. - -## Async/Await - -All methods of the chat feature are also available in Async/Await version. - -```swift -let result: Result = await swiftyGPT.chat(message: "Hi how are you ?") -``` diff --git a/Documentations/COMPLETION.md b/Documentations/COMPLETION.md deleted file mode 100644 index 14c8544..0000000 --- a/Documentations/COMPLETION.md +++ /dev/null @@ -1,36 +0,0 @@ -# Completion - -SwiftyGPT also provides methods for creating completions using models like Davinci or Babbage. Given a prompt, the model will return one or more predicted completions based on the 'choices' parameters which you have already seen before. -
-
-Also in this case it is obviously possible to set some parameters in such a way as to best condition our response. - -```swift -swiftyGPT.completion(prompt: "Say \"Hello\" in italian", model: .text_davinci_003) { result in - switch result { - case .success(let response): - print(response) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } -} -``` - -In case of success methods return a SwiftyGPTCompletionResponse object which is the entire transcript of HTTP response. -To get the concrete response text you have to check the content of the 'choices' attribute. - -```swift -let text = response.choices.first?.text -``` - -## Async/Await - -All methods of the completion feature are also available in Async/Await version. - -```swift -let result: Result = await swiftyGPT.completion(prompt: "Say \"Hello\" in italian") -``` diff --git a/Documentations/CORRECTION.md b/Documentations/CORRECTION.md deleted file mode 100644 index e6a23d3..0000000 --- a/Documentations/CORRECTION.md +++ /dev/null @@ -1,32 +0,0 @@ -# Grammar Correction - -This feature allows you to make grammar corrections in a text written in one of the supported languages. -
-It can be thought of as a simple wrapper around the 'Completion' feature which performs a pre-set task, using optimized parameters. -
-Specifying the language used in the text is not mandatory, in fact the model is able to understand it independently. However, in some cases specifying it speeds up response times and increases the quality of the output. - -```swift -swiftyGPT.correction(text: "She no went to the market", language: .english) { result in - switch result { - case .success(let response): - print(response) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } -} -``` - -If successful, the method returns the correct text directly in string format. - -## Async/Await - -The feature is also available in Async/Await version. - -```swift -let result: Result = await swiftyGPT.correction(text: "She no went to the market", language: .english) -``` diff --git a/Documentations/IMAGE.md b/Documentations/IMAGE.md deleted file mode 100644 index 1226971..0000000 --- a/Documentations/IMAGE.md +++ /dev/null @@ -1,50 +0,0 @@ -# Image Generation - -SwiftyGPT uses DALL-E to generate images from textual descriptions. You can describe an object or a scene in words, and SwiftyGPT can create a corresponding image of it. - -## Single Generation - -The easiest way to generate an image is to use the following method, that accept a prompt and a size. It has the limitation of generating only square images of the following sizes: 256x256, 512x512 and 1024x1024. Also in this case if necessary you can specify a user for each call. - -```swift -swiftyGPT.image(prompt: "Draw an unicorn", size: .x256) { result in - switch result { - case .success(let image): - print(image) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } -} -``` -If successful, the method returns an UIImage that you can use directly in UIKit or wrap with an Image if you use SwiftUI. - -## Multiple Generation - -In case you want to generate several different images starting from the same description, you can specify the choices parameter. In this case the method will return an array of Data. - -```swift -swiftyGPT.image(prompt: "Draw an unicorn", choices: 2, size: .x256) { result in - switch result { - case .success(let images): - print(images) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } -} -``` - -## Async/Await - -All methods of the image feature are also available in Async/Await version. - -```swift -let result: Result = await swiftyGPT.image(prompt: "Draw an unicorn", size: .x256) -``` diff --git a/Documentations/SENTIMENT.md b/Documentations/SENTIMENT.md deleted file mode 100644 index 909d6f0..0000000 --- a/Documentations/SENTIMENT.md +++ /dev/null @@ -1,33 +0,0 @@ -# Sentyment Analysis - -It allows you to understand the sentiment related to a text, and therefore to understand if it is positive, negative or neutral. -
-It can be thought of as a simple wrapper around the 'Completion' feature which performs a pre-set task, using optimized parameters. -
-Specifying the language used in the input text is not mandatory, in fact the model is able to understand it independently. However, in some cases specifying it speeds up response times and increases the quality of the output. - -```swift -swiftyGPT.sentiment(text: "I loved the new Batman movie!", language: .english) { result in - switch result { - case .success(let response): - print(response.rawValue) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } -} -``` - -If successful, the method returns a **SwiftyGPTSentiment** object. That is an enum that exposes all possible cases. - -## Async/Await - -The feature is also available in Async/Await version. - -```swift -let result: Result = await swiftyGPT.sentiment(text: "I loved the new Batman movie!", language: .english) -``` - diff --git a/Documentations/SUMMARY.md b/Documentations/SUMMARY.md deleted file mode 100644 index de1b73d..0000000 --- a/Documentations/SUMMARY.md +++ /dev/null @@ -1,37 +0,0 @@ -# Language Translation - -This feature allows you to summarise a text, choosing from all languages those supported by SwiftyGPT.. -
-It can be thought of as a simple wrapper around the 'Completion' feature which performs a pre-set task, using optimized parameters. -
-Specifying the language used in the input text is not mandatory, in fact the model is able to understand it independently. However, in some cases specifying it speeds up response times and increases the quality of the output. - -```swift -let text = """ - La mia istruttrice di nuoto Elke W. aveva un cane barbone bianco, piuttosto grande per la sua razza, di nome Martino. Lei stessa ammetteva che era un cane stupido e fifone, pieno di difetti che lei amava elencare anche a interlocutori occasionali o a perfetti sconosciuti che incontrava per strada. Martino aveva paura, tra le altre cose, degli oggetti appuntiti, delle oche (un cane barbone che aveva paura della oche!) e di entrare nei furgoni dalla parte posteriore. In compenso appena poteva si buttava nell’acqua a nuotare e non si faceva scoraggiare dalla stagione avversa e dalla veemenza dei flutti, talché un giorno di marzo fu proprio la padrona a dover entrare nell’acqua gelata fino alla vita e trascinarlo fuori dal mare per il collare, dopo che il povero Martino era stato sul punto di soccombere. Lo coperse di contumelie, un po’ in italiano e un po’ in dialetto, mentre lui uggiolava sotto un massaggio vigoroso a colpi di plaid. - """ - -swiftyGPT.summary(text: text, language: .italian) { result in - switch result { - case .success(let response): - print(response) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } - expectation.fulfill() -} -``` - -If successful, the method returns the summarized text directly in string format. - -## Async/Await - -The feature is also available in Async/Await version. - -```swift -let result: Result = await swiftyGPT.summary(text: text, language: .italian) -``` diff --git a/Documentations/TRANSLATION.md b/Documentations/TRANSLATION.md deleted file mode 100644 index e0b45af..0000000 --- a/Documentations/TRANSLATION.md +++ /dev/null @@ -1,33 +0,0 @@ -# Language Translation - -This feature allows you to translate a text in another language, choosing from all those supported by SwiftyGPT.. -
-It can be thought of as a simple wrapper around the 'Completion' feature which performs a pre-set task, using optimized parameters. -
-Specifying the language used in the input text is not mandatory, in fact the model is able to understand it independently. However, in some cases specifying it speeds up response times and increases the quality of the output. - -```swift -swiftyGPT.translation(text: "Hi, how are you ?", from: .english, to: .italian) { result in - switch result { - case .success(let response): - print(response) - case .failure(let error): - if let error = error as? SwiftyGPTError { - print(error.message) - } else { - print(error.localizedDescription) - } - } - expectation.fulfill() -} -``` - -If successful, the method returns the correct text directly in string format. - -## Async/Await - -The feature is also available in Async/Await version. - -```swift -let result: Result = await swiftyGPT.translation(text: "Hello", from: .english, to: .italian) -``` From 6cd0b75300955809a8bf7e2d15c843ce59bd7cfa Mon Sep 17 00:00:00 2001 From: Antonio War Date: Tue, 9 Apr 2024 21:23:55 +0200 Subject: [PATCH 04/46] fix: first models integrated --- Package.swift | 1 + .../Models/Messages/SwiftyGPTMessage.swift | 13 ++++++++ .../Messages/SwiftyGPTSystemMessage.swift | 19 +++++++++++ Sources/SwiftyGPT/Models/SwiftyGPTRole.swift | 15 +++++++++ .../SwiftyGPTSystemMessageTests.swift | 26 +++++++++++++++ .../Models/SwiftyGPTRoleTests.swift | 32 +++++++++++++++++++ 6 files changed, 106 insertions(+) create mode 100644 Sources/SwiftyGPT/Models/Messages/SwiftyGPTMessage.swift create mode 100644 Sources/SwiftyGPT/Models/Messages/SwiftyGPTSystemMessage.swift create mode 100644 Sources/SwiftyGPT/Models/SwiftyGPTRole.swift create mode 100644 Tests/SwiftyGPTTests/Models/Messages/SwiftyGPTSystemMessageTests.swift create mode 100644 Tests/SwiftyGPTTests/Models/SwiftyGPTRoleTests.swift diff --git a/Package.swift b/Package.swift index f7039ab..b93f221 100644 --- a/Package.swift +++ b/Package.swift @@ -26,6 +26,7 @@ let package = Package( .testTarget( name: "SwiftyGPTTests", dependencies: [ + "SwiftyGPT" ] ) ] diff --git a/Sources/SwiftyGPT/Models/Messages/SwiftyGPTMessage.swift b/Sources/SwiftyGPT/Models/Messages/SwiftyGPTMessage.swift new file mode 100644 index 0000000..d81fba7 --- /dev/null +++ b/Sources/SwiftyGPT/Models/Messages/SwiftyGPTMessage.swift @@ -0,0 +1,13 @@ +// +// SwiftyGPTMessage.swift +// +// +// Created by Antonio Guerra on 09/04/24. +// + +import Foundation + +protocol SwiftyGPTMessage { + var role: SwiftyGPTRole { get } + var content: String { get } +} diff --git a/Sources/SwiftyGPT/Models/Messages/SwiftyGPTSystemMessage.swift b/Sources/SwiftyGPT/Models/Messages/SwiftyGPTSystemMessage.swift new file mode 100644 index 0000000..e5257c9 --- /dev/null +++ b/Sources/SwiftyGPT/Models/Messages/SwiftyGPTSystemMessage.swift @@ -0,0 +1,19 @@ +// +// SwiftyGPTSystemMessage.swift +// +// +// Created by Antonio Guerra on 09/04/24. +// + +import Foundation + +struct SwiftyGPTSystemMessage: SwiftyGPTMessage { + let role: SwiftyGPTRole = .system + let content: String + let name: String? + + init(content: String, name: String? = nil) { + self.content = content + self.name = name + } +} diff --git a/Sources/SwiftyGPT/Models/SwiftyGPTRole.swift b/Sources/SwiftyGPT/Models/SwiftyGPTRole.swift new file mode 100644 index 0000000..91cce52 --- /dev/null +++ b/Sources/SwiftyGPT/Models/SwiftyGPTRole.swift @@ -0,0 +1,15 @@ +// +// SwiftyGPTRole.swift +// +// +// Created by Antonio Guerra on 09/04/24. +// + +import Foundation + +public enum SwiftyGPTRole: String { + case system + case user + case assistant + case tool +} diff --git a/Tests/SwiftyGPTTests/Models/Messages/SwiftyGPTSystemMessageTests.swift b/Tests/SwiftyGPTTests/Models/Messages/SwiftyGPTSystemMessageTests.swift new file mode 100644 index 0000000..0591e7f --- /dev/null +++ b/Tests/SwiftyGPTTests/Models/Messages/SwiftyGPTSystemMessageTests.swift @@ -0,0 +1,26 @@ +// +// SwiftyGPTSystemMessageTests.swift +// +// +// Created by Antonio Guerra on 09/04/24. +// + +@testable import SwiftyGPT +import XCTest + +final class SwiftyGPTSystemMessageTests: XCTestCase { + + func testInitWhenNameIsNil() { + let message = SwiftyGPTSystemMessage(content: "Test", name: nil) + XCTAssertEqual(message.role, .system) + XCTAssertEqual(message.content, "Test") + XCTAssertEqual(message.name, nil) + } + + func testInitWhenNameIsNotNil() { + let message = SwiftyGPTSystemMessage(content: "Test", name: "Test") + XCTAssertEqual(message.role, .system) + XCTAssertEqual(message.content, "Test") + XCTAssertEqual(message.name, "Test") + } +} diff --git a/Tests/SwiftyGPTTests/Models/SwiftyGPTRoleTests.swift b/Tests/SwiftyGPTTests/Models/SwiftyGPTRoleTests.swift new file mode 100644 index 0000000..233a481 --- /dev/null +++ b/Tests/SwiftyGPTTests/Models/SwiftyGPTRoleTests.swift @@ -0,0 +1,32 @@ +// +// SwiftyGPTRoleTests.swift +// +// +// Created by Antonio Guerra on 09/04/24. +// + +@testable import SwiftyGPT +import XCTest + +final class SwiftyGPTRoleTests: XCTestCase { + + func testSystemInitFromRawValue() throws { + let role = try XCTUnwrap(SwiftyGPTRole(rawValue: "system")) + XCTAssertEqual(role, .system) + } + + func testUserInitFromRawValue() throws { + let role = try XCTUnwrap(SwiftyGPTRole(rawValue: "user")) + XCTAssertEqual(role, .user) + } + + func testAssistantInitFromRawValue() throws { + let role = try XCTUnwrap(SwiftyGPTRole(rawValue: "assistant")) + XCTAssertEqual(role, .assistant) + } + + func testToolInitFromRawValue() throws { + let role = try XCTUnwrap(SwiftyGPTRole(rawValue: "tool")) + XCTAssertEqual(role, .tool) + } +} From d6b4b1a512934392b7c5ccc639581316d02edeeb Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 15:54:48 +0200 Subject: [PATCH 05/46] refactor: package structure --- .../xcshareddata/xcschemes/SwiftyGPT.xcscheme | 79 +++++++++++++++++++ Package.swift | 8 +- .../Models/Messages/SwiftyGPTMessage.swift | 13 --- .../Messages/SwiftyGPTSystemMessage.swift | 19 ----- .../Models/SwiftyGPTChatRole.swift} | 4 +- .../Models/SwiftyGPTChatRoleTests.swift} | 14 ++-- .../SwiftyGPTSystemMessageTests.swift | 26 ------ 7 files changed, 92 insertions(+), 71 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPT.xcscheme delete mode 100644 Sources/SwiftyGPT/Models/Messages/SwiftyGPTMessage.swift delete mode 100644 Sources/SwiftyGPT/Models/Messages/SwiftyGPTSystemMessage.swift rename Sources/{SwiftyGPT/Models/SwiftyGPTRole.swift => SwiftyGPTChat/Models/SwiftyGPTChatRole.swift} (67%) rename Tests/{SwiftyGPTTests/Models/SwiftyGPTRoleTests.swift => SwiftyGPTChatTests/Models/SwiftyGPTChatRoleTests.swift} (53%) delete mode 100644 Tests/SwiftyGPTTests/Models/Messages/SwiftyGPTSystemMessageTests.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPT.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPT.xcscheme new file mode 100644 index 0000000..eee7efe --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPT.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.swift b/Package.swift index b93f221..059ccf3 100644 --- a/Package.swift +++ b/Package.swift @@ -12,21 +12,21 @@ let package = Package( products: [ .library( name: "SwiftyGPT", - targets: ["SwiftyGPT"] + targets: ["SwiftyGPTChat"] ), ], dependencies: [ ], targets: [ .target( - name: "SwiftyGPT", + name: "SwiftyGPTChat", dependencies: [ ] ), .testTarget( - name: "SwiftyGPTTests", + name: "SwiftyGPTChatTests", dependencies: [ - "SwiftyGPT" + "SwiftyGPTChat" ] ) ] diff --git a/Sources/SwiftyGPT/Models/Messages/SwiftyGPTMessage.swift b/Sources/SwiftyGPT/Models/Messages/SwiftyGPTMessage.swift deleted file mode 100644 index d81fba7..0000000 --- a/Sources/SwiftyGPT/Models/Messages/SwiftyGPTMessage.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// SwiftyGPTMessage.swift -// -// -// Created by Antonio Guerra on 09/04/24. -// - -import Foundation - -protocol SwiftyGPTMessage { - var role: SwiftyGPTRole { get } - var content: String { get } -} diff --git a/Sources/SwiftyGPT/Models/Messages/SwiftyGPTSystemMessage.swift b/Sources/SwiftyGPT/Models/Messages/SwiftyGPTSystemMessage.swift deleted file mode 100644 index e5257c9..0000000 --- a/Sources/SwiftyGPT/Models/Messages/SwiftyGPTSystemMessage.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// SwiftyGPTSystemMessage.swift -// -// -// Created by Antonio Guerra on 09/04/24. -// - -import Foundation - -struct SwiftyGPTSystemMessage: SwiftyGPTMessage { - let role: SwiftyGPTRole = .system - let content: String - let name: String? - - init(content: String, name: String? = nil) { - self.content = content - self.name = name - } -} diff --git a/Sources/SwiftyGPT/Models/SwiftyGPTRole.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift similarity index 67% rename from Sources/SwiftyGPT/Models/SwiftyGPTRole.swift rename to Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift index 91cce52..c0a8c56 100644 --- a/Sources/SwiftyGPT/Models/SwiftyGPTRole.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift @@ -1,5 +1,5 @@ // -// SwiftyGPTRole.swift +// SwiftyGPTChatRole.swift // // // Created by Antonio Guerra on 09/04/24. @@ -7,7 +7,7 @@ import Foundation -public enum SwiftyGPTRole: String { +public enum SwiftyGPTChatRole: String { case system case user case assistant diff --git a/Tests/SwiftyGPTTests/Models/SwiftyGPTRoleTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRoleTests.swift similarity index 53% rename from Tests/SwiftyGPTTests/Models/SwiftyGPTRoleTests.swift rename to Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRoleTests.swift index 233a481..4eb643a 100644 --- a/Tests/SwiftyGPTTests/Models/SwiftyGPTRoleTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRoleTests.swift @@ -1,32 +1,32 @@ // -// SwiftyGPTRoleTests.swift +// SwiftyGPTChatRoleTests.swift // // // Created by Antonio Guerra on 09/04/24. // -@testable import SwiftyGPT +@testable import SwiftyGPTChat import XCTest -final class SwiftyGPTRoleTests: XCTestCase { +final class SwiftyGPTChatRoleTests: XCTestCase { func testSystemInitFromRawValue() throws { - let role = try XCTUnwrap(SwiftyGPTRole(rawValue: "system")) + let role = try XCTUnwrap(SwiftyGPTChatRole(rawValue: "system")) XCTAssertEqual(role, .system) } func testUserInitFromRawValue() throws { - let role = try XCTUnwrap(SwiftyGPTRole(rawValue: "user")) + let role = try XCTUnwrap(SwiftyGPTChatRole(rawValue: "user")) XCTAssertEqual(role, .user) } func testAssistantInitFromRawValue() throws { - let role = try XCTUnwrap(SwiftyGPTRole(rawValue: "assistant")) + let role = try XCTUnwrap(SwiftyGPTChatRole(rawValue: "assistant")) XCTAssertEqual(role, .assistant) } func testToolInitFromRawValue() throws { - let role = try XCTUnwrap(SwiftyGPTRole(rawValue: "tool")) + let role = try XCTUnwrap(SwiftyGPTChatRole(rawValue: "tool")) XCTAssertEqual(role, .tool) } } diff --git a/Tests/SwiftyGPTTests/Models/Messages/SwiftyGPTSystemMessageTests.swift b/Tests/SwiftyGPTTests/Models/Messages/SwiftyGPTSystemMessageTests.swift deleted file mode 100644 index 0591e7f..0000000 --- a/Tests/SwiftyGPTTests/Models/Messages/SwiftyGPTSystemMessageTests.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// SwiftyGPTSystemMessageTests.swift -// -// -// Created by Antonio Guerra on 09/04/24. -// - -@testable import SwiftyGPT -import XCTest - -final class SwiftyGPTSystemMessageTests: XCTestCase { - - func testInitWhenNameIsNil() { - let message = SwiftyGPTSystemMessage(content: "Test", name: nil) - XCTAssertEqual(message.role, .system) - XCTAssertEqual(message.content, "Test") - XCTAssertEqual(message.name, nil) - } - - func testInitWhenNameIsNotNil() { - let message = SwiftyGPTSystemMessage(content: "Test", name: "Test") - XCTAssertEqual(message.role, .system) - XCTAssertEqual(message.content, "Test") - XCTAssertEqual(message.name, "Test") - } -} From 1e6322147ab63a5fb6277b075337f90149c58731 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 17:00:12 +0200 Subject: [PATCH 06/46] feat: create SwiftyGPTChatMessage and related unit tests --- .../Models/SwiftyGPTChatMessage.swift | 58 ++++++++++++++++ .../Models/SwiftyGPTChatMessageTests.swift | 68 +++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift create mode 100644 Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift new file mode 100644 index 0000000..1dfb683 --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -0,0 +1,58 @@ +// +// SwiftyGPTChatMessage.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +import Foundation + +import Foundation + +protocol SwiftyGPTChatMessage { + var role: SwiftyGPTChatRole { get } +} + +struct SwiftyGPTSystemMessage: SwiftyGPTChatMessage { + let role: SwiftyGPTChatRole = .system + let content: String + let name: String? + + init(content: String, name: String? = nil) { + self.content = content + self.name = name + } +} + +struct SwiftyGPTUserMessage: SwiftyGPTChatMessage { + let role: SwiftyGPTChatRole = .user + let content: String + let name: String? + + init(content: String, name: String? = nil) { + self.content = content + self.name = name + } +} + +struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { + let role: SwiftyGPTChatRole = .assistant + let content: String? + let name: String? + // TODO: Add tool_calls + + init(content: String? = nil, name: String? = nil) { + self.content = content + self.name = name + } +} + +struct SwiftyGPTToolMessage: SwiftyGPTChatMessage { + let role: SwiftyGPTChatRole = .tool + let content: String + // TODO: Add tool_call_id + + init(content: String) { + self.content = content + } +} diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift new file mode 100644 index 0000000..00de10e --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift @@ -0,0 +1,68 @@ +// +// SwiftyGPTChatMessageTests.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +@testable import SwiftyGPTChat +import XCTest + +final class SwiftyGPTChatMessageTests: XCTestCase { + + func testSystemMessageInitWhenNameIsNil() throws { + let message = SwiftyGPTSystemMessage(content: "Test") + XCTAssertEqual(message.role, .system) + XCTAssertEqual(message.content, "Test") + XCTAssertEqual(message.name, nil) + } + + func testSystemMessageInitWhenNameIsNotNil() throws { + let message = SwiftyGPTSystemMessage(content: "Test", name: "Test") + XCTAssertEqual(message.role, .system) + XCTAssertEqual(message.content, "Test") + XCTAssertEqual(message.name, "Test") + } + + func testUserMessageInitWhenNameIsNil() throws { + let message = SwiftyGPTUserMessage(content: "Test") + XCTAssertEqual(message.role, .user) + XCTAssertEqual(message.content, "Test") + XCTAssertEqual(message.name, nil) + } + + func testUserMessageInitWhenNameIsNotNil() throws { + let message = SwiftyGPTUserMessage(content: "Test", name: "Test") + XCTAssertEqual(message.role, .user) + XCTAssertEqual(message.content, "Test") + XCTAssertEqual(message.name, "Test") + } + + func testAssistantMessageInitWhenContentIsNil() throws { + let message = SwiftyGPTAssistantMessage() + XCTAssertEqual(message.role, .assistant) + XCTAssertEqual(message.content, nil) + XCTAssertEqual(message.name, nil) + } + + func testUserMessageInitWhenContentIsNotNil() throws { + let message = SwiftyGPTAssistantMessage(content: "Test") + XCTAssertEqual(message.role, .assistant) + XCTAssertEqual(message.content, "Test") + XCTAssertEqual(message.name, nil) + } + + func testAssistantMessageInitWhenNameIsNil() throws { + let message = SwiftyGPTAssistantMessage() + XCTAssertEqual(message.role, .assistant) + XCTAssertEqual(message.content, nil) + XCTAssertEqual(message.name, nil) + } + + func testAssistantMessageInitWhenNameIsNotNil() throws { + let message = SwiftyGPTAssistantMessage(name: "Test") + XCTAssertEqual(message.role, .assistant) + XCTAssertEqual(message.content, nil) + XCTAssertEqual(message.name, "Test") + } +} From cbe9f63a3bccd90d0debdc56166ac981076351ff Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 17:49:30 +0200 Subject: [PATCH 07/46] feat: add SwiftyGPTChatModel and related tests --- .../Models/SwiftyGPTChatMessage.swift | 4 +- .../Models/SwiftyGPTChatModel.swift | 19 ++++++++ .../Models/SwiftyGPTChatModelTests.swift | 48 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift create mode 100644 Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatModelTests.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index 1dfb683..638587f 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -39,7 +39,7 @@ struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .assistant let content: String? let name: String? - // TODO: Add tool_calls + // TODO: add tool_calls init(content: String? = nil, name: String? = nil) { self.content = content @@ -50,7 +50,7 @@ struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { struct SwiftyGPTToolMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .tool let content: String - // TODO: Add tool_call_id + // TODO: add tool_call_id init(content: String) { self.content = content diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift new file mode 100644 index 0000000..ecbd211 --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift @@ -0,0 +1,19 @@ +// +// SwiftyGPTChatModel.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +import Foundation + +enum SwiftyGPTChatModel: String { + case gpt3_5_turbo = "gpt-3.5-turbo" + case gpt3_5_turbo_16k = "gpt-3.5-turbo-16k" + case gpt3_5_turbo_16k_0613 = "gpt-3.5-turbo-16k-0613" + case gpt3_5_turbo_0301 = "gpt-3.5-turbo-0301" + case gpt3_5_turbo_0613 = "gpt-3.5-turbo-0613" + case gpt3_5_turbo_1106 = "gpt-3.5-turbo-1106" + case gpt3_5_turbo_0125 = "gpt-3.5-turbo-0125" + // TODO: add gpt4 models +} diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatModelTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatModelTests.swift new file mode 100644 index 0000000..b9f18db --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatModelTests.swift @@ -0,0 +1,48 @@ +// +// SwiftyGPTChatModelTests.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +@testable import SwiftyGPTChat +import XCTest + +final class SwiftyGPTChatModelTests: XCTestCase { + + func testGpt3_5_turboInitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-3.5-turbo")) + XCTAssertEqual(model, .gpt3_5_turbo) + } + + func testGpt3_5_turbo_16kInitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-3.5-turbo-16k")) + XCTAssertEqual(model, .gpt3_5_turbo_16k) + } + + func testGpt3_5_turbo_16k_0613InitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-3.5-turbo-16k-0613")) + XCTAssertEqual(model, .gpt3_5_turbo_16k_0613) + } + + func testGpt3_5_turbo_0301InitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-3.5-turbo-0301")) + XCTAssertEqual(model, .gpt3_5_turbo_0301) + } + + func testGpt3_5_turbo_0613InitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-3.5-turbo-0613")) + XCTAssertEqual(model, .gpt3_5_turbo_0613) + } + + func testGpt3_5_turbo_1106InitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-3.5-turbo-1106")) + XCTAssertEqual(model, .gpt3_5_turbo_1106) + } + + func testGpt3_5_turbo_0125InitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-3.5-turbo-0125")) + XCTAssertEqual(model, .gpt3_5_turbo_0125) + } +} + From fef4200cfc7fc6c6d07f9f0613d325d8182f0e4b Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 18:07:42 +0200 Subject: [PATCH 08/46] fix: create SwiftyGPTChatRequest --- .../Models/SwiftyGPTChatRequest.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift new file mode 100644 index 0000000..3b6ce28 --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift @@ -0,0 +1,28 @@ +// +// SwiftyGPTChatRequest.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +import Foundation + +struct SwiftyGPTChatRequest { + let model: SwiftyGPTChatModel + let messages: [SwiftyGPTChatMessage] + let frequencyPenalty: Double? + // TODO: add logit_bias + let logprobs: Bool? + let topLogprobs: Int? + let maxTokens: Int? + let choices: Int? + let presencePenalty: Double? + // TODO: add response_format + let seed: Int? + // TODO: add stop + // TODO: add stream + let temperature: Double? + let nucleusSampling: Double? + // TODO: add tools + let user: String? +} From 6f2aed867eeed01ff47088401d66afc50f8442dc Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 18:42:56 +0200 Subject: [PATCH 09/46] feat: add SwiftyGPTChatResponse --- .../Models/SwiftyGPTChatResponse.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift new file mode 100644 index 0000000..3f6afc8 --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift @@ -0,0 +1,18 @@ +// +// SwiftyGPTChatResponse.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +import Foundation + +struct SwiftyGPTChatResponse: Identifiable { + let id: String + // TODO: add choices + let created: Date + let model: SwiftyGPTChatModel + let fingerprint: String + let object: String = "chat.completion" + // TODO: add usage +} From 77502a46619d299fa583164403a4de7cdfb1f63d Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 18:45:49 +0200 Subject: [PATCH 10/46] feat: add Codable support to SwiftyGPTChatResponse and SwiftyGPTChatModel --- .../SwiftyGPTChat/Models/SwiftyGPTChatModel.swift | 2 +- .../SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift index ecbd211..caeef9f 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift @@ -7,7 +7,7 @@ import Foundation -enum SwiftyGPTChatModel: String { +enum SwiftyGPTChatModel: String, Codable { case gpt3_5_turbo = "gpt-3.5-turbo" case gpt3_5_turbo_16k = "gpt-3.5-turbo-16k" case gpt3_5_turbo_16k_0613 = "gpt-3.5-turbo-16k-0613" diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift index 3f6afc8..c32b219 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift @@ -7,12 +7,20 @@ import Foundation -struct SwiftyGPTChatResponse: Identifiable { +struct SwiftyGPTChatResponse: Identifiable, Decodable { let id: String // TODO: add choices let created: Date let model: SwiftyGPTChatModel let fingerprint: String - let object: String = "chat.completion" + let object: String // TODO: add usage + + enum CodingKeys: String, CodingKey { + case id + case created + case model + case fingerprint = "system_fingerprint" + case object + } } From 056041742fd7081178866f5d22056ffac4eebe22 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 18:52:01 +0200 Subject: [PATCH 11/46] fix: remove SwiftGPTChatResponse.object decoding --- Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift index c32b219..a69c576 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift @@ -13,7 +13,7 @@ struct SwiftyGPTChatResponse: Identifiable, Decodable { let created: Date let model: SwiftyGPTChatModel let fingerprint: String - let object: String + let object: String = "chat.completion" // TODO: add usage enum CodingKeys: String, CodingKey { @@ -21,6 +21,5 @@ struct SwiftyGPTChatResponse: Identifiable, Decodable { case created case model case fingerprint = "system_fingerprint" - case object } } From 49a3de7128e6a9ecb5b21f85f63251d682fe4fe4 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 18:57:29 +0200 Subject: [PATCH 12/46] feat: add build_and_test.yml --- .github/workflows/build_and_test.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/build_and_test.yml diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml new file mode 100644 index 0000000..106af85 --- /dev/null +++ b/.github/workflows/build_and_test.yml @@ -0,0 +1,20 @@ +# This workflow will build a Swift project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift + +name: Build & Test + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Build + run: swift build -v + - name: Test + run: swift test -v From 4fd0e4bff750308e94922d4310f7bd004d49f0b5 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 19:13:19 +0200 Subject: [PATCH 13/46] test: add SwiftyGPTChatResponseTests --- .../Models/SwiftyGPTChatResponse.swift | 8 ++++ .../Models/SwiftyGPTChatResponseTests.swift | 46 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift index a69c576..5340095 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift @@ -22,4 +22,12 @@ struct SwiftyGPTChatResponse: Identifiable, Decodable { case model case fingerprint = "system_fingerprint" } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.created = try Date(timeIntervalSince1970: container.decode(TimeInterval.self, forKey: .created)) + self.model = try container.decode(SwiftyGPTChatModel.self, forKey: .model) + self.fingerprint = try container.decode(String.self, forKey: .fingerprint) + } } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift new file mode 100644 index 0000000..9091614 --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift @@ -0,0 +1,46 @@ +// +// SwiftyGPTChatResponseTests.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +@testable import SwiftyGPTChat +import XCTest + +final class SwiftyGPTChatResponseTests: XCTestCase { + + func testDecoding() throws { + let json = """ + { + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1677652288, + "model": "gpt-3.5-turbo-0125", + "system_fingerprint": "fp_44709d6fcb", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": "\n\nHello there, how may I assist you today?", + }, + "logprobs": null, + "finish_reason": "stop" + }], + "usage": { + "prompt_tokens": 9, + "completion_tokens": 12, + "total_tokens": 21 + } + } + """ + let data = try XCTUnwrap(json.data(using: .utf8)) + let decoder = JSONDecoder() + let response = try decoder.decode(SwiftyGPTChatResponse.self, from: data) + XCTAssertEqual(response.id, "chatcmpl-123") + XCTAssertEqual(response.object, "chat.completion") + XCTAssertEqual(response.created, Date(timeIntervalSince1970: 1677652288)) + XCTAssertEqual(response.model, .gpt3_5_turbo_0125) + XCTAssertEqual(response.fingerprint, "fp_44709d6fcb") + } +} From 1094ef0caf76f6c927637924470828cf8eb85067 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 19:20:15 +0200 Subject: [PATCH 14/46] feat: add SwiftyGPTNetworking and SwiftyGPTNetworking in package structure --- Package.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Package.swift b/Package.swift index 059ccf3..1120fb9 100644 --- a/Package.swift +++ b/Package.swift @@ -21,6 +21,7 @@ let package = Package( .target( name: "SwiftyGPTChat", dependencies: [ + "SwiftyGPTNetworking" ] ), .testTarget( @@ -28,6 +29,17 @@ let package = Package( dependencies: [ "SwiftyGPTChat" ] + ), + .target( + name: "SwiftyGPTNetworking", + dependencies: [ + ] + ), + .testTarget( + name: "SwiftyGPTNetworkingTests", + dependencies: [ + "SwiftyGPTNetworking" + ] ) ] ) From 76c619d239fc666298a2e43e8fa138f2ab1fa909 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 20:50:30 +0200 Subject: [PATCH 15/46] feat: add some networking models and clients --- .../xcshareddata/xcschemes/SwiftyGPT.xcscheme | 10 +++++ .../Clients/SwiftyGPTNetworkingClient.swift | 24 ++++++++++++ .../Models/SwiftyGPTNetworkingRequest.swift | 34 +++++++++++++++++ .../Models/SwiftyGPTNetworkingResponse.swift | 17 +++++++++ .../SwiftyGPTNetworkingClientTests.swift | 37 +++++++++++++++++++ 5 files changed, 122 insertions(+) create mode 100644 Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift create mode 100644 Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift create mode 100644 Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingResponse.swift create mode 100644 Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPT.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPT.xcscheme index eee7efe..00140a4 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPT.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPT.xcscheme @@ -40,6 +40,16 @@ ReferencedContainer = "container:"> + + + + SwiftyGPTNetworkingResponse { + let (body, underlyingResponse) = try await session.data(for: request.underlyingRequest) + guard let underlyingResponse = underlyingResponse as? HTTPURLResponse else { + throw URLError(.badServerResponse) + } + return SwiftyGPTNetworkingResponse(underlyingResponse: underlyingResponse, body: body) + } +} diff --git a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift new file mode 100644 index 0000000..9440805 --- /dev/null +++ b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift @@ -0,0 +1,34 @@ +// +// SwiftyGPTNetworkingRequest.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +import Foundation + +protocol SwiftyGPTNetworkingRequest { + var endpoint: URL? { get } + var path: String { get } + var url: URL { get throws } + var cachePolicy: URLRequest.CachePolicy { get } + var timeout: TimeInterval { get } + var underlyingRequest: URLRequest { get throws } +} + +extension SwiftyGPTNetworkingRequest { + var url: URL { + get throws { + guard let endpoint, let url = URL(string: path, relativeTo: endpoint) else { + throw URLError(.badURL) + } + return url + } + } + + var underlyingRequest: URLRequest { + get throws { + return try URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout) + } + } +} diff --git a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingResponse.swift b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingResponse.swift new file mode 100644 index 0000000..dc6b3bb --- /dev/null +++ b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingResponse.swift @@ -0,0 +1,17 @@ +// +// SwiftyGPTNetworkingResponse.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +import Foundation + +struct SwiftyGPTNetworkingResponse { + var underlyingResponse: HTTPURLResponse + var body: Data + + var statusCode: Int { + return underlyingResponse.statusCode + } +} diff --git a/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift new file mode 100644 index 0000000..1bcddb7 --- /dev/null +++ b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift @@ -0,0 +1,37 @@ +// +// SwiftyGPTNetworkingClientTests.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +@testable import SwiftyGPTNetworking +import XCTest + +final class SwiftyGPTNetworkingClientTests: XCTestCase { + + var client: SwiftyGPTNetworkingClient! + + override func setUpWithError() throws { + self.client = SwiftyGPTNetworkingClient(session: URLSession.shared) + } + + override func tearDownWithError() throws { + self.client = nil + } + + func testSend() async throws { + let url = try XCTUnwrap(URL(string: "https://picsum.photos/10")) + let request = TestSwiftyGPTNetworkingRequest() + let response = try await client.send(request: request) + XCTAssertEqual(response.statusCode, 200) + let _ = try XCTUnwrap(UIImage(data: response.body)) + } + + private struct TestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { + let endpoint: URL? = URL(string: "https://picsum.photos") + let path: String = "/10" + let cachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData + let timeout: TimeInterval = 60 + } +} From 8a9ee7c3d6c36200b82f6a5679e12c9fbf1bcc7e Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 21:00:56 +0200 Subject: [PATCH 16/46] test: add SwiftyGPTNetworkingRequestTests --- .../SwiftyGPTNetworkingClientTests.swift | 5 ++-- .../SwiftyGPTNetworkingRequestTests.swift | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift diff --git a/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift index 1bcddb7..f8c1bd5 100644 --- a/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift +++ b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift @@ -21,7 +21,6 @@ final class SwiftyGPTNetworkingClientTests: XCTestCase { } func testSend() async throws { - let url = try XCTUnwrap(URL(string: "https://picsum.photos/10")) let request = TestSwiftyGPTNetworkingRequest() let response = try await client.send(request: request) XCTAssertEqual(response.statusCode, 200) @@ -29,8 +28,8 @@ final class SwiftyGPTNetworkingClientTests: XCTestCase { } private struct TestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { - let endpoint: URL? = URL(string: "https://picsum.photos") - let path: String = "/10" + let endpoint: URL? = URL(string: "https://picsum.photos/") + let path: String = "10" let cachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData let timeout: TimeInterval = 60 } diff --git a/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift new file mode 100644 index 0000000..124c996 --- /dev/null +++ b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift @@ -0,0 +1,29 @@ +// +// SwiftyGPTNetworkingRequestTests.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +import XCTest +@testable import SwiftyGPTNetworking + +final class SwiftyGPTNetworkingRequestTests: XCTestCase { + + func testInit() throws { + let request = TestSwiftyGPTNetworkingRequest() + XCTAssertEqual(request.endpoint, URL(string: "https://picsum.photos/")) + XCTAssertEqual(request.path, "10") + XCTAssertEqual(request.cachePolicy, .reloadIgnoringLocalAndRemoteCacheData) + XCTAssertEqual(request.timeout, 60) + try XCTAssertEqual(request.url, URL(string: request.path, relativeTo: request.endpoint)) + try XCTAssertEqual(request.underlyingRequest, URLRequest(url: request.url, cachePolicy: request.cachePolicy, timeoutInterval: request.timeout)) + } + + private struct TestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { + let endpoint: URL? = URL(string: "https://picsum.photos/") + let path: String = "10" + let cachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData + let timeout: TimeInterval = 60 + } +} From ac0e37ce22ec673e2635e8a6b50c35aad4170416 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 21:01:59 +0200 Subject: [PATCH 17/46] refactor: rename chat request and response --- ...yGPTChatRequest.swift => SwiftyGPTChatRequestBody.swift} | 4 ++-- ...PTChatResponse.swift => SwiftyGPTChatResponseBody.swift} | 4 ++-- ...onseTests.swift => SwiftyGPTChatResponseBodyTests.swift} | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename Sources/SwiftyGPTChat/Models/{SwiftyGPTChatRequest.swift => SwiftyGPTChatRequestBody.swift} (88%) rename Sources/SwiftyGPTChat/Models/{SwiftyGPTChatResponse.swift => SwiftyGPTChatResponseBody.swift} (90%) rename Tests/SwiftyGPTChatTests/Models/{SwiftyGPTChatResponseTests.swift => SwiftyGPTChatResponseBodyTests.swift} (87%) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift similarity index 88% rename from Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift rename to Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index 3b6ce28..18e3a12 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -1,5 +1,5 @@ // -// SwiftyGPTChatRequest.swift +// SwiftyGPTChatRequestBody.swift // // // Created by Antonio Guerra on 11/04/24. @@ -7,7 +7,7 @@ import Foundation -struct SwiftyGPTChatRequest { +struct SwiftyGPTChatRequestBody { let model: SwiftyGPTChatModel let messages: [SwiftyGPTChatMessage] let frequencyPenalty: Double? diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift similarity index 90% rename from Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift rename to Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift index 5340095..a048b86 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift @@ -1,5 +1,5 @@ // -// SwiftyGPTChatResponse.swift +// SwiftyGPTChatResponseBody.swift // // // Created by Antonio Guerra on 11/04/24. @@ -7,7 +7,7 @@ import Foundation -struct SwiftyGPTChatResponse: Identifiable, Decodable { +struct SwiftyGPTChatResponseBody: Identifiable, Decodable { let id: String // TODO: add choices let created: Date diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift similarity index 87% rename from Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift rename to Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift index 9091614..5db7d67 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift @@ -1,5 +1,5 @@ // -// SwiftyGPTChatResponseTests.swift +// SwiftyGPTChatResponseBodyTests.swift // // // Created by Antonio Guerra on 11/04/24. @@ -8,7 +8,7 @@ @testable import SwiftyGPTChat import XCTest -final class SwiftyGPTChatResponseTests: XCTestCase { +final class SwiftyGPTChatResponseBodyTests: XCTestCase { func testDecoding() throws { let json = """ @@ -36,7 +36,7 @@ final class SwiftyGPTChatResponseTests: XCTestCase { """ let data = try XCTUnwrap(json.data(using: .utf8)) let decoder = JSONDecoder() - let response = try decoder.decode(SwiftyGPTChatResponse.self, from: data) + let response = try decoder.decode(SwiftyGPTChatResponseBody.self, from: data) XCTAssertEqual(response.id, "chatcmpl-123") XCTAssertEqual(response.object, "chat.completion") XCTAssertEqual(response.created, Date(timeIntervalSince1970: 1677652288)) From e194c85071971ac01af92e0782b6d88327b93ce3 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 22:04:29 +0200 Subject: [PATCH 18/46] feat: add method and body to SwiftyGPTNetworkingRequest --- .../Models/SwiftyGPTNetworkingMethod.swift | 15 +++++++++++++++ .../Models/SwiftyGPTNetworkingRequest.swift | 7 ++++++- .../Models/SwiftyGPTChatMessageTests.swift | 6 ++++++ .../Clients/SwiftyGPTNetworkingClientTests.swift | 2 ++ .../Models/SwiftyGPTNetworkingRequestTests.swift | 14 +++++++++++--- 5 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingMethod.swift diff --git a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingMethod.swift b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingMethod.swift new file mode 100644 index 0000000..b87a8d9 --- /dev/null +++ b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingMethod.swift @@ -0,0 +1,15 @@ +// +// SwiftyGPTNetworkingMethod.swift +// +// +// Created by Antonio Guerra on 11/04/24. +// + +import Foundation + +enum SwiftyGPTNetworkingMethod: String { + case get = "GET" + case post = "POST" + case put = "PUT" + case delete = "DELETE" +} diff --git a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift index 9440805..5b95149 100644 --- a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift +++ b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift @@ -11,6 +11,8 @@ protocol SwiftyGPTNetworkingRequest { var endpoint: URL? { get } var path: String { get } var url: URL { get throws } + var method: SwiftyGPTNetworkingMethod { get } + var body: Data? { get } var cachePolicy: URLRequest.CachePolicy { get } var timeout: TimeInterval { get } var underlyingRequest: URLRequest { get throws } @@ -28,7 +30,10 @@ extension SwiftyGPTNetworkingRequest { var underlyingRequest: URLRequest { get throws { - return try URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout) + var request = try URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout) + request.httpMethod = method.rawValue + request.httpBody = body + return request } } } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift index 00de10e..cc85692 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift @@ -65,4 +65,10 @@ final class SwiftyGPTChatMessageTests: XCTestCase { XCTAssertEqual(message.content, nil) XCTAssertEqual(message.name, "Test") } + + func testToolMessageInit() throws { + let message = SwiftyGPTToolMessage(content: "Test") + XCTAssertEqual(message.role, .tool) + XCTAssertEqual(message.content, "Test") + } } diff --git a/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift index f8c1bd5..2c16bf6 100644 --- a/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift +++ b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift @@ -30,6 +30,8 @@ final class SwiftyGPTNetworkingClientTests: XCTestCase { private struct TestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { let endpoint: URL? = URL(string: "https://picsum.photos/") let path: String = "10" + let method: SwiftyGPTNetworkingMethod = .get + let body: Data? = nil let cachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData let timeout: TimeInterval = 60 } diff --git a/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift index 124c996..dae924b 100644 --- a/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift +++ b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift @@ -14,15 +14,23 @@ final class SwiftyGPTNetworkingRequestTests: XCTestCase { let request = TestSwiftyGPTNetworkingRequest() XCTAssertEqual(request.endpoint, URL(string: "https://picsum.photos/")) XCTAssertEqual(request.path, "10") + XCTAssertEqual(request.method, .get) + XCTAssertEqual(request.body, nil) XCTAssertEqual(request.cachePolicy, .reloadIgnoringLocalAndRemoteCacheData) XCTAssertEqual(request.timeout, 60) - try XCTAssertEqual(request.url, URL(string: request.path, relativeTo: request.endpoint)) - try XCTAssertEqual(request.underlyingRequest, URLRequest(url: request.url, cachePolicy: request.cachePolicy, timeoutInterval: request.timeout)) + try XCTAssertEqual(request.url.absoluteString, "https://picsum.photos/10") + try XCTAssertEqual(request.underlyingRequest.url, URL(string: request.url.absoluteString)) + try XCTAssertEqual(request.underlyingRequest.httpMethod, request.method.rawValue) + try XCTAssertEqual(request.underlyingRequest.httpBody, request.body) + try XCTAssertEqual(request.underlyingRequest.cachePolicy, request.cachePolicy) + try XCTAssertEqual(request.underlyingRequest.timeoutInterval, request.timeout) } - + private struct TestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { let endpoint: URL? = URL(string: "https://picsum.photos/") let path: String = "10" + let method: SwiftyGPTNetworkingMethod = .get + let body: Data? = nil let cachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData let timeout: TimeInterval = 60 } From 591067da4e176ae6377947c4b8e62ab533cba6a6 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 22:11:59 +0200 Subject: [PATCH 19/46] feat: add headers to SwiftyGTPNetworkingRequest --- .../Models/SwiftyGPTNetworkingRequest.swift | 2 ++ .../Clients/SwiftyGPTNetworkingClientTests.swift | 1 + .../Models/SwiftyGPTNetworkingRequestTests.swift | 7 +++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift index 5b95149..545d59b 100644 --- a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift +++ b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift @@ -12,6 +12,7 @@ protocol SwiftyGPTNetworkingRequest { var path: String { get } var url: URL { get throws } var method: SwiftyGPTNetworkingMethod { get } + var headers: [String: String] { get } var body: Data? { get } var cachePolicy: URLRequest.CachePolicy { get } var timeout: TimeInterval { get } @@ -33,6 +34,7 @@ extension SwiftyGPTNetworkingRequest { var request = try URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeout) request.httpMethod = method.rawValue request.httpBody = body + request.allHTTPHeaderFields = headers return request } } diff --git a/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift index 2c16bf6..9462819 100644 --- a/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift +++ b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift @@ -32,6 +32,7 @@ final class SwiftyGPTNetworkingClientTests: XCTestCase { let path: String = "10" let method: SwiftyGPTNetworkingMethod = .get let body: Data? = nil + let headers: [String : String] = [:] let cachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData let timeout: TimeInterval = 60 } diff --git a/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift index dae924b..f49cd95 100644 --- a/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift +++ b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift @@ -15,13 +15,15 @@ final class SwiftyGPTNetworkingRequestTests: XCTestCase { XCTAssertEqual(request.endpoint, URL(string: "https://picsum.photos/")) XCTAssertEqual(request.path, "10") XCTAssertEqual(request.method, .get) - XCTAssertEqual(request.body, nil) + XCTAssertEqual(request.body, Data()) + XCTAssertEqual(request.headers, ["Key":"Value"]) XCTAssertEqual(request.cachePolicy, .reloadIgnoringLocalAndRemoteCacheData) XCTAssertEqual(request.timeout, 60) try XCTAssertEqual(request.url.absoluteString, "https://picsum.photos/10") try XCTAssertEqual(request.underlyingRequest.url, URL(string: request.url.absoluteString)) try XCTAssertEqual(request.underlyingRequest.httpMethod, request.method.rawValue) try XCTAssertEqual(request.underlyingRequest.httpBody, request.body) + try XCTAssertEqual(request.underlyingRequest.allHTTPHeaderFields, request.headers) try XCTAssertEqual(request.underlyingRequest.cachePolicy, request.cachePolicy) try XCTAssertEqual(request.underlyingRequest.timeoutInterval, request.timeout) } @@ -30,7 +32,8 @@ final class SwiftyGPTNetworkingRequestTests: XCTestCase { let endpoint: URL? = URL(string: "https://picsum.photos/") let path: String = "10" let method: SwiftyGPTNetworkingMethod = .get - let body: Data? = nil + let body: Data? = Data() + let headers: [String : String] = ["Key":"Value"] let cachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData let timeout: TimeInterval = 60 } From 8c544d55dd2269167ead8bb919f5eefa7064fae2 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Thu, 11 Apr 2024 22:38:50 +0200 Subject: [PATCH 20/46] feat: add url scheme checking --- .../Models/SwiftyGPTNetworkingRequest.swift | 2 +- .../Models/SwiftyGPTNetworkingRequestTests.swift | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift index 545d59b..a690bec 100644 --- a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift +++ b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift @@ -22,7 +22,7 @@ protocol SwiftyGPTNetworkingRequest { extension SwiftyGPTNetworkingRequest { var url: URL { get throws { - guard let endpoint, let url = URL(string: path, relativeTo: endpoint) else { + guard let endpoint, let scheme = endpoint.scheme, scheme.lowercased().hasPrefix("http"), let url = URL(string: path, relativeTo: endpoint) else { throw URLError(.badURL) } return url diff --git a/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift index f49cd95..ce1fce2 100644 --- a/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift +++ b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift @@ -28,6 +28,12 @@ final class SwiftyGPTNetworkingRequestTests: XCTestCase { try XCTAssertEqual(request.underlyingRequest.timeoutInterval, request.timeout) } + func testInitWhenRequestIsInvalid() { + let request = InvalidTestSwiftyGPTNetworkingRequest() + try XCTAssertThrowsError(request.url) + try XCTAssertThrowsError(request.underlyingRequest) + } + private struct TestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { let endpoint: URL? = URL(string: "https://picsum.photos/") let path: String = "10" @@ -37,4 +43,14 @@ final class SwiftyGPTNetworkingRequestTests: XCTestCase { let cachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData let timeout: TimeInterval = 60 } + + private struct InvalidTestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { + let endpoint: URL? = URL(string: "file://picsum.photos/") + let path: String = "10" + let method: SwiftyGPTNetworkingMethod = .get + let body: Data? = Data() + let headers: [String : String] = ["Key":"Value"] + let cachePolicy: URLRequest.CachePolicy = .reloadIgnoringLocalAndRemoteCacheData + let timeout: TimeInterval = 60 + } } From e573d3a3ce5877d2e6ecddcbba41d041d132f509 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 12 Apr 2024 14:12:30 +0200 Subject: [PATCH 21/46] fix: SwiftyGPTChatMessage encoding --- .../Models/SwiftyGPTChatMessage.swift | 60 +++++++++++++++++-- .../Models/SwiftyGPTChatModel.swift | 2 +- .../Models/SwiftyGPTChatRequestBody.swift | 19 +++++- .../Models/SwiftyGPTChatResponseBody.swift | 10 +--- .../Models/SwiftyGPTChatRole.swift | 2 +- 5 files changed, 74 insertions(+), 19 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index 638587f..cb92d3f 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -7,9 +7,7 @@ import Foundation -import Foundation - -protocol SwiftyGPTChatMessage { +protocol SwiftyGPTChatMessage: Encodable { var role: SwiftyGPTChatRole { get } } @@ -22,17 +20,43 @@ struct SwiftyGPTSystemMessage: SwiftyGPTChatMessage { self.content = content self.name = name } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(role, forKey: .role) + try container.encode(content, forKey: .content) + try container.encodeIfPresent(name, forKey: .name) + } + + enum CodingKeys: CodingKey { + case role + case content + case name + } } struct SwiftyGPTUserMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .user let content: String let name: String? - + init(content: String, name: String? = nil) { self.content = content self.name = name } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(role, forKey: .role) + try container.encode(content, forKey: .content) + try container.encodeIfPresent(name, forKey: .name) + } + + enum CodingKeys: CodingKey { + case role + case content + case name + } } struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { @@ -40,19 +64,43 @@ struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { let content: String? let name: String? // TODO: add tool_calls - + init(content: String? = nil, name: String? = nil) { self.content = content self.name = name } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.role, forKey: .role) + try container.encodeIfPresent(self.content, forKey: .content) + try container.encodeIfPresent(self.name, forKey: .name) + } + + enum CodingKeys: CodingKey { + case role + case content + case name + } } struct SwiftyGPTToolMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .tool let content: String // TODO: add tool_call_id - + init(content: String) { self.content = content } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.role, forKey: .role) + try container.encode(self.content, forKey: .content) + } + + enum CodingKeys: CodingKey { + case role + case content + } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift index caeef9f..27a0e8d 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift @@ -7,7 +7,7 @@ import Foundation -enum SwiftyGPTChatModel: String, Codable { +enum SwiftyGPTChatModel: String, Encodable { case gpt3_5_turbo = "gpt-3.5-turbo" case gpt3_5_turbo_16k = "gpt-3.5-turbo-16k" case gpt3_5_turbo_16k_0613 = "gpt-3.5-turbo-16k-0613" diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index 18e3a12..f813867 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -7,9 +7,9 @@ import Foundation -struct SwiftyGPTChatRequestBody { +struct SwiftyGPTChatRequestBody: Encodable { let model: SwiftyGPTChatModel - let messages: [SwiftyGPTChatMessage] +// let messages: [SwiftyGPTChatMessage] let frequencyPenalty: Double? // TODO: add logit_bias let logprobs: Bool? @@ -25,4 +25,19 @@ struct SwiftyGPTChatRequestBody { let nucleusSampling: Double? // TODO: add tools let user: String? + + enum CodingKeys: CodingKey { + case model +// case messages + case frequencyPenalty + case logprobs + case topLogprobs + case maxTokens + case choices + case presencePenalty + case seed + case temperature + case nucleusSampling + case user + } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift index a048b86..6722910 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift @@ -7,7 +7,7 @@ import Foundation -struct SwiftyGPTChatResponseBody: Identifiable, Decodable { +struct SwiftyGPTChatResponseBody: Identifiable { let id: String // TODO: add choices let created: Date @@ -22,12 +22,4 @@ struct SwiftyGPTChatResponseBody: Identifiable, Decodable { case model case fingerprint = "system_fingerprint" } - - init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.created = try Date(timeIntervalSince1970: container.decode(TimeInterval.self, forKey: .created)) - self.model = try container.decode(SwiftyGPTChatModel.self, forKey: .model) - self.fingerprint = try container.decode(String.self, forKey: .fingerprint) - } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift index c0a8c56..0049660 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift @@ -7,7 +7,7 @@ import Foundation -public enum SwiftyGPTChatRole: String { +public enum SwiftyGPTChatRole: String, Encodable { case system case user case assistant From 560dcba3e578832c883b8f24c13f3b47e07371b4 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 12 Apr 2024 14:47:19 +0200 Subject: [PATCH 22/46] fix: update SwiftyGPTChatMessage structure --- .../Models/SwiftyGPTChatMessage.swift | 29 +++++++++++++++---- .../Models/SwiftyGPTChatRequestBody.swift | 6 ++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index cb92d3f..e32bc06 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -7,11 +7,28 @@ import Foundation -protocol SwiftyGPTChatMessage: Encodable { - var role: SwiftyGPTChatRole { get } +enum SwiftyGPTChatMessage: Encodable { + case system(SwiftyGPTSystemMessage) + case user(SwiftyGPTUserMessage) + case assistant(SwiftyGPTAssistantMessage) + case tool(SwiftyGPTToolMessage) + + func encode(to encoder: any Encoder) throws { + var singleContainer = encoder.singleValueContainer() + switch self { + case .system(let message): + try singleContainer.encode(message) + case .user(let message): + try singleContainer.encode(message) + case .assistant(let message): + try singleContainer.encode(message) + case .tool(let message): + try singleContainer.encode(message) + } + } } -struct SwiftyGPTSystemMessage: SwiftyGPTChatMessage { +struct SwiftyGPTSystemMessage: Encodable { let role: SwiftyGPTChatRole = .system let content: String let name: String? @@ -35,7 +52,7 @@ struct SwiftyGPTSystemMessage: SwiftyGPTChatMessage { } } -struct SwiftyGPTUserMessage: SwiftyGPTChatMessage { +struct SwiftyGPTUserMessage: Encodable { let role: SwiftyGPTChatRole = .user let content: String let name: String? @@ -59,7 +76,7 @@ struct SwiftyGPTUserMessage: SwiftyGPTChatMessage { } } -struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { +struct SwiftyGPTAssistantMessage: Encodable { let role: SwiftyGPTChatRole = .assistant let content: String? let name: String? @@ -84,7 +101,7 @@ struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { } } -struct SwiftyGPTToolMessage: SwiftyGPTChatMessage { +struct SwiftyGPTToolMessage: Encodable { let role: SwiftyGPTChatRole = .tool let content: String // TODO: add tool_call_id diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index f813867..b19f61b 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -9,7 +9,7 @@ import Foundation struct SwiftyGPTChatRequestBody: Encodable { let model: SwiftyGPTChatModel -// let messages: [SwiftyGPTChatMessage] + let messages: [SwiftyGPTChatMessage] let frequencyPenalty: Double? // TODO: add logit_bias let logprobs: Bool? @@ -25,10 +25,10 @@ struct SwiftyGPTChatRequestBody: Encodable { let nucleusSampling: Double? // TODO: add tools let user: String? - + enum CodingKeys: CodingKey { case model -// case messages + case messages case frequencyPenalty case logprobs case topLogprobs From 4b6e82e71e801770172c056a579ba79563260ef6 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 12 Apr 2024 14:50:22 +0200 Subject: [PATCH 23/46] refactor: rename old message concept to message content --- .../Models/SwiftyGPTChatMessage.swift | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index e32bc06..804ccbc 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -8,27 +8,29 @@ import Foundation enum SwiftyGPTChatMessage: Encodable { - case system(SwiftyGPTSystemMessage) - case user(SwiftyGPTUserMessage) - case assistant(SwiftyGPTAssistantMessage) - case tool(SwiftyGPTToolMessage) + case system(SwiftyGPTSystemMessageContent) + case user(SwiftyGPTUserMessageContent) + case assistant(SwiftyGPTAssistantMessageContent) + case tool(SwiftyGPTToolMessageContent) func encode(to encoder: any Encoder) throws { var singleContainer = encoder.singleValueContainer() switch self { - case .system(let message): - try singleContainer.encode(message) - case .user(let message): - try singleContainer.encode(message) - case .assistant(let message): - try singleContainer.encode(message) - case .tool(let message): - try singleContainer.encode(message) + case .system(let content): + try singleContainer.encode(content) + case .user(let content): + try singleContainer.encode(content) + case .assistant(let content): + try singleContainer.encode(content) + case .tool(let content): + try singleContainer.encode(content) } } + + protocol Content: Encodable {} } -struct SwiftyGPTSystemMessage: Encodable { +struct SwiftyGPTSystemMessageContent: SwiftyGPTChatMessage.Content { let role: SwiftyGPTChatRole = .system let content: String let name: String? @@ -52,7 +54,7 @@ struct SwiftyGPTSystemMessage: Encodable { } } -struct SwiftyGPTUserMessage: Encodable { +struct SwiftyGPTUserMessageContent: SwiftyGPTChatMessage.Content { let role: SwiftyGPTChatRole = .user let content: String let name: String? @@ -76,7 +78,7 @@ struct SwiftyGPTUserMessage: Encodable { } } -struct SwiftyGPTAssistantMessage: Encodable { +struct SwiftyGPTAssistantMessageContent: SwiftyGPTChatMessage.Content { let role: SwiftyGPTChatRole = .assistant let content: String? let name: String? @@ -101,7 +103,7 @@ struct SwiftyGPTAssistantMessage: Encodable { } } -struct SwiftyGPTToolMessage: Encodable { +struct SwiftyGPTToolMessageContent: SwiftyGPTChatMessage.Content { let role: SwiftyGPTChatRole = .tool let content: String // TODO: add tool_call_id From 0f7b1f90b695fe526c09d0eafd11a8e588ed48a6 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 12 Apr 2024 14:56:59 +0200 Subject: [PATCH 24/46] Revert "refactor: rename old message concept to message content" This reverts commit 4b6e82e71e801770172c056a579ba79563260ef6. --- .../Models/SwiftyGPTChatMessage.swift | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index 804ccbc..e32bc06 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -8,29 +8,27 @@ import Foundation enum SwiftyGPTChatMessage: Encodable { - case system(SwiftyGPTSystemMessageContent) - case user(SwiftyGPTUserMessageContent) - case assistant(SwiftyGPTAssistantMessageContent) - case tool(SwiftyGPTToolMessageContent) + case system(SwiftyGPTSystemMessage) + case user(SwiftyGPTUserMessage) + case assistant(SwiftyGPTAssistantMessage) + case tool(SwiftyGPTToolMessage) func encode(to encoder: any Encoder) throws { var singleContainer = encoder.singleValueContainer() switch self { - case .system(let content): - try singleContainer.encode(content) - case .user(let content): - try singleContainer.encode(content) - case .assistant(let content): - try singleContainer.encode(content) - case .tool(let content): - try singleContainer.encode(content) + case .system(let message): + try singleContainer.encode(message) + case .user(let message): + try singleContainer.encode(message) + case .assistant(let message): + try singleContainer.encode(message) + case .tool(let message): + try singleContainer.encode(message) } } - - protocol Content: Encodable {} } -struct SwiftyGPTSystemMessageContent: SwiftyGPTChatMessage.Content { +struct SwiftyGPTSystemMessage: Encodable { let role: SwiftyGPTChatRole = .system let content: String let name: String? @@ -54,7 +52,7 @@ struct SwiftyGPTSystemMessageContent: SwiftyGPTChatMessage.Content { } } -struct SwiftyGPTUserMessageContent: SwiftyGPTChatMessage.Content { +struct SwiftyGPTUserMessage: Encodable { let role: SwiftyGPTChatRole = .user let content: String let name: String? @@ -78,7 +76,7 @@ struct SwiftyGPTUserMessageContent: SwiftyGPTChatMessage.Content { } } -struct SwiftyGPTAssistantMessageContent: SwiftyGPTChatMessage.Content { +struct SwiftyGPTAssistantMessage: Encodable { let role: SwiftyGPTChatRole = .assistant let content: String? let name: String? @@ -103,7 +101,7 @@ struct SwiftyGPTAssistantMessageContent: SwiftyGPTChatMessage.Content { } } -struct SwiftyGPTToolMessageContent: SwiftyGPTChatMessage.Content { +struct SwiftyGPTToolMessage: Encodable { let role: SwiftyGPTChatRole = .tool let content: String // TODO: add tool_call_id From 2b51f051bbba521b79785c878cbd7d08f38b52ca Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 12 Apr 2024 15:07:24 +0200 Subject: [PATCH 25/46] feat: introduce encoded message --- ...wift => SwiftyGPTChatEncodedMessage.swift} | 52 +++++++++------- .../Models/SwiftyGPTChatRequestBody.swift | 19 +++++- .../SwiftyGPTChatResponseBodyTests.swift | 62 +++++++++---------- 3 files changed, 77 insertions(+), 56 deletions(-) rename Sources/SwiftyGPTChat/Models/{SwiftyGPTChatMessage.swift => SwiftyGPTChatEncodedMessage.swift} (88%) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatEncodedMessage.swift similarity index 88% rename from Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift rename to Sources/SwiftyGPTChat/Models/SwiftyGPTChatEncodedMessage.swift index e32bc06..900310b 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatEncodedMessage.swift @@ -1,5 +1,5 @@ // -// SwiftyGPTChatMessage.swift +// SwiftyGPTChatEncodedMessage.swift // // // Created by Antonio Guerra on 11/04/24. @@ -7,28 +7,11 @@ import Foundation -enum SwiftyGPTChatMessage: Encodable { - case system(SwiftyGPTSystemMessage) - case user(SwiftyGPTUserMessage) - case assistant(SwiftyGPTAssistantMessage) - case tool(SwiftyGPTToolMessage) - - func encode(to encoder: any Encoder) throws { - var singleContainer = encoder.singleValueContainer() - switch self { - case .system(let message): - try singleContainer.encode(message) - case .user(let message): - try singleContainer.encode(message) - case .assistant(let message): - try singleContainer.encode(message) - case .tool(let message): - try singleContainer.encode(message) - } - } +protocol SwiftyGPTChatMessage: Encodable { + var role: SwiftyGPTChatRole { get } } -struct SwiftyGPTSystemMessage: Encodable { +struct SwiftyGPTSystemMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .system let content: String let name: String? @@ -52,7 +35,7 @@ struct SwiftyGPTSystemMessage: Encodable { } } -struct SwiftyGPTUserMessage: Encodable { +struct SwiftyGPTUserMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .user let content: String let name: String? @@ -76,7 +59,7 @@ struct SwiftyGPTUserMessage: Encodable { } } -struct SwiftyGPTAssistantMessage: Encodable { +struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .assistant let content: String? let name: String? @@ -101,7 +84,7 @@ struct SwiftyGPTAssistantMessage: Encodable { } } -struct SwiftyGPTToolMessage: Encodable { +struct SwiftyGPTToolMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .tool let content: String // TODO: add tool_call_id @@ -121,3 +104,24 @@ struct SwiftyGPTToolMessage: Encodable { case content } } + +enum SwiftyGPTChatEncodedMessage: Encodable { + case system(SwiftyGPTSystemMessage) + case user(SwiftyGPTUserMessage) + case assistant(SwiftyGPTAssistantMessage) + case tool(SwiftyGPTToolMessage) + + func encode(to encoder: any Encoder) throws { + var singleContainer = encoder.singleValueContainer() + switch self { + case .system(let message): + try singleContainer.encode(message) + case .user(let message): + try singleContainer.encode(message) + case .assistant(let message): + try singleContainer.encode(message) + case .tool(let message): + try singleContainer.encode(message) + } + } +} diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index b19f61b..1a13509 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -26,9 +26,26 @@ struct SwiftyGPTChatRequestBody: Encodable { // TODO: add tools let user: String? + var encodedMessages: [SwiftyGPTChatEncodedMessage] { + return messages.compactMap { + switch $0 { + case let message as SwiftyGPTSystemMessage: + return .system(message) + case let message as SwiftyGPTUserMessage: + return .user(message) + case let message as SwiftyGPTAssistantMessage: + return .assistant(message) + case let message as SwiftyGPTToolMessage: + return .tool(message) + default: + return .none + } + } + } + enum CodingKeys: CodingKey { case model - case messages +// case messages case frequencyPenalty case logprobs case topLogprobs diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift index 5db7d67..c57080f 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift @@ -11,36 +11,36 @@ import XCTest final class SwiftyGPTChatResponseBodyTests: XCTestCase { func testDecoding() throws { - let json = """ - { - "id": "chatcmpl-123", - "object": "chat.completion", - "created": 1677652288, - "model": "gpt-3.5-turbo-0125", - "system_fingerprint": "fp_44709d6fcb", - "choices": [{ - "index": 0, - "message": { - "role": "assistant", - "content": "\n\nHello there, how may I assist you today?", - }, - "logprobs": null, - "finish_reason": "stop" - }], - "usage": { - "prompt_tokens": 9, - "completion_tokens": 12, - "total_tokens": 21 - } - } - """ - let data = try XCTUnwrap(json.data(using: .utf8)) - let decoder = JSONDecoder() - let response = try decoder.decode(SwiftyGPTChatResponseBody.self, from: data) - XCTAssertEqual(response.id, "chatcmpl-123") - XCTAssertEqual(response.object, "chat.completion") - XCTAssertEqual(response.created, Date(timeIntervalSince1970: 1677652288)) - XCTAssertEqual(response.model, .gpt3_5_turbo_0125) - XCTAssertEqual(response.fingerprint, "fp_44709d6fcb") +// let json = """ +// { +// "id": "chatcmpl-123", +// "object": "chat.completion", +// "created": 1677652288, +// "model": "gpt-3.5-turbo-0125", +// "system_fingerprint": "fp_44709d6fcb", +// "choices": [{ +// "index": 0, +// "message": { +// "role": "assistant", +// "content": "\n\nHello there, how may I assist you today?", +// }, +// "logprobs": null, +// "finish_reason": "stop" +// }], +// "usage": { +// "prompt_tokens": 9, +// "completion_tokens": 12, +// "total_tokens": 21 +// } +// } +// """ +// let data = try XCTUnwrap(json.data(using: .utf8)) +// let decoder = JSONDecoder() +// let response = try decoder.decode(SwiftyGPTChatResponseBody.self, from: data) +// XCTAssertEqual(response.id, "chatcmpl-123") +// XCTAssertEqual(response.object, "chat.completion") +// XCTAssertEqual(response.created, Date(timeIntervalSince1970: 1677652288)) +// XCTAssertEqual(response.model, .gpt3_5_turbo_0125) +// XCTAssertEqual(response.fingerprint, "fp_44709d6fcb") } } From 31c27688db64f4b1aa45624c6467d97d40adcb8d Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 12 Apr 2024 15:08:35 +0200 Subject: [PATCH 26/46] fix: rename SwiftyGPTChatEncodedMessage to SwiftyGPTChatMessage --- ...tyGPTChatEncodedMessage.swift => SwiftyGPTChatMessage.swift} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Sources/SwiftyGPTChat/Models/{SwiftyGPTChatEncodedMessage.swift => SwiftyGPTChatMessage.swift} (98%) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatEncodedMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift similarity index 98% rename from Sources/SwiftyGPTChat/Models/SwiftyGPTChatEncodedMessage.swift rename to Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index 900310b..37115b3 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatEncodedMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -1,5 +1,5 @@ // -// SwiftyGPTChatEncodedMessage.swift +// SwiftyGPTChatMessage.swift // // // Created by Antonio Guerra on 11/04/24. From 18275e1ee736ebb3e49b9836e650ff41a9587758 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 12 Apr 2024 17:39:06 +0200 Subject: [PATCH 27/46] feat: add encoding to SwiftGPTChatMessage --- .../Models/SwiftyGPTChatMessage.swift | 2 +- .../Models/SwiftyGPTChatRequestBody.swift | 40 ++++++++++++++----- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index 37115b3..ee75124 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -105,7 +105,7 @@ struct SwiftyGPTToolMessage: SwiftyGPTChatMessage { } } -enum SwiftyGPTChatEncodedMessage: Encodable { +enum SwiftyGPTChatCodableMessage: Encodable { case system(SwiftyGPTSystemMessage) case user(SwiftyGPTUserMessage) case assistant(SwiftyGPTAssistantMessage) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index 1a13509..98b698b 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -8,10 +8,11 @@ import Foundation struct SwiftyGPTChatRequestBody: Encodable { - let model: SwiftyGPTChatModel let messages: [SwiftyGPTChatMessage] + let model: SwiftyGPTChatModel let frequencyPenalty: Double? // TODO: add logit_bias + // TODO: need to refactor logprobs and topLogprobs ?? let logprobs: Bool? let topLogprobs: Int? let maxTokens: Int? @@ -26,7 +27,7 @@ struct SwiftyGPTChatRequestBody: Encodable { // TODO: add tools let user: String? - var encodedMessages: [SwiftyGPTChatEncodedMessage] { + var codableMessages: [SwiftyGPTChatCodableMessage] { return messages.compactMap { switch $0 { case let message as SwiftyGPTSystemMessage: @@ -42,19 +43,36 @@ struct SwiftyGPTChatRequestBody: Encodable { } } } - - enum CodingKeys: CodingKey { + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(model, forKey: .model) + try container.encode(codableMessages, forKey: .messages) + try container.encodeIfPresent(frequencyPenalty, forKey: .frequencyPenalty) + try container.encodeIfPresent(logprobs, forKey: .logprobs) + try container.encodeIfPresent(topLogprobs, forKey: .topLogprobs) + try container.encodeIfPresent(maxTokens, forKey: .maxTokens) + try container.encodeIfPresent(choices, forKey: .choices) + try container.encodeIfPresent(presencePenalty, forKey: .presencePenalty) + try container.encodeIfPresent(seed, forKey: .seed) + try container.encodeIfPresent(temperature, forKey: .temperature) + try container.encodeIfPresent(nucleusSampling, forKey: .nucleusSampling) + try container.encodeIfPresent(user, forKey: .user) + } + + + enum CodingKeys: String, CodingKey { + case messages case model -// case messages - case frequencyPenalty + case frequencyPenalty = "frequency_penalty" case logprobs - case topLogprobs - case maxTokens - case choices - case presencePenalty + case topLogprobs = "top_logprobs" + case maxTokens = "max_tokens" + case choices = "n" + case presencePenalty = "presence_penalty" case seed case temperature - case nucleusSampling + case nucleusSampling = "top_p" case user } } From 26be4d602d2b8e8ba79df2eee47fbdd926141b64 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 12 Apr 2024 18:04:53 +0200 Subject: [PATCH 28/46] feat: add logProbability and Sampling --- .../Models/SwiftyGPTChatRequestBody.swift | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index 98b698b..ea4dd53 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -12,9 +12,7 @@ struct SwiftyGPTChatRequestBody: Encodable { let model: SwiftyGPTChatModel let frequencyPenalty: Double? // TODO: add logit_bias - // TODO: need to refactor logprobs and topLogprobs ?? - let logprobs: Bool? - let topLogprobs: Int? + let logProbability: LogProbability? let maxTokens: Int? let choices: Int? let presencePenalty: Double? @@ -22,8 +20,7 @@ struct SwiftyGPTChatRequestBody: Encodable { let seed: Int? // TODO: add stop // TODO: add stream - let temperature: Double? - let nucleusSampling: Double? + let sampling: Sampling? // TODO: add tools let user: String? @@ -49,17 +46,39 @@ struct SwiftyGPTChatRequestBody: Encodable { try container.encode(model, forKey: .model) try container.encode(codableMessages, forKey: .messages) try container.encodeIfPresent(frequencyPenalty, forKey: .frequencyPenalty) - try container.encodeIfPresent(logprobs, forKey: .logprobs) - try container.encodeIfPresent(topLogprobs, forKey: .topLogprobs) + if let logProbability { + switch logProbability { + case .enabled(let value): + try container.encode(true, forKey: .logprobs) + try container.encode(value, forKey: .topLogprobs) + case .disabled: + try container.encode(false, forKey: .logprobs) + } + } try container.encodeIfPresent(maxTokens, forKey: .maxTokens) try container.encodeIfPresent(choices, forKey: .choices) try container.encodeIfPresent(presencePenalty, forKey: .presencePenalty) try container.encodeIfPresent(seed, forKey: .seed) - try container.encodeIfPresent(temperature, forKey: .temperature) - try container.encodeIfPresent(nucleusSampling, forKey: .nucleusSampling) + if let sampling { + switch sampling { + case .temperature(let value): + try container.encode(value, forKey: .temperature) + case .nucleus(let value): + try container.encode(value, forKey: .nucleus) + } + } try container.encodeIfPresent(user, forKey: .user) } + enum LogProbability { + case enabled(Int) + case disabled + } + + enum Sampling { + case temperature(Double) + case nucleus(Double) + } enum CodingKeys: String, CodingKey { case messages @@ -72,7 +91,7 @@ struct SwiftyGPTChatRequestBody: Encodable { case presencePenalty = "presence_penalty" case seed case temperature - case nucleusSampling = "top_p" + case nucleus = "top_p" case user } } From 8c0f18e697dd7077586d7e8b0914fbae00e19a23 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 13 Apr 2024 09:11:48 +0200 Subject: [PATCH 29/46] feat: add SwiftyGTPChatRequestBodyTests --- .../Models/SwiftyGPTChatMessage.swift | 26 ++++++-- .../Models/SwiftyGPTChatRequestBody.swift | 62 +++++++++---------- .../Models/SwiftyGPTChatMessageTests.swift | 20 +----- .../SwiftyGPTChatRequestBodyTests.swift | 41 ++++++++++++ 4 files changed, 93 insertions(+), 56 deletions(-) create mode 100644 Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestBodyTests.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index ee75124..f9520b6 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -7,7 +7,7 @@ import Foundation -protocol SwiftyGPTChatMessage: Encodable { +protocol SwiftyGPTChatMessage: Equatable, Encodable { var role: SwiftyGPTChatRole { get } } @@ -61,11 +61,12 @@ struct SwiftyGPTUserMessage: SwiftyGPTChatMessage { struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .assistant - let content: String? + // TODO: maybe need to manage better content or tool_calls + let content: String let name: String? // TODO: add tool_calls - init(content: String? = nil, name: String? = nil) { + init(content: String, name: String? = nil) { self.content = content self.name = name } @@ -73,7 +74,7 @@ struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.role, forKey: .role) - try container.encodeIfPresent(self.content, forKey: .content) + try container.encode(self.content, forKey: .content) try container.encodeIfPresent(self.name, forKey: .name) } @@ -105,7 +106,7 @@ struct SwiftyGPTToolMessage: SwiftyGPTChatMessage { } } -enum SwiftyGPTChatCodableMessage: Encodable { +enum SwiftyGPTChatCodableMessage: Equatable, Encodable { case system(SwiftyGPTSystemMessage) case user(SwiftyGPTUserMessage) case assistant(SwiftyGPTAssistantMessage) @@ -124,4 +125,19 @@ enum SwiftyGPTChatCodableMessage: Encodable { try singleContainer.encode(message) } } + + static func == (lhs: SwiftyGPTChatCodableMessage, rhs: SwiftyGPTChatCodableMessage) -> Bool { + switch (lhs, rhs) { + case (.system(let lhsMessage), .system(let rhsMessage)): + return lhsMessage == rhsMessage + case (.user(let lhsMessage), .user(let rhsMessage)): + return lhsMessage == rhsMessage + case (.assistant(let lhsMessage), .assistant(let rhsMessage)): + return lhsMessage == rhsMessage + case (.tool(let lhsMessage), .tool(let rhsMessage)): + return lhsMessage == rhsMessage + default: + return false + } + } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index ea4dd53..09417f2 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -8,19 +8,21 @@ import Foundation struct SwiftyGPTChatRequestBody: Encodable { - let messages: [SwiftyGPTChatMessage] + let messages: [any SwiftyGPTChatMessage] let model: SwiftyGPTChatModel let frequencyPenalty: Double? // TODO: add logit_bias - let logProbability: LogProbability? + let logprobs: Bool? + let topLogprobs: Int? let maxTokens: Int? - let choices: Int? + let n: Int? let presencePenalty: Double? // TODO: add response_format let seed: Int? // TODO: add stop // TODO: add stream - let sampling: Sampling? + let temperature: Double? + let topP: Double? // TODO: add tools let user: String? @@ -41,44 +43,36 @@ struct SwiftyGPTChatRequestBody: Encodable { } } + init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) { + self.messages = messages + self.model = model + self.frequencyPenalty = frequencyPenalty + self.logprobs = logprobs + self.topLogprobs = topLogprobs + self.maxTokens = maxTokens + self.n = n + self.presencePenalty = presencePenalty + self.seed = seed + self.temperature = temperature + self.topP = topP + self.user = user + } + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(model, forKey: .model) try container.encode(codableMessages, forKey: .messages) try container.encodeIfPresent(frequencyPenalty, forKey: .frequencyPenalty) - if let logProbability { - switch logProbability { - case .enabled(let value): - try container.encode(true, forKey: .logprobs) - try container.encode(value, forKey: .topLogprobs) - case .disabled: - try container.encode(false, forKey: .logprobs) - } - } + try container.encodeIfPresent(logprobs, forKey: .logprobs) + try container.encodeIfPresent(topLogprobs, forKey: .topLogprobs) try container.encodeIfPresent(maxTokens, forKey: .maxTokens) - try container.encodeIfPresent(choices, forKey: .choices) + try container.encodeIfPresent(n, forKey: .n) try container.encodeIfPresent(presencePenalty, forKey: .presencePenalty) try container.encodeIfPresent(seed, forKey: .seed) - if let sampling { - switch sampling { - case .temperature(let value): - try container.encode(value, forKey: .temperature) - case .nucleus(let value): - try container.encode(value, forKey: .nucleus) - } - } + try container.encodeIfPresent(temperature, forKey: .temperature) + try container.encodeIfPresent(topP, forKey: .topP) try container.encodeIfPresent(user, forKey: .user) } - - enum LogProbability { - case enabled(Int) - case disabled - } - - enum Sampling { - case temperature(Double) - case nucleus(Double) - } enum CodingKeys: String, CodingKey { case messages @@ -87,11 +81,11 @@ struct SwiftyGPTChatRequestBody: Encodable { case logprobs case topLogprobs = "top_logprobs" case maxTokens = "max_tokens" - case choices = "n" + case n = "n" case presencePenalty = "presence_penalty" case seed case temperature - case nucleus = "top_p" + case topP = "top_p" case user } } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift index cc85692..e5e63b5 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift @@ -38,31 +38,17 @@ final class SwiftyGPTChatMessageTests: XCTestCase { XCTAssertEqual(message.name, "Test") } - func testAssistantMessageInitWhenContentIsNil() throws { - let message = SwiftyGPTAssistantMessage() - XCTAssertEqual(message.role, .assistant) - XCTAssertEqual(message.content, nil) - XCTAssertEqual(message.name, nil) - } - - func testUserMessageInitWhenContentIsNotNil() throws { + func testAssistantMessageInitWhenNameIsNil() throws { let message = SwiftyGPTAssistantMessage(content: "Test") XCTAssertEqual(message.role, .assistant) XCTAssertEqual(message.content, "Test") XCTAssertEqual(message.name, nil) } - func testAssistantMessageInitWhenNameIsNil() throws { - let message = SwiftyGPTAssistantMessage() - XCTAssertEqual(message.role, .assistant) - XCTAssertEqual(message.content, nil) - XCTAssertEqual(message.name, nil) - } - func testAssistantMessageInitWhenNameIsNotNil() throws { - let message = SwiftyGPTAssistantMessage(name: "Test") + let message = SwiftyGPTAssistantMessage(content: "Test", name: "Test") XCTAssertEqual(message.role, .assistant) - XCTAssertEqual(message.content, nil) + XCTAssertEqual(message.content, "Test") XCTAssertEqual(message.name, "Test") } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestBodyTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestBodyTests.swift new file mode 100644 index 0000000..e6ebc6f --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestBodyTests.swift @@ -0,0 +1,41 @@ +// +// SwiftyGPTChatRequestBodyTests.swift +// +// +// Created by Antonio Guerra on 12/04/24. +// + +@testable import SwiftyGPTChat +import XCTest + +final class SwiftyGPTChatRequestBodyTests: XCTestCase { + + func testCodableMessages() { + let requestBody = SwiftyGPTChatRequestBody(messages: [ + SwiftyGPTSystemMessage(content: "Test"), + SwiftyGPTAssistantMessage(content: "Test"), + SwiftyGPTUserMessage(content: "Test"), + SwiftyGPTToolMessage(content: "Test") + ], model: .gpt3_5_turbo) + + XCTAssertEqual(requestBody.codableMessages.count, 4) + XCTAssertEqual(requestBody.codableMessages[0], .system(SwiftyGPTSystemMessage(content: "Test"))) + XCTAssertEqual(requestBody.codableMessages[1], .assistant(SwiftyGPTAssistantMessage(content: "Test"))) + XCTAssertEqual(requestBody.codableMessages[2], .user(SwiftyGPTUserMessage(content: "Test"))) + XCTAssertEqual(requestBody.codableMessages[3], .tool(SwiftyGPTToolMessage(content: "Test"))) + } + + func testCodableMessagesWhenTypeIsUnknown() { + let requestBody = SwiftyGPTChatRequestBody(messages: [ + SwiftyGPTUnknownMessage(role: .user), + ], model: .gpt3_5_turbo) + + XCTAssertTrue(requestBody.codableMessages.isEmpty) + } + + private struct SwiftyGPTUnknownMessage: SwiftyGPTChatMessage { + var role: SwiftyGPTChatRole + } +} + + From 1e0356ba5bc606797d5a1440cfce7debb2da7b23 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 13 Apr 2024 09:13:50 +0200 Subject: [PATCH 30/46] refactor: rename SwiftyGPTChatMessage sub-types --- .../Models/SwiftyGPTChatMessage.swift | 16 ++++++++-------- .../Models/SwiftyGPTChatRequestBody.swift | 8 ++++---- .../Models/SwiftyGPTChatMessageTests.swift | 14 +++++++------- .../Models/SwiftyGPTChatRequestBodyTests.swift | 16 ++++++++-------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index f9520b6..3a35b1a 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -11,7 +11,7 @@ protocol SwiftyGPTChatMessage: Equatable, Encodable { var role: SwiftyGPTChatRole { get } } -struct SwiftyGPTSystemMessage: SwiftyGPTChatMessage { +struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .system let content: String let name: String? @@ -35,7 +35,7 @@ struct SwiftyGPTSystemMessage: SwiftyGPTChatMessage { } } -struct SwiftyGPTUserMessage: SwiftyGPTChatMessage { +struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .user let content: String let name: String? @@ -59,7 +59,7 @@ struct SwiftyGPTUserMessage: SwiftyGPTChatMessage { } } -struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { +struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .assistant // TODO: maybe need to manage better content or tool_calls let content: String @@ -85,7 +85,7 @@ struct SwiftyGPTAssistantMessage: SwiftyGPTChatMessage { } } -struct SwiftyGPTToolMessage: SwiftyGPTChatMessage { +struct SwiftyGPTChatToolMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .tool let content: String // TODO: add tool_call_id @@ -107,10 +107,10 @@ struct SwiftyGPTToolMessage: SwiftyGPTChatMessage { } enum SwiftyGPTChatCodableMessage: Equatable, Encodable { - case system(SwiftyGPTSystemMessage) - case user(SwiftyGPTUserMessage) - case assistant(SwiftyGPTAssistantMessage) - case tool(SwiftyGPTToolMessage) + case system(SwiftyGPTChatSystemMessage) + case user(SwiftyGPTChatUserMessage) + case assistant(SwiftyGPTChatAssistantMessage) + case tool(SwiftyGPTChatToolMessage) func encode(to encoder: any Encoder) throws { var singleContainer = encoder.singleValueContainer() diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index 09417f2..33837c4 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -29,13 +29,13 @@ struct SwiftyGPTChatRequestBody: Encodable { var codableMessages: [SwiftyGPTChatCodableMessage] { return messages.compactMap { switch $0 { - case let message as SwiftyGPTSystemMessage: + case let message as SwiftyGPTChatSystemMessage: return .system(message) - case let message as SwiftyGPTUserMessage: + case let message as SwiftyGPTChatUserMessage: return .user(message) - case let message as SwiftyGPTAssistantMessage: + case let message as SwiftyGPTChatAssistantMessage: return .assistant(message) - case let message as SwiftyGPTToolMessage: + case let message as SwiftyGPTChatToolMessage: return .tool(message) default: return .none diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift index e5e63b5..027c20d 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift @@ -11,49 +11,49 @@ import XCTest final class SwiftyGPTChatMessageTests: XCTestCase { func testSystemMessageInitWhenNameIsNil() throws { - let message = SwiftyGPTSystemMessage(content: "Test") + let message = SwiftyGPTChatSystemMessage(content: "Test") XCTAssertEqual(message.role, .system) XCTAssertEqual(message.content, "Test") XCTAssertEqual(message.name, nil) } func testSystemMessageInitWhenNameIsNotNil() throws { - let message = SwiftyGPTSystemMessage(content: "Test", name: "Test") + let message = SwiftyGPTChatSystemMessage(content: "Test", name: "Test") XCTAssertEqual(message.role, .system) XCTAssertEqual(message.content, "Test") XCTAssertEqual(message.name, "Test") } func testUserMessageInitWhenNameIsNil() throws { - let message = SwiftyGPTUserMessage(content: "Test") + let message = SwiftyGPTChatUserMessage(content: "Test") XCTAssertEqual(message.role, .user) XCTAssertEqual(message.content, "Test") XCTAssertEqual(message.name, nil) } func testUserMessageInitWhenNameIsNotNil() throws { - let message = SwiftyGPTUserMessage(content: "Test", name: "Test") + let message = SwiftyGPTChatUserMessage(content: "Test", name: "Test") XCTAssertEqual(message.role, .user) XCTAssertEqual(message.content, "Test") XCTAssertEqual(message.name, "Test") } func testAssistantMessageInitWhenNameIsNil() throws { - let message = SwiftyGPTAssistantMessage(content: "Test") + let message = SwiftyGPTChatAssistantMessage(content: "Test") XCTAssertEqual(message.role, .assistant) XCTAssertEqual(message.content, "Test") XCTAssertEqual(message.name, nil) } func testAssistantMessageInitWhenNameIsNotNil() throws { - let message = SwiftyGPTAssistantMessage(content: "Test", name: "Test") + let message = SwiftyGPTChatAssistantMessage(content: "Test", name: "Test") XCTAssertEqual(message.role, .assistant) XCTAssertEqual(message.content, "Test") XCTAssertEqual(message.name, "Test") } func testToolMessageInit() throws { - let message = SwiftyGPTToolMessage(content: "Test") + let message = SwiftyGPTChatToolMessage(content: "Test") XCTAssertEqual(message.role, .tool) XCTAssertEqual(message.content, "Test") } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestBodyTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestBodyTests.swift index e6ebc6f..33b2f4b 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestBodyTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestBodyTests.swift @@ -12,17 +12,17 @@ final class SwiftyGPTChatRequestBodyTests: XCTestCase { func testCodableMessages() { let requestBody = SwiftyGPTChatRequestBody(messages: [ - SwiftyGPTSystemMessage(content: "Test"), - SwiftyGPTAssistantMessage(content: "Test"), - SwiftyGPTUserMessage(content: "Test"), - SwiftyGPTToolMessage(content: "Test") + SwiftyGPTChatSystemMessage(content: "Test"), + SwiftyGPTChatAssistantMessage(content: "Test"), + SwiftyGPTChatUserMessage(content: "Test"), + SwiftyGPTChatToolMessage(content: "Test") ], model: .gpt3_5_turbo) XCTAssertEqual(requestBody.codableMessages.count, 4) - XCTAssertEqual(requestBody.codableMessages[0], .system(SwiftyGPTSystemMessage(content: "Test"))) - XCTAssertEqual(requestBody.codableMessages[1], .assistant(SwiftyGPTAssistantMessage(content: "Test"))) - XCTAssertEqual(requestBody.codableMessages[2], .user(SwiftyGPTUserMessage(content: "Test"))) - XCTAssertEqual(requestBody.codableMessages[3], .tool(SwiftyGPTToolMessage(content: "Test"))) + XCTAssertEqual(requestBody.codableMessages[0], .system(SwiftyGPTChatSystemMessage(content: "Test"))) + XCTAssertEqual(requestBody.codableMessages[1], .assistant(SwiftyGPTChatAssistantMessage(content: "Test"))) + XCTAssertEqual(requestBody.codableMessages[2], .user(SwiftyGPTChatUserMessage(content: "Test"))) + XCTAssertEqual(requestBody.codableMessages[3], .tool(SwiftyGPTChatToolMessage(content: "Test"))) } func testCodableMessagesWhenTypeIsUnknown() { From e15284e92006f315ecd6be88bec64960e60df3be Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 13 Apr 2024 09:26:14 +0200 Subject: [PATCH 31/46] fix: SwiftyGPTChatResponseBody decodable --- Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift | 2 +- .../SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift index 27a0e8d..caeef9f 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift @@ -7,7 +7,7 @@ import Foundation -enum SwiftyGPTChatModel: String, Encodable { +enum SwiftyGPTChatModel: String, Codable { case gpt3_5_turbo = "gpt-3.5-turbo" case gpt3_5_turbo_16k = "gpt-3.5-turbo-16k" case gpt3_5_turbo_16k_0613 = "gpt-3.5-turbo-16k-0613" diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift index 6722910..c7ead67 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift @@ -7,13 +7,14 @@ import Foundation -struct SwiftyGPTChatResponseBody: Identifiable { +struct SwiftyGPTChatResponseBody: Identifiable, Decodable { let id: String // TODO: add choices let created: Date let model: SwiftyGPTChatModel let fingerprint: String - let object: String = "chat.completion" + // TODO: object can be an enum ? + let object: String // TODO: add usage enum CodingKeys: String, CodingKey { @@ -21,5 +22,6 @@ struct SwiftyGPTChatResponseBody: Identifiable { case created case model case fingerprint = "system_fingerprint" + case object } } From a507087b4251a3e721d16500fbb9150b3c82892c Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 13 Apr 2024 09:43:17 +0200 Subject: [PATCH 32/46] feat: create some services --- .../Services/SwiftyGPTChatMockService.swift | 21 +++++++++++++++++++ .../SwiftyGPTChatNetworkingService.swift | 17 +++++++++++++++ .../Services/SwiftyGPTChatService.swift | 12 +++++++++++ .../SwiftyGPTNetworkingClientTests.swift | 4 ++-- .../SwiftyGPTNetworkingRequestTests.swift | 8 +++---- 5 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift create mode 100644 Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift create mode 100644 Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift new file mode 100644 index 0000000..b817994 --- /dev/null +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift @@ -0,0 +1,21 @@ +// +// SwiftyGPTChatMockService.swift +// +// +// Created by Antonio Guerra on 13/04/24. +// + +import Foundation + +struct SwiftyGPTChatMockService: SwiftyGPTChatService { + + private let responseBody: SwiftyGPTChatResponseBody + + init(responseBody: SwiftyGPTChatResponseBody) { + self.responseBody = responseBody + } + + func send(_ request: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody { + return responseBody + } +} diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift new file mode 100644 index 0000000..302391d --- /dev/null +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift @@ -0,0 +1,17 @@ +// +// SwiftyGPTChatNetworkingService.swift +// +// +// Created by Antonio Guerra on 13/04/24. +// + +import Foundation + +struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService { + private let apiKey: String + private let organization: String? + + func send(_ request: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody { + return SwiftyGPTChatResponseBody(id: "", created: Date(), model: .gpt3_5_turbo, fingerprint: "", object: "") + } +} diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift new file mode 100644 index 0000000..037af92 --- /dev/null +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift @@ -0,0 +1,12 @@ +// +// SwiftyGPTChatService.swift +// +// +// Created by Antonio Guerra on 13/04/24. +// + +import Foundation + +protocol SwiftyGPTChatService { + func send(_ request: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody +} diff --git a/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift index 9462819..cddbd89 100644 --- a/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift +++ b/Tests/SwiftyGPTNetworkingTests/Clients/SwiftyGPTNetworkingClientTests.swift @@ -21,13 +21,13 @@ final class SwiftyGPTNetworkingClientTests: XCTestCase { } func testSend() async throws { - let request = TestSwiftyGPTNetworkingRequest() + let request = SwiftyGPTNetworkingTestRequest() let response = try await client.send(request: request) XCTAssertEqual(response.statusCode, 200) let _ = try XCTUnwrap(UIImage(data: response.body)) } - private struct TestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { + private struct SwiftyGPTNetworkingTestRequest: SwiftyGPTNetworkingRequest { let endpoint: URL? = URL(string: "https://picsum.photos/") let path: String = "10" let method: SwiftyGPTNetworkingMethod = .get diff --git a/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift index ce1fce2..2bf77f6 100644 --- a/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift +++ b/Tests/SwiftyGPTNetworkingTests/Models/SwiftyGPTNetworkingRequestTests.swift @@ -11,7 +11,7 @@ import XCTest final class SwiftyGPTNetworkingRequestTests: XCTestCase { func testInit() throws { - let request = TestSwiftyGPTNetworkingRequest() + let request = SwiftyGPTNetworkingTestRequest() XCTAssertEqual(request.endpoint, URL(string: "https://picsum.photos/")) XCTAssertEqual(request.path, "10") XCTAssertEqual(request.method, .get) @@ -29,12 +29,12 @@ final class SwiftyGPTNetworkingRequestTests: XCTestCase { } func testInitWhenRequestIsInvalid() { - let request = InvalidTestSwiftyGPTNetworkingRequest() + let request = SwiftyGPTNetworkingInvalidRequest() try XCTAssertThrowsError(request.url) try XCTAssertThrowsError(request.underlyingRequest) } - private struct TestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { + private struct SwiftyGPTNetworkingTestRequest: SwiftyGPTNetworkingRequest { let endpoint: URL? = URL(string: "https://picsum.photos/") let path: String = "10" let method: SwiftyGPTNetworkingMethod = .get @@ -44,7 +44,7 @@ final class SwiftyGPTNetworkingRequestTests: XCTestCase { let timeout: TimeInterval = 60 } - private struct InvalidTestSwiftyGPTNetworkingRequest: SwiftyGPTNetworkingRequest { + private struct SwiftyGPTNetworkingInvalidRequest: SwiftyGPTNetworkingRequest { let endpoint: URL? = URL(string: "file://picsum.photos/") let path: String = "10" let method: SwiftyGPTNetworkingMethod = .get From 0aa82a597d3f6f1ed17301b469885d319b79a309 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sun, 14 Apr 2024 17:31:57 +0200 Subject: [PATCH 33/46] feat: create base architecture --- .../Managers/SwiftyGPTChatManager.swift | 29 ++++++++++ .../Models/SwiftyGPTChatRequest.swift | 55 +++++++++++++++++++ .../Models/SwiftyGPTChatRequestBody.swift | 15 ----- .../Models/SwiftyGPTChatResponse.swift | 13 +++++ .../Models/SwiftyGPTChatResponseBody.swift | 9 ++- .../Services/SwiftyGPTChatMockService.swift | 3 +- .../SwiftyGPTChatNetworkingService.swift | 24 +++++++- .../Services/SwiftyGPTChatService.swift | 2 +- .../Clients/SwiftyGPTNetworkingClient.swift | 6 +- .../Models/SwiftyGPTNetworkingMethod.swift | 2 +- .../Models/SwiftyGPTNetworkingRequest.swift | 4 +- .../Models/SwiftyGPTNetworkingResponse.swift | 8 +-- 12 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift diff --git a/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift new file mode 100644 index 0000000..2a909b8 --- /dev/null +++ b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift @@ -0,0 +1,29 @@ +// +// SwiftyGPTChatManager.swift +// +// +// Created by Antonio Guerra on 14/04/24. +// + +import Foundation + +struct SwiftyGPTChatManager { + private let service: SwiftyGPTChatService + + init(service: SwiftyGPTChatService) { + self.service = service + } + + func send(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) async throws -> SwiftyGPTChatResponse { + let requestBody = SwiftyGPTChatRequestBody(messages: messages, model: model, frequencyPenalty: frequencyPenalty, logprobs: logprobs, topLogprobs: topLogprobs, maxTokens: maxTokens, n: n, presencePenalty: presencePenalty, seed: seed, temperature: temperature, topP: topP, user: user) + let responseBody = try await service.request(body: requestBody) + switch responseBody { + case let body as SwiftyGPTChatResponseSuccessBody: + return .success(body: body) + case let body as SwiftyGPTChatResponseFailureBody: + return .failure(body: body) + default: + throw URLError(.cannotParseResponse) + } + } +} diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift new file mode 100644 index 0000000..acad349 --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift @@ -0,0 +1,55 @@ +// +// SwiftyGPTChatRequest.swift +// +// +// Created by Antonio Guerra on 14/04/24. +// + +import Foundation +import SwiftyGPTNetworking + +struct SwiftyGPTChatRequest: SwiftyGPTNetworkingRequest { + private let apiKey: String + private let organization: String? + internal let body: Data? + + init(apiKey: String, organization: String?, body: Data) { + self.apiKey = apiKey + self.organization = organization + self.body = body + } + + var endpoint: URL? { + return URL(string: "https://api.openai.com/") + } + + var path: String { + return "v1/chat/completions" + } + + var method: SwiftyGPTNetworkingMethod { + return .post + } + + public var headers: [String : String] { + guard let organization else { + return [ + "Content-Type": "application/json", + "Authorization": "Bearer \(apiKey)" + ] + } + return [ + "Content-Type": "application/json", + "Authorization": "Bearer \(apiKey)", + "OpenAI-Organization": organization + ] + } + + public var cachePolicy: URLRequest.CachePolicy { + return .reloadIgnoringLocalCacheData + } + + public var timeout: TimeInterval { + return 60.0 + } +} diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index 33837c4..c9ed92b 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -42,21 +42,6 @@ struct SwiftyGPTChatRequestBody: Encodable { } } } - - init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) { - self.messages = messages - self.model = model - self.frequencyPenalty = frequencyPenalty - self.logprobs = logprobs - self.topLogprobs = topLogprobs - self.maxTokens = maxTokens - self.n = n - self.presencePenalty = presencePenalty - self.seed = seed - self.temperature = temperature - self.topP = topP - self.user = user - } func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift new file mode 100644 index 0000000..031737c --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift @@ -0,0 +1,13 @@ +// +// SwiftyGPTChatResponse.swift +// +// +// Created by Antonio Guerra on 14/04/24. +// + +import Foundation + +enum SwiftyGPTChatResponse { + case success(body: SwiftyGPTChatResponseSuccessBody) + case failure(body: SwiftyGPTChatResponseFailureBody) +} diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift index c7ead67..44e6a62 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift @@ -7,10 +7,12 @@ import Foundation -struct SwiftyGPTChatResponseBody: Identifiable, Decodable { +protocol SwiftyGPTChatResponseBody: Decodable {} + +struct SwiftyGPTChatResponseSuccessBody: SwiftyGPTChatResponseBody, Identifiable { let id: String // TODO: add choices - let created: Date + let created: TimeInterval let model: SwiftyGPTChatModel let fingerprint: String // TODO: object can be an enum ? @@ -25,3 +27,6 @@ struct SwiftyGPTChatResponseBody: Identifiable, Decodable { case object } } + +struct SwiftyGPTChatResponseFailureBody: SwiftyGPTChatResponseBody, Decodable { +} diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift index b817994..8c081f4 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift @@ -8,14 +8,13 @@ import Foundation struct SwiftyGPTChatMockService: SwiftyGPTChatService { - private let responseBody: SwiftyGPTChatResponseBody init(responseBody: SwiftyGPTChatResponseBody) { self.responseBody = responseBody } - func send(_ request: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody { + func request(body: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody { return responseBody } } diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift index 302391d..a925043 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift @@ -6,12 +6,32 @@ // import Foundation +import SwiftyGPTNetworking struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService { + private let client: SwiftyGPTNetworkingClient + private let encoder: JSONEncoder + private let decoder: JSONDecoder private let apiKey: String private let organization: String? - func send(_ request: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody { - return SwiftyGPTChatResponseBody(id: "", created: Date(), model: .gpt3_5_turbo, fingerprint: "", object: "") + init(client: SwiftyGPTNetworkingClient = SwiftyGPTNetworkingClient(session: URLSession.shared), encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder(), apiKey: String, organization: String?) { + self.client = client + self.encoder = encoder + self.decoder = decoder + self.apiKey = apiKey + self.organization = organization + } + + func request(body: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody { + let body = try JSONEncoder().encode(body) + let request = SwiftyGPTChatRequest(apiKey: apiKey, organization: organization, body: body) + let response = try await client.send(request: request) + switch response.statusCode { + case 200..<300: + return try decoder.decode(SwiftyGPTChatResponseSuccessBody.self, from: response.body) + default: + return try decoder.decode(SwiftyGPTChatResponseFailureBody.self, from: response.body) + } } } diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift index 037af92..6168453 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift @@ -8,5 +8,5 @@ import Foundation protocol SwiftyGPTChatService { - func send(_ request: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody + func request(body: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody } diff --git a/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift b/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift index bd172f2..fc7623e 100644 --- a/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift +++ b/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift @@ -7,14 +7,14 @@ import Foundation -struct SwiftyGPTNetworkingClient { +public struct SwiftyGPTNetworkingClient { private let session: URLSession - init(session: URLSession) { + public init(session: URLSession) { self.session = session } - func send(request: SwiftyGPTNetworkingRequest) async throws -> SwiftyGPTNetworkingResponse { + public func send(request: SwiftyGPTNetworkingRequest) async throws -> SwiftyGPTNetworkingResponse { let (body, underlyingResponse) = try await session.data(for: request.underlyingRequest) guard let underlyingResponse = underlyingResponse as? HTTPURLResponse else { throw URLError(.badServerResponse) diff --git a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingMethod.swift b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingMethod.swift index b87a8d9..374fc2f 100644 --- a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingMethod.swift +++ b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingMethod.swift @@ -7,7 +7,7 @@ import Foundation -enum SwiftyGPTNetworkingMethod: String { +public enum SwiftyGPTNetworkingMethod: String { case get = "GET" case post = "POST" case put = "PUT" diff --git a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift index a690bec..de1053c 100644 --- a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift +++ b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingRequest.swift @@ -7,7 +7,7 @@ import Foundation -protocol SwiftyGPTNetworkingRequest { +public protocol SwiftyGPTNetworkingRequest { var endpoint: URL? { get } var path: String { get } var url: URL { get throws } @@ -19,7 +19,7 @@ protocol SwiftyGPTNetworkingRequest { var underlyingRequest: URLRequest { get throws } } -extension SwiftyGPTNetworkingRequest { +public extension SwiftyGPTNetworkingRequest { var url: URL { get throws { guard let endpoint, let scheme = endpoint.scheme, scheme.lowercased().hasPrefix("http"), let url = URL(string: path, relativeTo: endpoint) else { diff --git a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingResponse.swift b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingResponse.swift index dc6b3bb..e15a3ea 100644 --- a/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingResponse.swift +++ b/Sources/SwiftyGPTNetworking/Models/SwiftyGPTNetworkingResponse.swift @@ -7,11 +7,11 @@ import Foundation -struct SwiftyGPTNetworkingResponse { - var underlyingResponse: HTTPURLResponse - var body: Data +public struct SwiftyGPTNetworkingResponse { + public var underlyingResponse: HTTPURLResponse + public var body: Data - var statusCode: Int { + public var statusCode: Int { return underlyingResponse.statusCode } } From c8550ec4f46210604ae60c4cd70bb3ca2be3f320 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sun, 14 Apr 2024 18:16:22 +0200 Subject: [PATCH 34/46] test: add SwiftyGPTChatMockServiceTests --- .../Models/SwiftyGPTChatModel.swift | 2 +- .../Models/SwiftyGPTChatRequestBody.swift | 15 +++++++++++++ .../Models/SwiftyGPTChatResponseBody.swift | 16 +++++++------- .../Services/SwiftyGPTChatMockService.swift | 9 +++++--- .../SwiftyGPTChatNetworkingService.swift | 2 +- .../Services/SwiftyGPTChatService.swift | 2 +- .../SwiftyGPTChatMockServiceTests.swift | 22 +++++++++++++++++++ 7 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift index caeef9f..96127ec 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift @@ -7,7 +7,7 @@ import Foundation -enum SwiftyGPTChatModel: String, Codable { +public enum SwiftyGPTChatModel: String, Codable { case gpt3_5_turbo = "gpt-3.5-turbo" case gpt3_5_turbo_16k = "gpt-3.5-turbo-16k" case gpt3_5_turbo_16k_0613 = "gpt-3.5-turbo-16k-0613" diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index c9ed92b..70bc1f5 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -26,6 +26,21 @@ struct SwiftyGPTChatRequestBody: Encodable { // TODO: add tools let user: String? + init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = nil, logprobs: Bool? = nil, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = nil, presencePenalty: Double? = nil, seed: Int? = nil, temperature: Double? = nil, topP: Double? = nil, user: String? = nil) { + self.messages = messages + self.model = model + self.frequencyPenalty = frequencyPenalty + self.logprobs = logprobs + self.topLogprobs = topLogprobs + self.maxTokens = maxTokens + self.n = n + self.presencePenalty = presencePenalty + self.seed = seed + self.temperature = temperature + self.topP = topP + self.user = user + } + var codableMessages: [SwiftyGPTChatCodableMessage] { return messages.compactMap { switch $0 { diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift index 44e6a62..8c9f8e7 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift @@ -7,16 +7,16 @@ import Foundation -protocol SwiftyGPTChatResponseBody: Decodable {} +public protocol SwiftyGPTChatResponseBody: Decodable, Equatable {} -struct SwiftyGPTChatResponseSuccessBody: SwiftyGPTChatResponseBody, Identifiable { - let id: String +public struct SwiftyGPTChatResponseSuccessBody: SwiftyGPTChatResponseBody, Identifiable { + public let id: String // TODO: add choices - let created: TimeInterval - let model: SwiftyGPTChatModel - let fingerprint: String + public let created: TimeInterval + public let model: SwiftyGPTChatModel + public let fingerprint: String // TODO: object can be an enum ? - let object: String + public let object: String // TODO: add usage enum CodingKeys: String, CodingKey { @@ -28,5 +28,5 @@ struct SwiftyGPTChatResponseSuccessBody: SwiftyGPTChatResponseBody, Identifiable } } -struct SwiftyGPTChatResponseFailureBody: SwiftyGPTChatResponseBody, Decodable { +public struct SwiftyGPTChatResponseFailureBody: SwiftyGPTChatResponseBody, Decodable { } diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift index 8c081f4..0138fb2 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift @@ -8,13 +8,16 @@ import Foundation struct SwiftyGPTChatMockService: SwiftyGPTChatService { - private let responseBody: SwiftyGPTChatResponseBody + private let responseBody: any SwiftyGPTChatResponseBody + private let duration: TimeInterval - init(responseBody: SwiftyGPTChatResponseBody) { + init(responseBody: any SwiftyGPTChatResponseBody, duration: TimeInterval = 0.0) { self.responseBody = responseBody + self.duration = duration } - func request(body: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody { + func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody { + try await Task.sleep(nanoseconds: UInt64(duration) * 1_000_000_000) return responseBody } } diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift index a925043..22be78d 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift @@ -23,7 +23,7 @@ struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService { self.organization = organization } - func request(body: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody { + func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody { let body = try JSONEncoder().encode(body) let request = SwiftyGPTChatRequest(apiKey: apiKey, organization: organization, body: body) let response = try await client.send(request: request) diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift index 6168453..8989689 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift @@ -8,5 +8,5 @@ import Foundation protocol SwiftyGPTChatService { - func request(body: SwiftyGPTChatRequestBody) async throws -> SwiftyGPTChatResponseBody + func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody } diff --git a/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift new file mode 100644 index 0000000..c3021e8 --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift @@ -0,0 +1,22 @@ +// +// SwiftyGPTChatMockServiceTests.swift +// +// +// Created by Antonio Guerra on 14/04/24. +// + +@testable import SwiftyGPTChat +import XCTest + +final class SwiftyGPTChatMockServiceTests: XCTestCase { + + func testRequestWithSuccessResponseBody() async throws { + let responseBody = SwiftyGPTChatResponseSuccessBody(id: "Test", created: 0.0, model: .gpt3_5_turbo, fingerprint: "Test", object: "Test") + let service = SwiftyGPTChatMockService(responseBody: responseBody) + let requestBody = SwiftyGPTChatRequestBody(messages: [], model: .gpt3_5_turbo) + let genericResponseBody = try await service.request(body: requestBody) + let successResponseBody = try XCTUnwrap(genericResponseBody as? SwiftyGPTChatResponseSuccessBody) + XCTAssertEqual(responseBody, successResponseBody) + } +} + From a62782f6036bae01f17ed3d74f126a798ece6382 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sun, 14 Apr 2024 18:27:40 +0200 Subject: [PATCH 35/46] test: add SwiftyGPTChatManagerTests --- .../Models/SwiftyGPTChatMessage.swift | 15 -------------- .../Models/SwiftyGPTChatResponse.swift | 2 +- .../Managers/SwiftyGPTChatManagerTests.swift | 20 +++++++++++++++++++ .../SwiftyGPTChatMockServiceTests.swift | 8 ++++---- 4 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 Tests/SwiftyGPTChatTests/Managers/SwiftyGPTChatManagerTests.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index 3a35b1a..250e995 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -125,19 +125,4 @@ enum SwiftyGPTChatCodableMessage: Equatable, Encodable { try singleContainer.encode(message) } } - - static func == (lhs: SwiftyGPTChatCodableMessage, rhs: SwiftyGPTChatCodableMessage) -> Bool { - switch (lhs, rhs) { - case (.system(let lhsMessage), .system(let rhsMessage)): - return lhsMessage == rhsMessage - case (.user(let lhsMessage), .user(let rhsMessage)): - return lhsMessage == rhsMessage - case (.assistant(let lhsMessage), .assistant(let rhsMessage)): - return lhsMessage == rhsMessage - case (.tool(let lhsMessage), .tool(let rhsMessage)): - return lhsMessage == rhsMessage - default: - return false - } - } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift index 031737c..2b7009f 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift @@ -7,7 +7,7 @@ import Foundation -enum SwiftyGPTChatResponse { +enum SwiftyGPTChatResponse: Equatable { case success(body: SwiftyGPTChatResponseSuccessBody) case failure(body: SwiftyGPTChatResponseFailureBody) } diff --git a/Tests/SwiftyGPTChatTests/Managers/SwiftyGPTChatManagerTests.swift b/Tests/SwiftyGPTChatTests/Managers/SwiftyGPTChatManagerTests.swift new file mode 100644 index 0000000..f51da3b --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Managers/SwiftyGPTChatManagerTests.swift @@ -0,0 +1,20 @@ +// +// SwiftyGPTChatManagerTests.swift +// +// +// Created by Antonio Guerra on 14/04/24. +// + +@testable import SwiftyGPTChat +import XCTest + +final class SwiftyGPTChatManagerTests: XCTestCase { + + func testRequestWhenResponseIsSuccessful() async throws { + let mockedResponseBody = SwiftyGPTChatResponseSuccessBody(id: "Test", created: 0.0, model: .gpt3_5_turbo, fingerprint: "Test", object: "Test") + let service = SwiftyGPTChatMockService(responseBody: mockedResponseBody) + let manager = SwiftyGPTChatManager(service: service) + let response = try await manager.send(messages: [], model: .gpt3_5_turbo) + XCTAssertEqual(response, .success(body: mockedResponseBody)) + } +} diff --git a/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift index c3021e8..b2252d2 100644 --- a/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift +++ b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift @@ -10,13 +10,13 @@ import XCTest final class SwiftyGPTChatMockServiceTests: XCTestCase { - func testRequestWithSuccessResponseBody() async throws { - let responseBody = SwiftyGPTChatResponseSuccessBody(id: "Test", created: 0.0, model: .gpt3_5_turbo, fingerprint: "Test", object: "Test") - let service = SwiftyGPTChatMockService(responseBody: responseBody) + func testRequestWhenResponseIsSuccessful() async throws { + let mockedResponseBody = SwiftyGPTChatResponseSuccessBody(id: "Test", created: 0.0, model: .gpt3_5_turbo, fingerprint: "Test", object: "Test") + let service = SwiftyGPTChatMockService(responseBody: mockedResponseBody) let requestBody = SwiftyGPTChatRequestBody(messages: [], model: .gpt3_5_turbo) let genericResponseBody = try await service.request(body: requestBody) let successResponseBody = try XCTUnwrap(genericResponseBody as? SwiftyGPTChatResponseSuccessBody) - XCTAssertEqual(responseBody, successResponseBody) + XCTAssertEqual(mockedResponseBody, successResponseBody) } } From abfd35a27e0e117644ccd74a98aedff6d23328d8 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sun, 14 Apr 2024 18:46:27 +0200 Subject: [PATCH 36/46] test: add some SwiftyGPTChatMessage tests --- .../Models/SwiftyGPTChatMessageTests.swift | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift index 027c20d..e77aff0 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift @@ -10,6 +10,18 @@ import XCTest final class SwiftyGPTChatMessageTests: XCTestCase { + var encoder: JSONEncoder! + + override func setUpWithError() throws { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + self.encoder = encoder + } + + override func tearDownWithError() throws { + self.encoder = nil + } + func testSystemMessageInitWhenNameIsNil() throws { let message = SwiftyGPTChatSystemMessage(content: "Test") XCTAssertEqual(message.role, .system) @@ -17,6 +29,14 @@ final class SwiftyGPTChatMessageTests: XCTestCase { XCTAssertEqual(message.name, nil) } + func testSystemMessageEncodeWhenNameIsNil() throws { + let message = SwiftyGPTChatSystemMessage(content: "Test") + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\n \"role\" : \"system\",\n \"content\" : \"Test\"\n}" + XCTAssertEqual(encodedString, expectedString) + } + func testSystemMessageInitWhenNameIsNotNil() throws { let message = SwiftyGPTChatSystemMessage(content: "Test", name: "Test") XCTAssertEqual(message.role, .system) From 92047e6cd77328c0caa9058689491f2d993b261a Mon Sep 17 00:00:00 2001 From: Antonio War Date: Mon, 15 Apr 2024 15:13:52 +0200 Subject: [PATCH 37/46] test: improve some tests --- Package.swift | 4 ++- .../Services/SwiftyGPTChatMockService.swift | 8 +++++- .../Clients/SwiftyGPTNetworkingClient.swift | 2 +- .../Models/SwiftyGPTChatMessageTests.swift | 28 +++++++++++++++---- .../SwiftyGPTChatMockServiceTests.swift | 2 ++ 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 1120fb9..6f30ac2 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,9 @@ let package = Package( products: [ .library( name: "SwiftyGPT", - targets: ["SwiftyGPTChat"] + targets: [ + "SwiftyGPTChat", + ] ), ], dependencies: [ diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift index 0138fb2..1a27c91 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift @@ -7,10 +7,15 @@ import Foundation -struct SwiftyGPTChatMockService: SwiftyGPTChatService { +class SwiftyGPTChatMockService: SwiftyGPTChatService { private let responseBody: any SwiftyGPTChatResponseBody private let duration: TimeInterval + private (set) var requestCallCount: Int = 0 + var requestCalled: Bool { + return requestCallCount > 0 + } + init(responseBody: any SwiftyGPTChatResponseBody, duration: TimeInterval = 0.0) { self.responseBody = responseBody self.duration = duration @@ -18,6 +23,7 @@ struct SwiftyGPTChatMockService: SwiftyGPTChatService { func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody { try await Task.sleep(nanoseconds: UInt64(duration) * 1_000_000_000) + requestCallCount += 1 return responseBody } } diff --git a/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift b/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift index fc7623e..3fa5dce 100644 --- a/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift +++ b/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift @@ -17,7 +17,7 @@ public struct SwiftyGPTNetworkingClient { public func send(request: SwiftyGPTNetworkingRequest) async throws -> SwiftyGPTNetworkingResponse { let (body, underlyingResponse) = try await session.data(for: request.underlyingRequest) guard let underlyingResponse = underlyingResponse as? HTTPURLResponse else { - throw URLError(.badServerResponse) + throw URLError(.cannotParseResponse) } return SwiftyGPTNetworkingResponse(underlyingResponse: underlyingResponse, body: body) } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift index e77aff0..0b1520a 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift @@ -14,7 +14,7 @@ final class SwiftyGPTChatMessageTests: XCTestCase { override func setUpWithError() throws { let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted + encoder.outputFormatting = .sortedKeys self.encoder = encoder } @@ -29,19 +29,27 @@ final class SwiftyGPTChatMessageTests: XCTestCase { XCTAssertEqual(message.name, nil) } + func testSystemMessageInitWhenNameIsNotNil() throws { + let message = SwiftyGPTChatSystemMessage(content: "Test", name: "Test") + XCTAssertEqual(message.role, .system) + XCTAssertEqual(message.content, "Test") + XCTAssertEqual(message.name, "Test") + } + func testSystemMessageEncodeWhenNameIsNil() throws { let message = SwiftyGPTChatSystemMessage(content: "Test") let encodedMessage = try encoder.encode(message) let encodedString = String(data: encodedMessage, encoding: .utf8) - let expectedString = "{\n \"role\" : \"system\",\n \"content\" : \"Test\"\n}" + let expectedString = "{\"content\":\"Test\",\"role\":\"system\"}" XCTAssertEqual(encodedString, expectedString) } - func testSystemMessageInitWhenNameIsNotNil() throws { + func testSystemMessageEncodeWhenNameIsNotNil() throws { let message = SwiftyGPTChatSystemMessage(content: "Test", name: "Test") - XCTAssertEqual(message.role, .system) - XCTAssertEqual(message.content, "Test") - XCTAssertEqual(message.name, "Test") + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"name\":\"Test\",\"role\":\"system\"}" + XCTAssertEqual(encodedString, expectedString) } func testUserMessageInitWhenNameIsNil() throws { @@ -58,6 +66,14 @@ final class SwiftyGPTChatMessageTests: XCTestCase { XCTAssertEqual(message.name, "Test") } + func testUserMessageEncodeWhenNameIsNil() throws { + let message = SwiftyGPTChatUserMessage(content: "Test") + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"role\":\"user\"}" + XCTAssertEqual(encodedString, expectedString) + } + func testAssistantMessageInitWhenNameIsNil() throws { let message = SwiftyGPTChatAssistantMessage(content: "Test") XCTAssertEqual(message.role, .assistant) diff --git a/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift index b2252d2..edff5ab 100644 --- a/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift +++ b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatMockServiceTests.swift @@ -17,6 +17,8 @@ final class SwiftyGPTChatMockServiceTests: XCTestCase { let genericResponseBody = try await service.request(body: requestBody) let successResponseBody = try XCTUnwrap(genericResponseBody as? SwiftyGPTChatResponseSuccessBody) XCTAssertEqual(mockedResponseBody, successResponseBody) + XCTAssertEqual(service.requestCallCount, 1) + XCTAssertTrue(service.requestCalled) } } From c10a62f25c2688010cbc6d7571b1ad14ef68f36d Mon Sep 17 00:00:00 2001 From: Antonio War Date: Mon, 15 Apr 2024 15:22:47 +0200 Subject: [PATCH 38/46] feat: create networking service tests --- .../Models/SwiftyGPTChatMessage.swift | 12 +++++----- .../SwiftyGPTChatNetworkingService.swift | 2 +- .../SwiftyGPTChatNetworkingServiceTests.swift | 23 +++++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index 250e995..e85834c 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -11,17 +11,17 @@ protocol SwiftyGPTChatMessage: Equatable, Encodable { var role: SwiftyGPTChatRole { get } } -struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage { - let role: SwiftyGPTChatRole = .system - let content: String - let name: String? +public struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage { + public let role: SwiftyGPTChatRole = .system + public let content: String + public let name: String? - init(content: String, name: String? = nil) { + public init(content: String, name: String? = nil) { self.content = content self.name = name } - func encode(to encoder: any Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(role, forKey: .role) try container.encode(content, forKey: .content) diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift index 22be78d..c6abc7b 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift @@ -15,7 +15,7 @@ struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService { private let apiKey: String private let organization: String? - init(client: SwiftyGPTNetworkingClient = SwiftyGPTNetworkingClient(session: URLSession.shared), encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder(), apiKey: String, organization: String?) { + init(client: SwiftyGPTNetworkingClient = SwiftyGPTNetworkingClient(session: URLSession.shared), encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder(), apiKey: String, organization: String? = nil) { self.client = client self.encoder = encoder self.decoder = decoder diff --git a/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift new file mode 100644 index 0000000..575ff2f --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift @@ -0,0 +1,23 @@ +// +// SwiftyGPTChatNetworkingServiceTests.swift +// +// +// Created by Antonio Guerra on 14/04/24. +// + +@testable import SwiftyGPTChat +import XCTest + +final class SwiftyGPTChatNetworkingServiceTests: XCTestCase { + + func testRequestWhenApiKeyIsValid() async throws { + let service = SwiftyGPTChatNetworkingService(apiKey: "TestApi") + let requestBody = SwiftyGPTChatRequestBody(messages: [ + SwiftyGPTChatSystemMessage(content: "You are an helpful assistant"), + SwiftyGPTChatUserMessage(content: "Hello!") + ], model: .gpt3_5_turbo) + let genericResponseBody = try await service.request(body: requestBody) + let successResponseBody = try XCTUnwrap(genericResponseBody as? SwiftyGPTChatResponseSuccessBody) + print(successResponseBody) + } +} From 1a347ac0245bee7418b8bd6914670e6af48c7c62 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Mon, 15 Apr 2024 20:25:33 +0200 Subject: [PATCH 39/46] fix: remove not ready tests --- .../Managers/SwiftyGPTChatManager.swift | 9 +---- .../Models/SwiftyGPTChatResponse.swift | 11 ++++++ .../SwiftyGPTChatResponseBodyTests.swift | 34 ------------------- .../Models/SwiftyGPTChatResponseTests.swift | 13 +++++++ .../SwiftyGPTChatNetworkingServiceTests.swift | 23 ------------- 5 files changed, 25 insertions(+), 65 deletions(-) create mode 100644 Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift delete mode 100644 Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift diff --git a/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift index 2a909b8..bb792ba 100644 --- a/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift +++ b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift @@ -17,13 +17,6 @@ struct SwiftyGPTChatManager { func send(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) async throws -> SwiftyGPTChatResponse { let requestBody = SwiftyGPTChatRequestBody(messages: messages, model: model, frequencyPenalty: frequencyPenalty, logprobs: logprobs, topLogprobs: topLogprobs, maxTokens: maxTokens, n: n, presencePenalty: presencePenalty, seed: seed, temperature: temperature, topP: topP, user: user) let responseBody = try await service.request(body: requestBody) - switch responseBody { - case let body as SwiftyGPTChatResponseSuccessBody: - return .success(body: body) - case let body as SwiftyGPTChatResponseFailureBody: - return .failure(body: body) - default: - throw URLError(.cannotParseResponse) - } + return try SwiftyGPTChatResponse(body: responseBody) } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift index 2b7009f..2b55db6 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift @@ -10,4 +10,15 @@ import Foundation enum SwiftyGPTChatResponse: Equatable { case success(body: SwiftyGPTChatResponseSuccessBody) case failure(body: SwiftyGPTChatResponseFailureBody) + + init(body: any SwiftyGPTChatResponseBody) throws { + switch body { + case let body as SwiftyGPTChatResponseSuccessBody: + self = .success(body: body) + case let body as SwiftyGPTChatResponseFailureBody: + self = .failure(body: body) + default: + throw URLError(.cannotParseResponse) + } + } } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift index c57080f..e7150bf 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseBodyTests.swift @@ -9,38 +9,4 @@ import XCTest final class SwiftyGPTChatResponseBodyTests: XCTestCase { - - func testDecoding() throws { -// let json = """ -// { -// "id": "chatcmpl-123", -// "object": "chat.completion", -// "created": 1677652288, -// "model": "gpt-3.5-turbo-0125", -// "system_fingerprint": "fp_44709d6fcb", -// "choices": [{ -// "index": 0, -// "message": { -// "role": "assistant", -// "content": "\n\nHello there, how may I assist you today?", -// }, -// "logprobs": null, -// "finish_reason": "stop" -// }], -// "usage": { -// "prompt_tokens": 9, -// "completion_tokens": 12, -// "total_tokens": 21 -// } -// } -// """ -// let data = try XCTUnwrap(json.data(using: .utf8)) -// let decoder = JSONDecoder() -// let response = try decoder.decode(SwiftyGPTChatResponseBody.self, from: data) -// XCTAssertEqual(response.id, "chatcmpl-123") -// XCTAssertEqual(response.object, "chat.completion") -// XCTAssertEqual(response.created, Date(timeIntervalSince1970: 1677652288)) -// XCTAssertEqual(response.model, .gpt3_5_turbo_0125) -// XCTAssertEqual(response.fingerprint, "fp_44709d6fcb") - } } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift new file mode 100644 index 0000000..8a96071 --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseTests.swift @@ -0,0 +1,13 @@ +// +// SwiftyGPTChatResponseTests.swift +// +// +// Created by Antonio Guerra on 15/04/24. +// + +@testable import SwiftyGPTChat +import XCTest + +final class SwiftyGPTChatResponseTests: XCTestCase { +} + diff --git a/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift deleted file mode 100644 index 575ff2f..0000000 --- a/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// SwiftyGPTChatNetworkingServiceTests.swift -// -// -// Created by Antonio Guerra on 14/04/24. -// - -@testable import SwiftyGPTChat -import XCTest - -final class SwiftyGPTChatNetworkingServiceTests: XCTestCase { - - func testRequestWhenApiKeyIsValid() async throws { - let service = SwiftyGPTChatNetworkingService(apiKey: "TestApi") - let requestBody = SwiftyGPTChatRequestBody(messages: [ - SwiftyGPTChatSystemMessage(content: "You are an helpful assistant"), - SwiftyGPTChatUserMessage(content: "Hello!") - ], model: .gpt3_5_turbo) - let genericResponseBody = try await service.request(body: requestBody) - let successResponseBody = try XCTUnwrap(genericResponseBody as? SwiftyGPTChatResponseSuccessBody) - print(successResponseBody) - } -} From c2edededa3ff965afd79a2a64c7f391a53e29eea Mon Sep 17 00:00:00 2001 From: Antonio War Date: Mon, 15 Apr 2024 20:37:05 +0200 Subject: [PATCH 40/46] test: add SwiftyGPTChatRequestTests --- .../Models/SwiftyGPTChatRequest.swift | 2 +- .../Models/SwiftyGPTChatRequestTests.swift | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestTests.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift index acad349..7c78d85 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequest.swift @@ -13,7 +13,7 @@ struct SwiftyGPTChatRequest: SwiftyGPTNetworkingRequest { private let organization: String? internal let body: Data? - init(apiKey: String, organization: String?, body: Data) { + init(apiKey: String, organization: String? = nil, body: Data) { self.apiKey = apiKey self.organization = organization self.body = body diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestTests.swift new file mode 100644 index 0000000..4a9d11e --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatRequestTests.swift @@ -0,0 +1,43 @@ +// +// SwiftyGPTChatRequestTests.swift +// +// +// Created by Antonio Guerra on 15/04/24. +// + +@testable import SwiftyGPTChat +import XCTest + +final class SwiftyGPTChatRequestTests: XCTestCase { + + func testInitWhenOrganizationIsNil() throws { + let request = SwiftyGPTChatRequest(apiKey: "Test", body: Data()) + XCTAssertEqual(request.endpoint, URL(string: "https://api.openai.com/")) + XCTAssertEqual(request.path, "v1/chat/completions") + XCTAssertEqual(request.method, .post) + XCTAssertEqual(request.headers, [ + "Content-Type": "application/json", + "Authorization": "Bearer Test" + ]) + XCTAssertEqual(request.cachePolicy, .reloadIgnoringLocalCacheData) + XCTAssertEqual(request.timeout, 60.0) + XCTAssertEqual(request.body, Data()) + try XCTAssertEqual(request.url.absoluteString, "https://api.openai.com/v1/chat/completions") + } + + func testInitWhenOrganizationIsNotNil() throws { + let request = SwiftyGPTChatRequest(apiKey: "Test", organization: "Test", body: Data()) + XCTAssertEqual(request.endpoint, URL(string: "https://api.openai.com/")) + XCTAssertEqual(request.path, "v1/chat/completions") + XCTAssertEqual(request.method, .post) + XCTAssertEqual(request.headers, [ + "Content-Type": "application/json", + "Authorization": "Bearer Test", + "OpenAI-Organization": "Test" + ]) + XCTAssertEqual(request.cachePolicy, .reloadIgnoringLocalCacheData) + XCTAssertEqual(request.timeout, 60.0) + XCTAssertEqual(request.body, Data()) + try XCTAssertEqual(request.url.absoluteString, "https://api.openai.com/v1/chat/completions") + } +} From fa5cff028b3bf2a92417cc6954857d3eadac35d9 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 26 Apr 2024 21:58:13 +0200 Subject: [PATCH 41/46] test: add some other tests --- .../SwiftyGPTChatNetworkingService.swift | 10 +-- .../Clients/SwiftyGPTNetworkingClient.swift | 4 +- .../Models/SwiftyGPTChatMessageTests.swift | 64 +++++++++++++++++++ .../SwiftyGPTChatNetworkingServiceTests.swift | 23 +++++++ 4 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift index c6abc7b..e3f57b8 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift @@ -9,11 +9,11 @@ import Foundation import SwiftyGPTNetworking struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService { - private let client: SwiftyGPTNetworkingClient - private let encoder: JSONEncoder - private let decoder: JSONDecoder - private let apiKey: String - private let organization: String? + let client: SwiftyGPTNetworkingClient + let encoder: JSONEncoder + let decoder: JSONDecoder + let apiKey: String + let organization: String? init(client: SwiftyGPTNetworkingClient = SwiftyGPTNetworkingClient(session: URLSession.shared), encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder(), apiKey: String, organization: String? = nil) { self.client = client diff --git a/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift b/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift index 3fa5dce..9b97395 100644 --- a/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift +++ b/Sources/SwiftyGPTNetworking/Clients/SwiftyGPTNetworkingClient.swift @@ -7,10 +7,10 @@ import Foundation -public struct SwiftyGPTNetworkingClient { +public struct SwiftyGPTNetworkingClient: Equatable { private let session: URLSession - public init(session: URLSession) { + public init(session: URLSession = URLSession.shared) { self.session = session } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift index 0b1520a..3b1028e 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatMessageTests.swift @@ -74,6 +74,14 @@ final class SwiftyGPTChatMessageTests: XCTestCase { XCTAssertEqual(encodedString, expectedString) } + func testUserMessageEncodeWhenNameIsNotNil() throws { + let message = SwiftyGPTChatUserMessage(content: "Test", name: "Test") + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"name\":\"Test\",\"role\":\"user\"}" + XCTAssertEqual(encodedString, expectedString) + } + func testAssistantMessageInitWhenNameIsNil() throws { let message = SwiftyGPTChatAssistantMessage(content: "Test") XCTAssertEqual(message.role, .assistant) @@ -88,9 +96,65 @@ final class SwiftyGPTChatMessageTests: XCTestCase { XCTAssertEqual(message.name, "Test") } + func testAssistanteMessageEncodeWhenNameIsNil() throws { + let message = SwiftyGPTChatAssistantMessage(content: "Test") + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"role\":\"assistant\"}" + XCTAssertEqual(encodedString, expectedString) + } + + func testAssistantMessageEncodeWhenNameIsNotNil() throws { + let message = SwiftyGPTChatAssistantMessage(content: "Test", name: "Test") + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"name\":\"Test\",\"role\":\"assistant\"}" + XCTAssertEqual(encodedString, expectedString) + } + func testToolMessageInit() throws { let message = SwiftyGPTChatToolMessage(content: "Test") XCTAssertEqual(message.role, .tool) XCTAssertEqual(message.content, "Test") } + + func testToolMessageEncode() throws { + let message = SwiftyGPTChatToolMessage(content: "Test") + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"role\":\"tool\"}" + XCTAssertEqual(encodedString, expectedString) + } + + func testCodableMessageEncodeWhenRoleIsSystem() throws { + let message = SwiftyGPTChatCodableMessage.system(SwiftyGPTChatSystemMessage(content: "Test")) + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"role\":\"system\"}" + XCTAssertEqual(encodedString, expectedString) + } + + func testCodableMessageEncodeWhenRoleIsUser() throws { + let message = SwiftyGPTChatCodableMessage.user(SwiftyGPTChatUserMessage(content: "Test")) + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"role\":\"user\"}" + XCTAssertEqual(encodedString, expectedString) + } + + func testCodableMessageEncodeWhenRoleIsAssistant() throws { + let message = SwiftyGPTChatCodableMessage.assistant(SwiftyGPTChatAssistantMessage(content: "Test")) + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"role\":\"assistant\"}" + XCTAssertEqual(encodedString, expectedString) + } + + func testCodableMessageEncodeWhenRoleIsTool() throws { + let message = SwiftyGPTChatCodableMessage.tool(SwiftyGPTChatToolMessage(content: "Test")) + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"content\":\"Test\",\"role\":\"tool\"}" + XCTAssertEqual(encodedString, expectedString) + } } diff --git a/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift new file mode 100644 index 0000000..31c4e49 --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Services/SwiftyGPTChatNetworkingServiceTests.swift @@ -0,0 +1,23 @@ +// +// SwiftyGPTChatNetworkingServiceTests.swift +// +// +// Created by Antonio Guerra on 26/04/24. +// + +import XCTest +@testable import SwiftyGPTChat +@testable import SwiftyGPTNetworking + +final class SwiftyGPTChatNetworkingServiceTests: XCTestCase { + + func testInit() { + let client = SwiftyGPTNetworkingClient() + let apiKey = "testApiKey" + let organization = "testOrganization" + let service = SwiftyGPTChatNetworkingService(client: client, apiKey: apiKey, organization: organization) + XCTAssertEqual(service.client, client) + XCTAssertEqual(service.apiKey, apiKey) + XCTAssertEqual(service.organization, organization) + } +} From f1cbcc863029a7830641c7259bd47f4016180b1e Mon Sep 17 00:00:00 2001 From: Antonio War Date: Fri, 26 Apr 2024 22:24:45 +0200 Subject: [PATCH 42/46] feat: add gpt4 models --- .../Models/SwiftyGPTChatModel.swift | 17 +++++--- .../Models/SwiftyGPTChatModelTests.swift | 40 +++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift index 96127ec..15e6a25 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatModel.swift @@ -8,12 +8,19 @@ import Foundation public enum SwiftyGPTChatModel: String, Codable { + case gpt4_turbo = "gpt-4-turbo" + case gpt4_turbo_preview = "gpt-4-turbo-preview" + case gpt4_0125_preview = "gpt-4-0125-preview" + case gpt4_1106_preview = "gpt-4-1106-preview" + case gpt4 = "gpt-4" + case gpt4_0613 = "gpt-4-0613" + case gpt4_32k = "gpt-4-32k" + case gpt4_32k_0613 = "gpt-4-32k-0613" + case gpt3_5_turbo_0125 = "gpt-3.5-turbo-0125" case gpt3_5_turbo = "gpt-3.5-turbo" - case gpt3_5_turbo_16k = "gpt-3.5-turbo-16k" - case gpt3_5_turbo_16k_0613 = "gpt-3.5-turbo-16k-0613" case gpt3_5_turbo_0301 = "gpt-3.5-turbo-0301" - case gpt3_5_turbo_0613 = "gpt-3.5-turbo-0613" case gpt3_5_turbo_1106 = "gpt-3.5-turbo-1106" - case gpt3_5_turbo_0125 = "gpt-3.5-turbo-0125" - // TODO: add gpt4 models + case gpt3_5_turbo_16k = "gpt-3.5-turbo-16k" + case gpt3_5_turbo_0613 = "gpt-3.5-turbo-0613" + case gpt3_5_turbo_16k_0613 = "gpt-3.5-turbo-16k-0613" } diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatModelTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatModelTests.swift index b9f18db..9fb9d83 100644 --- a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatModelTests.swift +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatModelTests.swift @@ -10,6 +10,46 @@ import XCTest final class SwiftyGPTChatModelTests: XCTestCase { + func testGpt4_turboInitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-4-turbo")) + XCTAssertEqual(model, .gpt4_turbo) + } + + func testGpt4_turbo_previewInitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-4-turbo-preview")) + XCTAssertEqual(model, .gpt4_turbo_preview) + } + + func testGpt4_0125_previewInitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-4-0125-preview")) + XCTAssertEqual(model, .gpt4_0125_preview) + } + + func testGpt4_1106_previewInitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-4-1106-preview")) + XCTAssertEqual(model, .gpt4_1106_preview) + } + + func testGpt4InitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-4")) + XCTAssertEqual(model, .gpt4) + } + + func testGpt4_0613InitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-4-0613")) + XCTAssertEqual(model, .gpt4_0613) + } + + func testGpt4_32kInitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-4-32k")) + XCTAssertEqual(model, .gpt4_32k) + } + + func testGpt4_32k_0613InitFromRawValue() throws { + let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-4-32k-0613")) + XCTAssertEqual(model, .gpt4_32k_0613) + } + func testGpt3_5_turboInitFromRawValue() throws { let model = try XCTUnwrap(SwiftyGPTChatModel(rawValue: "gpt-3.5-turbo")) XCTAssertEqual(model, .gpt3_5_turbo) From 99ada03e059351d59d53906c4d43a18bdb952092 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 27 Apr 2024 11:43:28 +0200 Subject: [PATCH 43/46] feat: add SwiftyGPTChatResponseFormat and relative tests --- .../Models/SwiftyGPTChatRequestBody.swift | 8 +-- .../Models/SwiftyGPTChatResponseFormat.swift | 22 ++++++++ .../SwiftyGPTChatResponseFormatTests.swift | 50 +++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift create mode 100644 Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseFormatTests.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index 70bc1f5..d1cd4f9 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -11,13 +11,13 @@ struct SwiftyGPTChatRequestBody: Encodable { let messages: [any SwiftyGPTChatMessage] let model: SwiftyGPTChatModel let frequencyPenalty: Double? - // TODO: add logit_bias + let logitBias: [Int: Int]? let logprobs: Bool? let topLogprobs: Int? let maxTokens: Int? let n: Int? let presencePenalty: Double? - // TODO: add response_format + let responseFormat: SwiftyGPTChatResponseFormat? let seed: Int? // TODO: add stop // TODO: add stream @@ -26,15 +26,17 @@ struct SwiftyGPTChatRequestBody: Encodable { // TODO: add tools let user: String? - init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = nil, logprobs: Bool? = nil, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = nil, presencePenalty: Double? = nil, seed: Int? = nil, temperature: Double? = nil, topP: Double? = nil, user: String? = nil) { + init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = nil, logitBias: [Int: Int]? = nil, logprobs: Bool? = nil, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = nil, presencePenalty: Double? = nil, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = nil, topP: Double? = nil, user: String? = nil) { self.messages = messages self.model = model self.frequencyPenalty = frequencyPenalty + self.logitBias = logitBias self.logprobs = logprobs self.topLogprobs = topLogprobs self.maxTokens = maxTokens self.n = n self.presencePenalty = presencePenalty + self.responseFormat = responseFormat self.seed = seed self.temperature = temperature self.topP = topP diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift new file mode 100644 index 0000000..3bd1a8d --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift @@ -0,0 +1,22 @@ +// +// SwiftyGPTChatResponseFormat.swift +// +// +// Created by Antonio Guerra on 27/04/24. +// + +import Foundation + +enum SwiftyGPTChatResponseFormat: String, Encodable { + case text = "text" + case json = "json_object" + + enum CodingKeys: CodingKey { + case type + } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(rawValue, forKey: .type) + } +} diff --git a/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseFormatTests.swift b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseFormatTests.swift new file mode 100644 index 0000000..c5bda94 --- /dev/null +++ b/Tests/SwiftyGPTChatTests/Models/SwiftyGPTChatResponseFormatTests.swift @@ -0,0 +1,50 @@ +// +// SwiftyGPTChatResponseFormatTests.swift +// +// +// Created by Antonio Guerra on 27/04/24. +// + +import XCTest +@testable import SwiftyGPTChat + +final class SwiftyGPTChatResponseFormatTests: XCTestCase { + + var encoder: JSONEncoder! + + override func setUpWithError() throws { + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + self.encoder = encoder + } + + override func tearDownWithError() throws { + self.encoder = nil + } + + func testTextFormatInit() { + let format = SwiftyGPTChatResponseFormat.text + XCTAssertEqual(format.rawValue, "text") + } + + func testTextFormatEncode() throws { + let message = SwiftyGPTChatResponseFormat.text + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"type\":\"text\"}" + XCTAssertEqual(encodedString, expectedString) + } + + func testJsonFormatInit() { + let format = SwiftyGPTChatResponseFormat.json + XCTAssertEqual(format.rawValue, "json_object") + } + + func testJsonFormatEncode() throws { + let message = SwiftyGPTChatResponseFormat.json + let encodedMessage = try encoder.encode(message) + let encodedString = String(data: encodedMessage, encoding: .utf8) + let expectedString = "{\"type\":\"json_object\"}" + XCTAssertEqual(encodedString, expectedString) + } +} From 607e270ef8e3a6ebd7b13f13825c803634d50800 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 27 Apr 2024 11:49:08 +0200 Subject: [PATCH 44/46] fix: add missing encoded parameters to SwiftyGPTChatRequestBody --- .../Models/SwiftyGPTChatRequestBody.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index d1cd4f9..9959b5f 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -19,11 +19,12 @@ struct SwiftyGPTChatRequestBody: Encodable { let presencePenalty: Double? let responseFormat: SwiftyGPTChatResponseFormat? let seed: Int? - // TODO: add stop - // TODO: add stream + // TODO: add stop parameter support + // TODO: add stream parameter support let temperature: Double? let topP: Double? - // TODO: add tools + // TODO: add tools parameter support + // TODO: add tool_choice parameter support let user: String? init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = nil, logitBias: [Int: Int]? = nil, logprobs: Bool? = nil, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = nil, presencePenalty: Double? = nil, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = nil, topP: Double? = nil, user: String? = nil) { @@ -65,11 +66,13 @@ struct SwiftyGPTChatRequestBody: Encodable { try container.encode(model, forKey: .model) try container.encode(codableMessages, forKey: .messages) try container.encodeIfPresent(frequencyPenalty, forKey: .frequencyPenalty) + try container.encodeIfPresent(logitBias, forKey: .logitBias) try container.encodeIfPresent(logprobs, forKey: .logprobs) try container.encodeIfPresent(topLogprobs, forKey: .topLogprobs) try container.encodeIfPresent(maxTokens, forKey: .maxTokens) try container.encodeIfPresent(n, forKey: .n) try container.encodeIfPresent(presencePenalty, forKey: .presencePenalty) + try container.encodeIfPresent(responseFormat, forKey: .responseFormat) try container.encodeIfPresent(seed, forKey: .seed) try container.encodeIfPresent(temperature, forKey: .temperature) try container.encodeIfPresent(topP, forKey: .topP) @@ -80,11 +83,13 @@ struct SwiftyGPTChatRequestBody: Encodable { case messages case model case frequencyPenalty = "frequency_penalty" + case logitBias = "logit_bias" case logprobs case topLogprobs = "top_logprobs" case maxTokens = "max_tokens" case n = "n" case presencePenalty = "presence_penalty" + case responseFormat = "response_format" case seed case temperature case topP = "top_p" From 09ef83519c8df65c3a25e70760f3a12b3a63c84f Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 27 Apr 2024 12:14:45 +0200 Subject: [PATCH 45/46] feat: add some SwiftyGPTChatResponseBody properties --- .../Models/SwiftyGPTChatMessage.swift | 55 +++++++++++++++++-- .../Models/SwiftyGPTChatResponseBody.swift | 9 +-- .../Models/SwiftyGPTChatResponseChoice.swift | 27 +++++++++ .../SwiftyGPTChatResponseFinishReason.swift | 15 +++++ .../Models/SwiftyGPTChatResponseObject.swift | 12 ++++ .../SwiftyGPTChatResponseTokenUsage.swift | 20 +++++++ .../Models/SwiftyGPTChatRole.swift | 2 +- 7 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFinishReason.swift create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseObject.swift create mode 100644 Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index e85834c..4968661 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -7,7 +7,7 @@ import Foundation -protocol SwiftyGPTChatMessage: Equatable, Encodable { +protocol SwiftyGPTChatMessage: Equatable, Encodable, Decodable { var role: SwiftyGPTChatRole { get } } @@ -20,6 +20,12 @@ public struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage { self.content = content self.name = name } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.content = try container.decode(String.self, forKey: .content) + self.name = try container.decodeIfPresent(String.self, forKey: .name) + } public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -45,6 +51,12 @@ struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage { self.name = name } + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.content = try container.decode(String.self, forKey: .content) + self.name = try container.decodeIfPresent(String.self, forKey: .name) + } + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(role, forKey: .role) @@ -61,16 +73,22 @@ struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage { struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage { let role: SwiftyGPTChatRole = .assistant - // TODO: maybe need to manage better content or tool_calls + // TODO: content will be optional once tool_calls parameter will be supported let content: String let name: String? - // TODO: add tool_calls + // TODO: add tool_calls parameter support init(content: String, name: String? = nil) { self.content = content self.name = name } + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.content = try container.decode(String.self, forKey: .content) + self.name = try container.decodeIfPresent(String.self, forKey: .name) + } + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.role, forKey: .role) @@ -94,6 +112,11 @@ struct SwiftyGPTChatToolMessage: SwiftyGPTChatMessage { self.content = content } + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.content = try container.decode(String.self, forKey: .content) + } + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.role, forKey: .role) @@ -106,12 +129,32 @@ struct SwiftyGPTChatToolMessage: SwiftyGPTChatMessage { } } -enum SwiftyGPTChatCodableMessage: Equatable, Encodable { +enum SwiftyGPTChatCodableMessage: Equatable, Encodable, Decodable { case system(SwiftyGPTChatSystemMessage) case user(SwiftyGPTChatUserMessage) case assistant(SwiftyGPTChatAssistantMessage) case tool(SwiftyGPTChatToolMessage) + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let singleContainer = try decoder.singleValueContainer() + let role = try container.decode(SwiftyGPTChatRole.self, forKey: .role) + switch role { + case .system: + let message = try singleContainer.decode(SwiftyGPTChatSystemMessage.self) + self = .system(message) + case .user: + let message = try singleContainer.decode(SwiftyGPTChatUserMessage.self) + self = .user(message) + case .assistant: + let message = try singleContainer.decode(SwiftyGPTChatAssistantMessage.self) + self = .assistant(message) + case .tool: + let message = try singleContainer.decode(SwiftyGPTChatToolMessage.self) + self = .tool(message) + } + } + func encode(to encoder: any Encoder) throws { var singleContainer = encoder.singleValueContainer() switch self { @@ -125,4 +168,8 @@ enum SwiftyGPTChatCodableMessage: Equatable, Encodable { try singleContainer.encode(message) } } + + enum CodingKeys: CodingKey { + case role + } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift index 8c9f8e7..c3e72ec 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift @@ -11,20 +11,21 @@ public protocol SwiftyGPTChatResponseBody: Decodable, Equatable {} public struct SwiftyGPTChatResponseSuccessBody: SwiftyGPTChatResponseBody, Identifiable { public let id: String - // TODO: add choices + public let choices: [SwiftyGPTChatResponseChoice] public let created: TimeInterval public let model: SwiftyGPTChatModel public let fingerprint: String - // TODO: object can be an enum ? - public let object: String - // TODO: add usage + public let object: SwiftyGPTChatResponseObject + public let usage: SwiftyGPTChatResponseTokenUsage enum CodingKeys: String, CodingKey { case id + case choices case created case model case fingerprint = "system_fingerprint" case object + case usage } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift new file mode 100644 index 0000000..2fa97f6 --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift @@ -0,0 +1,27 @@ +// +// SwiftyGPTChatResponseChoice.swift +// +// +// Created by Antonio Guerra on 27/04/24. +// + +import Foundation + +public struct SwiftyGPTChatResponseChoice: Decodable, Equatable { + let index: Int + let codableMessage: SwiftyGPTChatCodableMessage + let finishReason: SwiftyGPTChatResponseFinishReason + + var message: any SwiftyGPTChatMessage { + switch codableMessage { + case .system(let message): + return message + case .user(let message): + return message + case .assistant(let message): + return message + case .tool(let message): + return message + } + } +} diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFinishReason.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFinishReason.swift new file mode 100644 index 0000000..a78df4c --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFinishReason.swift @@ -0,0 +1,15 @@ +// +// SwiftyGPTChatResponseFinishReason.swift +// +// +// Created by Antonio Guerra on 27/04/24. +// + +import Foundation + +enum SwiftyGPTChatResponseFinishReason: String, Decodable { + case stop + case lenght + case contentFilter = "content_filter" + case toolCalls = "tool_calls" +} diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseObject.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseObject.swift new file mode 100644 index 0000000..9ce5cd1 --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseObject.swift @@ -0,0 +1,12 @@ +// +// SwiftyGPTChatResponseObject.swift +// +// +// Created by Antonio Guerra on 27/04/24. +// + +import Foundation + +public enum SwiftyGPTChatResponseObject: String, Decodable { + case completion = "chat.completion" +} diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift new file mode 100644 index 0000000..09c0ed6 --- /dev/null +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift @@ -0,0 +1,20 @@ +// +// SwiftyGPTChatResponseUsage.swift +// +// +// Created by Antonio Guerra on 27/04/24. +// + +import Foundation + +public struct SwiftyGPTChatResponseTokenUsage: Decodable, Equatable { + let completion: Int + let prompt: Int + let total: Int + + enum CodingKeys: String, CodingKey { + case completion = "completion_tokens" + case prompt = "prompt_tokens" + case total = "total_tokens" + } +} diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift index 0049660..756f275 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRole.swift @@ -7,7 +7,7 @@ import Foundation -public enum SwiftyGPTChatRole: String, Encodable { +public enum SwiftyGPTChatRole: String, Encodable, Decodable { case system case user case assistant From 5478cb5c28aa16b01d6ddc90dae84e62de3adb89 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 27 Apr 2024 12:21:10 +0200 Subject: [PATCH 46/46] feat: update SwiftyGPTChatManager send method interface --- Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift index bb792ba..d2f6404 100644 --- a/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift +++ b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift @@ -13,9 +13,9 @@ struct SwiftyGPTChatManager { init(service: SwiftyGPTChatService) { self.service = service } - - func send(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) async throws -> SwiftyGPTChatResponse { - let requestBody = SwiftyGPTChatRequestBody(messages: messages, model: model, frequencyPenalty: frequencyPenalty, logprobs: logprobs, topLogprobs: topLogprobs, maxTokens: maxTokens, n: n, presencePenalty: presencePenalty, seed: seed, temperature: temperature, topP: topP, user: user) + + func send(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logitBias: [Int: Int]? = nil, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) async throws -> SwiftyGPTChatResponse { + let requestBody = SwiftyGPTChatRequestBody(messages: messages, model: model, frequencyPenalty: frequencyPenalty, logitBias: logitBias, logprobs: logprobs, topLogprobs: topLogprobs, maxTokens: maxTokens, n: n, presencePenalty: presencePenalty, responseFormat: responseFormat, seed: seed, temperature: temperature, topP: topP, user: user) let responseBody = try await service.request(body: requestBody) return try SwiftyGPTChatResponse(body: responseBody) }