Skip to content

Commit

Permalink
Removed the "object" type from JSONType and any associated usage, sin…
Browse files Browse the repository at this point in the history
…ce all my tests reveal that object function parameters are *not* handled appropriately by OpenAI's APIs. Also, completely reworked the parsing of most parameter datatypes to be based on parameter type expectations rather than attempting to infer them from their contents. Through extensive testing of functions with each parameter type, made sure all the datatypes actually work, including big changes to the way arrays work (addition of the "items" information).
  • Loading branch information
btfranklin committed Jul 4, 2023
1 parent 69bdf62 commit dd61c68
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 57 deletions.
26 changes: 25 additions & 1 deletion Sources/CleverBird/datatypes/Function.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,40 @@ public struct Function: Codable {
/// The allowed values for the parameter if it's an enum. The AI should choose from these values when invoking the function.
public let enumCases: [String]?

/// The definition of the individual items in the parameter if it's an array. The AI should populate the array with values based on this when invoking the function.
public let items: ArrayItems?

enum CodingKeys: String, CodingKey {
case type
case description
case enumCases = "enum"
case items
}

public init(type: JSONType, description: String? = nil, enumCases: [String]? = nil) {
public init(type: JSONType,
description: String? = nil,
enumCases: [String]? = nil,
items: ArrayItems? = nil) {
self.type = type
self.description = description
self.enumCases = enumCases
self.items = items
}

public struct ArrayItems: Codable {

/// The type of the items in the array. It could be "string", "integer", etc., based on the JSON Schema types.
public let type: JSONType

/// A description of what a single item in the array represents
public let description: String?

public init(type: JSONType,
description: String? = nil) {
self.type = type
self.description = description
}

}
}

Expand Down
9 changes: 3 additions & 6 deletions Sources/CleverBird/datatypes/FunctionCall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,12 @@ public struct FunctionCall: Codable {

// Decode each argument
for (argName, argValue) in argumentsDict {
let value = try JSONValue.processJSONValue(argValue)

// Perform type checking against expectedType
let expectedType = function?.parameters.properties[argName]?.type
if !value.conformsTo(type: expectedType) {
guard let argType = function?.parameters.properties[argName]?.type else {
throw DecodingError.dataCorruptedError(forKey: .arguments,
in: container,
debugDescription: "Invalid type for \(argName). Expected \(expectedType?.rawValue ?? "unknown"), but got \(value.typeDescription)")
debugDescription: "No type provided for \(argName). Decoding not possible.")
}
let value = try JSONValue.createFromValue(argValue, ofType: argType)

decodedArguments[argName] = value
}
Expand Down
1 change: 0 additions & 1 deletion Sources/CleverBird/datatypes/JSONType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ public enum JSONType: String, Codable {
case boolean
case number
case integer
case object
case array
}
87 changes: 38 additions & 49 deletions Sources/CleverBird/datatypes/JSONValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ public enum JSONValue: Codable {
case boolean(Bool)
case number(Double)
case integer(Int)
case object([String: JSONValue])
case array([JSONValue])

var typeDescription: String {
Expand All @@ -23,8 +22,6 @@ public enum JSONValue: Codable {
return "number"
case .integer(_):
return "integer"
case .object(_):
return "object"
case .array(_):
return "array"
}
Expand All @@ -40,8 +37,6 @@ public enum JSONValue: Codable {
self = .number(x)
} else if let x = try? container.decode(Int.self) {
self = .integer(x)
} else if let x = try? container.decode([String: JSONValue].self) {
self = .object(x)
} else if let x = try? container.decode([JSONValue].self) {
self = .array(x)
} else if container.decodeNil() {
Expand All @@ -54,6 +49,8 @@ public enum JSONValue: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .null:
try container.encodeNil()
case .string(let x):
try container.encode(x)
case .boolean(let x):
Expand All @@ -62,58 +59,50 @@ public enum JSONValue: Codable {
try container.encode(x)
case .integer(let x):
try container.encode(x)
case .object(let x):
try container.encode(x)
case .array(let x):
try container.encode(x)
case .null:
try container.encodeNil()
}
}

func conformsTo(type: JSONType?) -> Bool {
switch (self, type) {
case (.string(_), .string),
(.number(_), .number),
(.integer(_), .integer),
(.object(_), .object),
(.array(_), .array),
(.boolean(_), .boolean),
(.null, .null):
return true
default:
return false
}
}
static func createFromValue(_ value: Any, ofType type: JSONType) throws -> JSONValue {

static func processJSONValue(_ value: Any) throws -> JSONValue {
switch value {
case let stringValue as String:
return .string(stringValue)
case let numberValue as Double:
if numberValue.truncatingRemainder(dividingBy: 1) == 0 {
// The number is actually an integer
return .integer(Int(numberValue))
} else {
// The number is a true double
return .number(numberValue)
switch type {

case .null:
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Value provided for null type"))

case .string:
guard value is String else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Value was not expected type of String"))
}
return .string(value as! String)

case .boolean:
guard value is Bool else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Value was not expected type of Bool"))
}
return .boolean(value as! Bool)

case .number:
guard value is Double else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Value was not expected type of Double"))
}
return .number(value as! Double)

case .integer:
guard value is Int else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Value was not expected type of Int"))
}
case let intValue as Int:
return .integer(intValue)
case let boolValue as Bool:
return .boolean(boolValue)
case is NSNull:
return .null
case let arrayValue as [Any]:
return .array(try arrayValue.map(processJSONValue))
case let objectValue as [String: Any]:
var result: [String: JSONValue] = [:]
for (key, value) in objectValue {
result[key] = try processJSONValue(value)
return .integer(value as! Int)

case .array:
guard value is [Any] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Value was not expected type of [Any]"))
}
return .object(result)
default:
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON value"))
let arrayValue = value as! [Any]
return .array(try arrayValue.map { item in
try createFromValue(item, ofType: .string)
})
}
}

Expand Down

0 comments on commit dd61c68

Please sign in to comment.