Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 3 additions & 2 deletions firebase-ai/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Unreleased
* [fixed] Fixed an issue causing the accessor methods in `GenerateContentResponse` to throw an exception
when the response contained no candidates.
* [changed] Added better description for requests which fail due to the Gemini API not being
configured.
* [changed] Added a `dilation` parameter to `ImagenMaskReference.generateMaskAndPadForOutpainting`
(#7260)

# 17.1.0
=======
* [feature] added support for Imagen Editing, including inpainting, outpainting, control, style
* [feature] added support for Imagen Editing, including inpainting, outpainting, control, style
transfer, and subject references (#7075)
* [feature] **Preview:** Added support for bidirectional streaming in Gemini Developer Api

Expand Down Expand Up @@ -51,4 +53,3 @@

Note: This feature is in Public Preview, which means that it is not subject to any SLA or
deprecation policy and could change in backwards-incompatible ways.

Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,42 @@ public class GenerateContentResponse(
public val usageMetadata: UsageMetadata?,
) {
/**
* Convenience field representing all the text parts in the response as a single string, if they
* exists.
* Convenience field representing all the text parts in the response as a single string.
*
* The value is null if the response contains no [candidates].
*/
public val text: String? by lazy {
candidates.first().content.parts.filterIsInstance<TextPart>().joinToString(" ") { it.text }
candidates.firstOrNull()?.content?.parts?.filterIsInstance<TextPart>()?.joinToString(" ") {
it.text
}
}

/** Convenience field to list all the [FunctionCallPart]s in the response, if they exist. */
/**
* Convenience field to list all the [FunctionCallPart]s in the response.
*
* The value is an empty list if the response contains no [candidates].
*/
public val functionCalls: List<FunctionCallPart> by lazy {
candidates.first().content.parts.filterIsInstance<FunctionCallPart>()
candidates.firstOrNull()?.content?.parts?.filterIsInstance<FunctionCallPart>().orEmpty()
}

/**
* Convenience field representing all the [InlineDataPart]s in the first candidate, if they exist.
*
* This also includes any [ImagePart], but they will be represented as [InlineDataPart] instead.
*
* The value is an empty list if the response contains no [candidates].
*/
public val inlineDataParts: List<InlineDataPart> by lazy {
candidates.first().content.parts.let { parts ->
parts.filterIsInstance<ImagePart>().map { it.toInlineDataPart() } +
parts.filterIsInstance<InlineDataPart>()
}
candidates
.firstOrNull()
?.content
?.parts
?.let { parts ->
parts.filterIsInstance<ImagePart>().map { it.toInlineDataPart() } +
parts.filterIsInstance<InlineDataPart>()
}
.orEmpty()
}

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import com.google.firebase.ai.type.ResponseStoppedException
import com.google.firebase.ai.type.ServerException
import com.google.firebase.ai.util.goldenDevAPIUnaryFile
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.collections.shouldNotBeEmpty
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.ktor.http.HttpStatusCode
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.withTimeout
Expand All @@ -42,9 +42,24 @@ internal class DevAPIUnarySnapshotTests {
withTimeout(testTimeout) {
val response = model.generateContent("prompt")

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

@Test
fun `only prompt feedback reply`() =
goldenDevAPIUnaryFile("unary-failure-only-prompt-feedback.json") {
withTimeout(testTimeout) {
val response = model.generateContent("prompt")

response.candidates.shouldBeEmpty()

// Check response from accessors
response.text.shouldBeNull()
response.functionCalls.shouldBeEmpty()
response.inlineDataParts.shouldBeEmpty()
}
}

Expand All @@ -54,9 +69,9 @@ internal class DevAPIUnarySnapshotTests {
withTimeout(testTimeout) {
val response = model.generateContent("prompt")

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

Expand All @@ -66,11 +81,11 @@ internal class DevAPIUnarySnapshotTests {
withTimeout(testTimeout) {
val response = model.generateContent("prompt")

response.candidates.isEmpty() shouldBe false
response.candidates.shouldNotBeEmpty()
response.candidates.first().citationMetadata?.citations?.size shouldBe 4
response.candidates.first().citationMetadata?.citations?.forEach {
it.startIndex shouldNotBe null
it.endIndex shouldNotBe null
it.startIndex.shouldNotBeNull()
it.endIndex.shouldNotBeNull()
}
}
}
Expand Down
Loading