Skip to content

Commit d8b19b7

Browse files
release: 2.12.0 (#518)
* codegen metadata * chore(ci): only run for pushes and fork pull requests * fix(ci): correct conditional * fix(client): don't close client on `withOptions` usage when original is gc'd * refactor(internal): minor `ClientOptionsTest` change * feat: support new schema constraints for structured outputs (#520) * structured-outputs: support new OpenAI schema constraints * structured-outputs: JSON schema constraints doc link * docs: fix readme typoe (#521) * release: 2.12.0 --------- Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com> Co-authored-by: D Gardner <[email protected]>
1 parent a79fbac commit d8b19b7

File tree

13 files changed

+373
-48
lines changed

13 files changed

+373
-48
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
timeout-minutes: 10
1818
name: lint
1919
runs-on: ${{ github.repository == 'stainless-sdks/openai-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
20+
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
2021

2122
steps:
2223
- uses: actions/checkout@v4
@@ -39,6 +40,7 @@ jobs:
3940
timeout-minutes: 10
4041
name: test
4142
runs-on: ${{ github.repository == 'stainless-sdks/openai-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
43+
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
4244
steps:
4345
- uses: actions/checkout@v4
4446

@@ -60,7 +62,7 @@ jobs:
6062
timeout-minutes: 10
6163
name: examples
6264
runs-on: ${{ github.repository == 'stainless-sdks/openai-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
63-
if: github.repository == 'openai/openai-java'
65+
if: github.repository == 'openai/openai-java' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
6466

6567
steps:
6668
- uses: actions/checkout@v4

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "2.11.0"
2+
".": "2.12.0"
33
}

.stats.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 88
22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-a473967d1766dc155994d932fbc4a5bcbd1c140a37c20d0a4065e1bf0640536d.yml
33
openapi_spec_hash: 67cdc62b0d6c8b1de29b7dc54b265749
4-
config_hash: 05c7d4a6f4d5983fe9550457114b47dd
4+
config_hash: 7b53f96f897ca1b3407a5341a6f820db

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
# Changelog
22

3+
## 2.12.0 (2025-07-01)
4+
5+
Full Changelog: [v2.11.0...v2.12.0](https://github.com/openai/openai-java/compare/v2.11.0...v2.12.0)
6+
7+
### Features
8+
9+
* support new schema constraints for structured outputs ([#520](https://github.com/openai/openai-java/issues/520)) ([5c41ac5](https://github.com/openai/openai-java/commit/5c41ac5f1c8ed986e887e06adc3da73ec7e6b5e5))
10+
11+
12+
### Bug Fixes
13+
14+
* **ci:** correct conditional ([a8c7a16](https://github.com/openai/openai-java/commit/a8c7a16184376d0dcfc8dd6f954c303c02888b40))
15+
* **client:** don't close client on `withOptions` usage when original is gc'd ([e0890e3](https://github.com/openai/openai-java/commit/e0890e398aef9a8b6c14aba23b0b2a3d802ced8f))
16+
17+
18+
### Chores
19+
20+
* **ci:** only run for pushes and fork pull requests ([8dc0179](https://github.com/openai/openai-java/commit/8dc0179eeb96772c73e035668904201a86e245c5))
21+
22+
23+
### Documentation
24+
25+
* fix readme typoe ([#521](https://github.com/openai/openai-java/issues/521)) ([eb83a83](https://github.com/openai/openai-java/commit/eb83a83d5b32376497dbad8b74b15347a69ce1dd))
26+
27+
28+
### Refactors
29+
30+
* **internal:** minor `ClientOptionsTest` change ([a7379a2](https://github.com/openai/openai-java/commit/a7379a239d4f93fe631224df81787fdec08d14bd))
31+
332
## 2.11.0 (2025-06-27)
433

534
Full Changelog: [v2.10.0...v2.11.0](https://github.com/openai/openai-java/compare/v2.10.0...v2.11.0)

README.md

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
<!-- x-release-please-start-version -->
44

5-
[![Maven Central](https://img.shields.io/maven-central/v/com.openai/openai-java)](https://central.sonatype.com/artifact/com.openai/openai-java/2.11.0)
6-
[![javadoc](https://javadoc.io/badge2/com.openai/openai-java/2.11.0/javadoc.svg)](https://javadoc.io/doc/com.openai/openai-java/2.11.0)
5+
[![Maven Central](https://img.shields.io/maven-central/v/com.openai/openai-java)](https://central.sonatype.com/artifact/com.openai/openai-java/2.12.0)
6+
[![javadoc](https://javadoc.io/badge2/com.openai/openai-java/2.12.0/javadoc.svg)](https://javadoc.io/doc/com.openai/openai-java/2.12.0)
77

88
<!-- x-release-please-end -->
99

1010
The OpenAI Java SDK provides convenient access to the [OpenAI REST API](https://platform.openai.com/docs) from applications written in Java.
1111

1212
<!-- x-release-please-start-version -->
1313

14-
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/2.11.0).
14+
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/2.12.0).
1515

1616
<!-- x-release-please-end -->
1717

@@ -22,7 +22,7 @@ The REST API documentation can be found on [platform.openai.com](https://platfor
2222
### Gradle
2323

2424
```kotlin
25-
implementation("com.openai:openai-java:2.11.0")
25+
implementation("com.openai:openai-java:2.12.0")
2626
```
2727

2828
### Maven
@@ -31,7 +31,7 @@ implementation("com.openai:openai-java:2.11.0")
3131
<dependency>
3232
<groupId>com.openai</groupId>
3333
<artifactId>openai-java</artifactId>
34-
<version>2.11.0</version>
34+
<version>2.12.0</version>
3535
</dependency>
3636
```
3737

@@ -580,6 +580,53 @@ If you use `@JsonProperty(required = false)`, the `false` value will be ignored.
580580
must mark all properties as _required_, so the schema generated from your Java classes will respect
581581
that restriction and ignore any annotation that would violate it.
582582

583+
You can also use [OpenAPI Swagger 2](https://swagger.io/specification/v2/)
584+
[`@Schema`](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations#schema) and
585+
[`@ArraySchema`](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations#arrayschema)
586+
annotations. These allow type-specific constraints to be added to your schema properties. You can
587+
learn more about the supported constraints in the OpenAI documentation on
588+
[Supported properties](https://platform.openai.com/docs/guides/structured-outputs#supported-properties).
589+
590+
```java
591+
import io.swagger.v3.oas.annotations.media.Schema;
592+
import io.swagger.v3.oas.annotations.media.ArraySchema;
593+
594+
class Article {
595+
@ArraySchema(minItems = 1, maxItems = 10)
596+
public List<String> authors;
597+
598+
@Schema(pattern = "^[A-Za-z ]+$")
599+
public String title;
600+
601+
@Schema(format = "date")
602+
public String publicationDate;
603+
604+
@Schema(minimum = "1")
605+
public int pageCount;
606+
}
607+
```
608+
609+
Local validation will check that you have not used any unsupported constraint keywords. However, the
610+
values of the constraints are _not_ validated locally. For example, if you use a value for the
611+
`"format"` constraint of a string property that is not in the list of
612+
[supported format names](https://platform.openai.com/docs/guides/structured-outputs#supported-properties),
613+
then local validation will pass, but the AI model may report an error.
614+
615+
If you use both Jackson and Swagger annotations to set the same schema field, the Jackson annotation
616+
will take precedence. In the following example, the description of `myProperty` will be set to
617+
"Jackson description"; "Swagger description" will be ignored:
618+
619+
```java
620+
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
621+
import io.swagger.v3.oas.annotations.media.Schema;
622+
623+
class MyObject {
624+
@Schema(description = "Swagger description")
625+
@JsonPropertyDescription("Jackson description")
626+
public String myProperty;
627+
}
628+
```
629+
583630
## Function calling with JSON schemas
584631

585632
OpenAI [Function Calling](https://platform.openai.com/docs/guides/function-calling?api-mode=chat)

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ repositories {
88

99
allprojects {
1010
group = "com.openai"
11-
version = "2.11.0" // x-release-please-version
11+
version = "2.12.0" // x-release-please-version
1212
}
1313

1414
subprojects {

openai-java-core/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies {
2020
api("com.fasterxml.jackson.core:jackson-core:2.18.2")
2121
api("com.fasterxml.jackson.core:jackson-databind:2.18.2")
2222
api("com.google.errorprone:error_prone_annotations:2.33.0")
23+
api("io.swagger.core.v3:swagger-annotations:2.2.31")
2324

2425
implementation("com.fasterxml.jackson.core:jackson-annotations:2.18.2")
2526
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2")
@@ -29,6 +30,7 @@ dependencies {
2930
implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")
3031
implementation("com.github.victools:jsonschema-generator:4.38.0")
3132
implementation("com.github.victools:jsonschema-module-jackson:4.38.0")
33+
implementation("com.github.victools:jsonschema-module-swagger-2:4.38.0")
3234

3335
testImplementation(kotlin("test"))
3436
testImplementation(project(":openai-java-client-okhttp"))

openai-java-core/src/main/kotlin/com/openai/core/ClientOptions.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ private constructor(
115115
webhookSecret = clientOptions.webhookSecret
116116
}
117117

118-
fun httpClient(httpClient: HttpClient) = apply { this.httpClient = httpClient }
118+
fun httpClient(httpClient: HttpClient) = apply {
119+
this.httpClient = PhantomReachableClosingHttpClient(httpClient)
120+
}
119121

120122
fun checkJacksonVersionCompatibility(checkJacksonVersionCompatibility: Boolean) = apply {
121123
this.checkJacksonVersionCompatibility = checkJacksonVersionCompatibility
@@ -335,13 +337,11 @@ private constructor(
335337

336338
return ClientOptions(
337339
httpClient,
338-
PhantomReachableClosingHttpClient(
339-
RetryingHttpClient.builder()
340-
.httpClient(httpClient)
341-
.clock(clock)
342-
.maxRetries(maxRetries)
343-
.build()
344-
),
340+
RetryingHttpClient.builder()
341+
.httpClient(httpClient)
342+
.clock(clock)
343+
.maxRetries(maxRetries)
344+
.build(),
345345
checkJacksonVersionCompatibility,
346346
jsonMapper,
347347
streamHandlerExecutor

openai-java-core/src/main/kotlin/com/openai/core/JsonSchemaValidator.kt

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ internal class JsonSchemaValidator private constructor() {
3838
private const val ENUM = "enum"
3939
private const val ADDITIONAL_PROPS = "additionalProperties"
4040

41+
private const val PATTERN = "pattern"
42+
private const val FORMAT = "format"
43+
private const val MULTIPLE_OF = "multipleOf"
44+
private const val MINIMUM = "minimum"
45+
private const val EXCLUSIVE_MINIMUM = "exclusiveMinimum"
46+
private const val MAXIMUM = "maximum"
47+
private const val EXCLUSIVE_MAXIMUM = "exclusiveMaximum"
48+
private const val MIN_ITEMS = "minItems"
49+
private const val MAX_ITEMS = "maxItems"
50+
4151
// The names of the supported schema data types.
4252
//
4353
// JSON Schema does not define an "integer" type, only a "number" type, but it allows any
@@ -50,21 +60,8 @@ internal class JsonSchemaValidator private constructor() {
5060
private const val TYPE_INTEGER = "integer"
5161
private const val TYPE_NULL = "null"
5262

53-
// The validator checks that unsupported type-specific keywords are not present in a
54-
// property node. The OpenAI API specification states:
55-
//
56-
// "Notable keywords not supported include:
57-
//
58-
// - For strings: `minLength`, `maxLength`, `pattern`, `format`
59-
// - For numbers: `minimum`, `maximum`, `multipleOf`
60-
// - For objects: `patternProperties`, `unevaluatedProperties`, `propertyNames`,
61-
// `minProperties`, `maxProperties`
62-
// - For arrays: `unevaluatedItems`, `contains`, `minContains`, `maxContains`, `minItems`,
63-
// `maxItems`, `uniqueItems`"
64-
//
65-
// As that list is not exhaustive, and no keywords are explicitly named as supported, this
66-
// validation allows _no_ type-specific keywords. The following sets define the allowed
67-
// keywords in different contexts and all others are rejected.
63+
// The following sets define the allowed keywords in different contexts and all others are
64+
// rejected.
6865

6966
/**
7067
* The set of allowed keywords in the root schema only, not including the keywords that are
@@ -94,14 +91,40 @@ internal class JsonSchemaValidator private constructor() {
9491
* The set of allowed keywords when defining sub-schemas when the `"type"` field is set to
9592
* `"array"`.
9693
*/
97-
private val ALLOWED_KEYWORDS_ARRAY_SUB_SCHEMA = setOf(TYPE, TITLE, DESC, ITEMS)
94+
private val ALLOWED_KEYWORDS_ARRAY_SUB_SCHEMA =
95+
setOf(TYPE, TITLE, DESC, ITEMS, MIN_ITEMS, MAX_ITEMS)
9896

9997
/**
10098
* The set of allowed keywords when defining sub-schemas when the `"type"` field is set to
101-
* `"boolean"`, `"integer"`, `"number"`, or `"string"`.
99+
* `"boolean"`, or any other simple type not handled separately.
102100
*/
103101
private val ALLOWED_KEYWORDS_SIMPLE_SUB_SCHEMA = setOf(TYPE, TITLE, DESC, ENUM, CONST)
104102

103+
/**
104+
* The set of allowed keywords when defining sub-schemas when the `"type"` field is set to
105+
* `"string"`.
106+
*/
107+
private val ALLOWED_KEYWORDS_STRING_SUB_SCHEMA =
108+
setOf(TYPE, TITLE, DESC, ENUM, CONST, PATTERN, FORMAT)
109+
110+
/**
111+
* The set of allowed keywords when defining sub-schemas when the `"type"` field is set to
112+
* `"integer"` or `"number"`.
113+
*/
114+
private val ALLOWED_KEYWORDS_NUMBER_SUB_SCHEMA =
115+
setOf(
116+
TYPE,
117+
TITLE,
118+
DESC,
119+
ENUM,
120+
CONST,
121+
MINIMUM,
122+
EXCLUSIVE_MINIMUM,
123+
MAXIMUM,
124+
EXCLUSIVE_MAXIMUM,
125+
MULTIPLE_OF,
126+
)
127+
105128
/**
106129
* The maximum total length of all strings used in the schema for property names, definition
107130
* names, enum values and const values. The OpenAI specification states:
@@ -474,7 +497,15 @@ internal class JsonSchemaValidator private constructor() {
474497
* value of the non-`"null"` type name. For example `"string"`, or `"number"`.
475498
*/
476499
private fun validateSimpleSchema(schema: JsonNode, typeName: String, path: String, depth: Int) {
477-
validateKeywords(schema, ALLOWED_KEYWORDS_SIMPLE_SUB_SCHEMA, path, depth)
500+
val allowedKeywords =
501+
when (typeName) {
502+
TYPE_NUMBER,
503+
TYPE_INTEGER -> ALLOWED_KEYWORDS_NUMBER_SUB_SCHEMA
504+
TYPE_STRING -> ALLOWED_KEYWORDS_STRING_SUB_SCHEMA
505+
else -> ALLOWED_KEYWORDS_SIMPLE_SUB_SCHEMA
506+
}
507+
508+
validateKeywords(schema, allowedKeywords, path, depth)
478509

479510
val enumField = schema.get(ENUM)
480511

@@ -600,7 +631,7 @@ internal class JsonSchemaValidator private constructor() {
600631

601632
/**
602633
* Validates that the names of all fields in the given schema node are present in a collection
603-
* of allowed keywords.
634+
* of allowed keywords. The values associated with the keywords are _not_ validated.
604635
*
605636
* @param depth The nesting depth of the [schema] node. If this depth is zero, an additional set
606637
* of allowed keywords will be included automatically for keywords that are allowed to appear

openai-java-core/src/main/kotlin/com/openai/core/StructuredOutputs.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.github.victools.jsonschema.generator.OptionPreset
1212
import com.github.victools.jsonschema.generator.SchemaGenerator
1313
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder
1414
import com.github.victools.jsonschema.module.jackson.JacksonModule
15+
import com.github.victools.jsonschema.module.swagger2.Swagger2Module
1516
import com.openai.errors.OpenAIInvalidDataException
1617
import com.openai.models.FunctionDefinition
1718
import com.openai.models.ResponseFormatJsonSchema
@@ -201,6 +202,9 @@ internal fun extractSchema(type: Class<*>): ObjectNode {
201202
// Use `JacksonModule` to support the use of Jackson annotations to set property and
202203
// class names and descriptions and to mark fields with `@JsonIgnore`.
203204
.with(JacksonModule())
205+
// Use `Swagger2Module` to support OpenAPI Swagger 2 `@Schema` annotations to set
206+
// property constraints (e.g., a `"pattern"` constraint for a string property).
207+
.with(Swagger2Module())
204208

205209
configBuilder
206210
.forFields()

0 commit comments

Comments
 (0)