Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ constructor(public val role: String? = "user", public val parts: List<Part>) {
) {
internal fun toPublic(): Content {
val returnedParts =
parts.map { it.toPublic() }.filterNot { it is TextPart && it.text.isEmpty() }
parts.filterNot { it is UnknownPart.Internal }.map { it.toPublic() }.filterNot { it is TextPart && it.text.isEmpty() }
// If all returned parts were text and empty, we coalesce them into a single one-character
// string
// part so the backend doesn't fail if we send this back as part of a multi-turn interaction.
Expand Down
13 changes: 12 additions & 1 deletion firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Part.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.google.firebase.ai.type

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log
import java.io.ByteArrayOutputStream
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialName
Expand Down Expand Up @@ -270,6 +271,12 @@ internal constructor(
}
}


internal data class UnknownPart(public override val isThought: Boolean = false): Part {
@Serializable
internal data class Internal(val thought: Boolean? = null): InternalPart
}

/** Returns the part as a [String] if it represents text, and null otherwise */
public fun Part.asTextOrNull(): String? = (this as? TextPart)?.text

Expand Down Expand Up @@ -300,7 +307,10 @@ internal object PartSerializer :
"functionResponse" in jsonObject -> FunctionResponsePart.Internal.serializer()
"inlineData" in jsonObject -> InlineDataPart.Internal.serializer()
"fileData" in jsonObject -> FileDataPart.Internal.serializer()
else -> throw SerializationException("Unknown Part type")
else -> {
Log.w("PartSerializer", "Unknown part type received, ignoring.")
UnknownPart.Internal.serializer()
}
}
}
}
Expand Down Expand Up @@ -410,6 +420,7 @@ internal fun InternalPart.toPublic(): Part {
thought ?: false,
thoughtSignature
)
is UnknownPart.Internal -> UnknownPart()
else ->
throw com.google.firebase.ai.type.SerializationException(
"Unsupported part type \"${javaClass.simpleName}\" provided. This model may not be supported by this SDK."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ internal class VertexAIUnarySnapshotTests {
}
}

@Test
fun `reply including an empty part`() =
goldenVertexUnaryFile("unary-success-empty-part.json") {
withTimeout(testTimeout) {
val response = model.generateContent("prompt")

response.candidates.isEmpty() shouldBe false
response.text.shouldNotBeEmpty()
response.candidates.first().finishReason shouldBe FinishReason.STOP
response.candidates.first().content.parts.isEmpty() shouldBe false
}
}

@Test
fun `response with detailed token-based usageMetadata`() =
goldenVertexUnaryFile("unary-success-basic-response-long-usage-metadata.json") {
Expand Down
Loading