Skip to content
Draft
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
2 changes: 1 addition & 1 deletion Sources/AnyLanguageModel/Models/CoreMLLanguageModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@
{
header += ". Expected value: \(constString)"
} else if let enumValues = jsonSchema.enum, !enumValues.isEmpty,
let data = try? encoder.encode(JSONValue.array(enumValues)),
let data = try? encoder.encode(enumValues),
let enumString = String(data: data, encoding: .utf8)
{
header += ". Allowed values: \(enumString)"
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnyLanguageModel/Models/LlamaLanguageModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1602,7 +1602,7 @@ import Foundation
{
header += ". Expected value: \(constString)"
} else if let enumValues = jsonSchema.enum, !enumValues.isEmpty,
let data = try? encoder.encode(JSONValue.array(enumValues)),
let data = try? encoder.encode(enumValues),
let enumString = String(data: data, encoding: .utf8)
{
header += ". Allowed values: \(enumString)"
Expand Down
4 changes: 2 additions & 2 deletions Sources/AnyLanguageModel/Models/SystemLanguageModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@

case .integer(_, _, _, _, _, _, minimum: let minimum, maximum: let maximum, _, _, _):
if let enumValues = jsonSchema.enum {
let enumsSchema = enumValues.compactMap { convertConstToSchema($0) }
let enumsSchema = enumValues.compactMap { convertConstToSchema(JSONValue($0)) }
return .init(name: name ?? "", anyOf: enumsSchema)
}

Expand All @@ -495,7 +495,7 @@

case .number(_, _, _, _, _, _, minimum: let minimum, maximum: let maximum, _, _, _):
if let enumValues = jsonSchema.enum {
let enumsSchema = enumValues.compactMap { convertConstToSchema($0) }
let enumsSchema = enumValues.compactMap { convertConstToSchema(JSONValue($0)) }
return .init(name: name ?? "", anyOf: enumsSchema)
}

Expand Down
46 changes: 46 additions & 0 deletions Sources/AnyLanguageModel/Shared/JSONValue+JSONSchema.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import enum JSONSchema.JSONValue

// This file imports only `JSONSchema.JSONValue`,
// so `JSONSchema` below names the module, not the `JSONSchema` type.

extension AnyLanguageModel.JSONValue {
/// Creates a JSON value from the JSONSchema package's JSON value.
init(_ value: JSONSchema.JSONValue) {
switch value {
case .null:
self = .null
case .bool(let value):
self = .bool(value)
case .int(let value):
self = .int(value)
case .double(let value):
self = .double(value)
case .string(let value):
self = .string(value)
case .array(let values):
self = .array(values.map { AnyLanguageModel.JSONValue($0) })
case .object(let object):
self = .object(object.mapValues { AnyLanguageModel.JSONValue($0) })
}
}

/// This value as the JSONSchema package's JSON value.
var jsonSchemaValue: JSONSchema.JSONValue {
switch self {
case .null:
return .null
case .bool(let value):
return .bool(value)
case .int(let value):
return .int(value)
case .double(let value):
return .double(value)
case .string(let value):
return .string(value)
case .array(let values):
return .array(values.map(\.jsonSchemaValue))
case .object(let object):
return .object(object.mapValues(\.jsonSchemaValue))
}
}
}
213 changes: 210 additions & 3 deletions Sources/AnyLanguageModel/Shared/JSONValue.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,211 @@
import enum JSONSchema.JSONValue
import Foundation

/// A type-safe representation of JSON values used by AnyLanguageModel APIs.
public typealias JSONValue = JSONSchema.JSONValue
/// A JSON value.
///
/// Use `JSONValue` to pass arbitrary JSON to a provider,
/// such as extra request body fields or custom generation options.
/// It encodes and decodes as plain JSON.
///
/// You can create a JSON value with a literal:
///
/// ```swift
/// let value: JSONValue = [
/// "name": "Ada",
/// "age": 36,
/// "active": true,
/// "tags": ["math", "engines"],
/// "manager": nil,
/// ]
/// ```
@frozen public enum JSONValue: Sendable, Hashable {
/// A JSON null value.
case null

/// A JSON boolean value.
case bool(Bool)

/// A JSON number without a fractional part.
case int(Int)

/// A JSON number with a fractional part.
case double(Double)

/// A JSON string value.
case string(String)

/// A JSON array.
case array([JSONValue])

/// A JSON object.
case object([String: JSONValue])

/// Creates a JSON value from an encodable value.
///
/// This initializer encodes the value as JSON and decodes the result.
/// If the value is already a `JSONValue`, it's returned unchanged.
///
/// - Parameter value: The value to convert.
/// - Throws: An error if the value can't be encoded as JSON.
public init<T: Encodable>(_ value: T) throws {
if let value = value as? JSONValue {
self = value
} else {
let data = try JSONEncoder().encode(value)
self = try JSONDecoder().decode(JSONValue.self, from: data)
}
}

/// A Boolean value that indicates whether this is a null value.
public var isNull: Bool {
self == .null
}

/// The value of a boolean value, or `nil` for any other value.
public var boolValue: Bool? {
guard case .bool(let value) = self else { return nil }
return value
}

/// The value of an integer value, or `nil` for any other value.
///
/// This property returns `nil` for a ``double(_:)`` value,
/// even one without a fractional part.
public var intValue: Int? {
guard case .int(let value) = self else { return nil }
return value
}

/// The value of a number, or `nil` for any other value.
///
/// This property converts an ``int(_:)`` value to `Double`.
public var doubleValue: Double? {
switch self {
case .double(let value): return value
case .int(let value): return Double(value)
default: return nil
}
}

/// The value of a string value, or `nil` for any other value.
public var stringValue: String? {
guard case .string(let value) = self else { return nil }
return value
}

/// The elements of an array value, or `nil` for any other value.
public var arrayValue: [JSONValue]? {
guard case .array(let value) = self else { return nil }
return value
}

/// The dictionary of an object value, or `nil` for any other value.
public var objectValue: [String: JSONValue]? {
guard case .object(let value) = self else { return nil }
return value
}
}

// MARK: - Codable

extension JSONValue: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode(Int.self) {
self = .int(value)
} else if let value = try? container.decode(Double.self) {
self = .double(value)
} else if let value = try? container.decode(String.self) {
self = .string(value)
} else if let value = try? container.decode([JSONValue].self) {
self = .array(value)
} else if let value = try? container.decode([String: JSONValue].self) {
self = .object(value)
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Expected a JSON value."
)
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .null:
try container.encodeNil()
case .bool(let value):
try container.encode(value)
case .int(let value):
try container.encode(value)
case .double(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
case .array(let value):
try container.encode(value)
case .object(let value):
try container.encode(value)
}
}
}

