Repository: softwaremill/sttp-openai
Pull request #293 (2025-03-11 17:32:07)

Handle nullable finish_reason in the streamed chunk completion choice
`finishReason` can be null, some OpenAI protocol implementation like LiteLLM don't always return it. 

Similar to https://github.com/softwaremill/sttp-openai/pull/243

grzegorz-bielski: Hello @adamw, please take a look.

Patch:
diff --git a/core/src/main/scala/sttp/openai/requests/completions/chat/ChatChunkRequestResponseData.scala b/core/src/main/scala/sttp/openai/requests/completions/chat/ChatChunkRequestResponseData.scala
index 8af8332..096d177 100644
--- a/core/src/main/scala/sttp/openai/requests/completions/chat/ChatChunkRequestResponseData.scala
+++ b/core/src/main/scala/sttp/openai/requests/completions/chat/ChatChunkRequestResponseData.scala
@@ -25,7 +25,7 @@ object ChatChunkRequestResponseData {
 
   case class Choices(
       delta: Delta,
-      finishReason: Option[String],
+      finishReason: Option[String] = None,
       index: Int
   )
 


Test patch:
diff --git a/core/src/test/scala/sttp/openai/fixtures/ChatChunkFixture.scala b/core/src/test/scala/sttp/openai/fixtures/ChatChunkFixture.scala
index 1981724..d927e2c 100644
--- a/core/src/test/scala/sttp/openai/fixtures/ChatChunkFixture.scala
+++ b/core/src/test/scala/sttp/openai/fixtures/ChatChunkFixture.scala
@@ -119,6 +119,12 @@ object ChatChunkFixture {
       |  "system_fingerprint": "systemFingerprint",
       |  "choices": [
       |    {
+      |      "delta": { 
+      |        "content": "..." 
+      |      },
+      |      "index": 0
+      |    },
+      |    {
       |      "delta": {
       |        "role": "assistant",
       |        "content": "  Hi",
@@ -140,7 +146,7 @@ object ChatChunkFixture {
       |        ]
       |      },
       |      "finish_reason": "stop",
-      |      "index": 0
+      |      "index": 1
       |    }
       |  ]
       |}""".stripMargin
diff --git a/core/src/test/scala/sttp/openai/requests/completions/chat/ChatChunkDataSpec.scala b/core/src/test/scala/sttp/openai/requests/completions/chat/ChatChunkDataSpec.scala
index 822517d..6c6d9b3 100644
--- a/core/src/test/scala/sttp/openai/requests/completions/chat/ChatChunkDataSpec.scala
+++ b/core/src/test/scala/sttp/openai/requests/completions/chat/ChatChunkDataSpec.scala
@@ -17,16 +17,23 @@ class ChatChunkDataSpec extends AnyFlatSpec with Matchers with EitherValues {
     // given
     val jsonResponse = fixtures.ChatChunkFixture.jsonResponse
 
-    val delta: Delta = Delta(
-      role = Some(Role.Assistant),
-      content = Some("  Hi"),
-      toolCalls = toolCalls
-    )
-
-    val choices: Choices = Choices(
-      delta = delta,
-      finishReason = Some("stop"),
-      index = 0
+    val choices = Seq(
+      Choices(
+        delta = Delta(
+          content = Some("...")
+        ),
+        finishReason = None,
+        index = 0
+      ),
+      Choices(
+        delta = Delta(
+          role = Some(Role.Assistant),
+          content = Some("  Hi"),
+          toolCalls = toolCalls
+        ),
+        finishReason = Some("stop"),
+        index = 1
+      )
     )
 
     val expectedResponse: ChatChunkResponse = ChatChunkResponse(
@@ -34,7 +41,7 @@ class ChatChunkDataSpec extends AnyFlatSpec with Matchers with EitherValues {
       `object` = "chat.completion",
       created = 1681725687,
       model = "gpt-3.5-turbo-0301",
-      choices = Seq(choices),
+      choices = choices,
       systemFingerprint = Some("systemFingerprint")
     )