Skip to content

Commit e4c17c9

Browse files
committed
Fix schema properties order
1 parent 4021e45 commit e4c17c9

File tree

8 files changed

+78
-18
lines changed

8 files changed

+78
-18
lines changed

Package.resolved

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ let package = Package(
1313
.package(url: "https://github.com/ml-explore/mlx-swift", from: "0.25.6"),
1414
.package(url: "https://github.com/ml-explore/mlx-swift-examples", from: "2.25.7"),
1515
.package(url: "https://github.com/huggingface/swift-transformers", from: "0.1.24"),
16-
.package(url: "https://github.com/kevinhermawan/swift-json-schema", from: "2.0.1"),
16+
.package(url: "https://github.com/petrukha-ivan/swift-json-schema", from: "2.0.2"),
1717
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.4.0"),
1818
],
1919
targets: [
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// Grammar+Encoding.swift
3+
// MLXStructured
4+
//
5+
// Created by Ivan Petrukha on 05.10.2025.
6+
//
7+
8+
import Foundation
9+
import JSONSchema
10+
11+
extension JSONEncoder {
12+
13+
static let `default` = JSONEncoder()
14+
15+
static let sorted: JSONEncoder = {
16+
let encoder = JSONEncoder()
17+
encoder.outputFormatting = [.sortedKeys, .prettyPrinted]
18+
return encoder
19+
}()
20+
}
21+
22+
extension JSONDecoder {
23+
24+
static let `default` = JSONDecoder()
25+
26+
static func withPropertiesOrderInfo(_ order: [String]) -> JSONDecoder {
27+
let decoder = JSONDecoder()
28+
decoder.userInfo[JSONSchema.ObjectSchema.Properties.orderInfoKey] = order
29+
return decoder
30+
}
31+
}
32+
33+
extension JSONSchema: @retroactive CustomStringConvertible {
34+
public var description: String {
35+
do {
36+
let data = try JSONEncoder.sorted.encode(self)
37+
let string = String(decoding: data, as: UTF8.self).sanitizedSchema
38+
return string
39+
} catch {
40+
return "Invalid JSON Schema"
41+
}
42+
}
43+
}
44+
45+
extension String {
46+
var sanitizedSchema: String {
47+
replacingOccurrences(of: "__[0-9]+__", with: "", options: .regularExpression)
48+
}
49+
}

Sources/MLXStructured/Grammar/Grammar+Generable.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Foundation
9+
import JSONSchema
910

1011
#if canImport(FoundationModels)
1112
import FoundationModels
@@ -14,10 +15,22 @@ import FoundationModels
1415
#if compiler(>=6.2)
1516
@available(macOS 26.0, iOS 26.0, *)
1617
public extension Grammar {
18+
19+
struct OrderContainer: Codable {
20+
21+
let order: [String]
22+
23+
enum CodingKeys: String, CodingKey {
24+
case order = "x-order"
25+
}
26+
}
27+
1728
static func generable<Content: Generable>(_ type: Content.Type, indent: Int? = nil) throws -> Grammar {
18-
let encoder = JSONEncoder()
19-
let data = try encoder.encode(type.generationSchema)
20-
let string = String(decoding: data, as: UTF8.self)
29+
let generationSchemaData = try JSONEncoder.default.encode(type.generationSchema)
30+
let orderContainer = try JSONDecoder.default.decode(OrderContainer.self, from: generationSchemaData)
31+
let schema = try JSONDecoder.withPropertiesOrderInfo(orderContainer.order).decode(JSONSchema.self, from: generationSchemaData)
32+
let schemaData = try JSONEncoder.sorted.encode(schema)
33+
let string = String(decoding: schemaData, as: UTF8.self).sanitizedSchema
2134
return .schema(string, indent: indent)
2235
}
2336
}

Sources/MLXStructured/Grammar/Grammar+Schema.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import JSONSchema
1010

1111
public extension Grammar {
1212
static func schema(_ schema: JSONSchema = .object(), indent: Int? = nil) throws -> Grammar {
13-
let encoder = JSONEncoder()
14-
let data = try encoder.encode(schema)
15-
let string = String(decoding: data, as: UTF8.self)
13+
let data = try JSONEncoder.sorted.encode(schema)
14+
let string = String(decoding: data, as: UTF8.self).sanitizedSchema
1615
return .schema(string, indent: indent)
1716
}
1817
}

Sources/MLXStructured/Grammar/Grammar+Structural.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import Foundation
1010
public extension Grammar {
1111
init(@FormatBuilder _ content: () -> Encodable) throws {
1212
let tag = StructuralTag(format: content())
13-
let encoder = JSONEncoder()
14-
let data = try encoder.encode(tag)
15-
let string = String(decoding: data, as: UTF8.self)
13+
let data = try JSONEncoder.sorted.encode(tag)
14+
let string = String(decoding: data, as: UTF8.self).sanitizedSchema
1615
self = Grammar.structural(string)
1716
}
1817
}

Sources/MLXStructuredCLI/Commands/StructuralExample.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ struct StructuralExample: AsyncParsableCommand {
2626
let grammar = try Grammar {
2727
SequenceFormat {
2828
if includePrefix {
29-
ConstTextFormat(text: "According to my knowledge, my answer is ")
29+
ConstTextFormat(text: "Based on the data I was trained on, the answer is ")
3030
}
3131
OrFormat {
3232
ConstTextFormat(text: "YES")

Sources/MLXStructuredCLI/Commands/ToolCallingExample.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private extension Tool {
2424
"function": [
2525
"name": name,
2626
"description": description,
27-
"parameters": try! JSONSerialization.jsonObject(with: JSONEncoder().encode(getCurrentTimeTool.parameters))
27+
"parameters": try! JSONSerialization.jsonObject(with: JSONEncoder().encode(parameters))
2828
]
2929
]
3030
}

0 commit comments

Comments
 (0)