Skip to content

Commit 4ed3ea9

Browse files
authored
Merge pull request #395 from neelvirdy/nvirdy/verbosity
Add verbosity support for Responses API
2 parents e3d1767 + 208a2a8 commit 4ed3ea9

File tree

2 files changed

+103
-5
lines changed

2 files changed

+103
-5
lines changed

Sources/OpenAI/Public/Models/Responses API/CreateModelResponseQuery+TextResponseConfigurationOptions.swift

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,21 @@ extension CreateModelResponseQuery {
1818
///
1919
/// Setting to `{ "type": "json_object" }` enables the older JSON mode, which ensures the message the model generates is valid JSON. Using `json_schema` is preferred for models that support it.
2020
let format: OutputFormat?
21-
21+
22+
/// Controls the verbosity of the model's text output.
23+
///
24+
/// Possible values: "low", "medium", "high"
25+
let verbosity: Verbosity?
26+
2227
public static let text = TextResponseConfigurationOptions(format: .text)
2328
public static let jsonObject = TextResponseConfigurationOptions(format: .jsonObject)
2429
public static func jsonSchema(_ config: OutputFormat.StructuredOutputsConfig) -> TextResponseConfigurationOptions {
2530
.init(format: .jsonSchema(config))
2631
}
27-
28-
public init(format: OutputFormat?) {
32+
33+
public init(format: OutputFormat?, verbosity: Verbosity? = nil) {
2934
self.format = format
35+
self.verbosity = verbosity
3036
}
3137

3238
public enum OutputFormat: Codable, Hashable, Sendable {
@@ -36,7 +42,36 @@ extension CreateModelResponseQuery {
3642
case jsonSchema(StructuredOutputsConfig)
3743
/// JSON object response format. An older method of generating JSON responses. Using `json_schema` is recommended for models that support it. Note that the model will not generate JSON without a system or user message instructing it to do so.
3844
case jsonObject
39-
45+
46+
public init(from decoder: any Decoder) throws {
47+
let container = try decoder.singleValueContainer()
48+
49+
// Try to decode as ResponseFormatText
50+
if let _ = try? container.decode(Schemas.ResponseFormatText.self) {
51+
self = .text
52+
return
53+
}
54+
55+
// Try to decode as StructuredOutputsConfig (json_schema)
56+
if let config = try? container.decode(StructuredOutputsConfig.self) {
57+
self = .jsonSchema(config)
58+
return
59+
}
60+
61+
// Try to decode as ResponseFormatJsonObject
62+
if let _ = try? container.decode(Schemas.ResponseFormatJsonObject.self) {
63+
self = .jsonObject
64+
return
65+
}
66+
67+
throw DecodingError.dataCorrupted(
68+
DecodingError.Context(
69+
codingPath: decoder.codingPath,
70+
debugDescription: "Unable to decode OutputFormat"
71+
)
72+
)
73+
}
74+
4075
public func encode(to encoder: any Encoder) throws {
4176
var container = encoder.singleValueContainer()
4277
switch self {
@@ -70,5 +105,11 @@ extension CreateModelResponseQuery {
70105
}
71106
}
72107
}
108+
109+
public enum Verbosity: String, Codable, Hashable, Sendable {
110+
case low
111+
case medium
112+
case high
113+
}
73114
}
74115
}

Tests/OpenAITests/OpenAITestsDecoder.swift

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,40 @@ class OpenAITestsDecoder: XCTestCase {
384384
XCTAssertEqual(decoded.effort, .minimal)
385385
}
386386

387+
func testVerbosityDecoding() throws {
388+
// Test decoding "low"
389+
let jsonLow = """
390+
{ "format": { "type": "text" }, "verbosity": "low" }
391+
"""
392+
let dataLow = jsonLow.data(using: .utf8)!
393+
let decodedLow = try JSONDecoder().decode(CreateModelResponseQuery.TextResponseConfigurationOptions.self, from: dataLow)
394+
XCTAssertEqual(decodedLow.verbosity, .low)
395+
396+
// Test decoding "medium"
397+
let jsonMedium = """
398+
{ "format": { "type": "text" }, "verbosity": "medium" }
399+
"""
400+
let dataMedium = jsonMedium.data(using: .utf8)!
401+
let decodedMedium = try JSONDecoder().decode(CreateModelResponseQuery.TextResponseConfigurationOptions.self, from: dataMedium)
402+
XCTAssertEqual(decodedMedium.verbosity, .medium)
403+
404+
// Test decoding "high"
405+
let jsonHigh = """
406+
{ "format": { "type": "text" }, "verbosity": "high" }
407+
"""
408+
let dataHigh = jsonHigh.data(using: .utf8)!
409+
let decodedHigh = try JSONDecoder().decode(CreateModelResponseQuery.TextResponseConfigurationOptions.self, from: dataHigh)
410+
XCTAssertEqual(decodedHigh.verbosity, .high)
411+
412+
// Test decoding without verbosity (should be nil)
413+
let jsonNil = """
414+
{ "format": { "type": "text" } }
415+
"""
416+
let dataNil = jsonNil.data(using: .utf8)!
417+
let decodedNil = try JSONDecoder().decode(CreateModelResponseQuery.TextResponseConfigurationOptions.self, from: dataNil)
418+
XCTAssertNil(decodedNil.verbosity)
419+
}
420+
387421
func testChatQueryWithReasoningEffortNone() throws {
388422
let chatQuery = ChatQuery(
389423
messages: [
@@ -849,7 +883,18 @@ class OpenAITestsDecoder: XCTestCase {
849883
let data = try JSONEncoder().encode(query)
850884
try testEncodedCreateResponseQueryWithStructuredOutput(data)
851885
}
852-
886+
887+
func testCreateResponseQueryWithVerbosity() throws {
888+
let query = CreateModelResponseQuery(
889+
input: .textInput("Return a low verbosity response."),
890+
model: .gpt5,
891+
text: .init(format: .text, verbosity: .low)
892+
)
893+
894+
let data = try JSONEncoder().encode(query)
895+
try testEncodedCreateResponseQueryWithVerbosity(data)
896+
}
897+
853898
private func testEncodedChatQueryWithStructuredOutput(_ data: Data) throws {
854899
let dict = try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any])
855900
XCTAssertEqual(try XCTUnwrap(dict["model"] as? String), "gpt-4o")
@@ -891,4 +936,16 @@ class OpenAITestsDecoder: XCTestCase {
891936
XCTAssertEqual(titleSchema.count, 1)
892937
XCTAssertEqual(try XCTUnwrap(titleSchema["type"] as? String), "string")
893938
}
939+
940+
private func testEncodedCreateResponseQueryWithVerbosity(_ data: Data) throws {
941+
let dict = try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any])
942+
XCTAssertEqual(try XCTUnwrap(dict["model"] as? String), "gpt-5")
943+
944+
let textResponseConfigurationOptions = try XCTUnwrap(dict["text"] as? [String: Any])
945+
let outputFormat = try XCTUnwrap(textResponseConfigurationOptions["format"] as? [String: Any])
946+
let outputVerbosity = try XCTUnwrap(textResponseConfigurationOptions["verbosity"] as? String)
947+
948+
XCTAssertEqual(try XCTUnwrap(outputFormat["type"] as? String), "text")
949+
XCTAssertNotNil(CreateModelResponseQuery.TextResponseConfigurationOptions.Verbosity(rawValue: outputVerbosity))
950+
}
894951
}

0 commit comments

Comments
 (0)