diff --git a/README.md b/README.md index e15341a..f0090e8 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Import the framework in your project: [Create an OpenAI API key](https://platform.openai.com/account/api-keys) and add it to your configuration: -`let openAI = OpenAISwift(authToken: "TOKEN")` +`let openAI = OpenAISwift(config: .makeDefaultOpenAI(apiKey: "TOKEN"))` This framework supports Swift concurrency; each example below has both an async/await and completion handler variant. diff --git a/Sources/OpenAISwift/Models/ChatMessage.swift b/Sources/OpenAISwift/Models/ChatMessage.swift index c987cf9..7dc5cf0 100644 --- a/Sources/OpenAISwift/Models/ChatMessage.swift +++ b/Sources/OpenAISwift/Models/ChatMessage.swift @@ -19,7 +19,8 @@ public enum ChatRole: String, Codable { /// A structure that represents a single message in a chat conversation. public struct ChatMessage: Codable, Identifiable { - // uuid to conform to Identifiable protocol + /// UUID to conform to the Identifiable protocol + /// - Note: This property is not de- and encoded. A DTO or other logic might be required if the `ChatMessage` instance is stored locally. public var id = UUID() /// The role of the sender of the message. public let role: ChatRole? @@ -34,6 +35,26 @@ public struct ChatMessage: Codable, Identifiable { self.role = role self.content = content } + + enum CodingKeys: CodingKey { + case role + case content + } + + public init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: ChatMessage.CodingKeys.self) + + self.role = try container.decodeIfPresent(ChatRole.self, forKey: ChatMessage.CodingKeys.role) + self.content = try container.decodeIfPresent(String.self, forKey: ChatMessage.CodingKeys.content) + + } + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: ChatMessage.CodingKeys.self) + + try container.encodeIfPresent(self.role, forKey: ChatMessage.CodingKeys.role) + try container.encodeIfPresent(self.content, forKey: ChatMessage.CodingKeys.content) + } } /// A structure that represents a chat conversation. @@ -70,7 +91,7 @@ public struct ChatConversation: Encodable { /// Modify the likelihood of specified tokens appearing in the completion. Maps tokens (specified by their token ID in the OpenAI Tokenizer—not English words) to an associated bias value from -100 to 100. Values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token. let logitBias: [Int: Double]? - + /// If you're generating long completions, waiting for the response can take many seconds. To get responses sooner, you can 'stream' the completion as it's being generated. This allows you to start printing or processing the beginning of the completion before the full completion is finished. /// https://github.com/openai/openai-cookbook/blob/main/examples/How_to_stream_completions.ipynb let stream: Bool? @@ -96,6 +117,6 @@ public struct ChatError: Codable { public let message, type: String public let param, code: String? } - + public let error: Payload } diff --git a/Sources/OpenAISwift/Models/OpenAIEndpointModelType.swift b/Sources/OpenAISwift/Models/OpenAIEndpointModelType.swift new file mode 100644 index 0000000..6a4e7b8 --- /dev/null +++ b/Sources/OpenAISwift/Models/OpenAIEndpointModelType.swift @@ -0,0 +1,123 @@ +// +// OpenAIEndpointModelType.swift +// +// +// Created by Marco Boerner on 01.09.23. +// + +import Foundation + +/// Currently available and recommended models including some, but not all, legacy models +/// https://platform.openai.com/docs/models/model-endpoint-compatibility +public struct OpenAIEndpointModelType { + + public enum AudioTranscriptions: String, Codable { + /// Whisper v2-large model (under the name whisper1) is a general-purpose speech recognition model. It is trained on a large dataset of diverse audio and is also a multi-task model that can perform multilingual speech recognition as well as speech translation and language identification + case whisper1 = "whisper-1" + } + + public enum AudioTranslations: String, Codable { + /// Whisper v2-large model (under the name whisper1) is a general-purpose speech recognition model. It is trained on a large dataset of diverse audio and is also a multi-task model that can perform multilingual speech recognition as well as speech translation and language identification + case whisper1 = "whisper-1" + } + + public enum ChatCompletions: String, Codable { + /// More capable than any GPT-3.5 model, able to do more complex tasks, and optimized for chat. Will be updated with our latest model iteration 2 weeks after it is released. - 8,192 tokens + case gpt4 = "gpt-4" + + /// Snapshot of gpt-4 from June 13th 2023 with function calling data. Unlike gpt-4, this model will not receive updates, and will be deprecated 3 months after a new version is released. - 8,192 tokens + case gpt40613 = "gpt-4-0613" + + /// Same capabilities as the standard gpt-4 mode but with 4x the context length. Will be updated with our latest model iteration. - 32,768 tokens + case gpt432k = "gpt-4-32k" + + /// Snapshot of gpt-4-32 from June 13th 2023. Unlike gpt-4-32k, this model will not receive updates, and will be deprecated 3 months after a new version is released. - 32,768 tokens + case gpt432k0613 = "gpt-4-32k-0613" + + /// A faster version of GPT-3.5 with the same capabilities. Will be updated with our latest model iteration. - 4,096 tokens + case gpt35Turbo = "gpt-3.5-turbo" + + /// Snapshot of gpt-3.5-turbo from June 13th 2023. Unlike gpt-3.5-turbo, this model will not receive updates, and will be deprecated 3 months after a new version is released. - 4,096 tokens + case gpt35Turbo0613 = "gpt-3.5-turbo-0613" + + /// A faster version of GPT-3.5 with the same capabilities and 4x the context length. Will be updated with our latest model iteration. - 16,384 tokens + case gpt35Turbo16k = "gpt-3.5-turbo-16k" + + /// Snapshot of gpt-3.5-turbo-16k from June 13th 2023. Unlike gpt-3.5-turbo-16k, this model will not receive updates, and will be deprecated 3 months after a new version is released. - 16,384 tokens + case gpt35Turbo16k0613 = "gpt-3.5-turbo-16k-0613" + } + + public enum LegacyCompletions: String, Codable { + /// Can do any language task with better quality, longer output, and consistent instruction-following than the curie, babbage, or ada models. Also supports some additional features such as inserting text. - 4,097 tokens + case textDavinci003 = "text-davinci-003" + + /// Similar capabilities to text-davinci-003 but trained with supervised fine-tuning instead of reinforcement learning - 4,097 tokens + case textDavinci002 = "text-davinci-002" + + /// Optimized for code-completion tasks - 8,001 tokens + case textDavinci001 = "text-davinci-001" + + /// Very capable, faster and lower cost than Davinci. - 2,049 tokens + case textCurie001 = "text-curie-001" + + /// Capable of straightforward tasks, very fast, and lower cost. - 2,049 tokens + case textBabbage001 = "text-babbage-001" + + /// Capable of very simple tasks, usually the fastest model in the GPT-3 series, and lowest cost. - 2,049 tokens + case textAda001 = "text-ada-001" + + /// Most capable GPT-3 model. Can do any task the other models can do, often with higher quality. - 2,049 tokens + case davinci = "davinci" + + /// Very capable, but faster and lower cost than Davinci. - 2,049 tokens + case curie = "curie" + + /// Capable of straightforward tasks, very fast, and lower cost. - 2,049 tokens + case babbage = "babbage" + + /// Capable of very simple tasks, usually the fastest model in the GPT-3 series, and lowest cost. - 2,049 tokens + case ada = "ada" + } + + public enum Embeddings: String, Codable { + + /// The new model, text-embedding-ada-002, replaces five separate models for text search, text similarity, and code search, and outperforms previous most capable model, Davinci, at most tasks, while being priced 99.8% lower. + case textEmbeddingAda002 = "text-embedding-ada-002" + } + + public enum FineTuningJobs: String, Codable { + + /// Most capable GPT-3.5 model and optimized for chat at 1/10th the cost of text-davinci-003. Will be updated with our latest model iteration 2 weeks after it is released. - 4,096 tokens + case gpt35Turbo = "gpt-3.5-turbo" + + /// Replacement for the GPT-3 ada and babbage base models. - 16,384 tokens + case babbage002 = "babbage-002" + + /// Replacement for the GPT-3 curie and davinci base models. - 16,384 tokens + case davinci002 = "davinci-002" + } + + public enum FineTunes: String, Codable { + + /// Most capable GPT-3 model. Can do any task the other models can do, often with higher quality. - 2,049 tokens + case davinci = "davinci" + + /// Very capable, but faster and lower cost than Davinci. - 2,049 tokens + case curie = "curie" + + /// Capable of straightforward tasks, very fast, and lower cost. - 2,049 tokens + case babbage = "babbage" + + /// Capable of very simple tasks, usually the fastest model in the GPT-3 series, and lowest cost. - 2,049 tokens + case ada = "ada" + } + + public enum Moderations: String, Codable { + + /// Most capable moderation model. Accuracy will be slightly higher than the stable model. + case textModerationStable = "text-moderation-stable" + + /// Almost as capable as the latest model, but slightly older. + case textModerationLatest = "text-moderation-latest" + } +} diff --git a/Sources/OpenAISwift/Models/OpenAIModelType.swift b/Sources/OpenAISwift/Models/OpenAIModelType.swift index cdedbba..68855a7 100644 --- a/Sources/OpenAISwift/Models/OpenAIModelType.swift +++ b/Sources/OpenAISwift/Models/OpenAIModelType.swift @@ -45,7 +45,28 @@ public enum OpenAIModelType { case .other(let modelName): return modelName } } - + + /// Custom initializer that allows to enum to be constructed from a string value. + public init(rawValue: String) { + if let gtp3 = GPT3(rawValue: rawValue) { + self = .gpt3(gtp3) + } else if let codex = Codex(rawValue: rawValue) { + self = .codex(codex) + } else if let feature = Feature(rawValue: rawValue) { + self = .feature(feature) + } else if let chat = Chat(rawValue: rawValue) { + self = .chat(chat) + } else if let gpt4 = GPT4(rawValue: rawValue) { + self = .gpt4(gpt4) + } else if let embedding = Embedding(rawValue: rawValue) { + self = .embedding(embedding) + } else if let moderation = Moderation(rawValue: rawValue) { + self = .moderation(moderation) + } else { + self = .other(rawValue) + } + } + /// A set of models that can understand and generate natural language /// /// [GPT-3 Models OpenAI API Docs](https://beta.openai.com/docs/models/gpt-3) diff --git a/Sources/OpenAISwift/OpenAISwift.swift b/Sources/OpenAISwift/OpenAISwift.swift index 67090ac..cdf0c5c 100644 --- a/Sources/OpenAISwift/OpenAISwift.swift +++ b/Sources/OpenAISwift/OpenAISwift.swift @@ -46,17 +46,19 @@ public class OpenAISwift { } extension OpenAISwift { + /// Send a Completion to the OpenAI API /// - Parameters: /// - prompt: The Text Prompt - /// - model: The AI Model to Use. Set to `OpenAIModelType.gpt3(.davinci)` by default which is the most capable model + /// - model: The AI Model to Use. Set to `OpenAIEndpointModelType.LegacyCompletions.davinci,` by default which is the most capable model /// - maxTokens: The limit character for the returned response, defaults to 16 as per the API /// - completionHandler: Returns an OpenAI Data Model - public func sendCompletion(with prompt: String, model: OpenAIModelType = .gpt3(.davinci), maxTokens: Int = 16, temperature: Double = 1, completionHandler: @escaping (Result, OpenAIError>) -> Void) { + /// - Note: OpenAI marked this endpoint as legacy + public func sendCompletion(with prompt: String, model: OpenAIEndpointModelType.LegacyCompletions = .davinci, maxTokens: Int = 16, temperature: Double = 1, completionHandler: @escaping (Result, OpenAIError>) -> Void) { let endpoint = OpenAIEndpointProvider.API.completions - let body = Command(prompt: prompt, model: model.modelName, maxTokens: maxTokens, temperature: temperature) + let body = Command(prompt: prompt, model: model.rawValue, maxTokens: maxTokens, temperature: temperature) let request = prepareRequest(endpoint, body: body) - + makeRequest(request: request) { result in switch result { case .success(let success): @@ -71,6 +73,20 @@ extension OpenAISwift { } } } + + /// Send a Completion to the OpenAI API + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.LegacyCompletions` instead") + public func sendCompletion(with prompt: String, model: OpenAIModelType, maxTokens: Int = 16, temperature: Double = 1, completionHandler: @escaping (Result, OpenAIError>) -> Void) { + guard let model = OpenAIEndpointModelType.LegacyCompletions(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } + sendCompletion( + with: prompt, + model: model, + maxTokens: maxTokens, + temperature: temperature, + completionHandler: completionHandler) + } /// Send a Edit request to the OpenAI API /// - Parameters: @@ -97,17 +113,17 @@ extension OpenAISwift { } } } - + /// Send a Moderation request to the OpenAI API /// - Parameters: /// - input: The Input For Example "My nam is Adam" /// - model: The Model to use /// - completionHandler: Returns an OpenAI Data Model - public func sendModerations(with input: String, model: OpenAIModelType = .moderation(.latest), completionHandler: @escaping (Result, OpenAIError>) -> Void) { + public func sendModerations(with input: String, model: OpenAIEndpointModelType.Moderations = .textModerationLatest, completionHandler: @escaping (Result, OpenAIError>) -> Void) { let endpoint = OpenAIEndpointProvider.API.moderations - let body = Moderation(input: input, model: model.modelName) + let body = Moderation(input: input, model: model.rawValue) let request = prepareRequest(endpoint, body: body) - + makeRequest(request: request) { result in switch result { case .success(let success): @@ -122,11 +138,23 @@ extension OpenAISwift { } } } - + + /// Send a Moderation request to the OpenAI API + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.Moderations` instead") + public func sendModerations(with input: String, model: OpenAIModelType, completionHandler: @escaping (Result, OpenAIError>) -> Void) { + guard let model = OpenAIEndpointModelType.Moderations(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } + sendModerations(with: input, + model: model, + completionHandler: completionHandler + ) + } + /// Send a Chat request to the OpenAI API /// - Parameters: /// - messages: Array of `ChatMessages` - /// - model: The Model to use, the only support model is `gpt-3.5-turbo` + /// - model: The Model to use. /// - user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. /// - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or topProbabilityMass but not both. /// - topProbabilityMass: The OpenAI api equivalent of the "top_p" parameter. An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. @@ -138,7 +166,7 @@ extension OpenAISwift { /// - logitBias: Modify the likelihood of specified tokens appearing in the completion. Maps tokens (specified by their token ID in the OpenAI Tokenizer—not English words) to an associated bias value from -100 to 100. Values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token. /// - completionHandler: Returns an OpenAI Data Model public func sendChat(with messages: [ChatMessage], - model: OpenAIModelType = .chat(.chatgpt), + model: OpenAIEndpointModelType.ChatCompletions = .gpt35Turbo, user: String? = nil, temperature: Double? = 1, topProbabilityMass: Double? = 0, @@ -152,7 +180,7 @@ extension OpenAISwift { let endpoint = OpenAIEndpointProvider.API.chat let body = ChatConversation(user: user, messages: messages, - model: model.modelName, + model: model.rawValue, temperature: temperature, topProbabilityMass: topProbabilityMass, choices: choices, @@ -164,7 +192,7 @@ extension OpenAISwift { stream: false) let request = prepareRequest(endpoint, body: body) - + makeRequest(request: request) { result in switch result { case .success(let success): @@ -172,52 +200,95 @@ extension OpenAISwift { completionHandler(.failure(.chatError(error: chatErr.error))) return } - + do { let res = try JSONDecoder().decode(OpenAI.self, from: success) completionHandler(.success(res)) } catch { completionHandler(.failure(.decodingError(error: error))) } - + case .failure(let failure): completionHandler(.failure(.genericError(error: failure))) } } } - + + /// Send a Chat request to the OpenAI API + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.ChatCompletions` instead") + public func sendChat(with messages: [ChatMessage], + model: OpenAIModelType, + user: String? = nil, + temperature: Double? = 1, + topProbabilityMass: Double? = 0, + choices: Int? = 1, + stop: [String]? = nil, + maxTokens: Int? = nil, + presencePenalty: Double? = 0, + frequencyPenalty: Double? = 0, + logitBias: [Int: Double]? = nil, + completionHandler: @escaping (Result, OpenAIError>) -> Void) { + guard let model = OpenAIEndpointModelType.ChatCompletions(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } + sendChat( + with: messages, + model: model, + user: user, + temperature: temperature, + topProbabilityMass: topProbabilityMass, + choices: choices, + stop: stop, + maxTokens: maxTokens, + presencePenalty: presencePenalty, + frequencyPenalty: frequencyPenalty, + logitBias: logitBias, + completionHandler: completionHandler) + } + /// Send a Embeddings request to the OpenAI API /// - Parameters: - /// - input: The Input For Example "The food was delicious and the waiter..." - /// - model: The Model to use, the only support model is `text-embedding-ada-002` - /// - completionHandler: Returns an OpenAI Data Model + /// - input: The Input For Example "The food was delicious and the waiter..." + /// - model: The Model to use + /// - completionHandler: Returns an OpenAI Data Model public func sendEmbeddings(with input: String, - model: OpenAIModelType = .embedding(.ada), + model: OpenAIEndpointModelType.Embeddings = .textEmbeddingAda002, completionHandler: @escaping (Result, OpenAIError>) -> Void) { let endpoint = OpenAIEndpointProvider.API.embeddings let body = EmbeddingsInput(input: input, - model: model.modelName) + model: model.rawValue) let request = prepareRequest(endpoint, body: body) makeRequest(request: request) { result in switch result { - case .success(let success): - do { - let res = try JSONDecoder().decode(OpenAI.self, from: success) - completionHandler(.success(res)) - } catch { - completionHandler(.failure(.decodingError(error: error))) - } - case .failure(let failure): - completionHandler(.failure(.genericError(error: failure))) + case .success(let success): + do { + let res = try JSONDecoder().decode(OpenAI.self, from: success) + completionHandler(.success(res)) + } catch { + completionHandler(.failure(.decodingError(error: error))) + } + case .failure(let failure): + completionHandler(.failure(.genericError(error: failure))) } } } - + + /// Send a Embeddings request to the OpenAI API + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.Embeddings` instead") + public func sendEmbeddings(with input: String, + model: OpenAIModelType, + completionHandler: @escaping (Result, OpenAIError>) -> Void) { + guard let model = OpenAIEndpointModelType.Embeddings(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } + sendEmbeddings(with: input, model: model, completionHandler: completionHandler) + } + /// Send a Chat request to the OpenAI API with stream enabled /// - Parameters: /// - messages: Array of `ChatMessages` - /// - model: The Model to use, the only support model is `gpt-3.5-turbo` + /// - model: The Model to use. /// - user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. /// - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or topProbabilityMass but not both. /// - topProbabilityMass: The OpenAI api equivalent of the "top_p" parameter. An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. @@ -230,7 +301,7 @@ extension OpenAISwift { /// - onEventReceived: Called Multiple times, returns an OpenAI Data Model /// - onComplete: Triggers when sever complete sending the message public func sendStreamingChat(with messages: [ChatMessage], - model: OpenAIModelType = .chat(.chatgpt), + model: OpenAIEndpointModelType.ChatCompletions = .gpt35Turbo, user: String? = nil, temperature: Double? = 1, topProbabilityMass: Double? = 0, @@ -245,7 +316,7 @@ extension OpenAISwift { let endpoint = OpenAIEndpointProvider.API.chat let body = ChatConversation(user: user, messages: messages, - model: model.modelName, + model: model.rawValue, temperature: temperature, topProbabilityMass: topProbabilityMass, choices: choices, @@ -261,6 +332,40 @@ extension OpenAISwift { handler.connect(with: request) } + /// Send a Chat request to the OpenAI API with stream enabled + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.ChatCompletions` instead") + public func sendStreamingChat(with messages: [ChatMessage], + model: OpenAIModelType, + user: String? = nil, + temperature: Double? = 1, + topProbabilityMass: Double? = 0, + choices: Int? = 1, + stop: [String]? = nil, + maxTokens: Int? = nil, + presencePenalty: Double? = 0, + frequencyPenalty: Double? = 0, + logitBias: [Int: Double]? = nil, + onEventReceived: ((Result, OpenAIError>) -> Void)? = nil, + onComplete: (() -> Void)? = nil) { + guard let model = OpenAIEndpointModelType.ChatCompletions(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } + sendStreamingChat( + with: messages, + model: model, + user: user, + temperature: temperature, + topProbabilityMass: topProbabilityMass, + choices: choices, + stop: stop, + maxTokens: maxTokens, + presencePenalty: presencePenalty, + frequencyPenalty: frequencyPenalty, + logitBias: logitBias, + onEventReceived: onEventReceived, + onComplete: onComplete) + } + /// Send a Image generation request to the OpenAI API /// - Parameters: @@ -322,22 +427,35 @@ extension OpenAISwift { } extension OpenAISwift { + /// Send a Completion to the OpenAI API /// - Parameters: /// - prompt: The Text Prompt - /// - model: The AI Model to Use. Set to `OpenAIModelType.gpt3(.davinci)` by default which is the most capable model + /// - model: The AI Model to Use. Set to `OpenAIEndpointModelType.LegacyCompletions.davinci` by default which is the most capable model /// - maxTokens: The limit character for the returned response, defaults to 16 as per the API /// - temperature: Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. Defaults to 1 /// - Returns: Returns an OpenAI Data Model + /// - Note: OpenAI marked this endpoint as legacy @available(swift 5.5) @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) - public func sendCompletion(with prompt: String, model: OpenAIModelType = .gpt3(.davinci), maxTokens: Int = 16, temperature: Double = 1) async throws -> OpenAI { + public func sendCompletion(with prompt: String, model: OpenAIEndpointModelType.LegacyCompletions = .davinci, maxTokens: Int = 16, temperature: Double = 1) async throws -> OpenAI { return try await withCheckedThrowingContinuation { continuation in sendCompletion(with: prompt, model: model, maxTokens: maxTokens, temperature: temperature) { result in continuation.resume(with: result) } } } + + /// Send a Completion to the OpenAI API + @available(swift 5.5) + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.LegacyCompletions` instead") + public func sendCompletion(with prompt: String, model: OpenAIModelType, maxTokens: Int = 16, temperature: Double = 1) async throws -> OpenAI { + guard let model = OpenAIEndpointModelType.LegacyCompletions(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } + return try await sendCompletion(with: prompt, model: model, maxTokens: maxTokens, temperature: temperature) + } /// Send a Edit request to the OpenAI API /// - Parameters: @@ -354,11 +472,11 @@ extension OpenAISwift { } } } - + /// Send a Chat request to the OpenAI API /// - Parameters: /// - messages: Array of `ChatMessages` - /// - model: The Model to use, the only support model is `gpt-3.5-turbo` + /// - model: The Model to use. /// - user: A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. /// - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or topProbabilityMass but not both. /// - topProbabilityMass: The OpenAI api equivalent of the "top_p" parameter. An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. @@ -372,7 +490,7 @@ extension OpenAISwift { @available(swift 5.5) @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func sendChat(with messages: [ChatMessage], - model: OpenAIModelType = .chat(.chatgpt), + model: OpenAIEndpointModelType.ChatCompletions = .gpt35Turbo, user: String? = nil, temperature: Double? = 1, topProbabilityMass: Double? = 0, @@ -395,13 +513,43 @@ extension OpenAISwift { frequencyPenalty: frequencyPenalty, logitBias: logitBias) { result in switch result { - case .success: continuation.resume(with: result) - case .failure(let failure): continuation.resume(throwing: failure) + case .success: continuation.resume(with: result) + case .failure(let failure): continuation.resume(throwing: failure) } } } } - + + /// Send a Chat request to the OpenAI API + @available(swift 5.5) + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.ChatCompletions` instead") + public func sendChat(with messages: [ChatMessage], + model: OpenAIModelType, + user: String? = nil, + temperature: Double? = 1, + topProbabilityMass: Double? = 0, + choices: Int? = 1, + stop: [String]? = nil, + maxTokens: Int? = nil, + presencePenalty: Double? = 0, + frequencyPenalty: Double? = 0, + logitBias: [Int: Double]? = nil) async throws -> OpenAI { + guard let model = OpenAIEndpointModelType.ChatCompletions(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } + return try await sendChat(with: messages, + model: model, + user: user, + temperature: temperature, + topProbabilityMass: topProbabilityMass, + choices: choices, + stop: stop, + maxTokens: maxTokens, + presencePenalty: presencePenalty, + frequencyPenalty: frequencyPenalty, + logitBias: logitBias) + } /// Send a Chat request to the OpenAI API with stream enabled /// - Parameters: @@ -420,7 +568,43 @@ extension OpenAISwift { @available(swift 5.5) @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func sendStreamingChat(with messages: [ChatMessage], - model: OpenAIModelType = .chat(.chatgpt), + model: OpenAIEndpointModelType.ChatCompletions = .gpt35Turbo, + user: String? = nil, + temperature: Double? = 1, + topProbabilityMass: Double? = 0, + choices: Int? = 1, + stop: [String]? = nil, + maxTokens: Int? = nil, + presencePenalty: Double? = 0, + frequencyPenalty: Double? = 0, + logitBias: [Int: Double]? = nil) -> AsyncStream, OpenAIError>> { + return AsyncStream { continuation in + sendStreamingChat( + with: messages, + model: model, + user: user, + temperature: temperature, + topProbabilityMass: topProbabilityMass, + choices: choices, + stop: stop, + maxTokens: maxTokens, + presencePenalty: presencePenalty, + frequencyPenalty: frequencyPenalty, + logitBias: logitBias, + onEventReceived: { result in + continuation.yield(result) + }) { + continuation.finish() + } + } + } + + /// Send a Chat request to the OpenAI API with stream enabled + @available(swift 5.5) + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.ChatCompletions` instead") + public func sendStreamingChat(with messages: [ChatMessage], + model: OpenAIModelType, user: String? = nil, temperature: Double? = 1, topProbabilityMass: Double? = 0, @@ -430,6 +614,9 @@ extension OpenAISwift { presencePenalty: Double? = 0, frequencyPenalty: Double? = 0, logitBias: [Int: Double]? = nil) -> AsyncStream, OpenAIError>> { + guard let model = OpenAIEndpointModelType.ChatCompletions(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } return AsyncStream { continuation in sendStreamingChat( with: messages, @@ -459,14 +646,26 @@ extension OpenAISwift { @available(swift 5.5) @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) public func sendEmbeddings(with input: String, - model: OpenAIModelType = .embedding(.ada)) async throws -> OpenAI { + model: OpenAIEndpointModelType.Embeddings = .textEmbeddingAda002) async throws -> OpenAI { return try await withCheckedThrowingContinuation { continuation in sendEmbeddings(with: input) { result in continuation.resume(with: result) } } } - + + /// Send a Embeddings request to the OpenAI API + @available(swift 5.5) + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.Embeddings` instead") + public func sendEmbeddings(with input: String, + model: OpenAIModelType) async throws -> OpenAI { + guard let model = OpenAIEndpointModelType.Embeddings(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } + return try await sendEmbeddings(with: input, model: model) + } + /// Send a Moderation request to the OpenAI API /// - Parameters: /// - input: The Input For Example "My nam is Adam" @@ -474,13 +673,24 @@ extension OpenAISwift { /// - Returns: Returns an OpenAI Data Model @available(swift 5.5) @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) - public func sendModerations(with input: String = "", model: OpenAIModelType = .moderation(.latest)) async throws -> OpenAI { + public func sendModerations(with input: String = "", model: OpenAIEndpointModelType.Moderations = .textModerationLatest) async throws -> OpenAI { return try await withCheckedThrowingContinuation { continuation in sendModerations(with: input, model: model) { result in continuation.resume(with: result) } } } + + /// Send a Moderation request to the OpenAI API + @available(swift 5.5) + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + @available(*, deprecated, message: "Use method with `OpenAIEndpointModelType.Moderations` instead") + public func sendModerations(with input: String = "", model: OpenAIModelType) async throws -> OpenAI { + guard let model = OpenAIEndpointModelType.Moderations(rawValue: model.modelName) else { + preconditionFailure("Model \(model.modelName) not supported") + } + return try await sendModerations(with: input, model: model) + } /// Send a Image generation request to the OpenAI API /// - Parameters: