Skip to content
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

New content format #122

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Demo/DemoChat/Sources/ChatStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public final class ChatStore: ObservableObject {

@MainActor
func sendMessage(
_ message: Message,
_ message: MessageModel,
conversationId: Conversation.ID,
model: Model
) async {
Expand Down Expand Up @@ -103,7 +103,7 @@ public final class ChatStore: ObservableObject {
query: ChatQuery(
model: model,
messages: conversation.messages.map { message in
Chat(role: message.role, content: message.content)
Message(role: message.role, content: message.content)
},
functions: functions
)
Expand All @@ -128,7 +128,7 @@ public final class ChatStore: ObservableObject {
finishReason == "function_call" {
messageText += "Function call: name=\(functionCallName) arguments=\(functionCallArguments)"
}
let message = Message(
let message = MessageModel(
id: partialChatResult.id,
role: choice.delta.role ?? .assistant,
content: messageText,
Expand All @@ -137,7 +137,7 @@ public final class ChatStore: ObservableObject {
if let existingMessageIndex = existingMessages.firstIndex(where: { $0.id == partialChatResult.id }) {
// Meld into previous message
let previousMessage = existingMessages[existingMessageIndex]
let combinedMessage = Message(
let combinedMessage = MessageModel(
id: message.id, // id stays the same for different deltas
role: message.role,
content: previousMessage.content + message.content,
Expand Down
6 changes: 3 additions & 3 deletions Demo/DemoChat/Sources/MiscStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ public final class MiscStore: ObservableObject {
@Published var moderationConversationError: Error?

@MainActor
func sendModerationMessage(_ message: Message) async {
func sendModerationMessage(_ message: MessageModel) async {
moderationConversation.messages.append(message)
await completeModerationChat(message: message)
}

@MainActor
func completeModerationChat(message: Message) async {
func completeModerationChat(message: MessageModel) async {

moderationConversationError = nil

Expand Down Expand Up @@ -75,7 +75,7 @@ public final class MiscStore: ObservableObject {
\(circleEmoji(for: result.categories.violenceGraphic)) Violence/Graphic
"""

let message = Message(
let message = MessageModel(
id: response.id,
role: .assistant,
content: content,
Expand Down
4 changes: 2 additions & 2 deletions Demo/DemoChat/Sources/Models/Conversation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
import Foundation

struct Conversation {
init(id: String, messages: [Message] = []) {
init(id: String, messages: [MessageModel] = []) {
self.id = id
self.messages = messages
}

typealias ID = String

let id: String
var messages: [Message]
var messages: [MessageModel]
}

extension Conversation: Equatable, Identifiable {}
6 changes: 3 additions & 3 deletions Demo/DemoChat/Sources/Models/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import Foundation
import OpenAI

struct Message {
struct MessageModel {
var id: String
var role: Chat.Role
var role: Message.Role
var content: String
var createdAt: Date
}

extension Message: Equatable, Codable, Hashable, Identifiable {}
extension MessageModel: Equatable, Codable, Hashable, Identifiable {}
2 changes: 1 addition & 1 deletion Demo/DemoChat/Sources/UI/ChatView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public struct ChatView: View {
sendMessage: { message, selectedModel in
Task {
await store.sendMessage(
Message(
MessageModel(
id: idProvider(),
role: .user,
content: message,
Expand Down
10 changes: 5 additions & 5 deletions Demo/DemoChat/Sources/UI/DetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ struct DetailView: View {
}

struct ChatBubble: View {
let message: Message
let message: MessageModel

private var assistantBackgroundColor: Color {
#if os(iOS)
Expand Down Expand Up @@ -264,10 +264,10 @@ struct DetailView_Previews: PreviewProvider {
conversation: Conversation(
id: "1",
messages: [
Message(id: "1", role: .assistant, content: "Hello, how can I help you today?", createdAt: Date(timeIntervalSinceReferenceDate: 0)),
Message(id: "2", role: .user, content: "I need help with my subscription.", createdAt: Date(timeIntervalSinceReferenceDate: 100)),
Message(id: "3", role: .assistant, content: "Sure, what seems to be the problem with your subscription?", createdAt: Date(timeIntervalSinceReferenceDate: 200)),
Message(id: "4", role: .function, content:
MessageModel(id: "1", role: .assistant, content: "Hello, how can I help you today?", createdAt: Date(timeIntervalSinceReferenceDate: 0)),
MessageModel(id: "2", role: .user, content: "I need help with my subscription.", createdAt: Date(timeIntervalSinceReferenceDate: 100)),
MessageModel(id: "3", role: .assistant, content: "Sure, what seems to be the problem with your subscription?", createdAt: Date(timeIntervalSinceReferenceDate: 200)),
MessageModel(id: "4", role: .function, content:
"""
get_current_weather({
"location": "Glasgow, Scotland",
Expand Down
2 changes: 1 addition & 1 deletion Demo/DemoChat/Sources/UI/ModerationChatView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public struct ModerationChatView: View {
sendMessage: { message, _ in
Task {
await store.sendModerationMessage(
Message(
MessageModel(
id: idProvider(),
role: .user,
content: message,
Expand Down
84 changes: 84 additions & 0 deletions Sources/OpenAI/Public/Models/Chat/ChatContent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// ChatContent.swift
//
//
// Created by Federico Vitale on 14/11/23.
//

import Foundation

public struct ChatContent: Codable, Equatable {
public let type: ChatContentType
public let value: String

enum CodingKeys: CodingKey {
case type
case value
}

public enum ChatContentType: String, Codable {
case text
case imageUrl = "image_url"
}

public struct ChatImageUrl: Codable, Equatable {
let url: String

enum CodingKeys: CodingKey {
case url
}
}

public static func text(_ text: String) -> Self {
Self.init(text)
}

public static func imageUrl(_ url: String) -> Self {
Self.init(type: .imageUrl, value: url)
}

public init(type: ChatContentType, value: String) {
self.type = type
self.value = value
}

public init(_ text: String) {
self.type = .text
self.value = text
}

// Custom encoding since the `value` key is variable based on the `type`
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ChatContent.CodingKeys.self)
var dynamicContainer = encoder.container(keyedBy: DynamicKey.self)

try container.encode(type, forKey: .type)

switch self.type {
case .text:
try dynamicContainer.encode(value, forKey: .init(stringValue: "text"))
break
case .imageUrl:
var nested = dynamicContainer.nestedContainer(keyedBy: ChatImageUrl.CodingKeys.self, forKey: .init(stringValue: "image_url"))
try nested.encode(value, forKey: .url)
break
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = try container.decode(ChatContentType.self, forKey: .type)

let dynamicContainer = try decoder.container(keyedBy: DynamicKey.self)

switch self.type {
case .text:
self.value = try dynamicContainer.decode(String.self, forKey: .init(stringValue: "text"))
break
case .imageUrl:
let nested = try dynamicContainer.nestedContainer(keyedBy: ChatImageUrl.CodingKeys.self, forKey: .init(stringValue: "image_url"))
self.value = try nested.decode(String.self, forKey: .url)
break
}
}
}
39 changes: 39 additions & 0 deletions Sources/OpenAI/Public/Models/Chat/ChatFunction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// ChatFunction.swift
//
//
// Created by Federico Vitale on 14/11/23.
//

import Foundation

/// Only available for **ASSISTANT** user type.
public struct ChatFunctionCall: Codable, Equatable {
/// The name of the function to call.
public let name: String?
/// The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function.
public let arguments: String?

public init(name: String?, arguments: String?) {
self.name = name
self.arguments = arguments
}
}

public struct ChatFunctionDeclaration: Codable, Equatable {
/// The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
public let name: String

/// The description of what the function does.
public let description: String

/// The parameters the functions accepts, described as a JSON Schema object.
public let parameters: JSONSchema

public init(name: String, description: String, parameters: JSONSchema) {
self.name = name
self.description = description
self.parameters = parameters
}
}

50 changes: 50 additions & 0 deletions Sources/OpenAI/Public/Models/Chat/ChatQuery+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// ChatQuery+.swift
//
//
// Created by Federico Vitale on 14/11/23.
//

import Foundation


extension ChatQuery {
// See more https://platform.openai.com/docs/guides/text-generation/json-mode
public struct ResponseFormat: Codable, Equatable {
public static let jsonObject = ResponseFormat(type: .jsonObject)
public static let text = ResponseFormat(type: .text)

public let type: Self.ResponseFormatType

public enum ResponseFormatType: String, Codable, Equatable {
case jsonObject = "json_object"
case text
}
}

public enum FunctionCall: Codable, Equatable {
case none
case auto
case function(String)

enum CodingKeys: String, CodingKey {
case none
case auto
case function = "name"
}

public func encode(to encoder: Encoder) throws {
switch self {
case .none:
var container = encoder.singleValueContainer()
try container.encode(CodingKeys.none.rawValue)
case .auto:
var container = encoder.singleValueContainer()
try container.encode(CodingKeys.auto.rawValue)
case .function(let name):
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .function)
}
}
}
}
Loading
Loading