Skip to content

Commit 8d76ee4

Browse files
committed
Final implementation step
1 parent 38c121a commit 8d76ee4

File tree

4 files changed

+121
-69
lines changed

4 files changed

+121
-69
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ public class GenerateContentResponse(
4040
* [thinking](https://firebase.google.com/docs/ai-logic/thinking?api=dev).
4141
*/
4242
public val text: String? by lazy {
43-
candidates.firstOrNull()?.nonThoughtParts()?.filterIsInstance<TextPart>()?.joinToString(" ") {
44-
it.text
45-
}
43+
val parts = candidates.firstOrNull()?.nonThoughtParts()?.filterIsInstance<TextPart>()
44+
if (parts.isNullOrEmpty()) return@lazy null
45+
parts.joinToString(" ") { it.text }
4646
}
4747

4848
/**

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

Lines changed: 113 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -37,34 +37,43 @@ public interface Part {
3737

3838
/** Represents text or string based data sent to and received from requests. */
3939
public class TextPart
40-
internal constructor(public val text: String, public override val isThought: Boolean = false) :
41-
Part {
40+
internal constructor(
41+
public val text: String,
42+
public override val isThought: Boolean,
43+
internal val thoughtSignature: String?
44+
) : Part {
4245

43-
public constructor(text: String) : this(text, false)
46+
public constructor(text: String) : this(text, false, null)
4447

4548
@Serializable
46-
internal data class Internal(val text: String, val thought: Boolean? = null) : InternalPart
49+
internal data class Internal(
50+
val text: String,
51+
val thought: Boolean? = null,
52+
val thoughtSignature: String? = null
53+
) : InternalPart
4754
}
4855

4956
public class CodeExecutionResultPart
5057
internal constructor(
5158
public val outcome: String,
5259
public val output: String,
53-
public override val isThought: Boolean = false
60+
public override val isThought: Boolean,
61+
internal val thoughtSignature: String?
5462
) : Part {
5563

56-
public constructor(outcome: String, output: String) : this(outcome, output, false)
64+
public constructor(outcome: String, output: String) : this(outcome, output, false, null)
5765

5866
@Serializable
5967
internal data class Internal(
60-
@SerialName("codeExecutionResult") val codeExecutionResult: CodeExecutionResult
68+
@SerialName("codeExecutionResult") val codeExecutionResult: CodeExecutionResult,
69+
val thought: Boolean? = null,
70+
val thoughtSignature: String? = null
6171
) : InternalPart {
6272

6373
@Serializable
6474
internal data class CodeExecutionResult(
6575
@SerialName("outcome") val outcome: String,
66-
val output: String,
67-
val thought: Boolean? = null
76+
val output: String
6877
)
6978
}
7079
}
@@ -73,20 +82,23 @@ public class ExecutableCodePart
7382
internal constructor(
7483
public val language: String,
7584
public val code: String,
76-
public override val isThought: Boolean = false
85+
public override val isThought: Boolean,
86+
internal val thoughtSignature: String?
7787
) : Part {
7888

79-
public constructor(language: String, code: String) : this(language, code, false)
89+
public constructor(language: String, code: String) : this(language, code, false, null)
8090

8191
@Serializable
82-
internal data class Internal(@SerialName("executableCode") val executableCode: ExecutableCode) :
83-
InternalPart {
92+
internal data class Internal(
93+
@SerialName("executableCode") val executableCode: ExecutableCode,
94+
val thought: Boolean? = null,
95+
val thoughtSignature: String? = null
96+
) : InternalPart {
8497

8598
@Serializable
8699
internal data class ExecutableCode(
87100
@SerialName("language") val language: String,
88-
val code: String,
89-
val thought: Boolean? = null
101+
val code: String
90102
)
91103
}
92104
}
@@ -98,16 +110,20 @@ internal constructor(
98110
* @param image [Bitmap] to convert into a [Part]
99111
*/
100112
public class ImagePart
101-
internal constructor(public val image: Bitmap, public override val isThought: Boolean = false) :
102-
Part {
113+
internal constructor(
114+
public val image: Bitmap,
115+
public override val isThought: Boolean,
116+
internal val thoughtSignature: String?
117+
) : Part {
103118

104-
public constructor(image: Bitmap) : this(image, false)
119+
public constructor(image: Bitmap) : this(image, false, null)
105120

106121
internal fun toInlineDataPart() =
107122
InlineDataPart(
108123
android.util.Base64.decode(encodeBitmapToBase64Jpeg(image), BASE_64_FLAGS),
109124
"image/jpeg",
110-
isThought
125+
isThought,
126+
thoughtSignature
111127
)
112128
}
113129

@@ -122,21 +138,24 @@ public class InlineDataPart
122138
internal constructor(
123139
public val inlineData: ByteArray,
124140
public val mimeType: String,
125-
public override val isThought: Boolean = false
141+
public override val isThought: Boolean,
142+
internal val thoughtSignature: String?
126143
) : Part {
127144

128-
public constructor(inlineData: ByteArray, mimeType: String) : this(inlineData, mimeType, false)
145+
public constructor(
146+
inlineData: ByteArray,
147+
mimeType: String
148+
) : this(inlineData, mimeType, false, null)
129149

130150
@Serializable
131-
internal data class Internal(@SerialName("inlineData") val inlineData: InlineData) :
132-
InternalPart {
151+
internal data class Internal(
152+
@SerialName("inlineData") val inlineData: InlineData,
153+
val thought: Boolean? = null,
154+
val thoughtSignature: String? = null
155+
) : InternalPart {
133156

134157
@Serializable
135-
internal data class InlineData(
136-
@SerialName("mimeType") val mimeType: String,
137-
val data: Base64,
138-
val thought: Boolean? = null
139-
)
158+
internal data class InlineData(@SerialName("mimeType") val mimeType: String, val data: Base64)
140159
}
141160
}
142161

@@ -153,25 +172,29 @@ internal constructor(
153172
public val name: String,
154173
public val args: Map<String, JsonElement>,
155174
public val id: String? = null,
156-
public override val isThought: Boolean = false
175+
public override val isThought: Boolean,
176+
internal val thoughtSignature: String?
157177
) : Part {
158178

159179
@JvmOverloads
160180
public constructor(
161181
name: String,
162182
args: Map<String, JsonElement>,
163-
id: String? = null
164-
) : this(name, args, id, false)
183+
id: String? = null,
184+
) : this(name, args, id, false, null)
165185

166186
@Serializable
167-
internal data class Internal(val functionCall: FunctionCall) : InternalPart {
187+
internal data class Internal(
188+
val functionCall: FunctionCall,
189+
val thought: Boolean? = null,
190+
val thoughtSignature: String? = null
191+
) : InternalPart {
168192

169193
@Serializable
170194
internal data class FunctionCall(
171195
val name: String,
172196
val args: Map<String, JsonElement?>? = null,
173-
val id: String? = null,
174-
val thought: Boolean? = null
197+
val id: String? = null
175198
)
176199
}
177200
}
@@ -188,30 +211,34 @@ internal constructor(
188211
public val name: String,
189212
public val response: JsonObject,
190213
public val id: String? = null,
191-
public override val isThought: Boolean = false
214+
public override val isThought: Boolean,
215+
internal val thoughtSignature: String?
192216
) : Part {
193217

194218
@JvmOverloads
195219
public constructor(
196220
name: String,
197221
response: JsonObject,
198222
id: String? = null
199-
) : this(name, response, id, false)
223+
) : this(name, response, id, false, null)
200224

201225
@Serializable
202-
internal data class Internal(val functionResponse: FunctionResponse) : InternalPart {
226+
internal data class Internal(
227+
val functionResponse: FunctionResponse,
228+
val thought: Boolean? = null,
229+
val thoughtSignature: String? = null
230+
) : InternalPart {
203231

204232
@Serializable
205233
internal data class FunctionResponse(
206234
val name: String,
207235
val response: JsonObject,
208-
val id: String? = null,
209-
val thought: Boolean? = null
236+
val id: String? = null
210237
)
211238
}
212239

213240
internal fun toInternalFunctionCall(): Internal.FunctionResponse {
214-
return Internal.FunctionResponse(name, response, id, isThought)
241+
return Internal.FunctionResponse(name, response, id)
215242
}
216243
}
217244

@@ -227,19 +254,23 @@ public class FileDataPart
227254
internal constructor(
228255
public val uri: String,
229256
public val mimeType: String,
230-
public override val isThought: Boolean = false
257+
public override val isThought: Boolean,
258+
internal val thoughtSignature: String?
231259
) : Part {
232260

233-
public constructor(uri: String, mimeType: String) : this(uri, mimeType, false)
261+
public constructor(uri: String, mimeType: String) : this(uri, mimeType, false, null)
234262

235263
@Serializable
236-
internal data class Internal(@SerialName("file_data") val fileData: FileData) : InternalPart {
264+
internal data class Internal(
265+
@SerialName("file_data") val fileData: FileData,
266+
val thought: Boolean? = null,
267+
val thoughtSignature: String? = null
268+
) : InternalPart {
237269

238270
@Serializable
239271
internal data class FileData(
240272
@SerialName("mime_type") val mimeType: String,
241-
@SerialName("file_uri") val fileUri: String,
242-
val thought: Boolean? = null
273+
@SerialName("file_uri") val fileUri: String
243274
)
244275
}
245276
}
@@ -281,36 +312,51 @@ internal object PartSerializer :
281312

282313
internal fun Part.toInternal(): InternalPart {
283314
return when (this) {
284-
is TextPart -> TextPart.Internal(text, isThought)
315+
is TextPart -> TextPart.Internal(text, isThought, thoughtSignature)
285316
is ImagePart ->
286317
InlineDataPart.Internal(
287-
InlineDataPart.Internal.InlineData("image/jpeg", encodeBitmapToBase64Jpeg(image), isThought)
318+
InlineDataPart.Internal.InlineData("image/jpeg", encodeBitmapToBase64Jpeg(image)),
319+
isThought,
320+
thoughtSignature
288321
)
289322
is InlineDataPart ->
290323
InlineDataPart.Internal(
291324
InlineDataPart.Internal.InlineData(
292325
mimeType,
293-
android.util.Base64.encodeToString(inlineData, BASE_64_FLAGS),
294-
isThought
295-
)
326+
android.util.Base64.encodeToString(inlineData, BASE_64_FLAGS)
327+
),
328+
isThought,
329+
thoughtSignature
296330
)
297331
is FunctionCallPart ->
298-
FunctionCallPart.Internal(FunctionCallPart.Internal.FunctionCall(name, args, id, isThought))
332+
FunctionCallPart.Internal(
333+
FunctionCallPart.Internal.FunctionCall(name, args, id),
334+
isThought,
335+
thoughtSignature
336+
)
299337
is FunctionResponsePart ->
300338
FunctionResponsePart.Internal(
301-
FunctionResponsePart.Internal.FunctionResponse(name, response, id, isThought)
339+
FunctionResponsePart.Internal.FunctionResponse(name, response, id),
340+
isThought,
341+
thoughtSignature
302342
)
303343
is FileDataPart ->
304344
FileDataPart.Internal(
305-
FileDataPart.Internal.FileData(mimeType = mimeType, fileUri = uri, thought = isThought)
345+
FileDataPart.Internal.FileData(mimeType = mimeType, fileUri = uri),
346+
isThought,
347+
thoughtSignature
306348
)
307349
is ExecutableCodePart ->
308350
ExecutableCodePart.Internal(
309-
ExecutableCodePart.Internal.ExecutableCode(language, code, isThought)
351+
ExecutableCodePart.Internal.ExecutableCode(language, code),
352+
isThought,
353+
thoughtSignature
310354
)
311355
is CodeExecutionResultPart ->
312356
CodeExecutionResultPart.Internal(
313-
CodeExecutionResultPart.Internal.CodeExecutionResult(outcome, output, isThought)
357+
CodeExecutionResultPart.Internal.CodeExecutionResult(outcome, output),
358+
isThought,
359+
thoughtSignature
314360
)
315361
else ->
316362
throw com.google.firebase.ai.type.SerializationException(
@@ -328,42 +374,46 @@ private fun encodeBitmapToBase64Jpeg(input: Bitmap): String {
328374

329375
internal fun InternalPart.toPublic(): Part {
330376
return when (this) {
331-
is TextPart.Internal -> TextPart(text, thought ?: false)
377+
is TextPart.Internal -> TextPart(text, thought ?: false, thoughtSignature)
332378
is InlineDataPart.Internal -> {
333379
val data = android.util.Base64.decode(inlineData.data, BASE_64_FLAGS)
334380
if (inlineData.mimeType.contains("image")) {
335-
ImagePart(decodeBitmapFromImage(data), inlineData.thought ?: false)
381+
ImagePart(decodeBitmapFromImage(data), thought ?: false, thoughtSignature)
336382
} else {
337-
InlineDataPart(data, inlineData.mimeType, inlineData.thought ?: false)
383+
InlineDataPart(data, inlineData.mimeType, thought ?: false, thoughtSignature)
338384
}
339385
}
340386
is FunctionCallPart.Internal ->
341387
FunctionCallPart(
342388
functionCall.name,
343389
functionCall.args.orEmpty().mapValues { it.value ?: JsonNull },
344390
functionCall.id,
345-
functionCall.thought ?: false
391+
thought ?: false,
392+
thoughtSignature
346393
)
347394
is FunctionResponsePart.Internal ->
348395
FunctionResponsePart(
349396
functionResponse.name,
350397
functionResponse.response,
351398
functionResponse.id,
352-
functionResponse.thought ?: false
399+
thought ?: false,
400+
thoughtSignature
353401
)
354402
is FileDataPart.Internal ->
355-
FileDataPart(fileData.mimeType, fileData.fileUri, fileData.thought ?: false)
403+
FileDataPart(fileData.mimeType, fileData.fileUri, thought ?: false, thoughtSignature)
356404
is ExecutableCodePart.Internal ->
357405
ExecutableCodePart(
358406
executableCode.language,
359407
executableCode.code,
360-
executableCode.thought ?: false
408+
thought ?: false,
409+
thoughtSignature
361410
)
362411
is CodeExecutionResultPart.Internal ->
363412
CodeExecutionResultPart(
364413
codeExecutionResult.outcome,
365414
codeExecutionResult.output,
366-
codeExecutionResult.thought ?: false
415+
thought ?: false,
416+
thoughtSignature
367417
)
368418
else ->
369419
throw com.google.firebase.ai.type.SerializationException(

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,13 @@ internal class DevAPIUnarySnapshotTests {
141141
response.candidates.isNotEmpty()
142142
response.thoughtSummary.shouldNotBeNull()
143143
response.thoughtSummary?.isNotEmpty()
144+
response.functionCalls.isNotEmpty()
145+
response.functionCalls.first().let {
146+
it.thoughtSignature.shouldNotBeNull()
147+
it.thoughtSignature.isNotEmpty()
148+
}
144149
// There's no text in the response
145150
response.text.shouldBeNull()
146-
response.candidates.first().finishReason shouldBe FinishReason.STOP
147-
response.candidates.first().content.parts.isEmpty() shouldBe false
148151
}
149152
}
150153
}

0 commit comments

Comments
 (0)