Skip to content

Date type support #2

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

Merged
merged 5 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
150 changes: 112 additions & 38 deletions Sources/OpenAI/Public/Models/ChatQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -683,24 +683,29 @@ public struct ChatQuery: Equatable, Codable, Streamable {

private indirect enum PropertyValue: Codable {

case string(String)
case integer(Int)
case number(Double)
case boolean(Bool)
case object([String: PropertyValue])
case array(PropertyValue)
case string(isOptional: Bool)
case date(isOptional: Bool)
case integer(isOptional: Bool)
case number(isOptional: Bool)
case boolean(isOptional: Bool)
case `enum`(cases: [String], isOptional: Bool)
case object([String: PropertyValue], isOptional: Bool)
case array(PropertyValue, isOptional: Bool)

enum CodingKeys: String, CodingKey {
case type
case description
case value
case properties
case items
case additionalProperties
case required
case `enum`
}

enum ValueType: String, Codable {
case string
case date
case integer
case number
case boolean
Expand All @@ -712,22 +717,60 @@ public struct ChatQuery: Equatable, Codable, Streamable {
var container = encoder.container(keyedBy: CodingKeys.self)

switch self {
case .string:
try container.encode(String("string"), forKey: .type)
case .integer:
try container.encode(String("integer"), forKey: .type)
case .number:
try container.encode(String("number"), forKey: .type)
case .boolean:
try container.encode(String("boolean"), forKey: .type)
case .object(let object):
try container.encode(String("object"), forKey: .type)
case .string(let isOptional):
if isOptional {
try container.encode(["string", "null"], forKey: .type)
} else {
try container.encode(String("string"), forKey: .type)
}
case .date(let isOptional):
if isOptional {
try container.encode(["string", "null"], forKey: .type)
} else {
try container.encode(String("string"), forKey: .type)
}
try container.encode(String("Date in iso8601 format"), forKey: .description)
case .integer(let isOptional):
if isOptional {
try container.encode(["integer", "null"], forKey: .type)
} else {
try container.encode(String("integer"), forKey: .type)
}
case .number(let isOptional):
if isOptional {
try container.encode(["number", "null"], forKey: .type)
} else {
try container.encode(String("number"), forKey: .type)
}
case .boolean(let isOptional):
if isOptional {
try container.encode(["boolean", "null"], forKey: .type)
} else {
try container.encode(String("boolean"), forKey: .type)
}
case .enum(let cases, let isOptional):
if isOptional {
try container.encode(["string", "null"], forKey: .type)
} else {
try container.encode(String("string"), forKey: .type)
}
try container.encode(cases, forKey: .enum)
case .object(let object, let isOptional):
if isOptional {
try container.encode(["object", "null"], forKey: .type)
} else {
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)
case .array(let items, let isOptional):
if isOptional {
try container.encode(["array", "null"], forKey: .type)
} else {
try container.encode(String("array"), forKey: .type)
}
try container.encode(items, forKey: .items)

}
Expand All @@ -740,54 +783,82 @@ public struct ChatQuery: Equatable, Codable, Streamable {
switch type {
case .string:
let string = try container.decode(String.self, forKey: .value)
self = .string(string)
self = .string(isOptional: false)
case .date:
let date = try container.decode(Date.self, forKey: .value)
self = .date(isOptional: false)
case .integer:
let integer = try container.decode(Int.self, forKey: .value)
self = .integer(integer)
self = .integer(isOptional: false)
case .number:
let double = try container.decode(Double.self, forKey: .value)
self = .number(double)
self = .number(isOptional: false)
case .boolean:
let bool = try container.decode(Bool.self, forKey: .value)
self = .boolean(bool)
self = .boolean(isOptional: false)
case .object:
let object = try container.decode([String: PropertyValue].self, forKey: .value)
self = .object(object)
self = .object(object, isOptional: false)
case .array:
let array = try container.decode(PropertyValue.self, forKey: .value)
self = .array(array)
self = .array(array, isOptional: false)
}
}

static func generate<T: Any>(from value: T) throws -> PropertyValue {

let mirror = Mirror(reflecting: value)
let isOptional = mirror.displayStyle == .optional

switch value {
case _ as String:
return .string("string")
return .string(isOptional: isOptional)
case _ as Bool:
return .boolean(true)
return .boolean(isOptional: isOptional)
case _ as Int, _ as Int8, _ as Int16, _ as Int32, _ as Int64,
_ as UInt, _ as UInt8, _ as UInt16, _ as UInt32, _ as UInt64:
return .integer(0)
return .integer(isOptional: isOptional)
case _ as Double, _ as Float, _ as CGFloat:
return .integer(0)
return .number(isOptional: isOptional)
case _ as Date:
return .date(isOptional: isOptional)
default:
let mirror = Mirror(reflecting: value)
if let displayStyle = mirror.displayStyle {

var unwrappedMirror: Mirror!
if isOptional {
guard let child = mirror.children.first else {
throw StructuredOutputError.nilFoundInExample
}
unwrappedMirror = Mirror(reflecting: child.value)
} else {
unwrappedMirror = mirror
}

if let displayStyle = unwrappedMirror.displayStyle {

switch displayStyle {

case .struct, .class:
var dict = [String: PropertyValue]()
for child in mirror.children {
for child in unwrappedMirror.children {
dict[child.label!] = try generate(from: child.value)
}
return .object(dict)
return .object(dict, isOptional: isOptional)

case .collection:
if let child = mirror.children.first {
return .array(try generate(from: child.value))
if let child = unwrappedMirror.children.first {
return .array(try generate(from: child.value), isOptional: isOptional)
} else {
throw StructuredOutputError.typeUnsupported
}

case .enum:
throw StructuredOutputError.enumsUnsupported
if let structuredEnum = value as? any StructuredOutputEnum {
return .enum(cases: structuredEnum.caseNames, isOptional: isOptional)
} else {
throw StructuredOutputError.enumsConformance
}

default:
throw StructuredOutputError.typeUnsupported
}
Expand All @@ -798,15 +869,18 @@ public struct ChatQuery: Equatable, Codable, Streamable {
}

public enum StructuredOutputError: LocalizedError {
case enumsUnsupported
case enumsConformance
case typeUnsupported
case nilFoundInExample

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 .enumsConformance:
return "Conform the enum types to StructuredOutputEnum and provide the `caseNames` property with a list of available cases."
case .typeUnsupported:
return "Unsupported type. Supported types: String, Bool, Int, Double, Array, and Codable struct/class instances."
case .nilFoundInExample:
return "Found nils when serializing the StructuredOutput‘s example. Provide values for all optional properties in the example."
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions Sources/OpenAI/Public/Models/StructuredOutputEnum.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// StructuredOutputEnum.swift
//
//
// Created by Andriy Gordiyenko on 8/29/24.
//

import Foundation

public protocol StructuredOutputEnum: CaseIterable {
var caseNames: [String] { get }
}