// MARK: - Literals

extension JSONValue: ExpressibleByNilLiteral {
/// Creates a null JSON value.
public init(nilLiteral: ()) {
self = .null
}
}

extension JSONValue: ExpressibleByBooleanLiteral {
/// Creates a boolean JSON value.
public init(booleanLiteral value: Bool) {
self = .bool(value)
}
}

extension JSONValue: ExpressibleByIntegerLiteral {
/// Creates an integer JSON value.
public init(integerLiteral value: Int) {
self = .int(value)
}
}

extension JSONValue: ExpressibleByFloatLiteral {
/// Creates a floating-point JSON value.
public init(floatLiteral value: Double) {
self = .double(value)
}
}

extension JSONValue: ExpressibleByStringLiteral {
/// Creates a string JSON value.
public init(stringLiteral value: String) {
self = .string(value)
}
}

extension JSONValue: ExpressibleByArrayLiteral {
/// Creates an array JSON value.
public init(arrayLiteral elements: JSONValue...) {
self = .array(elements)
}
}

extension JSONValue: ExpressibleByDictionaryLiteral {
/// Creates an object JSON value.
///
/// If a key appears more than once, the last value wins.
public init(dictionaryLiteral elements: (String, JSONValue)...) {
var object: [String: JSONValue] = [:]
for (key, value) in elements {
object[key] = value
}
self = .object(object)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Foundation
import JSONSchema
import Testing

@testable import AnyLanguageModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Foundation

#if canImport(FoundationModels)
import FoundationModels
import JSONSchema
import enum JSONSchema.JSONSchema

private let isFoundationModelsAvailable: Bool = {
if #available(macOS 26.0, iOS 26.0, watchOS 27.0, tvOS 26.0, visionOS 26.0, *) {
Expand Down
Loading
Loading