diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d87cca6..5f3ea9b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.11.5" + ".": "0.11.6" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db45e1..d4b8e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.11.6 (2025-01-17) + +Full Changelog: [v0.11.5...v0.11.6](https://github.com/openai/openai-java/compare/v0.11.5...v0.11.6) + +### Chores + +* **internal:** move `StreamResponse` method ([#131](https://github.com/openai/openai-java/issues/131)) ([5888e39](https://github.com/openai/openai-java/commit/5888e398597b68a520b4bb3953d96403023331fd)) +* **internal:** refactor streaming implementation ([#129](https://github.com/openai/openai-java/issues/129)) ([d2831ec](https://github.com/openai/openai-java/commit/d2831ec587d37a2c2d51332f5f2f62ba3dd1181d)) + ## 0.11.5 (2025-01-16) Full Changelog: [v0.11.4...v0.11.5](https://github.com/openai/openai-java/compare/v0.11.4...v0.11.5) diff --git a/README.md b/README.md index f960fb5..443cc82 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.openai/openai-java)](https://central.sonatype.com/artifact/com.openai/openai-java/0.11.5) +[![Maven Central](https://img.shields.io/maven-central/v/com.openai/openai-java)](https://central.sonatype.com/artifact/com.openai/openai-java/0.11.6) @@ -30,7 +30,7 @@ The REST API documentation can be foundĀ on [platform.openai.com](https://platfo ```kotlin -implementation("com.openai:openai-java:0.11.5") +implementation("com.openai:openai-java:0.11.6") ``` #### Maven @@ -39,7 +39,7 @@ implementation("com.openai:openai-java:0.11.5") com.openai openai-java - 0.11.5 + 0.11.6 ``` diff --git a/build.gradle.kts b/build.gradle.kts index e06eb10..071e5e9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.openai" - version = "0.11.5" // x-release-please-version + version = "0.11.6" // x-release-please-version } subprojects { diff --git a/openai-java-core/src/main/kotlin/com/openai/core/handlers/SseHandler.kt b/openai-java-core/src/main/kotlin/com/openai/core/handlers/SseHandler.kt index f04c2cf..a5dd257 100644 --- a/openai-java-core/src/main/kotlin/com/openai/core/handlers/SseHandler.kt +++ b/openai-java-core/src/main/kotlin/com/openai/core/handlers/SseHandler.kt @@ -6,69 +6,40 @@ import com.fasterxml.jackson.databind.json.JsonMapper import com.openai.core.JsonValue import com.openai.core.http.HttpResponse import com.openai.core.http.HttpResponse.Handler -import com.openai.core.http.PhantomReachableClosingStreamResponse import com.openai.core.http.SseMessage import com.openai.core.http.StreamResponse +import com.openai.core.http.map import com.openai.errors.OpenAIException -import java.util.stream.Stream import kotlin.jvm.optionals.getOrNull -import kotlin.streams.asStream @JvmSynthetic internal fun sseHandler(jsonMapper: JsonMapper): Handler> = - object : Handler> { - - override fun handle(response: HttpResponse): StreamResponse { - val reader = response.body().bufferedReader() - val sequence = - sequence { - reader.useLines { lines -> - val state = SseState(jsonMapper) - var done = false - for (line in lines) { - // Stop emitting messages, but iterate through the full stream. - if (done) { - continue - } - val message = state.decode(line) ?: continue - - if (message.data.startsWith("[DONE]")) { - // In this case we don't break because we still want to iterate - // through the full stream. - done = true - continue - } - - val error = - message.json().asObject().getOrNull()?.get("error") - if (error != null) { - val errorMessage = - error.asString().getOrNull() - ?: error - .asObject() - .getOrNull() - ?.get("message") - ?.asString() - ?.getOrNull() - ?: "An error occurred during streaming" - throw OpenAIException(errorMessage) - } - yield(message) - } - } - } - .constrainOnce() - - return PhantomReachableClosingStreamResponse( - object : StreamResponse { - override fun stream(): Stream = sequence.asStream() - - override fun close() { - reader.close() - response.close() - } - } - ) + streamHandler { lines -> + val state = SseState(jsonMapper) + var done = false + for (line in lines) { + // Stop emitting messages, but iterate through the full stream. + if (done) { + continue + } + val message = state.decode(line) ?: continue + + if (message.data.startsWith("[DONE]")) { + // In this case we don't break because we still want to iterate through the full + // stream. + done = true + continue + } + + val error = message.json().asObject().getOrNull()?.get("error") + if (error != null) { + val errorMessage = + error.asString().getOrNull() + ?: error.asObject().getOrNull()?.get("message")?.asString()?.getOrNull() + ?: "An error occurred during streaming" + throw OpenAIException(errorMessage) + } + yield(message) } } @@ -158,11 +129,3 @@ internal inline fun Handler>.mapJson(): } } } - -@JvmSynthetic -internal fun StreamResponse.map(transform: (T) -> R): StreamResponse = - object : StreamResponse { - override fun stream(): Stream = this@map.stream().map(transform) - - override fun close() = this@map.close() - } diff --git a/openai-java-core/src/main/kotlin/com/openai/core/handlers/StreamHandler.kt b/openai-java-core/src/main/kotlin/com/openai/core/handlers/StreamHandler.kt new file mode 100644 index 0000000..27040d5 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/core/handlers/StreamHandler.kt @@ -0,0 +1,32 @@ +@file:JvmName("StreamHandler") + +package com.openai.core.handlers + +import com.openai.core.http.HttpResponse +import com.openai.core.http.HttpResponse.Handler +import com.openai.core.http.PhantomReachableClosingStreamResponse +import com.openai.core.http.StreamResponse +import java.util.stream.Stream +import kotlin.streams.asStream + +@JvmSynthetic +internal fun streamHandler( + block: suspend SequenceScope.(lines: Sequence) -> Unit +): Handler> = + object : Handler> { + override fun handle(response: HttpResponse): StreamResponse { + val reader = response.body().bufferedReader() + val sequence = sequence { reader.useLines { block(it) } }.constrainOnce() + + return PhantomReachableClosingStreamResponse( + object : StreamResponse { + override fun stream(): Stream = sequence.asStream() + + override fun close() { + reader.close() + response.close() + } + } + ) + } + } diff --git a/openai-java-core/src/main/kotlin/com/openai/core/http/StreamResponse.kt b/openai-java-core/src/main/kotlin/com/openai/core/http/StreamResponse.kt index 1a41bb8..c7c5e63 100644 --- a/openai-java-core/src/main/kotlin/com/openai/core/http/StreamResponse.kt +++ b/openai-java-core/src/main/kotlin/com/openai/core/http/StreamResponse.kt @@ -6,3 +6,11 @@ interface StreamResponse : AutoCloseable { fun stream(): Stream } + +@JvmSynthetic +internal fun StreamResponse.map(transform: (T) -> R): StreamResponse = + object : StreamResponse { + override fun stream(): Stream = this@map.stream().map(transform) + + override fun close() = this@map.close() + } diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/CompletionServiceAsyncImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/CompletionServiceAsyncImpl.kt index 53c0e8f..060946d 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/async/CompletionServiceAsyncImpl.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/async/CompletionServiceAsyncImpl.kt @@ -9,7 +9,6 @@ import com.openai.core.JsonValue import com.openai.core.RequestOptions import com.openai.core.handlers.errorHandler import com.openai.core.handlers.jsonHandler -import com.openai.core.handlers.map import com.openai.core.handlers.mapJson import com.openai.core.handlers.sseHandler import com.openai.core.handlers.withErrorHandler @@ -18,6 +17,7 @@ import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.HttpResponse.Handler import com.openai.core.http.StreamResponse +import com.openai.core.http.map import com.openai.core.http.toAsync import com.openai.core.json import com.openai.errors.OpenAIError diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/beta/ThreadServiceAsyncImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/beta/ThreadServiceAsyncImpl.kt index 939b47f..95d8a2f 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/async/beta/ThreadServiceAsyncImpl.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/async/beta/ThreadServiceAsyncImpl.kt @@ -7,7 +7,6 @@ import com.openai.core.JsonValue import com.openai.core.RequestOptions import com.openai.core.handlers.errorHandler import com.openai.core.handlers.jsonHandler -import com.openai.core.handlers.map import com.openai.core.handlers.mapJson import com.openai.core.handlers.sseHandler import com.openai.core.handlers.withErrorHandler @@ -16,6 +15,7 @@ import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.HttpResponse.Handler import com.openai.core.http.StreamResponse +import com.openai.core.http.map import com.openai.core.http.toAsync import com.openai.core.json import com.openai.errors.OpenAIError diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/beta/threads/RunServiceAsyncImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/beta/threads/RunServiceAsyncImpl.kt index 2fc1b4d..65a1cb9 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/async/beta/threads/RunServiceAsyncImpl.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/async/beta/threads/RunServiceAsyncImpl.kt @@ -7,7 +7,6 @@ import com.openai.core.JsonValue import com.openai.core.RequestOptions import com.openai.core.handlers.errorHandler import com.openai.core.handlers.jsonHandler -import com.openai.core.handlers.map import com.openai.core.handlers.mapJson import com.openai.core.handlers.sseHandler import com.openai.core.handlers.withErrorHandler @@ -16,6 +15,7 @@ import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.HttpResponse.Handler import com.openai.core.http.StreamResponse +import com.openai.core.http.map import com.openai.core.http.toAsync import com.openai.core.json import com.openai.errors.OpenAIError diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/chat/CompletionServiceAsyncImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/chat/CompletionServiceAsyncImpl.kt index 6482c72..7114e93 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/async/chat/CompletionServiceAsyncImpl.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/async/chat/CompletionServiceAsyncImpl.kt @@ -9,7 +9,6 @@ import com.openai.core.JsonValue import com.openai.core.RequestOptions import com.openai.core.handlers.errorHandler import com.openai.core.handlers.jsonHandler -import com.openai.core.handlers.map import com.openai.core.handlers.mapJson import com.openai.core.handlers.sseHandler import com.openai.core.handlers.withErrorHandler @@ -18,6 +17,7 @@ import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.HttpResponse.Handler import com.openai.core.http.StreamResponse +import com.openai.core.http.map import com.openai.core.http.toAsync import com.openai.core.json import com.openai.errors.OpenAIError diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/CompletionServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/CompletionServiceImpl.kt index a839a43..77edafc 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/CompletionServiceImpl.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/CompletionServiceImpl.kt @@ -9,7 +9,6 @@ import com.openai.core.JsonValue import com.openai.core.RequestOptions import com.openai.core.handlers.errorHandler import com.openai.core.handlers.jsonHandler -import com.openai.core.handlers.map import com.openai.core.handlers.mapJson import com.openai.core.handlers.sseHandler import com.openai.core.handlers.withErrorHandler @@ -17,6 +16,7 @@ import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.HttpResponse.Handler import com.openai.core.http.StreamResponse +import com.openai.core.http.map import com.openai.core.json import com.openai.errors.OpenAIError import com.openai.models.Completion diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/ThreadServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/ThreadServiceImpl.kt index d081660..533391e 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/ThreadServiceImpl.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/ThreadServiceImpl.kt @@ -7,7 +7,6 @@ import com.openai.core.JsonValue import com.openai.core.RequestOptions import com.openai.core.handlers.errorHandler import com.openai.core.handlers.jsonHandler -import com.openai.core.handlers.map import com.openai.core.handlers.mapJson import com.openai.core.handlers.sseHandler import com.openai.core.handlers.withErrorHandler @@ -15,6 +14,7 @@ import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.HttpResponse.Handler import com.openai.core.http.StreamResponse +import com.openai.core.http.map import com.openai.core.json import com.openai.errors.OpenAIError import com.openai.models.AssistantStreamEvent diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/threads/RunServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/threads/RunServiceImpl.kt index d29f20a..c0fb5e3 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/threads/RunServiceImpl.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/threads/RunServiceImpl.kt @@ -7,7 +7,6 @@ import com.openai.core.JsonValue import com.openai.core.RequestOptions import com.openai.core.handlers.errorHandler import com.openai.core.handlers.jsonHandler -import com.openai.core.handlers.map import com.openai.core.handlers.mapJson import com.openai.core.handlers.sseHandler import com.openai.core.handlers.withErrorHandler @@ -15,6 +14,7 @@ import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.HttpResponse.Handler import com.openai.core.http.StreamResponse +import com.openai.core.http.map import com.openai.core.json import com.openai.errors.OpenAIError import com.openai.models.AssistantStreamEvent diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/chat/CompletionServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/chat/CompletionServiceImpl.kt index 1653947..7a362b8 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/chat/CompletionServiceImpl.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/chat/CompletionServiceImpl.kt @@ -9,7 +9,6 @@ import com.openai.core.JsonValue import com.openai.core.RequestOptions import com.openai.core.handlers.errorHandler import com.openai.core.handlers.jsonHandler -import com.openai.core.handlers.map import com.openai.core.handlers.mapJson import com.openai.core.handlers.sseHandler import com.openai.core.handlers.withErrorHandler @@ -17,6 +16,7 @@ import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.HttpResponse.Handler import com.openai.core.http.StreamResponse +import com.openai.core.http.map import com.openai.core.json import com.openai.errors.OpenAIError import com.openai.models.ChatCompletion