-
Notifications
You must be signed in to change notification settings - Fork 120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add new GPT-4-Vision Support and support custom URL with port. #66
base: main
Are you sure you want to change the base?
Changes from 11 commits
bd3d0ea
d1b126d
c24f8cf
9e0af5a
ae3a7d2
dc43fb7
8f9076e
9b72207
f6eb7d2
aea0ce6
551db73
7f54fdc
377152d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by 贺峰煜 on 2023/11/29. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct ChatWithImage { | ||
public let id: String | ||
public let object: String | ||
public let created: Date | ||
public let model: String | ||
public let choices: [Choice] | ||
public let usage: Usage | ||
} | ||
|
||
extension ChatWithImage: Codable {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem right, you should be able to extend the pre-existing Chat model to support the inclusion of an image. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I revised the redundant parts of the code. |
||
|
||
extension ChatWithImage { | ||
public struct Choice { | ||
public let index: Int | ||
public let message: Message | ||
public let finishReason: FinishReason? | ||
} | ||
} | ||
|
||
extension ChatWithImage.Choice: Codable {} | ||
|
||
extension ChatWithImage { | ||
public struct ImageUrl: Codable { | ||
public let url: String | ||
|
||
public init(url: String) { | ||
self.url = url | ||
} | ||
} | ||
} | ||
|
||
extension ChatWithImage { | ||
public enum Message { | ||
case system(content: String) | ||
case user(content: [Content]) | ||
case assistant(content: String) | ||
} | ||
|
||
public enum Content { | ||
case text(String) | ||
case imageUrl(ImageUrl) | ||
} | ||
} | ||
|
||
extension ChatWithImage.Message: Codable { | ||
private enum CodingKeys: String, CodingKey { | ||
case role | ||
case content | ||
} | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
let role = try container.decode(String.self, forKey: .role) | ||
switch role { | ||
case "system": | ||
let content = try container.decode(String.self, forKey: .content) | ||
self = .system(content: content) | ||
case "user": | ||
let content = try container.decode([ChatWithImage.Content].self, forKey: .content) | ||
self = .user(content: content) | ||
case "assistant": | ||
let content = try container.decode(String.self, forKey: .content) | ||
self = .assistant(content: content) | ||
default: | ||
throw DecodingError.dataCorruptedError(forKey: .role, in: container, debugDescription: "Invalid role") | ||
} | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
switch self { | ||
case .system(content: let content): | ||
try container.encode("system", forKey: .role) | ||
try container.encode(content, forKey: .content) | ||
case .assistant(content: let content): | ||
try container.encode("assistant", forKey: .role) | ||
try container.encode(content, forKey: .content) | ||
case .user(content: let content): | ||
try container.encode("user", forKey: .role) | ||
try container.encode(content, forKey: .content) | ||
} | ||
} | ||
} | ||
|
||
extension ChatWithImage.Content: Codable { | ||
private enum CodingKeys: String, CodingKey { | ||
case type | ||
case text | ||
case imageUrl = "image_url" | ||
} | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
let type = try container.decode(String.self, forKey: .type) | ||
switch type { | ||
case "text": | ||
let text = try container.decode(String.self, forKey: .text) | ||
self = .text(text) | ||
case "image_url": | ||
let imageUrl = try container.decode(ChatWithImage.ImageUrl.self, forKey: .imageUrl) | ||
self = .imageUrl(imageUrl) | ||
default: | ||
throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Invalid type") | ||
} | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
switch self { | ||
case .text(let text): | ||
try container.encode("text", forKey: .type) | ||
try container.encode(text, forKey: .text) | ||
case .imageUrl(let imageUrl): | ||
try container.encode("imageurl", forKey: .type) | ||
try container.encode(imageUrl, forKey: .imageUrl) | ||
} | ||
} | ||
} | ||
|
||
extension ChatWithImage.Content: Equatable { | ||
public static func ==(lhs: ChatWithImage.Content, rhs: ChatWithImage.Content) -> Bool { | ||
switch (lhs, rhs) { | ||
case (.text(let lhsText), .text(let rhsText)): | ||
return lhsText == rhsText | ||
case (.imageUrl(let lhsUrl), .imageUrl(let rhsUrl)): | ||
return lhsUrl.url == rhsUrl.url | ||
default: | ||
return false | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// | ||
// ChatWithImageProvider.swift | ||
// | ||
// | ||
// Created by 贺峰煜 on 2023/11/28. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct ChatWithImageProvider { | ||
private let requesthandler: RequestHandler | ||
|
||
init(requesthandler: RequestHandler) { | ||
self.requesthandler = requesthandler | ||
} | ||
|
||
/** | ||
Create chat completion | ||
POST | ||
|
||
https://api.openai.com/v1/chat/completions | ||
|
||
Creates a chat completion for the provided prompt and parameters | ||
*/ | ||
|
||
public func create( | ||
model: ModelID, | ||
message: [ChatWithImage.Message] = [], | ||
temperature: Double = 1.0, | ||
topP: Double = 1.0, | ||
n: Int = 1, | ||
stops: [String] = [], | ||
maxTokens: Int? = nil, | ||
presencePenalty: Double = 0.0, | ||
frequencyPenalty: Double = 0.0, | ||
logitBias: [String : Int] = [:], | ||
user: String? = nil | ||
) async throws -> ChatWithImage { | ||
let request = try CreateChatWithImageRequest( | ||
model: model.id, | ||
messages: message, | ||
temperature: temperature, | ||
topP: topP, | ||
n: n, | ||
stream: false, | ||
stops: stops, | ||
maxTokens: maxTokens, | ||
presencePenalty: presencePenalty, | ||
frequencyPenalty: frequencyPenalty, | ||
logitBias: logitBias, | ||
user: user | ||
) | ||
|
||
return try await requesthandler.perform(request: request) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by 贺峰煜 on 2023/11/29. | ||
// | ||
|
||
import AsyncHTTPClient | ||
import NIOHTTP1 | ||
import Foundation | ||
|
||
struct CreateChatWithImageRequest: Request { | ||
let method: HTTPMethod = .POST | ||
let path: String = "/v1/chat/completions" | ||
let body: Data? | ||
|
||
init( | ||
model: String, | ||
messages: [ChatWithImage.Message], | ||
temperature: Double, | ||
topP: Double, | ||
n: Int, | ||
stream: Bool, | ||
stops: [String], | ||
maxTokens: Int?, | ||
presencePenalty: Double, | ||
frequencyPenalty: Double, | ||
logitBias: [String: Int], | ||
user: String? | ||
) throws { | ||
let body = Body( | ||
model: model, | ||
messages: messages, | ||
temperature: temperature, | ||
topP: topP, | ||
n: n, | ||
stream: stream, | ||
stops: stops, | ||
maxTokens: maxTokens, | ||
presencePenalty: presencePenalty, | ||
frequencyPenalty: frequencyPenalty, | ||
logitBias: logitBias, | ||
user: user | ||
) | ||
|
||
self.body = try Self.encoder.encode(body) | ||
} | ||
} | ||
|
||
extension CreateChatWithImageRequest { | ||
struct Body: Encodable { | ||
let model: String | ||
let messages: [ChatWithImage.Message] | ||
let temperature: Double | ||
let topP: Double | ||
let n: Int | ||
let stream: Bool | ||
let stops: [String] | ||
let maxTokens: Int? | ||
let presencePenalty: Double | ||
let frequencyPenalty: Double | ||
let logitBias: [String: Int] | ||
let user: String? | ||
|
||
enum CodingKeys: CodingKey { | ||
case model | ||
case messages | ||
case temperature | ||
case topP | ||
case n | ||
case stream | ||
case stop | ||
case maxTokens | ||
case presencePenalty | ||
case frequencyPenalty | ||
case logitBias | ||
case user | ||
} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(model, forKey: .model) | ||
|
||
if !messages.isEmpty { | ||
try container.encode(messages, forKey: .messages) | ||
} | ||
|
||
try container.encode(temperature, forKey: .temperature) | ||
try container.encode(topP, forKey: .topP) | ||
try container.encode(n, forKey: .n) | ||
try container.encode(stream, forKey: .stream) | ||
|
||
if !stops.isEmpty { | ||
try container.encode(stops, forKey: .stop) | ||
} | ||
|
||
if let maxTokens { | ||
try container.encode(maxTokens, forKey: .maxTokens) | ||
} | ||
|
||
try container.encode(presencePenalty, forKey: .presencePenalty) | ||
try container.encode(frequencyPenalty, forKey: .frequencyPenalty) | ||
|
||
if !logitBias.isEmpty { | ||
try container.encode(logitBias, forKey: .logitBias) | ||
} | ||
|
||
try container.encodeIfPresent(user, forKey: .user) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove these generated comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed