From 34279cea09fdef59056bb2c5c3c0e5fcd892acfd Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Tue, 27 Aug 2024 20:08:39 -0700 Subject: [PATCH 01/13] encoding to a dictionary worked --- Sources/OpenAI/Public/Models/ChatQuery.swift | 189 +++++++++++++++++- .../OpenAI/Public/Models/Models/Models.swift | 3 + 2 files changed, 186 insertions(+), 6 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index c7a88649..3d299778 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -598,17 +598,194 @@ public struct ChatQuery: Equatable, Codable, Streamable { self = .stringList(stringList) } } + + public protocol WithSample2: Codable { + static var sample: Self { get } + } // See more https://platform.openai.com/docs/guides/text-generation/json-mode - public enum ResponseFormat: String, Codable, Equatable { - case jsonObject = "json_object" + public enum ResponseFormat: Codable, Equatable { + + case jsonSchema(value: WithSample2) + case jsonObject case text - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(["type": self.rawValue]) + + enum CodingKeys: String, CodingKey { + case type + case jsonSchema = "json_schema" + } + + public init(from decoder: any Decoder) throws { + self = .text + } + + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .jsonSchema(let value): + try container.encode("json_schema", forKey: .type) + let schema = HighLevelSchema(name: "sample", schema: value) + try container.encode(schema, forKey: .jsonSchema) + case .jsonObject: + try container.encode("json_object", forKey: .type) + case .text: + try container.encode("text", forKey: .type) + } + } + + public static func == (lhs: ResponseFormat, rhs: ResponseFormat) -> Bool { + return false + } + } + + public struct HighLevelSchema: Codable, Equatable { + + let name: String + let schema: WithSample2 + + enum CodingKeys: String, CodingKey { + case name + case schema + } + + init(name: String, schema: WithSample2) { + self.name = name + self.schema = schema + } + + public init(from decoder: any Decoder) throws { + self = .init(name: "hi", schema: SampleWithSample2(name: "hi")) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode("sample", forKey: .name) + try container.encode(try Self.whatisgoingon(schema), forKey: .schema) + } + + public static func == (lhs: HighLevelSchema, rhs: HighLevelSchema) -> Bool { + return true + } + + static func whatisgoingon(_ value: T) throws -> PropertyValue { + + switch value { + case _ as String: + return .stringValue("string") + case _ as Bool: + return .boolValue(true) + case _ as Int, _ as Int8, _ as Int16, _ as Int32, _ as Int64, + _ as UInt, _ as UInt8, _ as UInt16, _ as UInt32, _ as UInt64: + return .intValue(0) + case _ as Double, _ as Float, _ as CGFloat: + return .intValue(0) + default: + let mirror = Mirror(reflecting: value) + if let displayStyle = mirror.displayStyle { + switch displayStyle { + case .struct, .class: + var dict = [String: PropertyValue]() + for child in mirror.children { + dict[child.label!] = try whatisgoingon(child.value) + } + return .propertyValueDictionary(dict) + case .collection: + if let child = mirror.children.first { + return .array(try whatisgoingon(child.value)) + } else { + print("no child") + throw StructuredAIError.unsupportedType + } + default: + print("no bueno") + throw StructuredAIError.unsupportedType + } + } + throw StructuredAIError.unsupportedType + } + } } + + private struct SampleWithSample2: WithSample2 { + let name: String + static var sample: SampleWithSample2 { .init(name: "sample") } + } + + + + indirect enum PropertyValue: Codable { + + case stringValue(String) + case intValue(Int) + case boolValue(Bool) + case propertyValueDictionary([String: PropertyValue]) + case array(PropertyValue) + + enum CodingKeys: String, CodingKey { + case type + case value + case properties + case items + } + + enum ValueType: String, Codable { + case stringValue + case intValue + case boolValue + case dictionary + case array + } + + func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .stringValue: + try container.encode(String("string"), forKey: .type) + case .intValue: + try container.encode(String("integer"), forKey: .type) + case .boolValue: + try container.encode(String("boolean"), forKey: .type) + case .propertyValueDictionary(let dictionary): + try container.encode(String("object"), forKey: .type) + try container.encode(dictionary, forKey: .properties) + case .array(let items): + try container.encode(String("array"), forKey: .type) + try container.encode(items, forKey: .items) + + } + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(ValueType.self, forKey: .type) + + switch type { + case .stringValue: + let string = try container.decode(String.self, forKey: .value) + self = .stringValue(string) + case .intValue: + let int = try container.decode(Int.self, forKey: .value) + self = .intValue(int) + case .boolValue: + let bool = try container.decode(Bool.self, forKey: .value) + self = .boolValue(bool) + case .dictionary: + let dictionary = try container.decode([String: PropertyValue].self, forKey: .value) + self = .propertyValueDictionary(dictionary) + case .array: + let arr = try container.decode(PropertyValue.self, forKey: .value) + self = .array(arr) + } + } + } + + enum StructuredAIError: Error { + case unsupportedType + } public enum ChatCompletionFunctionCallOptionParam: Codable, Equatable { case none diff --git a/Sources/OpenAI/Public/Models/Models/Models.swift b/Sources/OpenAI/Public/Models/Models/Models.swift index 6b46c1a4..1399e630 100644 --- a/Sources/OpenAI/Public/Models/Models/Models.swift +++ b/Sources/OpenAI/Public/Models/Models/Models.swift @@ -12,6 +12,9 @@ public extension Model { // Chat Completion // GPT-4 + /// TODO: Write a proper description + static let gpt4_o_2024_08_06 = "gpt-4o-2024-08-06" + /// `gpt-4o`, currently the most advanced, multimodal flagship model that's cheaper and faster than GPT-4 Turbo. static let gpt4_o = "gpt-4o" From 6bc560dfc01b0224d216148a72a04d273520833e Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 09:48:11 -0700 Subject: [PATCH 02/13] Model description --- Sources/OpenAI/Public/Models/Models/Models.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenAI/Public/Models/Models/Models.swift b/Sources/OpenAI/Public/Models/Models/Models.swift index 1399e630..8cbbab70 100644 --- a/Sources/OpenAI/Public/Models/Models/Models.swift +++ b/Sources/OpenAI/Public/Models/Models/Models.swift @@ -12,7 +12,7 @@ public extension Model { // Chat Completion // GPT-4 - /// TODO: Write a proper description + /// `gpt-4o-2024-08-06`, latest snapshot that supports Structured Outputs static let gpt4_o_2024_08_06 = "gpt-4o-2024-08-06" /// `gpt-4o`, currently the most advanced, multimodal flagship model that's cheaper and faster than GPT-4 Turbo. From e56a5813ecbb9b06bbc212cab41a910105c639d1 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 11:12:28 -0700 Subject: [PATCH 03/13] Renaming and refactoring --- Sources/OpenAI/Public/Models/ChatQuery.swift | 97 ++++++++++---------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index 3d299778..6ac03263 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -603,7 +603,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { static var sample: Self { get } } - // See more https://platform.openai.com/docs/guides/text-generation/json-mode + // See more https://platform.openai.com/docs/guides/structured-outputs/introduction public enum ResponseFormat: Codable, Equatable { case jsonSchema(value: WithSample2) @@ -615,17 +615,17 @@ public struct ChatQuery: Equatable, Codable, Streamable { case jsonSchema = "json_schema" } + /// A formal initializer reqluired for the inherited Decodable conformance. This type is never returned from the server and is never decoded into. public init(from decoder: any Decoder) throws { self = .text } - public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .jsonSchema(let value): try container.encode("json_schema", forKey: .type) - let schema = HighLevelSchema(name: "sample", schema: value) + let schema = JSONSchema(name: "sample", schema: value) try container.encode(schema, forKey: .jsonSchema) case .jsonObject: try container.encode("json_object", forKey: .type) @@ -639,7 +639,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { } } - public struct HighLevelSchema: Codable, Equatable { + public struct JSONSchema: Codable, Equatable { let name: String let schema: WithSample2 @@ -654,58 +654,21 @@ public struct ChatQuery: Equatable, Codable, Streamable { self.schema = schema } + /// A formal initializer reqluired for the inherited Decodable conformance. This type is never returned from the server and is never decoded into. public init(from decoder: any Decoder) throws { - self = .init(name: "hi", schema: SampleWithSample2(name: "hi")) + self = .init(name: "sample", schema: SampleWithSample2(name: "hi")) } public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode("sample", forKey: .name) - try container.encode(try Self.whatisgoingon(schema), forKey: .schema) + try container.encode(try PropertyValue.generate(from: schema), forKey: .schema) } - public static func == (lhs: HighLevelSchema, rhs: HighLevelSchema) -> Bool { + public static func == (lhs: JSONSchema, rhs: JSONSchema) -> Bool { + // TODO: Implement a proper comparison return true } - - static func whatisgoingon(_ value: T) throws -> PropertyValue { - - switch value { - case _ as String: - return .stringValue("string") - case _ as Bool: - return .boolValue(true) - case _ as Int, _ as Int8, _ as Int16, _ as Int32, _ as Int64, - _ as UInt, _ as UInt8, _ as UInt16, _ as UInt32, _ as UInt64: - return .intValue(0) - case _ as Double, _ as Float, _ as CGFloat: - return .intValue(0) - default: - let mirror = Mirror(reflecting: value) - if let displayStyle = mirror.displayStyle { - switch displayStyle { - case .struct, .class: - var dict = [String: PropertyValue]() - for child in mirror.children { - dict[child.label!] = try whatisgoingon(child.value) - } - return .propertyValueDictionary(dict) - case .collection: - if let child = mirror.children.first { - return .array(try whatisgoingon(child.value)) - } else { - print("no child") - throw StructuredAIError.unsupportedType - } - default: - print("no bueno") - throw StructuredAIError.unsupportedType - } - } - throw StructuredAIError.unsupportedType - } - - } } private struct SampleWithSample2: WithSample2 { @@ -713,8 +676,6 @@ public struct ChatQuery: Equatable, Codable, Streamable { static var sample: SampleWithSample2 { .init(name: "sample") } } - - indirect enum PropertyValue: Codable { case stringValue(String) @@ -781,8 +742,48 @@ public struct ChatQuery: Equatable, Codable, Streamable { self = .array(arr) } } + + static func generate(from value: T) throws -> PropertyValue { + + switch value { + case _ as String: + return .stringValue("string") + case _ as Bool: + return .boolValue(true) + case _ as Int, _ as Int8, _ as Int16, _ as Int32, _ as Int64, + _ as UInt, _ as UInt8, _ as UInt16, _ as UInt32, _ as UInt64: + return .intValue(0) + case _ as Double, _ as Float, _ as CGFloat: + return .intValue(0) + default: + let mirror = Mirror(reflecting: value) + if let displayStyle = mirror.displayStyle { + switch displayStyle { + case .struct, .class: + var dict = [String: PropertyValue]() + for child in mirror.children { + dict[child.label!] = try generate(from: child.value) + } + return .propertyValueDictionary(dict) + case .collection: + if let child = mirror.children.first { + return .array(try generate(from: child.value)) + } else { + print("no child") + throw StructuredAIError.unsupportedType + } + default: + print("no bueno") + throw StructuredAIError.unsupportedType + } + } + throw StructuredAIError.unsupportedType + } + + } } + // TODO: Implement other options. Move to a separate file too? Public? enum StructuredAIError: Error { case unsupportedType } From feb50bd6423e4dff20301703ad0f8b8853c66f85 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 11:45:13 -0700 Subject: [PATCH 04/13] More renaming and refactoring --- Sources/OpenAI/Public/Models/ChatQuery.swift | 35 ++++--------------- .../Public/Models/StructuredOutput.swift | 12 +++++++ 2 files changed, 19 insertions(+), 28 deletions(-) create mode 100644 Sources/OpenAI/Public/Models/StructuredOutput.swift diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index 6ac03263..7114d29b 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -598,15 +598,11 @@ public struct ChatQuery: Equatable, Codable, Streamable { self = .stringList(stringList) } } - - public protocol WithSample2: Codable { - static var sample: Self { get } - } // See more https://platform.openai.com/docs/guides/structured-outputs/introduction public enum ResponseFormat: Codable, Equatable { - case jsonSchema(value: WithSample2) + case jsonSchema(value: StructuredOutput.Type) case jsonObject case text @@ -625,7 +621,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { switch self { case .jsonSchema(let value): try container.encode("json_schema", forKey: .type) - let schema = JSONSchema(name: "sample", schema: value) + let schema = JSONSchema(name: "sample", schema: value.example) try container.encode(schema, forKey: .jsonSchema) case .jsonObject: try container.encode("json_object", forKey: .type) @@ -635,45 +631,28 @@ public struct ChatQuery: Equatable, Codable, Streamable { } public static func == (lhs: ResponseFormat, rhs: ResponseFormat) -> Bool { + // TODO: Implement a proper comparison return false } } - public struct JSONSchema: Codable, Equatable { + public struct JSONSchema: Encodable { let name: String - let schema: WithSample2 + let schema: StructuredOutput enum CodingKeys: String, CodingKey { case name case schema - } - - init(name: String, schema: WithSample2) { - self.name = name - self.schema = schema - } - - /// A formal initializer reqluired for the inherited Decodable conformance. This type is never returned from the server and is never decoded into. - public init(from decoder: any Decoder) throws { - self = .init(name: "sample", schema: SampleWithSample2(name: "hi")) + case strict } public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode("sample", forKey: .name) + try container.encode(true, forKey: .strict) try container.encode(try PropertyValue.generate(from: schema), forKey: .schema) } - - public static func == (lhs: JSONSchema, rhs: JSONSchema) -> Bool { - // TODO: Implement a proper comparison - return true - } - } - - private struct SampleWithSample2: WithSample2 { - let name: String - static var sample: SampleWithSample2 { .init(name: "sample") } } indirect enum PropertyValue: Codable { diff --git a/Sources/OpenAI/Public/Models/StructuredOutput.swift b/Sources/OpenAI/Public/Models/StructuredOutput.swift new file mode 100644 index 00000000..7f7470e8 --- /dev/null +++ b/Sources/OpenAI/Public/Models/StructuredOutput.swift @@ -0,0 +1,12 @@ +// +// StructuredOutput.swift +// OpenAI +// +// Created by Andriy Gordiyenko on 8/28/24. +// + +import Foundation + +public protocol StructuredOutput: Codable { + static var example: Self { get } +} From aa2d9c26131672f39fffd795bbececc6003c5475 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 11:54:57 -0700 Subject: [PATCH 05/13] Schema name --- Sources/OpenAI/Public/Models/ChatQuery.swift | 22 +++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index 7114d29b..6bfff5e9 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -602,9 +602,9 @@ public struct ChatQuery: Equatable, Codable, Streamable { // See more https://platform.openai.com/docs/guides/structured-outputs/introduction public enum ResponseFormat: Codable, Equatable { - case jsonSchema(value: StructuredOutput.Type) - case jsonObject case text + case jsonObject + case jsonSchema(name: String, type: StructuredOutput.Type) enum CodingKeys: String, CodingKey { case type @@ -619,14 +619,14 @@ public struct ChatQuery: Equatable, Codable, Streamable { public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { - case .jsonSchema(let value): - try container.encode("json_schema", forKey: .type) - let schema = JSONSchema(name: "sample", schema: value.example) - try container.encode(schema, forKey: .jsonSchema) - case .jsonObject: - try container.encode("json_object", forKey: .type) case .text: try container.encode("text", forKey: .type) + case .jsonObject: + try container.encode("json_object", forKey: .type) + case .jsonSchema(let name, let type): + try container.encode("json_schema", forKey: .type) + let schema = JSONSchema(name: name, schema: type.example) + try container.encode(schema, forKey: .jsonSchema) } } @@ -647,6 +647,12 @@ public struct ChatQuery: Equatable, Codable, Streamable { case strict } + // TODO: Create an init that validates the name and throws or auto-fixes and prints a warning +// init(name: String, schema: StructuredOutput) { +// self.name = name +// self.schema = schema +// } + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode("sample", forKey: .name) From 5527d94d6fd7092de1e2a4ae160909719d2b65d8 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 11:59:14 -0700 Subject: [PATCH 06/13] Cleanup --- Sources/OpenAI/Public/Models/ChatQuery.swift | 10 +++++----- Sources/OpenAI/Public/Models/StructuredOutput.swift | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index 6bfff5e9..94932f38 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -611,11 +611,6 @@ public struct ChatQuery: Equatable, Codable, Streamable { case jsonSchema = "json_schema" } - /// A formal initializer reqluired for the inherited Decodable conformance. This type is never returned from the server and is never decoded into. - public init(from decoder: any Decoder) throws { - self = .text - } - public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { @@ -634,6 +629,11 @@ public struct ChatQuery: Equatable, Codable, Streamable { // TODO: Implement a proper comparison return false } + + /// A formal initializer reqluired for the inherited Decodable conformance. This type is never returned from the server and is never decoded into. + public init(from decoder: any Decoder) throws { + self = .text + } } public struct JSONSchema: Encodable { diff --git a/Sources/OpenAI/Public/Models/StructuredOutput.swift b/Sources/OpenAI/Public/Models/StructuredOutput.swift index 7f7470e8..36142bcb 100644 --- a/Sources/OpenAI/Public/Models/StructuredOutput.swift +++ b/Sources/OpenAI/Public/Models/StructuredOutput.swift @@ -1,6 +1,6 @@ // // StructuredOutput.swift -// OpenAI +// // // Created by Andriy Gordiyenko on 8/28/24. // From 2e155a3dab68b78ea9b9b5a771047c10522df677 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 14:08:19 -0700 Subject: [PATCH 07/13] Proper Equatable conformance --- Sources/OpenAI/Public/Models/ChatQuery.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index 94932f38..f6f6ddfc 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -626,11 +626,18 @@ public struct ChatQuery: Equatable, Codable, Streamable { } public static func == (lhs: ResponseFormat, rhs: ResponseFormat) -> Bool { - // TODO: Implement a proper comparison - return false + switch (lhs, rhs) { + case (.text, .text): return true + case (.jsonObject, .jsonObject): return true + case (.jsonSchema(let lhsName, let lhsType), .jsonSchema(let rhsName, let rhsType)): + return lhsName == rhsName && lhsType == rhsType + default: + return false + } } - /// A formal initializer reqluired for the inherited Decodable conformance. This type is never returned from the server and is never decoded into. + /// A formal initializer reqluired for the inherited Decodable conformance. + /// This type is never returned from the server and is never decoded into. public init(from decoder: any Decoder) throws { self = .text } From 54e5ed5b792abc3a30c332f97afe1c3efc87a650 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 14:15:18 -0700 Subject: [PATCH 08/13] Access --- Sources/OpenAI/Public/Models/ChatQuery.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index f6f6ddfc..d19a64d7 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -643,7 +643,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { } } - public struct JSONSchema: Encodable { + private struct JSONSchema: Encodable { let name: String let schema: StructuredOutput @@ -654,11 +654,13 @@ public struct ChatQuery: Equatable, Codable, Streamable { case strict } - // TODO: Create an init that validates the name and throws or auto-fixes and prints a warning -// init(name: String, schema: StructuredOutput) { -// self.name = name -// self.schema = schema -// } + init(name: String, schema: StructuredOutput) { + + self.name = name + + + self.schema = schema + } public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -668,7 +670,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { } } - indirect enum PropertyValue: Codable { + private indirect enum PropertyValue: Codable { case stringValue(String) case intValue(Int) From 577eaa3b9a1e7ba471d3d8f90e04542e276f7f5a Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 14:23:57 -0700 Subject: [PATCH 09/13] Renames --- Sources/OpenAI/Public/Models/ChatQuery.swift | 82 ++++++++++---------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index d19a64d7..183cc78f 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -664,7 +664,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode("sample", forKey: .name) + try container.encode(name, forKey: .name) try container.encode(true, forKey: .strict) try container.encode(try PropertyValue.generate(from: schema), forKey: .schema) } @@ -672,10 +672,11 @@ public struct ChatQuery: Equatable, Codable, Streamable { private indirect enum PropertyValue: Codable { - case stringValue(String) - case intValue(Int) - case boolValue(Bool) - case propertyValueDictionary([String: PropertyValue]) + case string(String) + case integer(Int) + case number(Double) + case boolean(Bool) + case object([String: PropertyValue]) case array(PropertyValue) enum CodingKeys: String, CodingKey { @@ -686,27 +687,29 @@ public struct ChatQuery: Equatable, Codable, Streamable { } enum ValueType: String, Codable { - case stringValue - case intValue - case boolValue - case dictionary + case string + case integer + case number + case boolean + case object case array } func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) switch self { - case .stringValue: + case .string: try container.encode(String("string"), forKey: .type) - case .intValue: + case .integer: try container.encode(String("integer"), forKey: .type) - case .boolValue: + case .number: + try container.encode(String("number"), forKey: .type) + case .boolean: try container.encode(String("boolean"), forKey: .type) - case .propertyValueDictionary(let dictionary): + case .object(let object): try container.encode(String("object"), forKey: .type) - try container.encode(dictionary, forKey: .properties) + try container.encode(object, forKey: .properties) case .array(let items): try container.encode(String("array"), forKey: .type) try container.encode(items, forKey: .items) @@ -719,36 +722,38 @@ public struct ChatQuery: Equatable, Codable, Streamable { let type = try container.decode(ValueType.self, forKey: .type) switch type { - case .stringValue: + case .string: let string = try container.decode(String.self, forKey: .value) - self = .stringValue(string) - case .intValue: - let int = try container.decode(Int.self, forKey: .value) - self = .intValue(int) - case .boolValue: + self = .string(string) + case .integer: + let integer = try container.decode(Int.self, forKey: .value) + self = .integer(integer) + case .number: + let double = try container.decode(Double.self, forKey: .value) + self = .number(double) + case .boolean: let bool = try container.decode(Bool.self, forKey: .value) - self = .boolValue(bool) - case .dictionary: - let dictionary = try container.decode([String: PropertyValue].self, forKey: .value) - self = .propertyValueDictionary(dictionary) + self = .boolean(bool) + case .object: + let object = try container.decode([String: PropertyValue].self, forKey: .value) + self = .object(object) case .array: - let arr = try container.decode(PropertyValue.self, forKey: .value) - self = .array(arr) + let array = try container.decode(PropertyValue.self, forKey: .value) + self = .array(array) } } static func generate(from value: T) throws -> PropertyValue { - switch value { case _ as String: - return .stringValue("string") + return .string("string") case _ as Bool: - return .boolValue(true) + return .boolean(true) case _ as Int, _ as Int8, _ as Int16, _ as Int32, _ as Int64, _ as UInt, _ as UInt8, _ as UInt16, _ as UInt32, _ as UInt64: - return .intValue(0) + return .integer(0) case _ as Double, _ as Float, _ as CGFloat: - return .intValue(0) + return .integer(0) default: let mirror = Mirror(reflecting: value) if let displayStyle = mirror.displayStyle { @@ -758,27 +763,24 @@ public struct ChatQuery: Equatable, Codable, Streamable { for child in mirror.children { dict[child.label!] = try generate(from: child.value) } - return .propertyValueDictionary(dict) + return .object(dict) case .collection: if let child = mirror.children.first { return .array(try generate(from: child.value)) } else { - print("no child") - throw StructuredAIError.unsupportedType + throw StructuredOutputError.unsupportedType } default: - print("no bueno") - throw StructuredAIError.unsupportedType + throw StructuredOutputError.unsupportedType } } - throw StructuredAIError.unsupportedType + throw StructuredOutputError.unsupportedType } - } } // TODO: Implement other options. Move to a separate file too? Public? - enum StructuredAIError: Error { + public enum StructuredOutputError: Error { case unsupportedType } From d78677b04d6f6e565fcb53b26c21c03243459643 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 14:46:34 -0700 Subject: [PATCH 10/13] Schema name formatting --- Sources/OpenAI/Public/Models/ChatQuery.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index 183cc78f..c714ff97 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -656,10 +656,21 @@ public struct ChatQuery: Equatable, Codable, Streamable { init(name: String, schema: StructuredOutput) { - self.name = name - + func format(_ name: String) -> String { + let underscored = name.replacingOccurrences(of: " ", with: "_") + let regex = try! NSRegularExpression(pattern: "[^a-zA-Z0-9_-]", options: []) + let range = NSRange(location: 0, length: underscored.utf16.count) + let sanitized = regex.stringByReplacingMatches(in: underscored, options: [], range: range, withTemplate: "") + let nonempty = sanitized.isEmpty ? "sample" : sanitized + return String(nonempty.prefix(64)) + } + self.name = format(name) self.schema = schema + + if self.name != name { + print("The name was changed to \(self.name) to satisfy the API requirements. See more: https://platform.openai.com/docs/api-reference/chat/create") + } } public func encode(to encoder: any Encoder) throws { From 15c2fb2aa1e8b390f05ea2538613eab2f0aa6089 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 14:49:28 -0700 Subject: [PATCH 11/13] Support: additionalProperties --- Sources/OpenAI/Public/Models/ChatQuery.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index c714ff97..56ffe87c 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -695,6 +695,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { case value case properties case items + case additionalProperties } enum ValueType: String, Codable { @@ -720,6 +721,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { try container.encode(String("boolean"), forKey: .type) case .object(let object): try container.encode(String("object"), forKey: .type) + try container.encode(false, forKey: .additionalProperties) try container.encode(object, forKey: .properties) case .array(let items): try container.encode(String("array"), forKey: .type) From 56ea6938a0bcbed814ab7df43f5576dd1a7ccea9 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 14:54:41 -0700 Subject: [PATCH 12/13] Support: required --- Sources/OpenAI/Public/Models/ChatQuery.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index 56ffe87c..fb135704 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -696,6 +696,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { case properties case items case additionalProperties + case required } enum ValueType: String, Codable { @@ -723,6 +724,8 @@ public struct ChatQuery: Equatable, Codable, Streamable { try container.encode(String("object"), forKey: .type) try container.encode(false, forKey: .additionalProperties) try container.encode(object, forKey: .properties) + let fields = try object.map { key, value in key } + try container.encode(fields, forKey: .required) case .array(let items): try container.encode(String("array"), forKey: .type) try container.encode(items, forKey: .items) From 54c7a2e675d5ce24e7375100c3969fafbec39320 Mon Sep 17 00:00:00 2001 From: Andriy Gordiyenko Date: Wed, 28 Aug 2024 15:15:16 -0700 Subject: [PATCH 13/13] Explicit lack of support for enums --- Sources/OpenAI/Public/Models/ChatQuery.swift | 23 +++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index fb135704..b7e85715 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -784,20 +784,31 @@ public struct ChatQuery: Equatable, Codable, Streamable { if let child = mirror.children.first { return .array(try generate(from: child.value)) } else { - throw StructuredOutputError.unsupportedType + throw StructuredOutputError.typeUnsupported } + case .enum: + throw StructuredOutputError.enumsUnsupported default: - throw StructuredOutputError.unsupportedType + throw StructuredOutputError.typeUnsupported } } - throw StructuredOutputError.unsupportedType + throw StructuredOutputError.typeUnsupported } } } - // TODO: Implement other options. Move to a separate file too? Public? - public enum StructuredOutputError: Error { - case unsupportedType + public enum StructuredOutputError: LocalizedError { + case enumsUnsupported + case typeUnsupported + + public var errorDescription: String? { + switch self { + case .enumsUnsupported: + return "Enums are not supported at the moment. Consider using one of the basics types and specifying the accepted values in the prompt." + case .typeUnsupported: + return "Unsupported type. Supported types: String, Bool, Int, Double, Array, and Codable struct/class instances." + } + } } public enum ChatCompletionFunctionCallOptionParam: Codable, Equatable {