Skip to content

Commit 5688be6

Browse files
committed
[AI] Ignore, and log, unknown parts.
1 parent 546cbcb commit 5688be6

File tree

3 files changed

+26
-2
lines changed

3 files changed

+26
-2
lines changed

firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Content.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ constructor(public val role: String? = "user", public val parts: List<Part>) {
9494
) {
9595
internal fun toPublic(): Content {
9696
val returnedParts =
97-
parts.map { it.toPublic() }.filterNot { it is TextPart && it.text.isEmpty() }
97+
parts.filterNot { it is UnknownPart.Internal }.map { it.toPublic() }.filterNot { it is TextPart && it.text.isEmpty() }
9898
// If all returned parts were text and empty, we coalesce them into a single one-character
9999
// string
100100
// part so the backend doesn't fail if we send this back as part of a multi-turn interaction.

firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Part.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.google.firebase.ai.type
1818

1919
import android.graphics.Bitmap
2020
import android.graphics.BitmapFactory
21+
import android.util.Log
2122
import java.io.ByteArrayOutputStream
2223
import kotlinx.serialization.DeserializationStrategy
2324
import kotlinx.serialization.SerialName
@@ -270,6 +271,12 @@ internal constructor(
270271
}
271272
}
272273

274+
275+
internal data class UnknownPart(public override val isThought: Boolean = false): Part {
276+
@Serializable
277+
internal data class Internal(val thought: Boolean? = null): InternalPart
278+
}
279+
273280
/** Returns the part as a [String] if it represents text, and null otherwise */
274281
public fun Part.asTextOrNull(): String? = (this as? TextPart)?.text
275282

@@ -300,7 +307,10 @@ internal object PartSerializer :
300307
"functionResponse" in jsonObject -> FunctionResponsePart.Internal.serializer()
301308
"inlineData" in jsonObject -> InlineDataPart.Internal.serializer()
302309
"fileData" in jsonObject -> FileDataPart.Internal.serializer()
303-
else -> throw SerializationException("Unknown Part type")
310+
else -> {
311+
Log.w("PartSerializer", "Unknown part type received, ignoring.")
312+
UnknownPart.Internal.serializer()
313+
}
304314
}
305315
}
306316
}
@@ -410,6 +420,7 @@ internal fun InternalPart.toPublic(): Part {
410420
thought ?: false,
411421
thoughtSignature
412422
)
423+
is UnknownPart.Internal -> UnknownPart()
413424
else ->
414425
throw com.google.firebase.ai.type.SerializationException(
415426
"Unsupported part type \"${javaClass.simpleName}\" provided. This model may not be supported by this SDK."

firebase-ai/src/test/java/com/google/firebase/ai/VertexAIUnarySnapshotTests.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ internal class VertexAIUnarySnapshotTests {
9090
}
9191
}
9292

93+
@Test
94+
fun `reply including an empty part`() =
95+
goldenVertexUnaryFile("unary-success-empty-part.json") {
96+
withTimeout(testTimeout) {
97+
val response = model.generateContent("prompt")
98+
99+
response.candidates.isEmpty() shouldBe false
100+
response.text.shouldNotBeEmpty()
101+
response.candidates.first().finishReason shouldBe FinishReason.STOP
102+
response.candidates.first().content.parts.isEmpty() shouldBe false
103+
}
104+
}
105+
93106
@Test
94107
fun `response with detailed token-based usageMetadata`() =
95108
goldenVertexUnaryFile("unary-success-basic-response-long-usage-metadata.json") {

0 commit comments

Comments
 (0)