Skip to content

Instantly share code, notes, and snippets.

@nathanborror
Last active November 4, 2023 21:07
Show Gist options
  • Save nathanborror/c500c4e27a564b638206553d510c509f to your computer and use it in GitHub Desktop.
Save nathanborror/c500c4e27a564b638206553d510c509f to your computer and use it in GitHub Desktop.
// NEW WAY
// Annotate fields directly on the expected response and use an
// encoder to convert to a JSONSchema object.
public struct MoonPhaseParameters: Codable {
@Schema(description: "The location latitude")
public var latitude: Double = 0.0
@Schema(description: "The location longitude")
public var longitude: Double = 0.0
}
ChatFunctionDeclaration(
name: "get_moon_phase",
description: "Returns the current moon phase for a location given the location's latitude and longitude.",
parameters: JSONSchemaEncoder(MoonPhaseParameters())
)
// OLD WAY
// Expected response is disconnected from the function declaration.
// Forces you to make updates in two locations and keep both in sync.
struct MoonPhaseParameters: Codable {
public let latitude: Double
public let longitude: Double
}
ChatFunctionDeclaration(
name: "get_moon_phase",
description: "Returns the current moon phase for a location given the location's latitude and longitude.",
parameters: JSONSchema(
type: .object,
properties: [
"latitude": .init(type: .number, description: "The location latitude"),
"longitude": .init(type: .number, description: "The location longitude"),
],
required: ["latitude", "longitude"]
)
)
import Foundation
import OpenAI
func JSONSchemaEncoder<T>(_ instance: T) -> JSONSchema {
var properties = [String: JSONSchema.Property]()
var required = [String]()
let mirror = Mirror(reflecting: instance)
for child in mirror.children {
if let label = child.label, let schemaInfo = child.value as? any SchemaProtocol {
let name = String(label.trimmingPrefix("_"))
let type = newJSONSchemaType(schemaInfo.type)
let childMirror = Mirror(reflecting: child.value)
if childMirror.displayStyle != .optional {
required.append(name)
}
let prop = JSONSchema.Property(
type: type,
description: schemaInfo.description,
format: schemaInfo.format,
items: schemaInfo.items,
enumValues: schemaInfo.enumValues
)
properties[name] = prop
}
}
return JSONSchema(type: .object, properties: properties, required: required)
}
func newJSONSchemaType(_ type: String) -> JSONSchema.JSONType {
if type.hasPrefix("Array") {
return .array
}
return .init(rawValue: type.lowercased()) ?? .null
}
protocol SchemaProtocol {
var description: String { get }
var format: String? { get }
var items: JSONSchema.Items? { get }
var required: [String]? { get }
var pattern: String? { get }
var const: String? { get }
var enumValues: [String]? { get }
var multipleOf: Int? { get }
var minimum: Double? { get }
var maximum: Double? { get }
var minItems: Int? { get }
var maxItems: Int? { get }
var uniqueItems: Bool? { get }
var type: String { get }
}
extension Schema: SchemaProtocol {
var type: String {
String(describing: Value.self)
}
}
@propertyWrapper
public struct Schema<Value: Codable>: Codable {
public var wrappedValue: Value
let description: String
let format: String?
let items: JSONSchema.Items?
let required: [String]?
let pattern: String?
let const: String?
let enumValues: [String]?
let multipleOf: Int?
let minimum: Double?
let maximum: Double?
let minItems: Int?
let maxItems: Int?
let uniqueItems: Bool?
init(wrappedValue: Value, description: String, format: String? = nil, items: JSONSchema.Items? = nil, required: [String]? = nil,
pattern: String? = nil, const: String? = nil, enumValues: [String]? = nil, multipleOf: Int? = nil,
minimum: Double? = nil, maximum: Double? = nil, minItems: Int? = nil, maxItems: Int? = nil, uniqueItems: Bool? = nil) {
self.wrappedValue = wrappedValue
self.description = description
self.format = format
self.items = items
self.required = required
self.pattern = pattern
self.const = const
self.enumValues = enumValues
self.multipleOf = multipleOf
self.minimum = minimum
self.maximum = maximum
self.minItems = minItems
self.maxItems = maxItems
self.uniqueItems = uniqueItems
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self)
description = ""
format = nil
items = nil
required = nil
pattern = nil
const = nil
enumValues = nil
multipleOf = nil
minimum = nil
maximum = nil
minItems = nil
maxItems = nil
uniqueItems = nil
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment