├── .gitignore ├── .spi.yml ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ ├── ChatGPT.xcscheme │ └── GPT.xcscheme ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md └── Sources ├── Base ├── API.swift └── APIClientRequestHandler.swift ├── ChatGPT ├── ChatGPT+StreamedAnswer.swift ├── ChatGPT.swift └── Models │ ├── ChatGPTModel.swift │ ├── ChatMessage.swift │ ├── ChatMessageRole.swift │ ├── ChatRequest.swift │ ├── ChatResponse.swift │ ├── ChatStreamedMessage.swift │ └── ChatStreamedResponse.swift ├── GPT ├── GPT+StreamedAnswer.swift ├── GPT.swift └── Models │ ├── CompletionRequest.swift │ ├── CompletionResponse.swift │ ├── CompletionStreamedResponse.swift │ └── GPTModel.swift ├── GPTSwiftSharedTypes ├── GPTSwiftError.swift ├── OpenAIError.swift └── URLRequest+curl.swift └── OpenAI ├── Models └── ModelList.swift └── OpenAI.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [GPTSwift] 5 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/ChatGPT.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 43 | 49 | 50 | 56 | 57 | 58 | 59 | 61 | 62 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/GPT.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 43 | 49 | 50 | 56 | 57 | 58 | 59 | 61 | 62 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.0.0 4 | 5 | > **Note** 6 | > Fun Fact: Parts of the documentation for this update have been written by GPT4! I have proof-read them to make sure everything is fine but GPT4 just did it perfectly. 7 | 8 | ### Added 9 | 10 | - Added GPT4 models (also including stable and large context versions) to `ChatGPT`. If you have access, enjoy! 11 | - Added `GPT`, which is a wrapper around the completion API for GPT3. Also supports streaming the answers. 12 | - Added `OpenAI`, which is a wrapper around two general OpenAI endpoints that list available models. 13 | - Added more robust and convenient error handling. All errors are now exposed through [`GPTSwiftError`](https://github.com/SwiftedMind/GPTSwift/blob/main/Sources/GPTSwiftSharedTypes/GPTSwiftError.swift). 14 | - OpenAI error messages are now also passed through, which is useful to debug invalid requests (like out-of-bounds values). 15 | - Added option to configure underlying URLSession in the initializers of `ChatGPT`, `GPT` and `OpenAI`. 16 | - Added option to set the default model to use for instances of `ChatGPT`, `GPT` and `OpenAI`. You can set that in the initializer and then, if needed, override it in the individual methods. 17 | - Added convenience methods to initialize requests and messages, for example `ChatRequest.gpt3(_:)` or `ChatMessage.system(_:)`. 18 | - Added `curl(for:pretty:formatOutput:)` methods to `ChatGPT` and `GPT` that turns requests into a usable `curl` prompt that you can paste into a terminal to manually call the OpenAI endpoints. 19 | - `OpenAI` also has these convenience methods: `curlForAvailableModels(pretty:formatOutput:)` and `curlForModel(withId:pretty:formatOutput:)` 20 | 21 | ### Changed 22 | 23 | - Renamed `ChatGPTSwift` to `ChatGPT`. 24 | - `ChatGPT.ask(_:)` and `ChatGPT.ask(messages:)` have been made easier to use. They now return a String as the answer, instead of a complex object. 25 | - The intention behind this is to make basic usage of the framework as easy as possible while still allowing full control through `ChatGPT.ask(request:)` 26 | - `ChatGPT`, `GPT` and `OpenAI` now each have their own product to reduce namespace pollution. Simply import whichever you are using. 27 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | dennis@swiftedmind.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dennis Müller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "get", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/kean/Get", 7 | "state" : { 8 | "revision" : "12830cc64f31789ae6f4352d2d51d03a25fc3741", 9 | "version" : "2.1.6" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "GPTSwift", 8 | platforms: [ 9 | .iOS(.v13), 10 | .macOS(.v10_15), 11 | .watchOS(.v8), 12 | .tvOS(.v15) 13 | ], 14 | products: [ 15 | .library( 16 | name: "ChatGPT", 17 | targets: ["ChatGPT", "GPTSwiftSharedTypes"] 18 | ), 19 | .library( 20 | name: "GPT", 21 | targets: ["GPT", "GPTSwiftSharedTypes"] 22 | ), 23 | .library( 24 | name: "OpenAI", 25 | targets: ["OpenAI", "GPTSwiftSharedTypes"] 26 | ) 27 | ], 28 | dependencies: [ 29 | .package(url: "https://github.com/kean/Get", from: "2.1.6") 30 | ], 31 | targets: [ 32 | .target( 33 | name: "Base", 34 | dependencies: [ 35 | "GPTSwiftSharedTypes", 36 | .product(name: "Get", package: "Get") 37 | ] 38 | ), 39 | .target( 40 | name: "ChatGPT", 41 | dependencies: [ 42 | "Base", 43 | "GPTSwiftSharedTypes", 44 | .product(name: "Get", package: "Get") 45 | ] 46 | ), 47 | .target( 48 | name: "GPT", 49 | dependencies: [ 50 | "Base", 51 | "GPTSwiftSharedTypes", 52 | .product(name: "Get", package: "Get") 53 | ] 54 | ), 55 | .target( 56 | name: "OpenAI", 57 | dependencies: [ 58 | "Base", 59 | "GPTSwiftSharedTypes", 60 | .product(name: "Get", package: "Get") 61 | ] 62 | ), 63 | .target( 64 | name: "GPTSwiftSharedTypes", 65 | dependencies: [] 66 | ) 67 | ] 68 | ) 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # GPTSwift 4 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSwiftedMind%2FGPTSwift%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/SwiftedMind/GPTSwift) 5 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSwiftedMind%2FGPTSwift%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/SwiftedMind/GPTSwift) 6 | ![GitHub](https://img.shields.io/github/license/SwiftedMind/GPTSwift) 7 | 8 | GPTSwift is a lightweight and convenient wrapper around the OpenAI API. It is completely written in Swift. 9 | 10 | Using GPTSwift is easy: 11 | 12 | ```swift 13 | import ChatGPT 14 | 15 | let chatGPT = ChatGPT(apiKey: "YOUR_API_KEY", defaultModel: .gpt3) 16 | let answer = try await chatGPT.ask("What is the answer to life, the universe and everything in it?") 17 | 18 | // Stream the answer token by token 19 | var streamedAnswer = "" 20 | for try await nextWord in try await chatGPT.streamedAnswer.ask("Tell me a story about birds") { 21 | streamedAnswer += nextWord 22 | } 23 | ``` 24 | 25 | An example project can be found here: [GPTPlayground](https://github.com/SwiftedMind/GPTPlayground/tree/main) 26 | 27 | ## Content 28 | 29 | - [**Getting Started**](#getting-started) 30 | - [Requirements](#requirements) 31 | - [Installation](#installation) 32 | - [**How to Use**](#how-to-use) 33 | - [Generating cURL Prompts](#generating-curl-prompts) 34 | - [License](#license) 35 | 36 | ## Getting Started 37 | 38 | ### Requirements 39 | 40 | GPTSwift supports iOS 15+, macOS 12+, watchOS 8+ and tvOS 15+. 41 | 42 | ### Installation 43 | 44 | The package is installed through the Swift Package Manager. Simply add the following line to your `Package.swift` dependencies: 45 | 46 | ```swift 47 | .package(url: "https://github.com/SwiftedMind/GPTSwift", from: "3.0.0") 48 | ``` 49 | 50 | Alternatively, if you want to add the package to an Xcode project, go to `File` > `Add Packages...` and enter the URL "https://github.com/SwiftedMind/GPTSwift" into the search field at the top. GPTSwift should appear in the list. Select it and click "Add Package" in the bottom right. 51 | 52 | ## How to Use 53 | 54 | GPTSwift is just a lightweight wrapper around the API that abstracts away all the unnecessary details of calling endpoints and handling requests and responses. 55 | 56 | ### ChatGPT 57 | 58 | The `ChatGPT` target gives you access to the ChatGPT API (including the GPT4 models). 59 | 60 | The easiest way of using `ChatGPT` is by simply passing in a prompt string or an array of chat messages via the `ask(_:)` and `ask(messages:)` methods. These take care of turning the arguments into a request as well as parsing the response. All you get is the answer as a simple string, which is often all that's needed. 61 | 62 | ```swift 63 | import ChatGPT 64 | 65 | func askChatGPT() async throws { 66 | let chatGPT = ChatGPT(apiKey: "YOUR_API_KEY", defaultModel: .gpt3) 67 | 68 | // Basic query 69 | let firstResponse = try await chatGPT.ask("What is the answer to life, the universe and everything in it?") 70 | print(firstResponse) 71 | 72 | // Send multiple messages 73 | let secondResponse = try await chatGPT.ask( 74 | messages: [ 75 | ChatMessage(role: .system, content: "You are a dog."), 76 | ChatMessage(role: .user, content: "Do you actually like playing fetch?") 77 | ], 78 | model: .gpt3.stableVersion() // Override default model, if needed 79 | ) 80 | print(secondResponse) 81 | } 82 | ``` 83 | 84 | However, if you need full control, you can also pass a `ChatRequest` to the `ask(request:)` method. With this, you can adjust all parameters and have access to the full response object, while everything is still fully type-safe. 85 | 86 | ```swift 87 | import ChatGPT 88 | 89 | func askChatGPT() async throws { 90 | let chatGPT = ChatGPT(apiKey: "YOUR_API_KEY", defaultModel: .gpt3) 91 | 92 | let fullRequest = ChatRequest.gpt3 { request in 93 | request.messages = [ 94 | .init(role: .system, content: "You are the pilot of an alien UFO. Be creative."), 95 | .init(role: .user, content: "Where do you come from?") 96 | ] 97 | request.temperature = 0.8 98 | request.numberOfAnswers = 2 99 | } 100 | 101 | let response = try await chatGPT.ask(request: fullRequest) 102 | print(response.choices.map(\.message)) 103 | } 104 | ``` 105 | 106 | Finally, all of the above methods have a variant that lets you stream GPT's answer token by token, right as they are generated. The stream is provided via an `AsyncThrowingStream`. All you have to do is add a `streamedAnswer` before the call to `ask()`. For example: 107 | 108 | ```swift 109 | import ChatGPT 110 | 111 | // In your view model 112 | @Published var gptAnswer = "" 113 | 114 | func askChatGPT() async throws { 115 | let chatGPT = ChatGPT(apiKey: "YOUR_API_KEY") 116 | 117 | gptAnswer = "" 118 | for try await nextWord in try await chatGPT.streamedAnswer.ask("Tell me a funny story about birds") { 119 | gptAnswer += nextWord 120 | } 121 | } 122 | ``` 123 | 124 | For more information about the ChatGPT API, you can look at OpenAI's documentation: 125 | - [ChatGPT API Introduction](https://platform.openai.com/docs/guides/chat/chat-completions-beta) 126 | - [ChatGPT API documentation](https://platform.openai.com/docs/api-reference/chat/create) 127 | 128 | 129 | ### GPT 130 | 131 | Like `ChatGPT`, `GPT` is a wrapper around the completion API. There is a basic `complete(_:)` method for convenient use, as well as a `complete(request:)` method that gives you full control. 132 | 133 | ```swift 134 | import GPT 135 | 136 | func askGPT() async throws { 137 | let gpt = GPT(apiKey: "YOUR_API_KEY", defaultModel: .davinci) 138 | 139 | let response = try await gpt.complete("What is the answer to life, the universe and everything in it?") 140 | print(response) 141 | } 142 | ``` 143 | 144 | ```swift 145 | import GPT 146 | 147 | func askGPT() async throws { 148 | let gpt = GPT(apiKey: "YOUR_API_KEY", defaultModel: .davinci) 149 | 150 | let fullRequest = CompletionRequest.davinci(prompt: "Why is the sky blue?") { request in 151 | request.temperature = 0.8 152 | request.numberOfAnswers = 2 153 | } 154 | 155 | let response = try await gpt.complete(request: fullRequest) 156 | print(response.choices.map(\.text)) 157 | } 158 | ``` 159 | 160 | Additionally, just like `ChatGPT`, `GPT` also supports streaming answers: 161 | 162 | ```swift 163 | import GPT 164 | 165 | // In your view model 166 | @Published var gptAnswer = "" 167 | 168 | func askGPT() async throws { 169 | let gpt = GPT(apiKey: "YOUR_API_KEY") 170 | 171 | gptAnswer = "" 172 | for try await nextWord in try await gpt.streamedAnswer.complete("Tell me a funny story about birds") { 173 | gptAnswer += nextWord 174 | } 175 | } 176 | ``` 177 | 178 | ### OpenAI 179 | 180 | Finally, you can access the available models through the `OpenAI` class. 181 | 182 | ```swift 183 | import OpenAI 184 | 185 | func openAI() async throws { 186 | let openAI = OpenAI(apiKey: "YOUR_API_KEY") 187 | 188 | let models = try await openAI.availableModels() 189 | let model = try await openAI.model(withId: "gpt-3.5-turbo") 190 | } 191 | ``` 192 | 193 | ## Generating cURL Prompts 194 | 195 | Sometimes, it might be useful to see the generated requests, so all three classes introduced above come with methods that generate a usable `cURL` prompt from a request that you can simply paste into a terminal. For example: 196 | 197 | ```swift 198 | import ChatGPT 199 | 200 | func askChatGPT() async throws { 201 | let chatGPT = ChatGPT(apiKey: "YOUR_API_KEY", defaultModel: .gpt3) 202 | 203 | let request = ChatRequest.gpt3 { request in 204 | request.messages = [ 205 | .init(role: .system, content: "You are the pilot of an alien UFO. Be creative."), 206 | .init(role: .user, content: "Where do you come from?") 207 | ] 208 | request.numberOfAnswers = 2 209 | } 210 | 211 | try await print(chatGPT.curl(for: request)) 212 | } 213 | ``` 214 | 215 | This generates the following: 216 | 217 | ``` 218 | curl --request POST \ 219 | --url 'https://api.openai.com/v1/chat/completions' \ 220 | --header 'Accept: application/json' \ 221 | --header 'Content-Type: application/json' \ 222 | --header 'Authorization: Bearer YOUR_API_KEY' \ 223 | --data '{"messages":[{"content":"You are the pilot of an alien UFO. Be creative.","role":"system"},{"content":"Where do you come from?","role":"user"}],"model":"gpt-3.5-turbo","n":2,"stream":false}' | json_pp 224 | 225 | ``` 226 | 227 | ## License 228 | 229 | MIT License 230 | 231 | Copyright (c) 2023 Dennis Müller and all collaborators 232 | 233 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 234 | 235 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 236 | 237 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 238 | -------------------------------------------------------------------------------- /Sources/Base/API.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import Foundation 23 | 24 | public struct API { 25 | private init() {} 26 | public static let base = "https://api.openai.com/" 27 | public static let v1ChatCompletion = "v1/chat/completions" 28 | public static let v1Completion = "v1/completions" 29 | public static let v1Models = "v1/models" 30 | 31 | public static func v1Model(withId id: String) -> String { 32 | "v1/models/\(id)" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Base/APIClientRequestHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import Foundation 23 | import Get 24 | import GPTSwiftSharedTypes 25 | 26 | public func _addHeaders(to request: inout URLRequest, apiKey: String) { 27 | request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") 28 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 29 | } 30 | 31 | public class _APIClientRequestHandler: APIClientDelegate { 32 | public let apiKey: String 33 | 34 | public init(apiKey: String) { 35 | self.apiKey = apiKey 36 | } 37 | 38 | public func client(_ client: APIClient, willSendRequest request: inout URLRequest) async throws { 39 | _addHeaders(to: &request, apiKey: apiKey) 40 | } 41 | 42 | public func client(_ client: APIClient, validateResponse response: HTTPURLResponse, data: Data, task: URLSessionTask) throws { 43 | if (200...299).contains(response.statusCode) { return } 44 | 45 | // If we can decode an error object from OpenAI, we use that as the error, as it contains useful information. 46 | if let openAIErrorDTO = try? JSONDecoder().decode(OpenAIErrorDTO.self, from: data) { 47 | throw openAIErrorDTO.error 48 | } 49 | } 50 | } 51 | 52 | /// Converts a `Swift.Error` object to a `GPTSwiftError`. 53 | /// - Parameter error: The error to convert. 54 | /// - Returns: A `GPTSwiftError`. 55 | public func _errorToGPTSwiftError(_ error: Swift.Error) -> GPTSwiftError { 56 | switch error { 57 | case let openAIError as OpenAIError: 58 | return .openAIError(openAIError) 59 | case let apiError as APIError: 60 | return .requestFailed(message: apiError.localizedDescription) 61 | case let urlError as URLError: 62 | return .urlError(urlError) 63 | default: 64 | return .other(error) 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /Sources/ChatGPT/ChatGPT+StreamedAnswer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | import Get 25 | import Base 26 | import GPTSwiftSharedTypes 27 | 28 | extension ChatGPT { 29 | public class StreamedAnswer { 30 | private let client: APIClient 31 | private let apiKey: String 32 | private let defaultModel: ChatGPTModel 33 | 34 | init(client: APIClient, apiKey: String, defaultModel: ChatGPTModel) { 35 | self.client = client 36 | self.apiKey = apiKey 37 | self.defaultModel = defaultModel 38 | } 39 | 40 | /// Ask ChatGPT a single prompt without any special configuration. 41 | /// - Parameter userPrompt: The prompt to send 42 | /// - Parameter systemPrompt: An optional system prompt to give GPT instructions on how to answer. 43 | /// - Parameter model: The model that should be used. 44 | /// - Returns: The response. 45 | /// - Throws: A `GPTSwiftError`. 46 | @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) 47 | public func ask( 48 | _ userPrompt: String, 49 | withSystemPrompt systemPrompt: String? = nil, 50 | model: ChatGPTModel = .default 51 | ) async throws -> AsyncThrowingStream { 52 | var messages: [ChatMessage] = [] 53 | 54 | if let systemPrompt { 55 | messages.insert(.init(role: .system, content: systemPrompt), at: 0) 56 | } 57 | 58 | messages.append(.init(role: .user, content: userPrompt)) 59 | let usingModel = model is DefaultChatGPTModel ? defaultModel : model 60 | let chatRequest = ChatRequest.streamed( 61 | model: usingModel, 62 | messages: messages 63 | ) 64 | 65 | return try await ask(request: chatRequest) 66 | } 67 | 68 | /// Ask ChatGPT something by sending multiple messages without any special configuration. 69 | /// - Parameter messages: The chat messages. 70 | /// - Parameter model: The model that should be used. 71 | /// - Returns: The response. 72 | /// - Throws: A `GPTSwiftError`. 73 | @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) 74 | public func ask( 75 | messages: [ChatMessage], 76 | model: ChatGPTModel = .default 77 | ) async throws -> AsyncThrowingStream { 78 | let usingModel = model is DefaultChatGPTModel ? defaultModel : model 79 | let chatRequest = ChatRequest.streamed(model: usingModel, messages: messages) 80 | return try await ask(request: chatRequest) 81 | } 82 | 83 | /// Ask ChatGPT something by providing a chat request object, giving you full control over the request's configuration. 84 | /// - Parameter chatRequest: The request. 85 | /// - Returns: The response. 86 | /// - Throws: A `GPTSwiftError`. 87 | @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) 88 | public func ask(request chatRequest: ChatRequest) async throws -> AsyncThrowingStream { 89 | 90 | var chatRequest = chatRequest 91 | chatRequest.stream = true 92 | 93 | let request = Request(path: API.v1ChatCompletion, method: .post, body: chatRequest) 94 | var urlRequest = try await client.makeURLRequest(for: request) 95 | _addHeaders(to: &urlRequest, apiKey: apiKey) 96 | 97 | do { 98 | let (result, response) = try await client.session.bytes(for: urlRequest) 99 | 100 | guard let response = response as? HTTPURLResponse else { 101 | throw GPTSwiftError.responseParsingFailed 102 | } 103 | 104 | guard response.statusCode.isStatusCodeOkay else { 105 | throw GPTSwiftError.requestFailed(message: "Response status code was unacceptable: \(response.statusCode)") 106 | } 107 | 108 | return AsyncThrowingStream { continuation in 109 | Task { 110 | do { 111 | for try await line in result.lines { 112 | guard let chatResponse = line.asStreamedResponse else { 113 | break 114 | } 115 | 116 | // Ignore lines where only the role is specified 117 | if chatResponse.choices.first?.delta.role != nil { 118 | continue 119 | } 120 | 121 | guard let message = chatResponse.choices.first?.delta.content else { 122 | continue 123 | } 124 | 125 | // Yield next token 126 | continuation.yield(message) 127 | } 128 | } catch { 129 | throw GPTSwiftError.responseParsingFailed 130 | } 131 | 132 | continuation.finish() 133 | } 134 | } 135 | } catch { 136 | throw _errorToGPTSwiftError(error) 137 | } 138 | } 139 | 140 | /// Turns a chat request into a curl prompt that you can paste into a terminal. 141 | /// 142 | /// This method will change the provided chat request to include a `stream: true` argument. 143 | /// The rest of the request will not be changed. 144 | /// 145 | /// This might be useful for debugging to experimenting. 146 | /// Taken from [Abhishek Maurya](https://gist.github.com/abhi21git/3dc611aab9e1cf5e5343ba4b58573596) and slightly adjusted. 147 | /// - Parameters: 148 | /// - chatRequest: The request. 149 | /// - pretty: An option to make the curl prompt pretty. 150 | /// - Returns: The curl prompt. 151 | public func curl(for chatRequest: ChatRequest, pretty: Bool = true) async throws -> String { 152 | var chatRequest = chatRequest 153 | chatRequest.stream = true 154 | let request = Request(path: API.v1ChatCompletion, method: .post, body: chatRequest) 155 | var urlRequest = try await client.makeURLRequest(for: request) 156 | _addHeaders(to: &urlRequest, apiKey: apiKey) 157 | return urlRequest.curl(pretty: pretty, formatOutput: false) 158 | } 159 | } 160 | } 161 | 162 | private let decoder = JSONDecoder() 163 | private extension String { 164 | var asStreamedResponse: ChatStreamedResponse? { 165 | guard hasPrefix("data: "), 166 | let data = dropFirst(6).data(using: .utf8) else { 167 | return nil 168 | } 169 | return try? decoder.decode(ChatStreamedResponse.self, from: data) 170 | } 171 | } 172 | 173 | private extension Int { 174 | var isStatusCodeOkay: Bool { 175 | (200...299).contains(self) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Sources/ChatGPT/ChatGPT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | import Get 25 | import Base 26 | @_exported import GPTSwiftSharedTypes 27 | 28 | /// A simple and easy to use wrapper around the ChatGPT API from OpenAI, with support for GPT 3.5 turbo as well as GPT 4 and its large context variant. 29 | public class ChatGPT { 30 | 31 | private let client: APIClient 32 | private let apiKey: String 33 | private let apiClientRequestHandler: _APIClientRequestHandler 34 | 35 | private let defaultModel: ChatGPTModel 36 | 37 | /// A variant of `ChatGPT` that streams all the answers. 38 | /// 39 | /// This exposes the exact same methods as before, but it returns an `AsyncThrowingStream` that yields individual tokens as soon as they are generated by GPT. 40 | public let streamedAnswer: StreamedAnswer 41 | 42 | /// A simple and easy to use wrapper around the ChatGPT API from OpenAI, with support for GPT 3.5 turbo as well as GPT 4 and its large context variant. 43 | /// 44 | /// - Parameter apiKey: The api key you can generate in your account page on OpenAI's website. 45 | /// - Parameter defaultModel: Sets the default model for all requests coming from this `ChatGPT` instance. 46 | /// - Parameter urlSessionConfiguration: An optional URL session configuration object. 47 | public init( 48 | apiKey: String, 49 | defaultModel: ChatGPTModel = .gpt3, 50 | urlSessionConfiguration: URLSessionConfiguration? = nil 51 | ) { 52 | self.apiKey = apiKey 53 | self.apiClientRequestHandler = .init(apiKey: apiKey) 54 | self.defaultModel = defaultModel 55 | self.client = APIClient(baseURL: URL(string: API.base)) { [apiClientRequestHandler] configuration in 56 | configuration.delegate = apiClientRequestHandler 57 | if let urlSessionConfiguration { 58 | configuration.sessionConfiguration = urlSessionConfiguration 59 | } 60 | } 61 | self.streamedAnswer = .init(client: client, apiKey: apiKey, defaultModel: defaultModel) 62 | } 63 | 64 | /// Ask ChatGPT a single prompt without any special configuration. 65 | /// - Parameter userPrompt: The prompt to send 66 | /// - Parameter systemPrompt: an optional system prompt to give GPT instructions on how to answer. 67 | /// - Parameter model: The model that should be used. 68 | /// - Returns: The response as string. 69 | /// - Throws: A `GPTSwiftError` if the request fails or the server returns an unauthorized status code. 70 | public func ask( 71 | _ userPrompt: String, 72 | withSystemPrompt systemPrompt: String? = nil, 73 | model: ChatGPTModel = .default 74 | ) async throws -> String { 75 | var messages: [ChatMessage] = [] 76 | 77 | if let systemPrompt { 78 | messages.insert(.init(role: .system, content: systemPrompt), at: 0) 79 | } 80 | 81 | messages.append(.init(role: .user, content: userPrompt)) 82 | 83 | let usingModel = model is DefaultChatGPTModel ? defaultModel : model 84 | let request = Request( 85 | path: API.v1ChatCompletion, 86 | method: .post, 87 | body: ChatRequest(model: usingModel, messages: messages) 88 | ) 89 | 90 | let response = try await send(request: request) 91 | guard let answer = response.choices.first?.message.content else { 92 | throw GPTSwiftError.responseParsingFailed 93 | } 94 | 95 | return answer 96 | } 97 | 98 | /// Ask ChatGPT something by sending multiple messages without any special configuration. 99 | /// - Parameter messages: The chat messages. 100 | /// - Parameter model: The model that should be used. 101 | /// - Returns: The response as string. 102 | /// - Throws: A `GPTSwiftError` if the request fails or the server returns an unauthorized status code. 103 | public func ask( 104 | messages: [ChatMessage], 105 | model: ChatGPTModel = .default 106 | ) async throws -> String { 107 | let usingModel = model is DefaultChatGPTModel ? defaultModel : model 108 | let request = Request( 109 | path: API.v1ChatCompletion, 110 | method: .post, 111 | body: ChatRequest(model: usingModel, messages: messages) 112 | ) 113 | 114 | let response = try await send(request: request) 115 | guard let answer = response.choices.first?.message.content else { 116 | throw GPTSwiftError.responseParsingFailed 117 | } 118 | 119 | return answer 120 | } 121 | 122 | /// Ask ChatGPT something by providing a chat request object, giving you full control over the request's configuration. 123 | /// - Parameter request: The request. 124 | /// - Returns: The response. 125 | /// - Throws: A `GPTSwiftError` if the request fails or the server returns an unauthorized status code. 126 | public func ask(request: ChatRequest) async throws -> ChatResponse { 127 | let request = Request( 128 | path: API.v1ChatCompletion, 129 | method: .post, 130 | body: request 131 | ) 132 | 133 | return try await send(request: request) 134 | } 135 | 136 | /// Turns a chat request into a curl prompt that you can paste into a terminal. 137 | /// 138 | /// This might be useful for debugging to experimenting. 139 | /// Taken from [Abhishek Maurya](https://gist.github.com/abhi21git/3dc611aab9e1cf5e5343ba4b58573596) and slightly adjusted. 140 | /// - Parameters: 141 | /// - chatRequest: The request. 142 | /// - pretty: An option to make the curl prompt pretty. 143 | /// - formatOutput: An option that, if set, puts the response through `json_pp` to prettify the json object. 144 | /// - Returns: The curl prompt. 145 | public func curl(for chatRequest: ChatRequest, pretty: Bool = true, formatOutput: Bool = true) async throws -> String { 146 | let request = Request(path: API.v1ChatCompletion, method: .post, body: chatRequest) 147 | var urlRequest = try await client.makeURLRequest(for: request) 148 | _addHeaders(to: &urlRequest, apiKey: apiKey) 149 | return urlRequest.curl(pretty: pretty, formatOutput: formatOutput) 150 | } 151 | 152 | /// Sends the request, catches all errors and replaces them with a `GPTSwiftError`. If successful, it returns the response value. 153 | /// - Parameter request: The request to send. 154 | /// - Returns: The response object, already decoded. 155 | private func send(request: Request) async throws -> Response { 156 | do { 157 | return try await client.send(request).value 158 | } catch { 159 | throw _errorToGPTSwiftError(error) 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Sources/ChatGPT/Models/ChatGPTModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import Foundation 23 | 24 | public protocol ChatGPTModel { 25 | var rawValue: String { get } 26 | } 27 | 28 | // MARK: - Default ChatGPTModel 29 | 30 | public struct DefaultChatGPTModel: ChatGPTModel { 31 | public var rawValue: String = "" 32 | } 33 | 34 | // MARK: - Custom ChatGPTModel 35 | 36 | public struct CustomChatGPTModel: ChatGPTModel { 37 | public var rawValue: String 38 | } 39 | 40 | // MARK: - GPT 3 ChatGPTModel 41 | 42 | public struct GPT3ChatGPTModel: ChatGPTModel { 43 | public var rawValue: String = "gpt-3.5-turbo" 44 | 45 | public func stableVersion(_ identifier: StableVersionIdentifier = .march_1_2023) -> GPT3ChatGPTModel { 46 | .init(rawValue: "gpt-3.5-turbo-0301") 47 | } 48 | 49 | public enum StableVersionIdentifier { 50 | case march_1_2023 51 | } 52 | } 53 | 54 | // MARK: - GPT 4 ChatGPTModel 55 | 56 | public struct GPT4ChatGPTModel: ChatGPTModel { 57 | public var rawValue: String = "gpt-4" 58 | 59 | public func stableVersion(_ identifier: StableVersionIdentifier = .march_14_2023) -> GPT4ChatGPTModel { 60 | .init(rawValue: "gpt-4-0314") 61 | } 62 | 63 | public enum StableVersionIdentifier { 64 | case march_14_2023 65 | } 66 | } 67 | 68 | // MARK: - GPT 4 ChatGPTModel With Large Context 69 | 70 | public struct GPT4LargeContextChatGPTModel: ChatGPTModel { 71 | public var rawValue: String = "gpt-4-32k" 72 | 73 | public func stableVersion(_ identifier: StableVersionIdentifier = .march_14_2023) -> GPT4ChatGPTModel { 74 | .init(rawValue: "gpt-4-32k-0314") 75 | } 76 | 77 | public enum StableVersionIdentifier { 78 | case march_14_2023 79 | } 80 | } 81 | 82 | // MARK: - Protocol Extensions 83 | 84 | public extension ChatGPTModel where Self == DefaultChatGPTModel { 85 | static var `default`: Self { 86 | DefaultChatGPTModel() 87 | } 88 | } 89 | 90 | public extension ChatGPTModel where Self == CustomChatGPTModel { 91 | /// A fallback model descriptor with which you can set a custom model id string yourself. 92 | /// 93 | /// Use this, if you need to use a model that is not explicitly provided by GPTSwift. Keep in mind, however, that the model has to behave exactly like the provided ones. Otherwise, the request will likely fail. 94 | /// - Parameter modelId: The model id to use, e.g. "gpt-4-some-awesome-variation". 95 | /// - Returns: The model descriptor. 96 | static func custom(modelId: String) -> Self { 97 | CustomChatGPTModel(rawValue: modelId) 98 | } 99 | } 100 | 101 | public extension ChatGPTModel where Self == GPT3ChatGPTModel { 102 | /// The GPT3 model descriptor. 103 | static var gpt3: GPT3ChatGPTModel { 104 | GPT3ChatGPTModel() 105 | } 106 | } 107 | 108 | public extension ChatGPTModel where Self == GPT4ChatGPTModel { 109 | /// The GPT4 model descriptor. 110 | static var gpt4: GPT4ChatGPTModel { 111 | GPT4ChatGPTModel() 112 | } 113 | } 114 | 115 | public extension ChatGPTModel where Self == GPT4LargeContextChatGPTModel { 116 | /// The GPT4 "Large Context" model descriptor. 117 | static var gpt4LargeContext: GPT4LargeContextChatGPTModel { 118 | GPT4LargeContextChatGPTModel() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/ChatGPT/Models/ChatMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | 25 | /// A message is a part of the chat conversation. 26 | /// 27 | /// GPT does not remember any previous messages, so if you want to keep a conversation coherent, 28 | /// you should always send previous messages with every new chat request. It might also be useful to send GPT's answers with the request as well (using the message `role` `assistant`). 29 | /// 30 | /// For more about this, see the [OpenAI documentation](https://platform.openai.com/docs/guides/chat/introduction). 31 | public struct ChatMessage: Codable { 32 | 33 | /// The role of the message. 34 | public var role: ChatMessageRole 35 | 36 | /// The content of the message. 37 | public var content: String 38 | 39 | /// A message is a part of the chat conversation. 40 | public init(role: ChatMessageRole, content: String) { 41 | self.role = role 42 | self.content = content 43 | } 44 | 45 | /// Convenience method to create a message with the `user` role. 46 | /// - Parameter content: The content of the message. 47 | /// - Returns: An instance of `ChatMessage` with the `user` role. 48 | public static func user(_ content: String) -> Self { 49 | .init(role: .user, content: content) 50 | } 51 | 52 | /// Convenience method to create a message with the `system` role. 53 | /// - Parameter content: The content of the message. 54 | /// - Returns: An instance of `ChatMessage` with the `system` role. 55 | public static func system(_ content: String) -> Self { 56 | .init(role: .system, content: content) 57 | } 58 | 59 | /// Convenience method to create a message with the `assistant` role. 60 | /// - Parameter content: The content of the message. 61 | /// - Returns: An instance of `ChatMessage` with the `assistant` role. 62 | public static func assistant(_ content: String) -> Self { 63 | .init(role: .assistant, content: content) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/ChatGPT/Models/ChatMessageRole.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Dennis Müller on 05.03.23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// A role can be seen as the "owner", or "author" of a given message. You use this to allow GPT to differentiate between actual user prompts, behavior instructions (by you, the developer) and previous answers by GPT. 11 | /// 12 | /// For more information, see the [OpenAI documentation](https://platform.openai.com/docs/guides/chat/introduction). 13 | public enum ChatMessageRole: String, Codable { 14 | 15 | /// The user role is the used for the prompts made by the end-user. 16 | case user 17 | 18 | /// The system role is used to instruct GPT on how to behave and what to generate. 19 | case system 20 | 21 | /// The assistant role is usually meant to indicate that a message originates from a previous GPT answer. 22 | /// 23 | /// This is useful because it allows GPT to recall previous answers and know about the general context of the conversation. 24 | case assistant 25 | } 26 | -------------------------------------------------------------------------------- /Sources/ChatGPT/Models/ChatRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | import Base 25 | 26 | /// A chat request is the main interface to ChatGPT's API. 27 | /// 28 | /// For more information, see the [OpenAI documentation](https://platform.openai.com/docs/guides/chat/introduction). 29 | /// And for detailed information about the parameters, see the [API documentation](https://platform.openai.com/docs/api-reference/chat). 30 | public struct ChatRequest { 31 | 32 | /// The model to use. 33 | public var model: String 34 | 35 | /// The messages for the request. 36 | public var messages: [ChatMessage] 37 | 38 | /// The temperature for the request. This determines the randomness of the response. 39 | public var temperature: Double? 40 | 41 | /// An alternative way of controlling the temperature. Do not use at the same time as `temperature`. 42 | public var topP: Double? 43 | 44 | /// The number of answers that GPT should generate. 45 | /// 46 | /// The default is 1. 47 | public var numberOfAnswers: Double? 48 | 49 | /// A boolean flag indicating if the answers should be streamed. 50 | var stream: Bool 51 | 52 | /// Up to 4 sequences where the API will stop generating further tokens. 53 | public var stop: [String]? 54 | 55 | /// The maximum number of tokens allowed for the response. If this is `nil`, it will default to allow the maximum number of tokens (4096 - message tokens). This does not mean, that all answers will generate that many tokens. 56 | public var maximumTokens: Int? 57 | 58 | /// A mechanism to penalize the occurrence of new tokens based on whether they appear in the text so far. 59 | /// 60 | /// Should be a number between `-2.0` and `2.0`. 61 | public var presencePenalty: Double? 62 | 63 | /// A mechanism to penalize the occurrence of new tokens based on their frequency in the text. 64 | /// 65 | /// Should be a number between `-2.0` and `2.0`. 66 | public var frequencyPenalty: Double? 67 | 68 | /// Modifies the likelihood of specified tokens appearing in the answer. 69 | /// 70 | /// This maps token IDs to their bias value. 71 | public var logitBias: [String: Double]? 72 | 73 | /// An optional user identifier to help detect misuse of the API. 74 | public var user: String? 75 | 76 | /// A chat request is the main interface to ChatGPT's API. 77 | public init( 78 | model: ChatGPTModel = .gpt3, 79 | messages: [ChatMessage] = [], 80 | maximumTokens: Int? = nil, 81 | temperature: Double? = nil, 82 | topP: Double? = nil, 83 | numberOfAnswers: Double? = nil, 84 | stop: [String]? = nil, 85 | presencePenalty: Double? = nil, 86 | frequencyPenalty: Double? = nil, 87 | logitBias: [String : Double]? = nil, 88 | user: String? = nil 89 | ) { 90 | self.model = model.rawValue 91 | self.messages = messages 92 | self.maximumTokens = maximumTokens 93 | self.temperature = temperature 94 | self.topP = topP 95 | self.numberOfAnswers = numberOfAnswers 96 | self.stop = stop 97 | self.presencePenalty = presencePenalty 98 | self.frequencyPenalty = frequencyPenalty 99 | self.logitBias = logitBias 100 | self.user = user 101 | self.stream = false 102 | } 103 | 104 | /// Convenience initializer that sets the model to `gpt3` and configures the request with the provided closure. 105 | /// - Parameters: 106 | /// - configure: the configuration. 107 | /// - Returns: A configured instance of `ChatRequest`. 108 | public static func gpt3(configuration configure: ((_ request: inout ChatRequest) -> Void)? = nil) -> ChatRequest { 109 | var request = ChatRequest(model: .gpt3) 110 | configure?(&request) 111 | return request 112 | } 113 | 114 | /// Convenience initializer that sets the model to `gpt4` and configures the request with the provided closure. 115 | /// - Parameters: 116 | /// - configure: the configuration. 117 | /// - Returns: A configured instance of `ChatRequest`. 118 | public static func gpt4(configuration configure: ((_ request: inout ChatRequest) -> Void)? = nil) -> ChatRequest { 119 | var request = ChatRequest(model: .gpt4) 120 | configure?(&request) 121 | return request 122 | } 123 | 124 | /// Convenience initializer that sets the model to `gpt4LargeContext` and configures the request with the provided closure. 125 | /// - Parameters: 126 | /// - configure: the configuration. 127 | /// - Returns: A configured instance of `ChatRequest`. 128 | public static func gpt4LargeContext(configuration configure: ((_ request: inout ChatRequest) -> Void)? = nil) -> ChatRequest { 129 | var request = ChatRequest(model: .gpt4LargeContext) 130 | configure?(&request) 131 | return request 132 | } 133 | 134 | static func streamed(model: ChatGPTModel, messages: [ChatMessage]) -> Self { 135 | var request = ChatRequest(model: model, messages: messages) 136 | request.stream = true 137 | return request 138 | } 139 | } 140 | 141 | extension ChatRequest: Codable { 142 | 143 | enum CodingKeys: String, CodingKey { 144 | case model 145 | case messages 146 | case stop 147 | case maximumTokens = "max_tokens" 148 | case temperature 149 | case topP = "top_p" 150 | case numberOfAnswers = "n" 151 | case presencePenalty = "presence_penalty" 152 | case frequencyPenalty = "frequency_penalty" 153 | case logitBias = "logit_bias" 154 | case stream 155 | case user 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Sources/ChatGPT/Models/ChatResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | /// The response for a GPT `ChatRequest`. 23 | public struct ChatResponse: Codable { 24 | 25 | /// An identifier for the response. 26 | public var id: String 27 | 28 | /// The description of the response. Usually, this should be "chat.completion". 29 | public var object: String 30 | 31 | /// The creation date of the response. 32 | public var created: Int 33 | 34 | /// The model used for generating the response. 35 | public var model: String 36 | 37 | /// Usage statistics, for example the used tokens. 38 | public var usage: Usage 39 | 40 | /// The answers that GPT generated. 41 | public var choices: [Choice] 42 | } 43 | 44 | // MARK: - Subtypes 45 | 46 | extension ChatResponse { 47 | 48 | /// Usage statistics, for example the used tokens. 49 | public struct Usage: Codable { 50 | 51 | /// The number of tokens used to encode the request messages. 52 | public var promptTokens: Int 53 | 54 | /// The number of tokens used to encode the answers. 55 | public var completionTokens: Int 56 | 57 | /// The total number of tokens used. 58 | public var totalTokens: Int 59 | 60 | public enum CodingKeys: String, CodingKey { 61 | case promptTokens = "prompt_tokens" 62 | case completionTokens = "completion_tokens" 63 | case totalTokens = "total_tokens" 64 | } 65 | } 66 | } 67 | 68 | extension ChatResponse { 69 | 70 | /// An answer that GPT generated. 71 | public struct Choice: Codable { 72 | 73 | /// The actual message of the answer. 74 | public var message: ChatMessage 75 | 76 | /// An optional reason for the termination of the message. 77 | /// 78 | /// For example, this can be "stop" to indicate GPT considers the answer as done. 79 | public var finishReason: String? 80 | 81 | /// The index of the message in the messages array of `CompletionResponse`. 82 | public var index: Int 83 | 84 | public enum CodingKeys: String, CodingKey { 85 | case message 86 | case finishReason = "finish_reason" 87 | case index 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/ChatGPT/Models/ChatStreamedMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | 25 | /// A streamed message is a part of the chat conversation. 26 | /// 27 | /// For more about this, see the [OpenAI documentation](https://platform.openai.com/docs/guides/chat/introduction). 28 | struct ChatStreamedMessage: Codable { 29 | 30 | /// The role of the message. 31 | var role: ChatMessageRole? 32 | 33 | /// The content of the message. 34 | var content: String? 35 | 36 | /// A message is a part of the chat conversation. 37 | init(role: ChatMessageRole?, content: String?) { 38 | self.role = role 39 | self.content = content 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/ChatGPT/Models/ChatStreamedResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | /// The response for a GPT `ChatRequest` that is streamed. 23 | struct ChatStreamedResponse: Codable { 24 | 25 | /// An identifier for the response. 26 | var id: String 27 | 28 | /// The description of the response. Usually, this should be "chat.completion". 29 | var object: String 30 | 31 | /// The creation date of the response. 32 | var created: Int 33 | 34 | /// The model used for generating the response. 35 | var model: String 36 | 37 | /// The answers that GPT generated. 38 | var choices: [Choice] 39 | } 40 | 41 | extension ChatStreamedResponse { 42 | 43 | /// An answer that GPT generated. 44 | struct Choice: Codable { 45 | 46 | /// The actual message of the answer. 47 | var delta: ChatStreamedMessage 48 | 49 | /// An optional reason for the termination of the message. 50 | /// 51 | /// For example, this can be "stop" to indicate GPT considers the answer as done. 52 | var finishReason: String? 53 | 54 | /// The index of the message in the messages array of `CompletionResponse`. 55 | var index: Int 56 | 57 | enum CodingKeys: String, CodingKey { 58 | case delta 59 | case finishReason = "finish_reason" 60 | case index 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/GPT/GPT+StreamedAnswer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | import Get 25 | import Base 26 | import GPTSwiftSharedTypes 27 | 28 | extension GPT { 29 | public class StreamedAnswer { 30 | private let client: APIClient 31 | private let apiKey: String 32 | private let defaultModel: GPTModel 33 | 34 | init(client: APIClient, apiKey: String, defaultModel: GPTModel) { 35 | self.client = client 36 | self.apiKey = apiKey 37 | self.defaultModel = defaultModel 38 | } 39 | 40 | /// Generates a completion for the given user prompt using the specified model or the default model, streaming the response token by token. 41 | /// 42 | /// This method is a convenience method for the "Create completion" OpenAI API endpoint with the `stream` option enabled. 43 | /// 44 | /// - Parameter userPrompt: The prompt to complete. 45 | /// - Parameter model: The GPT model to use for generating the completion. Defaults to `.default`. 46 | /// - Returns: An `AsyncThrowingStream` of `String` objects representing tokens in the generated completion. 47 | /// - Throws: A `Swift.Error` if the request fails or the server returns an unauthorized status code. 48 | @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) 49 | public func complete( 50 | _ userPrompt: String, 51 | model: GPTModel = .default 52 | ) async throws -> AsyncThrowingStream { 53 | let usingModel = model is DefaultGPTModel ? defaultModel : model 54 | let completionRequest = CompletionRequest.streamed( 55 | model: usingModel, 56 | prompt: userPrompt 57 | ) 58 | 59 | return try await complete(request: completionRequest) 60 | } 61 | 62 | /// Generates a completion for the given `CompletionRequest`, streaming the response token by token. 63 | /// 64 | /// This method provides full control over the completion request and corresponds to the "Create completion" OpenAI API endpoint with the `stream` option enabled. 65 | /// 66 | /// - Parameter completionRequest: A `CompletionRequest` object containing the details of the completion request. 67 | /// 68 | /// - Returns: An `AsyncThrowingStream` of `String` objects representing tokens in the generated completion. 69 | /// - Throws: A `Swift.Error` if the request fails or the server returns an unauthorized status code. 70 | @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) 71 | public func complete(request completionRequest: CompletionRequest) async throws -> AsyncThrowingStream { 72 | 73 | var completionRequest = completionRequest 74 | completionRequest.stream = true 75 | 76 | let request = Request(path: API.v1Completion, method: .post, body: completionRequest) 77 | var urlRequest = try await client.makeURLRequest(for: request) 78 | _addHeaders(to: &urlRequest, apiKey: apiKey) 79 | 80 | do { 81 | let (result, response) = try await client.session.bytes(for: urlRequest) 82 | 83 | guard let response = response as? HTTPURLResponse else { 84 | throw GPTSwiftError.responseParsingFailed 85 | } 86 | 87 | guard response.statusCode.isStatusCodeOkay else { 88 | throw GPTSwiftError.requestFailed(message: "Response status code was unacceptable: \(response.statusCode)") 89 | } 90 | 91 | return AsyncThrowingStream { continuation in 92 | Task { 93 | do { 94 | for try await line in result.lines { 95 | guard let chatResponse = line.asStreamedResponse else { 96 | break 97 | } 98 | 99 | guard let message = chatResponse.choices.first?.text else { 100 | continue 101 | } 102 | 103 | // Yield next token 104 | continuation.yield(message) 105 | } 106 | } catch { 107 | throw GPTSwiftError.responseParsingFailed 108 | } 109 | 110 | continuation.finish() 111 | } 112 | } 113 | } catch { 114 | throw _errorToGPTSwiftError(error) 115 | } 116 | } 117 | 118 | /// Turns a completion request into a curl prompt that you can paste into a terminal. 119 | /// 120 | /// This method will change the provided completion request to include a `stream: true` argument. 121 | /// The rest of the request will not be changed. 122 | /// 123 | /// This might be useful for debugging to experimenting. 124 | /// Taken from [Abhishek Maurya](https://gist.github.com/abhi21git/3dc611aab9e1cf5e5343ba4b58573596) and slightly adjusted. 125 | /// - Parameters: 126 | /// - completionRequest: The request. 127 | /// - pretty: An option to make the curl prompt pretty. 128 | /// - Returns: The curl prompt. 129 | public func curl(for completionRequest: CompletionRequest, pretty: Bool = true) async throws -> String { 130 | var completionRequest = completionRequest 131 | completionRequest.stream = true 132 | let request = Request(path: API.v1Completion, method: .post, body: completionRequest) 133 | var urlRequest = try await client.makeURLRequest(for: request) 134 | _addHeaders(to: &urlRequest, apiKey: apiKey) 135 | return urlRequest.curl(pretty: pretty, formatOutput: false) 136 | } 137 | } 138 | } 139 | 140 | private let decoder = JSONDecoder() 141 | private extension String { 142 | var asStreamedResponse: CompletionStreamedResponse? { 143 | guard hasPrefix("data: "), 144 | let data = dropFirst(6).data(using: .utf8) else { 145 | return nil 146 | } 147 | return try? decoder.decode(CompletionStreamedResponse.self, from: data) 148 | } 149 | } 150 | 151 | private extension Int { 152 | var isStatusCodeOkay: Bool { 153 | (200...299).contains(self) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Sources/GPT/GPT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | import Get 25 | import Base 26 | @_exported import GPTSwiftSharedTypes 27 | 28 | /// A simple wrapper around the API for OpenAI's GPT. 29 | public class GPT { 30 | 31 | private let client: APIClient 32 | private let apiKey: String 33 | private let apiClientRequestHandler: _APIClientRequestHandler 34 | private let defaultModel: GPTModel 35 | 36 | /// A variant of `GPT` that streams all the answers. 37 | /// 38 | /// This exposes the exact same methods as before, but it returns an `AsyncThrowingStream` that yields individual tokens as soon as they are generated by GPT. 39 | public let streamedAnswer: StreamedAnswer 40 | 41 | /// A simple wrapper around the API for OpenAI. 42 | /// - Parameter apiKey: The api key you can generate in your account page on OpenAI's website. 43 | /// - Parameter defaultModel: Sets the default model for all requests coming from this `GPT` instance. 44 | /// - Parameter urlSessionConfiguration: An optional URL session configuration object. 45 | public init( 46 | apiKey: String, 47 | defaultModel: GPTModel = .davinci, 48 | urlSessionConfiguration: URLSessionConfiguration? = nil 49 | ) { 50 | self.apiKey = apiKey 51 | self.apiClientRequestHandler = .init(apiKey: apiKey) 52 | self.defaultModel = defaultModel 53 | self.client = APIClient(baseURL: URL(string: API.base)) { [apiClientRequestHandler] configuration in 54 | configuration.delegate = apiClientRequestHandler 55 | if let urlSessionConfiguration { 56 | configuration.sessionConfiguration = urlSessionConfiguration 57 | } 58 | } 59 | self.streamedAnswer = .init(client: client, apiKey: apiKey, defaultModel: defaultModel) 60 | } 61 | 62 | /// Generates a completion for the given user prompt using the specified model or the default model. 63 | /// 64 | /// This method is a convenience method for the "Create completion" OpenAI API endpoint. 65 | /// 66 | /// - Parameter userPrompt: The prompt to complete. 67 | /// - Parameter model: The GPT model to use for generating the completion. Defaults to `.default`. 68 | /// 69 | /// - Returns: A `CompletionResponse` object containing the generated completion. 70 | /// - Throws: A `GPTSwiftError` if the request fails or the server returns an unauthorized status code. 71 | public func complete( 72 | _ userPrompt: String, 73 | model: GPTModel = .default 74 | ) async throws -> CompletionResponse { 75 | let usingModel = model is DefaultGPTModel ? defaultModel : model 76 | let request = Request( 77 | path: API.v1Completion, 78 | method: .post, 79 | body: CompletionRequest(model: usingModel, prompt: userPrompt) 80 | ) 81 | 82 | return try await send(request: request) 83 | } 84 | 85 | /// Generates a completion for the given `CompletionRequest`. 86 | /// 87 | /// This method provides full control over the completion request and corresponds to the "Create completion" OpenAI API endpoint. 88 | /// 89 | /// - Parameter completionRequest: A `CompletionRequest` object containing the details of the completion request. 90 | /// 91 | /// - Returns: A `CompletionResponse` object containing the generated completion. 92 | /// - Throws: A `GPTSwiftError` if the request fails or the server returns an unauthorized status code. 93 | public func complete(request completionRequest: CompletionRequest) async throws -> CompletionResponse { 94 | let request = Request( 95 | path: API.v1Completion, 96 | method: .post, 97 | body: completionRequest 98 | ) 99 | 100 | return try await send(request: request) 101 | } 102 | 103 | /// Turns a completion request into a curl prompt that you can paste into a terminal. 104 | /// 105 | /// This might be useful for debugging to experimenting. 106 | /// Taken from [Abhishek Maurya](https://gist.github.com/abhi21git/3dc611aab9e1cf5e5343ba4b58573596) and slightly adjusted. 107 | /// - Parameters: 108 | /// - completionRequest: The request. 109 | /// - pretty: An option to make the curl prompt pretty. 110 | /// - formatOutput: An option that, if set, puts the response through `json_pp` to prettify the json object. 111 | /// - Returns: The curl prompt. 112 | public func curl(for completionRequest: CompletionRequest, pretty: Bool = true, formatOutput: Bool = true) async throws -> String { 113 | let request = Request(path: API.v1Completion, method: .post, body: completionRequest) 114 | var urlRequest = try await client.makeURLRequest(for: request) 115 | _addHeaders(to: &urlRequest, apiKey: apiKey) 116 | return urlRequest.curl(pretty: pretty, formatOutput: formatOutput) 117 | } 118 | 119 | /// Sends the request, catches all errors and replaces them with a `GPTSwiftError`. If successful, it returns the response value. 120 | /// - Parameter request: The request to send. 121 | /// - Returns: The response object, already decoded. 122 | private func send(request: Request) async throws -> Response { 123 | do { 124 | return try await client.send(request).value 125 | } catch { 126 | throw _errorToGPTSwiftError(error) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Sources/GPT/Models/CompletionRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | import Base 25 | 26 | public struct CompletionRequest { 27 | 28 | /// The model to use. 29 | /// 30 | /// The default is to use the current model, that continuously receives updates. 31 | public var model: String 32 | 33 | /// The prompt for the request. 34 | public var prompt: String 35 | 36 | /// The suffix that comes after a completion. 37 | public var suffix: String? 38 | 39 | /// The maximum number of tokens allowed for the response. If this is `nil`, it will default to allow the maximum number of tokens (4096 - message tokens). This does not mean, that all answers will generate that many tokens. 40 | public var maximumTokens: Int? 41 | 42 | /// The temperature for the request. This determines the randomness of the response. 43 | public var temperature: Double? 44 | 45 | /// An alternative way of controlling the temperature. Do not use at the same time as `temperature`. 46 | public var topP: Double? 47 | 48 | /// The number of answers that GPT should generate. 49 | /// 50 | /// The default is 1. 51 | public var numberOfAnswers: Double? 52 | 53 | public var logProbabilities: Int? 54 | 55 | public var includePromptsInResponse: Bool 56 | 57 | public var stopSequences: [String]? 58 | 59 | /// A mechanism to penalize the occurrence of new tokens based on whether they appear in the text so far. 60 | /// 61 | /// Should be a number between `-2.0` and `2.0`. 62 | public var presencePenalty: Double? 63 | 64 | /// A mechanism to penalize the occurrence of new tokens based on their frequency in the text. 65 | /// 66 | /// Should be a number between `-2.0` and `2.0`. 67 | public var frequencyPenalty: Double? 68 | 69 | /// Generates n results server-side, where n is the provided value, and returns the one with the highest log probability per token. 70 | public var bestOf: Int? 71 | 72 | /// Modifies the likelihood of specified tokens appearing in the answer. 73 | /// 74 | /// This maps token IDs to their bias value. 75 | public var logitBias: [String: Double]? 76 | 77 | /// An optional user identifier to help detect misuse of the API. 78 | public var user: String? 79 | 80 | var stream: Bool 81 | 82 | /// A chat request is the main interface to ChatGPT's API. 83 | public init( 84 | model: GPTModel = .davinci, 85 | prompt: String, 86 | suffix: String? = nil, 87 | maximumTokens: Int? = nil, 88 | temperature: Double? = nil, 89 | topP: Double? = nil, 90 | numberOfAnswers: Double? = nil, 91 | logProbabilities: Int? = nil, 92 | includePromptsInResponse: Bool = false, 93 | stopSequences: [String]? = nil, 94 | presencePenalty: Double? = nil, 95 | frequencyPenalty: Double? = nil, 96 | bestOf: Int? = nil, 97 | logitBias: [String : Double]? = nil, 98 | user: String? = nil 99 | ) { 100 | self.model = model.rawValue 101 | self.prompt = prompt 102 | self.suffix = suffix 103 | self.maximumTokens = maximumTokens 104 | self.temperature = temperature 105 | self.topP = topP 106 | self.numberOfAnswers = numberOfAnswers 107 | self.logProbabilities = logProbabilities 108 | self.includePromptsInResponse = includePromptsInResponse 109 | self.stopSequences = stopSequences 110 | self.presencePenalty = presencePenalty 111 | self.frequencyPenalty = frequencyPenalty 112 | self.bestOf = bestOf 113 | self.logitBias = logitBias 114 | self.user = user 115 | self.stream = false 116 | } 117 | 118 | /// Convenience initializer that sets the model to `davinci` and configures the request with the provided closure. 119 | /// - Parameters: 120 | /// - prompt: The prompt for the request. 121 | /// - configure: the configuration. 122 | /// - Returns: A configured instance of `CompletionRequest`. 123 | public static func davinci(prompt: String, configuration configure: ((_ request: inout CompletionRequest) -> Void)? = nil) -> CompletionRequest { 124 | var request = CompletionRequest(model: .davinci, prompt: prompt) 125 | configure?(&request) 126 | return request 127 | } 128 | 129 | static func streamed(model: GPTModel, prompt: String) -> Self { 130 | var request = CompletionRequest(model: model, prompt: prompt) 131 | request.stream = true 132 | return request 133 | } 134 | } 135 | 136 | extension CompletionRequest: Codable { 137 | enum CodingKeys: String, CodingKey { 138 | case model 139 | case prompt 140 | case suffix 141 | case maximumTokens = "max_tokens" 142 | case temperature 143 | case topP = "top_p" 144 | case logProbabilities = "logprobs" 145 | case includePromptsInResponse = "echo" 146 | case stopSequences = "stop" 147 | case numberOfAnswers = "n" 148 | case presencePenalty = "presence_penalty" 149 | case frequencyPenalty = "frequency_penalty" 150 | case bestOf = "best_of" 151 | case logitBias = "logit_bias" 152 | case stream 153 | case user 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Sources/GPT/Models/CompletionResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | /// The response for a GPT `ChatRequest`. 23 | public struct CompletionResponse: Codable { 24 | 25 | /// An identifier for the response. 26 | public var id: String 27 | 28 | /// The description of the response. Usually, this should be "chat.completion". 29 | public var object: String 30 | 31 | /// The creation date of the response. 32 | public var created: Int 33 | 34 | /// The model used for generating the response. 35 | public var model: String 36 | 37 | /// Usage statistics, for example the used tokens. 38 | public var usage: Usage 39 | 40 | /// The answers that GPT generated. 41 | public var choices: [Choice] 42 | } 43 | 44 | // MARK: - Subtypes 45 | 46 | extension CompletionResponse { 47 | 48 | /// Usage statistics, for example the used tokens. 49 | public struct Usage: Codable { 50 | 51 | /// The number of tokens used to encode the request messages. 52 | public var promptTokens: Int 53 | 54 | /// The number of tokens used to encode the answers. 55 | public var completionTokens: Int 56 | 57 | /// The total number of tokens used. 58 | public var totalTokens: Int 59 | 60 | public enum CodingKeys: String, CodingKey { 61 | case promptTokens = "prompt_tokens" 62 | case completionTokens = "completion_tokens" 63 | case totalTokens = "total_tokens" 64 | } 65 | } 66 | } 67 | 68 | extension CompletionResponse { 69 | 70 | /// An answer that GPT generated. 71 | public struct Choice: Codable { 72 | 73 | /// The actual message of the answer. 74 | public var text: String 75 | 76 | /// An optional reason for the termination of the message. 77 | /// 78 | /// For example, this can be "stop" to indicate GPT considers the answer as done. 79 | public var finishReason: String? 80 | 81 | /// The index of the message in the messages array of `CompletionResponse`. 82 | public var index: Int 83 | 84 | public enum CodingKeys: String, CodingKey { 85 | case text 86 | case finishReason = "finish_reason" 87 | case index 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/GPT/Models/CompletionStreamedResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | /// The response for a GPT `CompletionResponse` that is streamed. 23 | struct CompletionStreamedResponse: Codable { 24 | var id: String 25 | var object: String 26 | var created: Int 27 | var model: String 28 | var choices: [Choice] 29 | } 30 | 31 | extension CompletionStreamedResponse { 32 | struct Choice: Codable { 33 | var text: String 34 | var finishReason: String? 35 | var logProbabilities: Double? 36 | var index: Int 37 | 38 | enum CodingKeys: String, CodingKey { 39 | case text 40 | case finishReason = "finish_reason" 41 | case logProbabilities = "logProbs" 42 | case index 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/GPT/Models/GPTModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import Foundation 23 | 24 | public protocol GPTModel { 25 | var rawValue: String { get } 26 | } 27 | 28 | // MARK: - Default ChatGPTModel 29 | 30 | public struct DefaultGPTModel: GPTModel { 31 | public var rawValue: String = "" 32 | } 33 | 34 | // MARK: - Custom GPTModel 35 | 36 | public struct CustomGPTModel: GPTModel { 37 | public var rawValue: String 38 | } 39 | 40 | // MARK: - GPT 3 Model 41 | 42 | public struct DavinciGPTModel: GPTModel { 43 | public var rawValue: String = "text-davinci-003" 44 | } 45 | 46 | // MARK: - Protocol Extensions 47 | 48 | public extension GPTModel where Self == DefaultGPTModel { 49 | static var `default`: Self { 50 | DefaultGPTModel() 51 | } 52 | } 53 | 54 | public extension GPTModel where Self == CustomGPTModel { 55 | /// A fallback model descriptor with which you can set a custom model id string yourself. 56 | /// 57 | /// Use this, if you need to use a model that is not explicitly provided by GPTSwift. Keep in mind, however, that the model has to behave exactly like the provided ones. Otherwise, the request will likely fail. 58 | /// - Parameter modelId: The model id to use, e.g. "gpt-4-some-awesome-variation". 59 | /// - Returns: The model descriptor. 60 | static func custom(modelId: String) -> Self { 61 | CustomGPTModel(rawValue: modelId) 62 | } 63 | } 64 | 65 | public extension GPTModel where Self == DavinciGPTModel { 66 | /// The davinci model descriptor. 67 | static var davinci: DavinciGPTModel { 68 | DavinciGPTModel() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/GPTSwiftSharedTypes/GPTSwiftError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import Foundation 23 | 24 | /// A custom error type representing errors related to GPTSwift. 25 | public struct GPTSwiftError: Swift.Error { 26 | 27 | /// Creates a new `GPTSwiftError` instance with the request failed error kind. 28 | /// 29 | /// - Parameter message: A string describing the error that occurred during the request. 30 | /// - Returns: A new `GPTSwiftError` instance. 31 | public static func requestFailed(message: String) -> Self { 32 | .init(kind: .requestFailed(message)) 33 | } 34 | 35 | /// Creates a new `GPTSwiftError` instance with the response parsing failed error kind. 36 | /// 37 | /// - Returns: A new `GPTSwiftError` instance. 38 | public static var responseParsingFailed: Self { 39 | .init(kind: .responseParsingFailed) 40 | } 41 | 42 | /// Creates a new `GPTSwiftError` instance with the URL error kind. 43 | /// 44 | /// - Parameter error: The `URLError` instance that occurred. 45 | /// - Returns: A new `GPTSwiftError` instance. 46 | public static func urlError(_ error: URLError) -> Self { 47 | .init(kind: .urlError(error)) 48 | } 49 | 50 | /// Creates a new `GPTSwiftError` instance with the other error kind. 51 | /// 52 | /// - Parameter error: The `Error` instance representing the unknown error that occurred. 53 | /// - Returns: A new `GPTSwiftError` instance. 54 | public static func other(_ error: Swift.Error) -> Self { 55 | .init(kind: .other(error)) 56 | } 57 | 58 | /// Creates a new `GPTSwiftError` instance with the OpenAI error kind. 59 | /// 60 | /// - Parameter error: The `OpenAIError` instance that occurred. 61 | /// - Returns: A new `GPTSwiftError` instance. 62 | public static func openAIError(_ error: OpenAIError) -> Self { 63 | .init(kind: .openAIError(error)) 64 | } 65 | 66 | /// The specific kind of `GPTSwiftError`. 67 | public var kind: Kind 68 | 69 | /// A string prefix used for error messages. 70 | private var prefix: String { "GPTSwiftError: " } 71 | 72 | /// A string describing the error. 73 | public var message: String { 74 | switch kind { 75 | case .requestFailed(let message): 76 | return prefix + message 77 | case .responseParsingFailed: 78 | return prefix + "Something went wrong with parsing the response." 79 | case .urlError(let error): 80 | return prefix + error.localizedDescription 81 | case .other(let error): 82 | return prefix + "An unknown error occurred: »\(error)«" 83 | case .openAIError(let error): 84 | return prefix + "(OpenAI response) »\(error.message)« - type »\(error.type)« - code: \(error.code ?? "not provided")" 85 | } 86 | } 87 | 88 | /// Initializes a new `GPTSwiftError` instance with the given kind. 89 | /// 90 | /// - Parameter kind: The specific kind of `GPTSwiftError`. 91 | init(kind: Kind) { 92 | self.kind = kind 93 | } 94 | 95 | /// A localized description of the error. 96 | public var localizedDescription: String { 97 | message 98 | } 99 | } 100 | 101 | /// An extension to `GPTSwiftError` containing an enumeration for different error kinds. 102 | extension GPTSwiftError { 103 | public enum Kind { 104 | /// An error kind indicating that a request failed. 105 | /// 106 | /// - Associated value: A string describing the error that occurred during the request. 107 | case requestFailed(String) 108 | 109 | /// An error kind indicating that parsing the response failed. 110 | case responseParsingFailed 111 | 112 | /// An error kind indicating that a URL error occurred. 113 | /// 114 | /// - Associated value: The `URLError` instance that occurred. 115 | case urlError(URLError) 116 | 117 | /// An error kind indicating that an OpenAI error occurred. 118 | /// 119 | /// - Associated value: The `OpenAIError` instance that occurred. 120 | case openAIError(OpenAIError) 121 | 122 | /// An error kind indicating that an unknown error occurred. 123 | /// 124 | /// - Associated value: The `Error` instance representing the unknown error that occurred. 125 | case other(Swift.Error) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Sources/GPTSwiftSharedTypes/OpenAIError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import Foundation 23 | 24 | public struct OpenAIErrorDTO: Codable { 25 | public var error: OpenAIError 26 | } 27 | 28 | /// An error object from the OpenAI API. 29 | public struct OpenAIError: Swift.Error, Codable { 30 | 31 | /// A description of the error. 32 | public var message: String 33 | 34 | /// A type identifier for the error. 35 | public var type: String 36 | 37 | /// A code identifying the error. 38 | public var code: String? 39 | } 40 | -------------------------------------------------------------------------------- /Sources/GPTSwiftSharedTypes/URLRequest+curl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import Foundation 23 | 24 | extension URLRequest { 25 | 26 | /// Turns a request into a curl prompt that you can paste into a terminal. 27 | /// 28 | /// This might be useful for debugging to experimenting. 29 | /// 30 | /// Taken from https://gist.github.com/abhi21git/3dc611aab9e1cf5e5343ba4b58573596 and slightly adjusted. 31 | /// - Parameters: 32 | /// - pretty: An option to make the curl prompt pretty. 33 | /// - formatOutput: An option that, if set, puts the response through `json_pp` to prettify the json object. 34 | /// - Returns: The curl prompt 35 | public func curl(pretty: Bool = true, formatOutput: Bool = true) -> String { 36 | let newLine = pretty ? "\\\n" : "" 37 | let method = (pretty ? "--request " : "-X ") + "\(httpMethod ?? "GET") \(newLine)" 38 | let url: String = (pretty ? "--url " : "") + "\'\(url?.absoluteString ?? "")\' \(newLine)" 39 | 40 | var curl = "curl " 41 | var header = "" 42 | var data: String = "" 43 | 44 | if let httpHeaders = allHTTPHeaderFields, httpHeaders.keys.count > 0 { 45 | for (key,value) in httpHeaders { 46 | header += (pretty ? "--header " : "-H ") + "\'\(key): \(value)\' \(newLine)" 47 | } 48 | } 49 | 50 | if let bodyData = httpBody, let bodyString = String(data: bodyData, encoding: .utf8), !bodyString.isEmpty { 51 | data = "--data '\(bodyString)'" 52 | } 53 | 54 | curl += method + url + header + data 55 | 56 | if formatOutput { 57 | curl += " | json_pp" 58 | } 59 | 60 | return curl 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/OpenAI/Models/ModelList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | import Get 25 | import Base 26 | 27 | struct ModelListResponse: Codable { 28 | var data: [ModelData] 29 | } 30 | 31 | public struct ModelData: Codable { 32 | var id: String 33 | var object: String 34 | var owner: String 35 | var root: String 36 | var permission: [Permissions] 37 | 38 | public enum CodingKeys: String, CodingKey { 39 | case id 40 | case object 41 | case owner = "owned_by" 42 | case root 43 | case permission 44 | } 45 | 46 | public struct Permissions: Codable { 47 | public var id: String 48 | public var object: String 49 | public var created: Int 50 | public var allowCreateEngine: Bool 51 | public var allowSampling: Bool 52 | public var allowLogprobs: Bool 53 | public var allowSearchIndices: Bool 54 | public var allowView: Bool 55 | public var allowFineTuning: Bool 56 | public var organization: String 57 | public var group: String? 58 | public var isBlocking: Bool 59 | 60 | public enum CodingKeys: String, CodingKey { 61 | case id 62 | case object 63 | case created 64 | case allowCreateEngine = "allow_create_engine" 65 | case allowSampling = "allow_sampling" 66 | case allowLogprobs = "allow_logprobs" 67 | case allowSearchIndices = "allow_search_indices" 68 | case allowView = "allow_view" 69 | case allowFineTuning = "allow_fine_tuning" 70 | case organization 71 | case group 72 | case isBlocking = "is_blocking" 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/OpenAI/OpenAI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2023 Dennis Müller and all collaborators 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | import Foundation 24 | import Get 25 | import Base 26 | @_exported import GPTSwiftSharedTypes 27 | 28 | /// A class for interacting with the OpenAI API to fetch information about available models and retrieve specific models. 29 | public class OpenAI { 30 | 31 | /// The API client used for making API requests. 32 | private let client: APIClient 33 | 34 | /// The api key. 35 | private let apiKey: String 36 | 37 | /// The API client request handler used for managing API request configurations. 38 | private let apiClientRequestHandler: _APIClientRequestHandler 39 | 40 | /// Initializes a new instance of the OpenAI class with the provided API key. 41 | /// 42 | /// - Parameter apiKey: The API key used for authentication when making API requests. 43 | /// - Parameter urlSessionConfiguration: An optional URL session configuration object. 44 | public init( 45 | apiKey: String, 46 | urlSessionConfiguration: URLSessionConfiguration? = nil 47 | ) { 48 | self.apiKey = apiKey 49 | self.apiClientRequestHandler = .init(apiKey: apiKey) 50 | self.client = APIClient(baseURL: URL(string: API.base)) { [apiClientRequestHandler] configuration in 51 | configuration.delegate = apiClientRequestHandler 52 | if let urlSessionConfiguration { 53 | configuration.sessionConfiguration = urlSessionConfiguration 54 | } 55 | } 56 | } 57 | 58 | /// Asynchronously fetches a list of available models from the OpenAI API. 59 | /// 60 | /// This method corresponds to the "List models" API endpoint. 61 | /// 62 | /// - Returns: An array of `ModelData` objects representing the available models. 63 | /// - Throws: A `GPTSwiftError` if the request fails or the server returns an unauthorized status code. 64 | public func availableModels() async throws -> [ModelData] { 65 | let request = Request(path: API.v1Models) 66 | return try await send(request: request).data 67 | } 68 | 69 | /// Asynchronously retrieves a specific model from the OpenAI API using the provided model ID. 70 | /// 71 | /// This method corresponds to the "Retrieve model" API endpoint. 72 | /// 73 | /// - Parameter id: The ID of the model to retrieve. 74 | /// - Returns: A `ModelData` object representing the requested model. 75 | /// - Throws: A `GPTSwiftError` if the request fails or the server returns an unauthorized status code. 76 | public func model(withId id: String) async throws -> ModelData { 77 | let request = Request(path: API.v1Model(withId: id)) 78 | return try await send(request: request) 79 | } 80 | 81 | /// Returns a usable curl prompt that you can paste into a terminal to receive all available models. 82 | /// 83 | /// This might be useful for debugging to experimenting. 84 | /// Taken from [Abhishek Maurya](https://gist.github.com/abhi21git/3dc611aab9e1cf5e5343ba4b58573596) and slightly adjusted. 85 | /// - Parameters: 86 | /// - pretty: An option to make the curl prompt pretty. 87 | /// - formatOutput: An option that, if set, puts the response through `json_pp` to prettify the json object. 88 | /// - Returns: The curl prompt. 89 | public func curlForAvailableModels(pretty: Bool = true, formatOutput: Bool = true) async throws -> String { 90 | let request = Request(path: API.v1Models) 91 | var urlRequest = try await client.makeURLRequest(for: request) 92 | _addHeaders(to: &urlRequest, apiKey: apiKey) 93 | return urlRequest.curl(pretty: pretty, formatOutput: formatOutput) 94 | } 95 | 96 | /// Returns a usable curl prompt that you can paste into a terminal to receive the model with the given id. 97 | /// 98 | /// This might be useful for debugging to experimenting. 99 | /// Taken from [Abhishek Maurya](https://gist.github.com/abhi21git/3dc611aab9e1cf5e5343ba4b58573596) and slightly adjusted. 100 | /// - Parameters: 101 | /// - id: The model id. 102 | /// - pretty: An option to make the curl prompt pretty. 103 | /// - formatOutput: An option that, if set, puts the response through `json_pp` to prettify the json object. 104 | /// - Returns: The curl prompt. 105 | public func curlForModel(withId id: String, pretty: Bool = true, formatOutput: Bool = true) async throws -> String { 106 | let request = Request(path: API.v1Model(withId: id)) 107 | var urlRequest = try await client.makeURLRequest(for: request) 108 | _addHeaders(to: &urlRequest, apiKey: apiKey) 109 | return urlRequest.curl(pretty: pretty, formatOutput: formatOutput) 110 | } 111 | 112 | /// Sends the request, catches all errors and replaces them with a `GPTSwiftError`. If successful, it returns the response value. 113 | /// - Parameter request: The request to send. 114 | /// - Returns: The response object, already decoded. 115 | private func send(request: Request) async throws -> Response { 116 | do { 117 | return try await client.send(request).value 118 | } catch { 119 | throw _errorToGPTSwiftError(error) 120 | } 121 | } 122 | } 123 | --------------------------------------------------------------------------------