From d7cd8d9c34e66328f11d481131eab8a9141299ba Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Sat, 18 Jul 2026 23:02:12 -0300 Subject: [PATCH 1/6] fix: turn diarized duration into optional Signed-off-by: Lucas Christian --- .../transcriptions/TranscriptionDiarized.kt | 25 ++++++++++------ .../TranscriptionDiarizedTest.kt | 30 +++++++++++++++++-- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt index f8a368ef3..4b3ad8395 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt @@ -58,10 +58,10 @@ private constructor( /** * Duration of the input audio in seconds. * - * @throws OpenAIInvalidDataException if the JSON field has an unexpected type or is - * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). */ - fun duration(): Double = duration.getRequired("duration") + fun duration(): Optional = duration.getOptional("duration") /** * Segments of the transcript annotated with timestamps and speaker labels. @@ -149,7 +149,6 @@ private constructor( * * The following fields are required: * ```java - * .duration() * .segments() * .text() * ``` @@ -160,7 +159,7 @@ private constructor( /** A builder for [TranscriptionDiarized]. */ class Builder internal constructor() { - private var duration: JsonField? = null + private var duration: JsonField = JsonMissing.of() private var segments: JsonField>? = null private var task: JsonValue = JsonValue.from("transcribe") private var text: JsonField? = null @@ -178,7 +177,17 @@ private constructor( } /** Duration of the input audio in seconds. */ - fun duration(duration: Double) = duration(JsonField.of(duration)) + fun duration(duration: Double?) = duration(JsonField.ofNullable(duration)) + + /** + * Alias for [Builder.duration]. + * + * This unboxed primitive overload exists for backwards compatibility. + */ + fun duration(duration: Double) = duration(duration as Double?) + + /** Alias for calling [Builder.duration] with `duration.orElse(null)`. */ + fun duration(duration: Optional) = duration(duration.getOrNull()) /** * Sets [Builder.duration] to an arbitrary JSON value. @@ -294,7 +303,6 @@ private constructor( * * The following fields are required: * ```java - * .duration() * .segments() * .text() * ``` @@ -303,7 +311,7 @@ private constructor( */ fun build(): TranscriptionDiarized = TranscriptionDiarized( - checkRequired("duration", duration), + duration, checkRequired("segments", segments).map { it.toImmutable() }, task, checkRequired("text", text), @@ -327,7 +335,6 @@ private constructor( return@apply } - duration() segments().forEach { it.validate() } _task().let { if (it != JsonValue.from("transcribe")) { diff --git a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt index 0d0dfd851..60e3e1994 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt @@ -13,7 +13,6 @@ internal class TranscriptionDiarizedTest { fun create() { val transcriptionDiarized = TranscriptionDiarized.builder() - .duration(0.0) .addSegment( TranscriptionDiarizedSegment.builder() .id("id") @@ -39,7 +38,7 @@ internal class TranscriptionDiarizedTest { ) .build() - assertThat(transcriptionDiarized.duration()).isEqualTo(0.0) + assertThat(transcriptionDiarized.duration()).isEmpty assertThat(transcriptionDiarized.segments()) .containsExactly( TranscriptionDiarizedSegment.builder() @@ -69,6 +68,32 @@ internal class TranscriptionDiarizedTest { ) } + @Test + fun deserializeWithoutDuration() { + val transcriptionDiarized = + jsonMapper() + .readValue( + """ + { + "text": "text", + "segments": [ + { + "id": "id", + "end": 1.0, + "speaker": "speaker", + "start": 0.0, + "text": "text" + } + ] + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + assertThat(transcriptionDiarized.duration()).isEmpty + } + @Test fun roundtrip() { val jsonMapper = jsonMapper() @@ -107,5 +132,6 @@ internal class TranscriptionDiarizedTest { ) assertThat(roundtrippedTranscriptionDiarized).isEqualTo(transcriptionDiarized) + assertThat(roundtrippedTranscriptionDiarized.duration()).contains(0.0) } } From b1703369d81803088a6d80e4fae744cce3836b8b Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Sat, 18 Jul 2026 23:10:44 -0300 Subject: [PATCH 2/6] fix: allow missing task in diarized transcriptions Signed-off-by: Lucas Christian --- .../transcriptions/TranscriptionDiarized.kt | 2 +- .../TranscriptionDiarizedTest.kt | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt index 4b3ad8395..079bbd162 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt @@ -337,7 +337,7 @@ private constructor( segments().forEach { it.validate() } _task().let { - if (it != JsonValue.from("transcribe")) { + if (it != JsonMissing.of() && it != JsonValue.from("transcribe")) { throw OpenAIInvalidDataException("'task' is invalid, received $it") } } diff --git a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt index 60e3e1994..17cb877f8 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt @@ -3,6 +3,7 @@ package com.openai.models.audio.transcriptions import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.JsonValue import com.openai.core.jsonMapper import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -69,7 +70,7 @@ internal class TranscriptionDiarizedTest { } @Test - fun deserializeWithoutDuration() { + fun deserializeWithoutOptionalFields() { val transcriptionDiarized = jsonMapper() .readValue( @@ -78,6 +79,7 @@ internal class TranscriptionDiarizedTest { "text": "text", "segments": [ { + "type": "transcript.text.segment", "id": "id", "end": 1.0, "speaker": "speaker", @@ -92,6 +94,27 @@ internal class TranscriptionDiarizedTest { ) assertThat(transcriptionDiarized.duration()).isEmpty + assertThat(transcriptionDiarized.isValid()).isTrue() + } + + @Test + fun rejectsUnexpectedTask() { + val transcriptionDiarized = + TranscriptionDiarized.builder() + .addSegment( + TranscriptionDiarizedSegment.builder() + .id("id") + .end(1.0) + .speaker("speaker") + .start(0.0) + .text("text") + .build() + ) + .task(JsonValue.from("translate")) + .text("text") + .build() + + assertThat(transcriptionDiarized.isValid()).isFalse() } @Test From 7c8d0d2db55f95394328ee84278523cd384c8fdf Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Sat, 18 Jul 2026 23:33:47 -0300 Subject: [PATCH 3/6] fix: validate optional diarized duration Signed-off-by: Lucas Christian --- .../transcriptions/TranscriptionDiarized.kt | 2 ++ .../TranscriptionCreateResponseTest.kt | 18 ++++++++++++ .../TranscriptionDiarizedTest.kt | 28 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt index 079bbd162..9482a0d9b 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt @@ -335,6 +335,7 @@ private constructor( return@apply } + duration() segments().forEach { it.validate() } _task().let { if (it != JsonMissing.of() && it != JsonValue.from("transcribe")) { @@ -362,6 +363,7 @@ private constructor( @JvmSynthetic internal fun validity(): Int = (if (duration.asKnown().isPresent) 1 else 0) + + (if (segments.asKnown().isPresent) 1 else 0) + (segments.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + task.let { if (it == JsonValue.from("transcribe")) 1 else 0 } + (if (text.asKnown().isPresent) 1 else 0) + diff --git a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt index c83af3003..1193fbf2a 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt @@ -162,6 +162,24 @@ internal class TranscriptionCreateResponseTest { assertThat(roundtrippedTranscriptionCreateResponse).isEqualTo(transcriptionCreateResponse) } + @Test + fun deserializeDiarizedWithEmptySegments() { + val transcriptionCreateResponse = + jsonMapper() + .readValue( + """ + { + "text": "", + "segments": [] + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + assertThat(transcriptionCreateResponse.isDiarized()).isTrue() + } + @Test fun ofVerbose() { val verbose = diff --git a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt index 17cb877f8..931e5039e 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt @@ -97,6 +97,34 @@ internal class TranscriptionDiarizedTest { assertThat(transcriptionDiarized.isValid()).isTrue() } + @Test + fun rejectsInvalidDuration() { + val transcriptionDiarized = + jsonMapper() + .readValue( + """ + { + "duration": "invalid", + "text": "text", + "segments": [ + { + "type": "transcript.text.segment", + "id": "id", + "end": 1.0, + "speaker": "speaker", + "start": 0.0, + "text": "text" + } + ] + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + assertThat(transcriptionDiarized.isValid()).isFalse() + } + @Test fun rejectsUnexpectedTask() { val transcriptionDiarized = From 83a861f0b6b5689c4a8df141430ca8b7935101d4 Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Sat, 18 Jul 2026 23:58:27 -0300 Subject: [PATCH 4/6] fix: preserve transcription response variants with empty segments Signed-off-by: Lucas Christian --- .../transcriptions/TranscriptionVerbose.kt | 1 + .../TranscriptionCreateResponseTest.kt | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionVerbose.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionVerbose.kt index 46980eaf9..18224dcf9 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionVerbose.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionVerbose.kt @@ -370,6 +370,7 @@ private constructor( (if (duration.asKnown().isPresent) 1 else 0) + (if (language.asKnown().isPresent) 1 else 0) + (if (text.asKnown().isPresent) 1 else 0) + + (if (segments.asKnown().isPresent) 1 else 0) + (segments.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + (usage.asKnown().getOrNull()?.validity() ?: 0) + (words.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) diff --git a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt index 1193fbf2a..a7abf6d02 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt @@ -180,6 +180,26 @@ internal class TranscriptionCreateResponseTest { assertThat(transcriptionCreateResponse.isDiarized()).isTrue() } + @Test + fun deserializeVerboseWithEmptySegments() { + val transcriptionCreateResponse = + jsonMapper() + .readValue( + """ + { + "duration": 0.0, + "language": "en", + "text": "", + "segments": [] + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + assertThat(transcriptionCreateResponse.isVerbose()).isTrue() + } + @Test fun ofVerbose() { val verbose = From c92aa805240752803ce9b1fbb1d16c4591067748 Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Tue, 4 Aug 2026 21:08:50 -0300 Subject: [PATCH 5/6] fix: preserve the binary-compatible duration() getter while adding durationOptional() Signed-off-by: Lucas Christian --- .../TranscriptionCreateResponse.kt | 6 +-- .../transcriptions/TranscriptionDiarized.kt | 18 ++++++-- .../TranscriptionCreateResponseTest.kt | 7 ++- .../TranscriptionDiarizedTest.kt | 43 +++++++++++++++++-- 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponse.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponse.kt index a935a9a7d..30ce95c27 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponse.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponse.kt @@ -258,12 +258,12 @@ private constructor( tryDeserialize(node, jacksonTypeRef())?.let { TranscriptionCreateResponse(transcription = it, _json = json) }, - tryDeserialize(node, jacksonTypeRef())?.let { - TranscriptionCreateResponse(diarized = it, _json = json) - }, tryDeserialize(node, jacksonTypeRef())?.let { TranscriptionCreateResponse(verbose = it, _json = json) }, + tryDeserialize(node, jacksonTypeRef())?.let { + TranscriptionCreateResponse(diarized = it, _json = json) + }, ) .filterNotNull() .allMaxBy { it.validity() } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt index 9482a0d9b..dd9b11ef3 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt @@ -58,10 +58,18 @@ private constructor( /** * Duration of the input audio in seconds. * + * @throws OpenAIInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun duration(): Double = duration.getRequired("duration") + + /** + * Returns the duration of the input audio when provided by the server. + * * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). */ - fun duration(): Optional = duration.getOptional("duration") + fun durationOptional(): Optional = duration.getOptional("duration") /** * Segments of the transcript annotated with timestamps and speaker labels. @@ -335,10 +343,10 @@ private constructor( return@apply } - duration() + durationOptional() segments().forEach { it.validate() } _task().let { - if (it != JsonMissing.of() && it != JsonValue.from("transcribe")) { + if (!it.isMissing() && !it.isNull() && it != JsonValue.from("transcribe")) { throw OpenAIInvalidDataException("'task' is invalid, received $it") } } @@ -365,7 +373,9 @@ private constructor( (if (duration.asKnown().isPresent) 1 else 0) + (if (segments.asKnown().isPresent) 1 else 0) + (segments.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + - task.let { if (it == JsonValue.from("transcribe")) 1 else 0 } + + task.let { + if (it.isMissing() || it.isNull() || it == JsonValue.from("transcribe")) 1 else 0 + } + (if (text.asKnown().isPresent) 1 else 0) + (usage.asKnown().getOrNull()?.validity() ?: 0) diff --git a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt index e1c81ecb3..4b933fd39 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource +import org.junit.jupiter.params.provider.ValueSource internal class TranscriptionCreateResponseTest { @@ -164,14 +165,16 @@ internal class TranscriptionCreateResponseTest { assertThat(roundtrippedTranscriptionCreateResponse).isEqualTo(transcriptionCreateResponse) } - @Test - fun deserializeDiarizedWithEmptySegments() { + @ParameterizedTest + @ValueSource(strings = ["", "\"task\": null,", "\"task\": \"transcribe\","]) + fun deserializeDiarizedWithEmptySegments(task: String) { val transcriptionCreateResponse = jsonMapper() .readValue( """ { "text": "", + $task "segments": [] } """ diff --git a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt index 931e5039e..825116158 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt @@ -10,6 +10,12 @@ import org.junit.jupiter.api.Test internal class TranscriptionDiarizedTest { + @Test + fun durationGetterKeepsItsBinaryCompatibleReturnType() { + assertThat(TranscriptionDiarized::class.java.getMethod("duration").returnType) + .isEqualTo(Double::class.javaPrimitiveType) + } + @Test fun create() { val transcriptionDiarized = @@ -39,7 +45,7 @@ internal class TranscriptionDiarizedTest { ) .build() - assertThat(transcriptionDiarized.duration()).isEmpty + assertThat(transcriptionDiarized.durationOptional()).isEmpty assertThat(transcriptionDiarized.segments()) .containsExactly( TranscriptionDiarizedSegment.builder() @@ -93,7 +99,7 @@ internal class TranscriptionDiarizedTest { jacksonTypeRef(), ) - assertThat(transcriptionDiarized.duration()).isEmpty + assertThat(transcriptionDiarized.durationOptional()).isEmpty assertThat(transcriptionDiarized.isValid()).isTrue() } @@ -145,6 +151,36 @@ internal class TranscriptionDiarizedTest { assertThat(transcriptionDiarized.isValid()).isFalse() } + @Test + fun acceptsMissingAndNullTask() { + val json = + """ + { + "text": "text", + %s + "segments": [] + } + """ + .trimIndent() + + val missingTask = + jsonMapper().readValue(json.format(""), jacksonTypeRef()) + val nullTask = + jsonMapper() + .readValue(json.format("\"task\": null,"), jacksonTypeRef()) + val explicitTask = + jsonMapper() + .readValue( + json.format("\"task\": \"transcribe\","), + jacksonTypeRef(), + ) + + assertThat(missingTask.isValid()).isTrue() + assertThat(nullTask.isValid()).isTrue() + assertThat(missingTask.validity()).isEqualTo(explicitTask.validity()) + assertThat(nullTask.validity()).isEqualTo(explicitTask.validity()) + } + @Test fun roundtrip() { val jsonMapper = jsonMapper() @@ -183,6 +219,7 @@ internal class TranscriptionDiarizedTest { ) assertThat(roundtrippedTranscriptionDiarized).isEqualTo(transcriptionDiarized) - assertThat(roundtrippedTranscriptionDiarized.duration()).contains(0.0) + assertThat(roundtrippedTranscriptionDiarized.duration()).isEqualTo(0.0) + assertThat(roundtrippedTranscriptionDiarized.durationOptional()).contains(0.0) } } From 43495ad63502659a72bb6c9978df8ac49a626915 Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Tue, 4 Aug 2026 22:02:51 -0300 Subject: [PATCH 6/6] fix(audio): don't let missing diarized task win response deserialization Signed-off-by: Lucas Christian --- .../transcriptions/TranscriptionDiarized.kt | 8 +++++--- .../TranscriptionCreateResponseTest.kt | 17 +++++++++++++++++ .../transcriptions/TranscriptionDiarizedTest.kt | 4 ++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt index dd9b11ef3..690e88837 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarized.kt @@ -58,6 +58,9 @@ private constructor( /** * Duration of the input audio in seconds. * + * This accessor preserves the existing behavior and throws when the field is missing or null. + * Use [durationOptional] when the server may omit the field. + * * @throws OpenAIInvalidDataException if the JSON field has an unexpected type or is * unexpectedly missing or null (e.g. if the server responded with an unexpected value). */ @@ -345,6 +348,7 @@ private constructor( durationOptional() segments().forEach { it.validate() } + // The API may omit task or return null; when present, it must be "transcribe". _task().let { if (!it.isMissing() && !it.isNull() && it != JsonValue.from("transcribe")) { throw OpenAIInvalidDataException("'task' is invalid, received $it") @@ -373,9 +377,7 @@ private constructor( (if (duration.asKnown().isPresent) 1 else 0) + (if (segments.asKnown().isPresent) 1 else 0) + (segments.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) + - task.let { - if (it.isMissing() || it.isNull() || it == JsonValue.from("transcribe")) 1 else 0 - } + + task.let { if (it == JsonValue.from("transcribe")) 1 else 0 } + (if (text.asKnown().isPresent) 1 else 0) + (usage.asKnown().getOrNull()?.validity() ?: 0) diff --git a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt index 4b933fd39..c2a937fa5 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionCreateResponseTest.kt @@ -185,6 +185,23 @@ internal class TranscriptionCreateResponseTest { assertThat(transcriptionCreateResponse.isDiarized()).isTrue() } + @Test + fun deserializePlainTranscriptionWithoutDiarizedFields() { + val transcriptionCreateResponse = + jsonMapper() + .readValue( + """ + { + "text": "hello" + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + assertThat(transcriptionCreateResponse.isTranscription()).isTrue() + } + @Test fun deserializeVerboseWithEmptySegments() { val transcriptionCreateResponse = diff --git a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt index 825116158..c02e2179b 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/audio/transcriptions/TranscriptionDiarizedTest.kt @@ -177,8 +177,8 @@ internal class TranscriptionDiarizedTest { assertThat(missingTask.isValid()).isTrue() assertThat(nullTask.isValid()).isTrue() - assertThat(missingTask.validity()).isEqualTo(explicitTask.validity()) - assertThat(nullTask.validity()).isEqualTo(explicitTask.validity()) + assertThat(missingTask.validity()).isEqualTo(nullTask.validity()) + assertThat(explicitTask.validity()).isEqualTo(missingTask.validity() + 1) } @Test