2023-01-13 17:43:02 +00:00
import Foundation
2023-03-09 12:46:04 +00:00
protocol OpenAIRequest : Encodable {
2023-12-04 19:04:12 +00:00
var model : String { get }
}
2023-01-13 17:43:02 +00:00
public struct OpenAIClient {
2023-12-09 09:58:42 +00:00
private let endpoint : URL = . init ( string : " https://icecubesrelay.fly.dev/openai " ) !
2023-01-17 10:36:01 +00:00
2023-01-13 17:43:02 +00:00
private var encoder : JSONEncoder {
let encoder = JSONEncoder ( )
encoder . keyEncodingStrategy = . convertToSnakeCase
return encoder
}
2023-01-17 10:36:01 +00:00
2023-01-13 17:43:02 +00:00
private var decoder : JSONDecoder {
let decoder = JSONDecoder ( )
decoder . keyDecodingStrategy = . convertFromSnakeCase
return decoder
}
2023-03-13 12:38:28 +00:00
2023-03-09 12:46:04 +00:00
public struct ChatRequest : OpenAIRequest {
public struct Message : Encodable {
public let role = " user "
public let content : String
}
2023-03-13 12:38:28 +00:00
2023-03-09 12:46:04 +00:00
let model = " gpt-3.5-turbo "
let messages : [ Message ]
2023-03-13 12:38:28 +00:00
2023-03-09 12:46:04 +00:00
let temperature : CGFloat
2023-03-13 12:38:28 +00:00
2023-03-09 12:46:04 +00:00
public init ( content : String , temperature : CGFloat ) {
2023-03-13 12:38:28 +00:00
messages = [ . init ( content : content ) ]
2023-01-13 17:43:02 +00:00
self . temperature = temperature
}
}
2023-12-04 19:04:12 +00:00
public struct VisionRequest : OpenAIRequest {
public struct Message : Encodable {
public struct MessageContent : Encodable {
public struct ImageUrl : Encodable {
public let url : URL
}
public let type : String
public let text : String ?
public let imageUrl : ImageUrl ?
}
public let role = " user "
public let content : [ MessageContent ]
}
let model = " gpt-4-vision-preview "
let messages : [ Message ]
let maxTokens = 50
}
2023-03-13 12:38:28 +00:00
2023-02-21 17:52:30 +00:00
public enum Prompt {
2023-01-13 17:43:02 +00:00
case correct ( input : String )
case shorten ( input : String )
case emphasize ( input : String )
2023-03-09 12:46:04 +00:00
case addTags ( input : String )
case insertTags ( input : String )
2023-12-04 19:04:12 +00:00
case imageDescription ( image : URL )
2023-01-17 10:36:01 +00:00
2023-03-09 12:46:04 +00:00
var request : OpenAIRequest {
2023-01-13 17:43:02 +00:00
switch self {
case let . correct ( input ) :
2023-09-16 12:15:03 +00:00
ChatRequest ( content : " Fix the spelling and grammar mistakes in the following text: \( input ) " , temperature : 0.2 )
2023-03-09 12:46:04 +00:00
case let . addTags ( input ) :
2023-09-16 12:15:03 +00:00
ChatRequest ( content : " Replace relevant words with camel-cased hashtags in the following text. Don't try to search for context or add hashtags if there is not enough context: \( input ) " , temperature : 0.1 )
2023-03-09 12:46:04 +00:00
case let . insertTags ( input ) :
2023-09-16 12:15:03 +00:00
ChatRequest ( content : " Return the input with added camel-cased hashtags at the end of the input. Don't try to search for context or add hashtags if there is not enough context: \( input ) " , temperature : 0.2 )
2023-01-13 17:43:02 +00:00
case let . shorten ( input ) :
2023-09-16 12:15:03 +00:00
ChatRequest ( content : " Make a shorter version of this text: \( input ) " , temperature : 0.5 )
2023-01-13 17:43:02 +00:00
case let . emphasize ( input ) :
2023-09-16 12:15:03 +00:00
ChatRequest ( content : " Make this text catchy, more fun: \( input ) " , temperature : 1 )
2023-12-04 19:04:12 +00:00
case let . imageDescription ( image ) :
2023-12-04 19:36:15 +00:00
VisionRequest ( messages : [ . init ( content : [ . init ( type : " text " , text : " What’ s in this image? Be brief, it's for image alt description on a social network. Don't write in the first person. " , imageUrl : nil )
2023-12-04 19:04:12 +00:00
, . init ( type : " image_url " , text : nil , imageUrl : . init ( url : image ) ) ] ) ] )
2023-01-13 17:43:02 +00:00
}
}
}
2023-01-17 10:36:01 +00:00
2023-01-13 17:43:02 +00:00
public struct Response : Decodable {
public struct Choice : Decodable {
2023-03-09 12:46:04 +00:00
public struct Message : Decodable {
public let role : String
public let content : String
}
2023-03-13 12:38:28 +00:00
2023-03-09 12:46:04 +00:00
public let message : Message ?
2023-01-13 17:43:02 +00:00
}
2023-01-17 10:36:01 +00:00
2023-01-13 17:43:02 +00:00
public let choices : [ Choice ]
2023-01-22 05:38:30 +00:00
2023-01-17 18:41:46 +00:00
public var trimmedText : String {
2023-03-09 12:46:04 +00:00
guard var text = choices . first ? . message ? . content else {
2023-01-17 18:41:46 +00:00
return " "
}
while text . first ? . isNewline = = true || text . first ? . isWhitespace = = true {
text . removeFirst ( )
}
return text
}
2023-01-13 17:43:02 +00:00
}
2023-01-17 10:36:01 +00:00
public init ( ) { }
2023-02-21 17:52:30 +00:00
public func request ( _ prompt : Prompt ) async throws -> Response {
2023-01-13 17:43:02 +00:00
do {
let jsonData = try encoder . encode ( prompt . request )
2023-12-09 09:58:42 +00:00
var request = URLRequest ( url : endpoint )
2023-01-13 17:43:02 +00:00
request . httpMethod = " POST "
request . setValue ( " application/json " , forHTTPHeaderField : " Content-Type " )
request . httpBody = jsonData
let ( result , _ ) = try await URLSession . shared . data ( for : request )
let response = try decoder . decode ( Response . self , from : result )
return response
2023-01-17 10:36:01 +00:00
} catch {
2023-01-13 17:43:02 +00:00
throw error
}
}
}
2023-02-27 05:39:07 +00:00
extension OpenAIClient : Sendable { }
extension OpenAIClient . Prompt : Sendable { }
2023-03-09 12:46:04 +00:00
extension OpenAIClient . ChatRequest : Sendable { }
extension OpenAIClient . ChatRequest . Message : Sendable { }
2023-02-27 05:39:07 +00:00
extension OpenAIClient . Response : Sendable { }
extension OpenAIClient . Response . Choice : Sendable { }
2023-03-09 12:46:04 +00:00
extension OpenAIClient . Response . Choice . Message : Sendable { }