diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index a9e40ee0..ff1c7af5 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "3.2.1"
+ ".": "3.3.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8dd6c075..bf7f5d1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 3.3.0 (2025-09-03)
+
+Full Changelog: [v3.2.1...v3.3.0](https://github.com/openai/openai-java/compare/v3.2.1...v3.3.0)
+
+### Features
+
+* **client:** support verbosity with structured outputs ([#603](https://github.com/openai/openai-java/issues/603)) ([2496464](https://github.com/openai/openai-java/commit/24964646ab02a3ff61efb9802facac66b204bdaf))
+
## 3.2.1 (2025-09-02)
Full Changelog: [v3.2.0...v3.2.1](https://github.com/openai/openai-java/compare/v3.2.0...v3.2.1)
diff --git a/README.md b/README.md
index feebec7e..ed3ee973 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
-[](https://central.sonatype.com/artifact/com.openai/openai-java/3.2.1)
-[](https://javadoc.io/doc/com.openai/openai-java/3.2.1)
+[](https://central.sonatype.com/artifact/com.openai/openai-java/3.3.0)
+[](https://javadoc.io/doc/com.openai/openai-java/3.3.0)
@@ -11,7 +11,7 @@ The OpenAI Java SDK provides convenient access to the [OpenAI REST API](https://
-The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.openai/openai-java/3.2.1).
+The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.openai/openai-java/3.3.0).
@@ -24,7 +24,7 @@ The REST API documentation can be found on [platform.openai.com](https://platfor
### Gradle
```kotlin
-implementation("com.openai:openai-java:3.2.1")
+implementation("com.openai:openai-java:3.3.0")
```
### Maven
@@ -33,7 +33,7 @@ implementation("com.openai:openai-java:3.2.1")
com.openai
openai-java
- 3.2.1
+ 3.3.0
```
@@ -565,6 +565,18 @@ the latter when `ResponseCreateParams.Builder.text(Class)` is called.
For a full example of the usage of _Structured Outputs_ with the Responses API, see
[`ResponsesStructuredOutputsExample`](openai-java-example/src/main/java/com/openai/example/ResponsesStructuredOutputsExample.java).
+Instead of using `ResponseCreateParams.text(Class)`, you can build a
+[`StructuredResponseTextConfig`](openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseTextConfig.kt)
+and set it on the `ResponseCreateParams` using the `text(StructuredResponseTextConfig)` method.
+Similar to using `ResponseCreateParams`, you can start with a `ResponseTextConfig.Builder` and its
+`format(Class)` method will change it to a `StructuredResponseTextConfig.Builder`. This also
+allows you to set the `verbosity` configuration parameter on the text configuration before adding it
+to the `ResponseCreateParams`.
+
+For a full example of the usage of _Structured Outputs_ with the `ResponseTextConfig` and its
+`verbosity` parameter, see
+[`ResponsesStructuredOutputsVerbosityExample`](openai-java-example/src/main/java/com/openai/example/ResponsesStructuredOutputsVerbosityExample.java).
+
### Usage with streaming
_Structured Outputs_ can also be used with [Streaming](#streaming) and the Chat Completions API. As
@@ -1330,7 +1342,7 @@ If you're using Spring Boot, then you can use the SDK's [Spring Boot starter](ht
#### Gradle
```kotlin
-implementation("com.openai:openai-java-spring-boot-starter:3.2.1")
+implementation("com.openai:openai-java-spring-boot-starter:3.3.0")
```
#### Maven
@@ -1339,7 +1351,7 @@ implementation("com.openai:openai-java-spring-boot-starter:3.2.1")
com.openai
openai-java-spring-boot-starter
- 3.2.1
+ 3.3.0
```
diff --git a/build.gradle.kts b/build.gradle.kts
index 81688f78..2c2a57a4 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ repositories {
allprojects {
group = "com.openai"
- version = "3.2.1" // x-release-please-version
+ version = "3.3.0" // x-release-please-version
}
subprojects {
diff --git a/openai-java-core/src/main/kotlin/com/openai/core/JsonSchemaValidator.kt b/openai-java-core/src/main/kotlin/com/openai/core/JsonSchemaValidator.kt
index 87db4dac..f0f35422 100644
--- a/openai-java-core/src/main/kotlin/com/openai/core/JsonSchemaValidator.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/core/JsonSchemaValidator.kt
@@ -312,7 +312,7 @@ internal class JsonSchemaValidator private constructor() {
/**
* Validates a schema if it has an `"anyOf"` field. OpenAI does not support the use of `"anyOf"`
- * at the root of a JSON schema. The value is the field is expected to be an array of valid
+ * at the root of a JSON schema. The value of the field is expected to be an array of valid
* sub-schemas. If the schema has no `"anyOf"` field, no action is taken.
*/
private fun validateAnyOfSchema(schema: JsonNode, path: String, depth: Int) {
diff --git a/openai-java-core/src/main/kotlin/com/openai/core/StructuredOutputs.kt b/openai-java-core/src/main/kotlin/com/openai/core/StructuredOutputs.kt
index aceb5448..b0198313 100644
--- a/openai-java-core/src/main/kotlin/com/openai/core/StructuredOutputs.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/core/StructuredOutputs.kt
@@ -85,6 +85,20 @@ internal fun validateSchema(
return schema
}
+/** Builds a text configuration's JSON schema, deriving it from the structure of a class. */
+@JvmSynthetic
+internal fun jsonSchemaFromClass(
+ type: Class<*>,
+ localValidation: JsonSchemaLocalValidation = JsonSchemaLocalValidation.YES,
+): ResponseFormatTextJsonSchemaConfig =
+ ResponseFormatTextJsonSchemaConfig.builder()
+ .name("json-schema-from-${type.simpleName}")
+ .schema(JsonValue.fromJsonNode(validateSchema(extractSchema(type), type, localValidation)))
+ // Ensure the model's output strictly adheres to this JSON schema. This is the essential
+ // "ON switch" for Structured Outputs.
+ .strict(true)
+ .build()
+
/**
* Builds a text configuration with its format set to a JSON schema derived from the structure of an
* arbitrary Java class.
@@ -94,21 +108,7 @@ internal fun textConfigFromClass(
type: Class<*>,
localValidation: JsonSchemaLocalValidation = JsonSchemaLocalValidation.YES,
): ResponseTextConfig =
- ResponseTextConfig.builder()
- .format(
- ResponseFormatTextJsonSchemaConfig.builder()
- .name("json-schema-from-${type.simpleName}")
- .schema(
- JsonValue.fromJsonNode(
- validateSchema(extractSchema(type), type, localValidation)
- )
- )
- // Ensure the model's output strictly adheres to this JSON schema. This is the
- // essential "ON switch" for Structured Outputs.
- .strict(true)
- .build()
- )
- .build()
+ ResponseTextConfig.builder().format(jsonSchemaFromClass(type, localValidation)).build()
// "internal" instead of "private" for testing purposes.
internal data class FunctionInfo(
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionMessageFunctionToolCall.kt b/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionMessageFunctionToolCall.kt
index 9b050f92..2d28d20d 100644
--- a/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionMessageFunctionToolCall.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionMessageFunctionToolCall.kt
@@ -267,7 +267,7 @@ private constructor(
* Gets the arguments to the function call, converting the values from the model in JSON
* format to an instance of a class that holds those values. The class must previously have
* been used to define the JSON schema for the function definition's parameters, so that the
- * JSON corresponds to structure of the given class.
+ * JSON corresponds to the structure of the given class.
*
* @throws OpenAIInvalidDataException If the JSON data is missing, `null`, or cannot be
* parsed to an instance of the [functionParametersType] class. This might occur if the
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/StructuredChatCompletionCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/StructuredChatCompletionCreateParams.kt
index 5809e2e8..fd9c0eef 100644
--- a/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/StructuredChatCompletionCreateParams.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/StructuredChatCompletionCreateParams.kt
@@ -15,7 +15,7 @@ import java.util.Optional
/**
* A wrapper for [ChatCompletionCreateParams] that provides a type-safe [Builder] that can record
* the [responseType] used to derive a JSON schema from an arbitrary class when using the
- * _Structured Outputs_ feature. When a JSON response is received, it is deserialized to am instance
+ * _Structured Outputs_ feature. When a JSON response is received, it is deserialized to an instance
* of that type. See the SDK documentation for more details on _Structured Outputs_.
*
* @param T The type of the class that will be used to derive the JSON schema in the request and to
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt
index 212b9af9..d842e305 100644
--- a/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt
@@ -1098,6 +1098,8 @@ private constructor(
* [StructuredResponseCreateParams.Builder] that will build a
* [StructuredResponseCreateParams] instance when `build()` is called.
*
+ * Use this method or the `text(StructuredResponseTextConfig)` method, but not both.
+ *
* @param responseType A class from which a JSON schema will be derived to define the text
* configuration's format.
* @param localValidation [JsonSchemaLocalValidation.YES] (the default) to validate the JSON
@@ -1114,6 +1116,21 @@ private constructor(
localValidation: JsonSchemaLocalValidation = JsonSchemaLocalValidation.YES,
) = StructuredResponseCreateParams.builder().wrap(responseType, this, localValidation)
+ /**
+ * Sets the text configuration to a [StructuredResponseTextConfig] where the format was set
+ * to a JSON schema derived from the structure of a class. This changes the builder to a
+ * type-safe [StructuredResponseCreateParams.Builder] that will build a
+ * [StructuredResponseCreateParams] instance when `build()` is called.
+ *
+ * Use this method or the `text(Class)` method, but not both.
+ *
+ * @param text A text configuration in which the JSON schema defining the format was derived
+ * from the structure of a class. The `verbosity` parameter can also be set on the text
+ * configuration, if required.
+ */
+ fun text(text: StructuredResponseTextConfig) =
+ StructuredResponseCreateParams.builder().wrap(text, this)
+
/**
* How the model should select which tool (or tools) to use when generating a response. See
* the `tools` parameter to see how to specify which tools the model can call.
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseTextConfig.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseTextConfig.kt
index 03bbec9b..e68b8101 100644
--- a/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseTextConfig.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseTextConfig.kt
@@ -10,6 +10,7 @@ import com.openai.core.Enum
import com.openai.core.ExcludeMissing
import com.openai.core.JsonField
import com.openai.core.JsonMissing
+import com.openai.core.JsonSchemaLocalValidation
import com.openai.core.JsonValue
import com.openai.errors.OpenAIInvalidDataException
import com.openai.models.ResponseFormatJsonObject
@@ -157,6 +158,28 @@ private constructor(
fun format(jsonObject: ResponseFormatJsonObject) =
format(ResponseFormatTextConfig.ofJsonObject(jsonObject))
+ /**
+ * Sets the text configuration's format to a JSON schema derived from the structure of the
+ * given class. This changes the builder to a type-safe
+ * [StructuredResponseTextConfig.Builder] that will build a [StructuredResponseTextConfig]
+ * instance when `build()` is called.
+ *
+ * @param responseType A class from which a JSON schema will be derived to define the text
+ * configuration's format.
+ * @param localValidation [JsonSchemaLocalValidation.YES] (the default) to validate the JSON
+ * schema locally when it is generated by this method to confirm that it adheres to the
+ * requirements and restrictions on JSON schemas imposed by the OpenAI specification; or
+ * [JsonSchemaLocalValidation.NO] to skip local validation and rely only on remote
+ * validation. See the SDK documentation for more details.
+ * @throws IllegalArgumentException If local validation is enabled, but it fails because a
+ * valid JSON schema cannot be derived from the given class.
+ */
+ @JvmOverloads
+ fun format(
+ responseType: Class,
+ localValidation: JsonSchemaLocalValidation = JsonSchemaLocalValidation.YES,
+ ) = StructuredResponseTextConfig.builder().wrap(responseType, this, localValidation)
+
/**
* Constrains the verbosity of the model's response. Lower values will result in more
* concise responses, while higher values will result in more verbose responses. Currently
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseCreateParams.kt
index 80b5a1f2..0568621c 100644
--- a/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseCreateParams.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseCreateParams.kt
@@ -16,7 +16,7 @@ import java.util.Optional
/**
* A wrapper for [ResponseCreateParams] that provides a type-safe [Builder] that can record the
* [responseType] used to derive a JSON schema from an arbitrary class when using the _Structured
- * Outputs_ feature. When a JSON response is received, it is deserialized to am instance of that
+ * Outputs_ feature. When a JSON response is received, it is deserialized to an instance of that
* type. See the SDK documentation for more details on _Structured Outputs_.
*
* @param T The type of the class that will be used to derive the JSON schema in the request and to
@@ -51,6 +51,16 @@ class StructuredResponseCreateParams(
text(responseType, localValidation)
}
+ @JvmSynthetic
+ internal fun wrap(
+ textConfig: StructuredResponseTextConfig,
+ paramsBuilder: ResponseCreateParams.Builder,
+ ) = apply {
+ this.responseType = textConfig.responseType
+ this.paramsBuilder = paramsBuilder
+ text(textConfig)
+ }
+
/** Injects a given `ResponseCreateParams.Builder`. For use only when testing. */
@JvmSynthetic
internal fun inject(paramsBuilder: ResponseCreateParams.Builder) = apply {
@@ -342,6 +352,17 @@ class StructuredResponseCreateParams(
paramsBuilder.text(textConfigFromClass(responseType, localValidation))
}
+ /**
+ * Sets the text configuration to a [StructuredResponseTextConfig] where the format was set
+ * to a JSON schema derived from the structure of a class.
+ *
+ * @see ResponseCreateParams.Builder.text
+ */
+ fun text(text: StructuredResponseTextConfig) = apply {
+ this.responseType = text.responseType
+ paramsBuilder.text(text.rawConfig)
+ }
+
/** @see ResponseCreateParams.Builder.toolChoice */
fun toolChoice(toolChoice: ResponseCreateParams.ToolChoice) = apply {
paramsBuilder.toolChoice(toolChoice)
@@ -665,7 +686,7 @@ class StructuredResponseCreateParams(
}
/**
- * Returns an immutable instance of [ResponseCreateParams].
+ * Returns an immutable instance of [StructuredResponseCreateParams].
*
* Further updates to this [Builder] will not mutate the returned instance.
*
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseTextConfig.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseTextConfig.kt
new file mode 100644
index 00000000..050def9c
--- /dev/null
+++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseTextConfig.kt
@@ -0,0 +1,143 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.openai.models.responses
+
+import com.openai.core.JsonField
+import com.openai.core.JsonSchemaLocalValidation
+import com.openai.core.JsonValue
+import com.openai.core.checkRequired
+import com.openai.core.jsonSchemaFromClass
+import java.util.Objects
+import java.util.Optional
+
+/**
+ * A wrapper for [ResponseTextConfig] that provides a type-safe [Builder] that can record the
+ * [responseType] used to derive a JSON schema from an arbitrary class when using the _Structured
+ * Outputs_ feature. When a JSON response is received, it is deserialized to an instance of that
+ * type. See the SDK documentation for more details on _Structured Outputs_.
+ *
+ * @param T The type of the class that will be used to derive the JSON schema in the request and to
+ * which the JSON response will be deserialized.
+ */
+class StructuredResponseTextConfig
+private constructor(
+ @get:JvmName("responseType") val responseType: Class,
+ /**
+ * The raw, underlying response text configuration wrapped by this structured instance of the
+ * configuration.
+ */
+ @get:JvmName("rawConfig") val rawConfig: ResponseTextConfig,
+) {
+ companion object {
+ /**
+ * Returns a mutable builder for constructing an instance of [StructuredResponseTextConfig].
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [StructuredResponseTextConfig]. */
+ class Builder internal constructor() {
+ private var responseType: Class? = null
+ private var configBuilder = ResponseTextConfig.builder()
+
+ @JvmSynthetic
+ internal fun wrap(
+ responseType: Class,
+ configBuilder: ResponseTextConfig.Builder,
+ localValidation: JsonSchemaLocalValidation,
+ ) = apply {
+ this.responseType = responseType
+ this.configBuilder = configBuilder
+ format(responseType, localValidation)
+ }
+
+ /** Injects a given `ResponseTextConfig.Builder`. For use only when testing. */
+ @JvmSynthetic
+ internal fun inject(configBuilder: ResponseTextConfig.Builder) = apply {
+ this.configBuilder = configBuilder
+ }
+
+ /**
+ * Sets the text configuration's format to a JSON schema derived from the structure of the
+ * given class.
+ *
+ * @see ResponseTextConfig.Builder.format
+ */
+ @JvmOverloads
+ fun format(
+ responseType: Class,
+ localValidation: JsonSchemaLocalValidation = JsonSchemaLocalValidation.YES,
+ ) = apply {
+ this.responseType = responseType
+ configBuilder.format(jsonSchemaFromClass(responseType, localValidation))
+ }
+
+ /** @see ResponseTextConfig.Builder.verbosity */
+ fun verbosity(verbosity: ResponseTextConfig.Verbosity?) = apply {
+ configBuilder.verbosity(verbosity)
+ }
+
+ /** @see ResponseTextConfig.Builder.verbosity */
+ fun verbosity(verbosity: Optional) = apply {
+ configBuilder.verbosity(verbosity)
+ }
+
+ /** @see ResponseTextConfig.Builder.verbosity */
+ fun verbosity(verbosity: JsonField) = apply {
+ configBuilder.verbosity(verbosity)
+ }
+
+ /** @see ResponseTextConfig.Builder.additionalProperties */
+ fun additionalProperties(additionalProperties: Map) = apply {
+ configBuilder.additionalProperties(additionalProperties)
+ }
+
+ /** @see ResponseTextConfig.Builder.putAdditionalProperty */
+ fun putAdditionalProperty(key: String, value: JsonValue) = apply {
+ configBuilder.putAdditionalProperty(key, value)
+ }
+
+ /** @see ResponseTextConfig.Builder.putAllAdditionalProperties */
+ fun putAllAdditionalProperties(additionalProperties: Map) = apply {
+ configBuilder.putAllAdditionalProperties(additionalProperties)
+ }
+
+ /** @see ResponseTextConfig.Builder.removeAdditionalProperty */
+ fun removeAdditionalProperty(key: String) = apply {
+ configBuilder.removeAdditionalProperty(key)
+ }
+
+ /** @see ResponseTextConfig.Builder.removeAllAdditionalProperties */
+ fun removeAllAdditionalProperties(keys: Set) = apply {
+ configBuilder.removeAllAdditionalProperties(keys)
+ }
+
+ /**
+ * Returns an immutable instance of [StructuredResponseTextConfig].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ */
+ fun build(): StructuredResponseTextConfig =
+ StructuredResponseTextConfig(
+ checkRequired("responseType", responseType),
+ configBuilder.build(),
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is StructuredResponseTextConfig<*> &&
+ responseType == other.responseType &&
+ rawConfig == other.rawConfig
+ }
+
+ private val hashCode: Int by lazy { Objects.hash(responseType, rawConfig) }
+
+ override fun hashCode(): Int = hashCode
+
+ override fun toString() =
+ "${javaClass.simpleName}{responseType=$responseType, rawConfig=$rawConfig}"
+}
diff --git a/openai-java-core/src/test/kotlin/com/openai/core/StructuredOutputsTestUtils.kt b/openai-java-core/src/test/kotlin/com/openai/core/StructuredOutputsTestUtils.kt
index 267e17d4..54945fba 100644
--- a/openai-java-core/src/test/kotlin/com/openai/core/StructuredOutputsTestUtils.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/core/StructuredOutputsTestUtils.kt
@@ -321,14 +321,15 @@ internal fun checkAllDelegatorReadFunctionsAreTested(
* @param delegationTestCases The tests cases that identify the names of delegating functions for
* which parameterized unit tests have been defined.
* @param exceptionalTestedFns The names of delegating functions that are tested separately, not as
- * parameterized unit tests. This is usually because they require special handling in the test.
+ * parameterized unit tests. This is usually because they require special handling in the test. If
+ * functions are overloaded, repeat the name for of the function for each overload.
* @param nonDelegatingFns The names of functions that do not perform any delegation and for which
* delegation tests are not required.
*/
internal fun checkAllDelegatorWriteFunctionsAreTested(
delegatorClass: KClass<*>,
delegationTestCases: List,
- exceptionalTestedFns: Set,
+ exceptionalTestedFns: List,
nonDelegatingFns: Set,
) {
// There are exceptional test cases for some functions. Most other functions are part of the
diff --git a/openai-java-core/src/test/kotlin/com/openai/models/chat/completions/StructuredChatCompletionCreateParamsTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/chat/completions/StructuredChatCompletionCreateParamsTest.kt
index 1283732b..14c54bfc 100644
--- a/openai-java-core/src/test/kotlin/com/openai/models/chat/completions/StructuredChatCompletionCreateParamsTest.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/models/chat/completions/StructuredChatCompletionCreateParamsTest.kt
@@ -337,7 +337,7 @@ internal class StructuredChatCompletionCreateParamsTest {
checkAllDelegatorWriteFunctionsAreTested(
builderDelegator::class,
builderDelegationTestCases(),
- exceptionalTestedFns = setOf("responseFormat"),
+ exceptionalTestedFns = listOf("responseFormat"),
nonDelegatingFns = setOf("build", "wrap", "inject"),
)
}
diff --git a/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseCreateParamsTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseCreateParamsTest.kt
index c711cff6..fa9b3ba4 100644
--- a/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseCreateParamsTest.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseCreateParamsTest.kt
@@ -284,8 +284,8 @@ internal class StructuredResponseCreateParamsTest {
// For Structured Outputs, setting `body` would overwrite the previously set `text`
// property, which would break the Structured Outputs behavior.
"body",
- // For Structured Outputs, a new type-safe generic`text` function replaces all existing
- // text functions, as they are mutually incompatible. This function has its own
+ // For Structured Outputs, new type-safe generic `text` functions replace all existing
+ // `text` functions, as they are mutually incompatible. These functions have their own
// dedicated unit tests.
"text",
)
@@ -303,7 +303,7 @@ internal class StructuredResponseCreateParamsTest {
checkAllDelegatorWriteFunctionsAreTested(
builderDelegator::class,
builderDelegationTestCases(),
- exceptionalTestedFns = setOf("text"),
+ exceptionalTestedFns = listOf("text", "text"), // Two overloads. Two custom tests below.
nonDelegatingFns = setOf("build", "wrap", "inject"),
)
}
@@ -315,7 +315,7 @@ internal class StructuredResponseCreateParamsTest {
}
@Test
- fun `delegation of text`() {
+ fun `delegation of text with class`() {
// Special unit test case as the delegator method signature does not match that of the
// delegate method.
val delegatorTestCase = DelegationWriteTestCase("text", X::class.java)
@@ -332,4 +332,23 @@ internal class StructuredResponseCreateParamsTest {
}
verifyNoMoreInteractions(mockBuilderDelegate)
}
+
+ @Test
+ fun `delegation of text with text config`() {
+ // Special unit test case as the delegator method signature does not match that of the
+ // delegate method.
+ val textConfig = StructuredResponseTextConfig.builder().format(X::class.java).build()
+ val delegatorTestCase = DelegationWriteTestCase("text", textConfig)
+ val delegatorMethod = findDelegationMethod(builderDelegator, delegatorTestCase)
+ val mockDelegateTestCase = DelegationWriteTestCase("text", textConfig.rawConfig)
+ val mockDelegateMethod = findDelegationMethod(mockBuilderDelegate, mockDelegateTestCase)
+
+ delegatorMethod.invoke(builderDelegator, delegatorTestCase.inputValues[0])
+
+ // Verify that the corresponding method on the mock delegate was called exactly once.
+ verify(mockBuilderDelegate, times(1)).apply {
+ mockDelegateMethod.invoke(mockBuilderDelegate, mockDelegateTestCase.inputValues[0])
+ }
+ verifyNoMoreInteractions(mockBuilderDelegate)
+ }
}
diff --git a/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseTextConfigTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseTextConfigTest.kt
new file mode 100644
index 00000000..f4f348fb
--- /dev/null
+++ b/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseTextConfigTest.kt
@@ -0,0 +1,130 @@
+package com.openai.models.responses
+
+import com.openai.core.DelegationWriteTestCase
+import com.openai.core.JSON_FIELD
+import com.openai.core.JSON_VALUE
+import com.openai.core.MAP
+import com.openai.core.OPTIONAL
+import com.openai.core.SET
+import com.openai.core.STRING
+import com.openai.core.X
+import com.openai.core.checkAllDelegation
+import com.openai.core.checkAllDelegatorWriteFunctionsAreTested
+import com.openai.core.checkOneDelegationWrite
+import com.openai.core.findDelegationMethod
+import com.openai.core.jsonSchemaFromClass
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Unit tests for the [StructuredResponseTextConfig] class (delegator) and its delegation of most
+ * functions to a wrapped [ResponseTextConfig] (delegate). The tests include confirmation of the
+ * following:
+ * - All functions in the delegator correspond to a function in the delegate and _vice versa_.
+ * - All functions in the delegator call their corresponding function in the delegate and only that
+ * function.
+ * - A unit test exists for all functions.
+ *
+ * There are some exceptions to the above that are handled differently.
+ */
+internal class StructuredResponseTextConfigTest {
+ companion object {
+ private val VERBOSITY = ResponseTextConfig.Verbosity.HIGH
+
+ // The list order follows the declaration order in `ResponseTextConfig.Builder` for easier
+ // maintenance.
+ @JvmStatic
+ private fun builderDelegationTestCases() =
+ listOf(
+ // The `format` function is a special case and is handled separately.
+ DelegationWriteTestCase("verbosity", VERBOSITY),
+ DelegationWriteTestCase("verbosity", OPTIONAL),
+ DelegationWriteTestCase("verbosity", JSON_FIELD),
+ DelegationWriteTestCase("additionalProperties", MAP),
+ DelegationWriteTestCase("putAdditionalProperty", STRING, JSON_VALUE),
+ DelegationWriteTestCase("putAllAdditionalProperties", MAP),
+ DelegationWriteTestCase("removeAdditionalProperty", STRING),
+ DelegationWriteTestCase("removeAllAdditionalProperties", SET),
+ )
+ }
+
+ // New instances of the `mockBuilderDelegate` and `builderDelegator` are required for each test
+ // case (each test case runs in its own instance of the test class).
+ private val mockBuilderDelegate: ResponseTextConfig.Builder =
+ mock(ResponseTextConfig.Builder::class.java)
+ private val builderDelegator =
+ StructuredResponseTextConfig.builder().inject(mockBuilderDelegate)
+
+ @Test
+ fun allBuilderDelegateFunctionsExistInDelegator() {
+ checkAllDelegation(
+ mockBuilderDelegate::class,
+ builderDelegator::class,
+ // ************************************************************************************
+ // NOTE: THIS TEST EXISTS TO ENSURE THAT WHEN NEW FUNCTIONS ARE ADDED MANUALLY OR VIA
+ // CODE GEN TO `ResponseTextConfig.Builder`, THAT THOSE FUNCTIONS ARE _ALSO_ ADDED
+ // _MANUALLY_ TO `StructuredResponseTextConfig.Builder`. FAILURE TO ADD THOSE FUNCTIONS
+ // RESULTS IN _MISSING_ FUNCTIONALITY WHEN USING STRUCTURED OUTPUTS. EXCEPTIONS ADDED TO
+ // THIS LIST ARE PRESENT BY DESIGN, NOT BECAUSE THE FUNCTIONS ARE SIMPLY NOT YET
+ // IMPLEMENTED IN THE DELEGATOR CLASS.
+ //
+ // DO NOT ADD EXCEPTIONS TO THIS LIST SIMPLY BECAUSE TESTS ARE FAILING. THE TESTS ARE
+ // SUPPOSED TO FAIL. ADD THE NEW FUNCTIONS TO `StructuredResponseTextConfig.Builder`
+ // AND ADD A PARAMETERIZED TEST TO `builderDelegationTestCases` (above) TO ENSURE
+ // CORRECT DELEGATION BEHAVIOR.
+ // ************************************************************************************
+
+ // For Structured Outputs, a new type-safe generic `format` function replaces all
+ // existing `format` functions, as they are mutually incompatible. This function has its
+ // own dedicated unit tests.
+ "format",
+ )
+ }
+
+ @Test
+ fun allBuilderDelegatorFunctionsExistInDelegate() {
+ // The delegator implements a different `text` function from those overloads in the delegate
+ // class.
+ checkAllDelegation(builderDelegator::class, mockBuilderDelegate::class, "text")
+ }
+
+ @Test
+ fun allBuilderDelegatorFunctionsAreTested() {
+ checkAllDelegatorWriteFunctionsAreTested(
+ builderDelegator::class,
+ builderDelegationTestCases(),
+ exceptionalTestedFns = listOf("format"),
+ nonDelegatingFns = setOf("build", "wrap", "inject"),
+ )
+ }
+
+ @ParameterizedTest
+ @MethodSource("builderDelegationTestCases")
+ fun `delegation of Builder write functions`(testCase: DelegationWriteTestCase) {
+ checkOneDelegationWrite(builderDelegator, mockBuilderDelegate, testCase)
+ }
+
+ @Test
+ fun `delegation of format from class`() {
+ // Special unit test case as the delegator method signature does not match that of the
+ // delegate method.
+ val delegatorTestCase = DelegationWriteTestCase("format", X::class.java)
+ val delegatorMethod = findDelegationMethod(builderDelegator, delegatorTestCase)
+ val mockDelegateTestCase =
+ DelegationWriteTestCase("format", jsonSchemaFromClass(X::class.java))
+ val mockDelegateMethod = findDelegationMethod(mockBuilderDelegate, mockDelegateTestCase)
+
+ delegatorMethod.invoke(builderDelegator, delegatorTestCase.inputValues[0])
+
+ // Verify that the corresponding method on the mock delegate was called exactly once.
+ verify(mockBuilderDelegate, times(1)).apply {
+ mockDelegateMethod.invoke(mockBuilderDelegate, mockDelegateTestCase.inputValues[0])
+ }
+ verifyNoMoreInteractions(mockBuilderDelegate)
+ }
+}
diff --git a/openai-java-example/src/main/java/com/openai/example/ResponsesStructuredOutputsVerbosityExample.java b/openai-java-example/src/main/java/com/openai/example/ResponsesStructuredOutputsVerbosityExample.java
new file mode 100644
index 00000000..5bdb3328
--- /dev/null
+++ b/openai-java-example/src/main/java/com/openai/example/ResponsesStructuredOutputsVerbosityExample.java
@@ -0,0 +1,77 @@
+package com.openai.example;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import com.openai.client.OpenAIClient;
+import com.openai.client.okhttp.OpenAIOkHttpClient;
+import com.openai.models.ChatModel;
+import com.openai.models.responses.ResponseCreateParams;
+import com.openai.models.responses.ResponseTextConfig;
+import com.openai.models.responses.StructuredResponseCreateParams;
+import java.util.List;
+
+public final class ResponsesStructuredOutputsVerbosityExample {
+
+ public static class Person {
+ @JsonPropertyDescription("The first name and surname of the person.")
+ public String name;
+
+ public int birthYear;
+
+ @JsonPropertyDescription("The year the person died, or 'present' if the person is living.")
+ public String deathYear;
+
+ @Override
+ public String toString() {
+ return name + " (" + birthYear + '-' + deathYear + ')';
+ }
+ }
+
+ public static class Book {
+ public String title;
+
+ public Person author;
+
+ @JsonPropertyDescription("The year in which the book was first published.")
+ public int publicationYear;
+
+ public String genre;
+
+ @JsonIgnore
+ public String isbn;
+
+ @Override
+ public String toString() {
+ return '"' + title + "\" (" + publicationYear + ") [" + genre + "] by " + author;
+ }
+ }
+
+ public static class BookList {
+ public List books;
+ }
+
+ private ResponsesStructuredOutputsVerbosityExample() {}
+
+ public static void main(String[] args) {
+ // Configures using one of:
+ // - The `OPENAI_API_KEY` environment variable
+ // - The `OPENAI_BASE_URL` and `AZURE_OPENAI_KEY` environment variables
+ OpenAIClient client = OpenAIOkHttpClient.fromEnv();
+
+ StructuredResponseCreateParams createParams = ResponseCreateParams.builder()
+ .input("List some famous late twentieth century novels.")
+ .text(ResponseTextConfig.builder()
+ .verbosity(ResponseTextConfig.Verbosity.HIGH)
+ .format(BookList.class)
+ .build())
+ .model(ChatModel.GPT_5)
+ .build();
+
+ client.responses().create(createParams).output().stream()
+ .flatMap(item -> item.message().stream())
+ .flatMap(message -> message.content().stream())
+ .flatMap(content -> content.outputText().stream())
+ .flatMap(bookList -> bookList.books.stream())
+ .forEach(book -> System.out.println(" - " + book));
+ }
+}