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..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 @@ -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), @@ -330,7 +338,7 @@ private constructor( duration() 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") } } @@ -355,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/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 c83af3003..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 @@ -162,6 +162,44 @@ internal class TranscriptionCreateResponseTest { assertThat(roundtrippedTranscriptionCreateResponse).isEqualTo(transcriptionCreateResponse) } + @Test + fun deserializeDiarizedWithEmptySegments() { + val transcriptionCreateResponse = + jsonMapper() + .readValue( + """ + { + "text": "", + "segments": [] + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + 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 = 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..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 @@ -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 @@ -13,7 +14,6 @@ internal class TranscriptionDiarizedTest { fun create() { val transcriptionDiarized = TranscriptionDiarized.builder() - .duration(0.0) .addSegment( TranscriptionDiarizedSegment.builder() .id("id") @@ -39,7 +39,7 @@ internal class TranscriptionDiarizedTest { ) .build() - assertThat(transcriptionDiarized.duration()).isEqualTo(0.0) + assertThat(transcriptionDiarized.duration()).isEmpty assertThat(transcriptionDiarized.segments()) .containsExactly( TranscriptionDiarizedSegment.builder() @@ -69,6 +69,82 @@ internal class TranscriptionDiarizedTest { ) } + @Test + fun deserializeWithoutOptionalFields() { + val transcriptionDiarized = + jsonMapper() + .readValue( + """ + { + "text": "text", + "segments": [ + { + "type": "transcript.text.segment", + "id": "id", + "end": 1.0, + "speaker": "speaker", + "start": 0.0, + "text": "text" + } + ] + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + assertThat(transcriptionDiarized.duration()).isEmpty + 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 = + 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 fun roundtrip() { val jsonMapper = jsonMapper() @@ -107,5 +183,6 @@ internal class TranscriptionDiarizedTest { ) assertThat(roundtrippedTranscriptionDiarized).isEqualTo(transcriptionDiarized) + assertThat(roundtrippedTranscriptionDiarized.duration()).contains(0.0) } }