Skip to content

[AI] Add support for thinkingSummaries and thoughtSignatures #7272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Aug 20, 2025
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
2 changes: 2 additions & 0 deletions firebase-ai/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Unreleased
* [feature] Added support for returning thought summaries, which are synthesized
versions of a model's internal reasoning process.
* [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
Expand Down
21 changes: 21 additions & 0 deletions firebase-ai/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ package com.google.firebase.ai.type {
ctor public CodeExecutionResultPart(String outcome, String output);
method public String getOutcome();
method public String getOutput();
method public boolean isThought();
property public boolean isThought;
property public final String outcome;
property public final String output;
}
Expand Down Expand Up @@ -292,14 +294,18 @@ package com.google.firebase.ai.type {
ctor public ExecutableCodePart(String language, String code);
method public String getCode();
method public String getLanguage();
method public boolean isThought();
property public final String code;
property public boolean isThought;
property public final String language;
}

public final class FileDataPart implements com.google.firebase.ai.type.Part {
ctor public FileDataPart(String uri, String mimeType);
method public String getMimeType();
method public String getUri();
method public boolean isThought();
property public boolean isThought;
property public final String mimeType;
property public final String uri;
}
Expand Down Expand Up @@ -334,8 +340,10 @@ package com.google.firebase.ai.type {
method public java.util.Map<java.lang.String,kotlinx.serialization.json.JsonElement> getArgs();
method public String? getId();
method public String getName();
method public boolean isThought();
property public final java.util.Map<java.lang.String,kotlinx.serialization.json.JsonElement> args;
property public final String? id;
property public boolean isThought;
property public final String name;
}

Expand Down Expand Up @@ -364,7 +372,9 @@ package com.google.firebase.ai.type {
method public String? getId();
method public String getName();
method public kotlinx.serialization.json.JsonObject getResponse();
method public boolean isThought();
property public final String? id;
property public boolean isThought;
property public final String name;
property public final kotlinx.serialization.json.JsonObject response;
}
Expand All @@ -376,12 +386,14 @@ package com.google.firebase.ai.type {
method public java.util.List<com.google.firebase.ai.type.InlineDataPart> getInlineDataParts();
method public com.google.firebase.ai.type.PromptFeedback? getPromptFeedback();
method public String? getText();
method public String? getThoughtSummary();
method public com.google.firebase.ai.type.UsageMetadata? getUsageMetadata();
property public final java.util.List<com.google.firebase.ai.type.Candidate> candidates;
property public final java.util.List<com.google.firebase.ai.type.FunctionCallPart> functionCalls;
property public final java.util.List<com.google.firebase.ai.type.InlineDataPart> inlineDataParts;
property public final com.google.firebase.ai.type.PromptFeedback? promptFeedback;
property public final String? text;
property public final String? thoughtSummary;
property public final com.google.firebase.ai.type.UsageMetadata? usageMetadata;
}

Expand Down Expand Up @@ -552,7 +564,9 @@ package com.google.firebase.ai.type {
public final class ImagePart implements com.google.firebase.ai.type.Part {
ctor public ImagePart(android.graphics.Bitmap image);
method public android.graphics.Bitmap getImage();
method public boolean isThought();
property public final android.graphics.Bitmap image;
property public boolean isThought;
}

@com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenAspectRatio {
Expand Down Expand Up @@ -777,7 +791,9 @@ package com.google.firebase.ai.type {
ctor public InlineDataPart(byte[] inlineData, String mimeType);
method public byte[] getInlineData();
method public String getMimeType();
method public boolean isThought();
property public final byte[] inlineData;
property public boolean isThought;
property public final String mimeType;
}

Expand Down Expand Up @@ -886,6 +902,8 @@ package com.google.firebase.ai.type {
}

public interface Part {
method public boolean isThought();
property public abstract boolean isThought;
}

public final class PartKt {
Expand Down Expand Up @@ -1146,6 +1164,8 @@ package com.google.firebase.ai.type {
public final class TextPart implements com.google.firebase.ai.type.Part {
ctor public TextPart(String text);
method public String getText();
method public boolean isThought();
property public boolean isThought;
property public final String text;
}

Expand All @@ -1155,6 +1175,7 @@ package com.google.firebase.ai.type {
public static final class ThinkingConfig.Builder {
ctor public ThinkingConfig.Builder();
method public com.google.firebase.ai.type.ThinkingConfig build();
method public com.google.firebase.ai.type.ThinkingConfig.Builder setIncludeThoughts(boolean includeThoughts);
method public com.google.firebase.ai.type.ThinkingConfig.Builder setThinkingBudget(int thinkingBudget);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,42 +34,66 @@ public class GenerateContentResponse(
/**
* Convenience field representing all the text parts in the response as a single string.
*
* The value is null if the response contains no [candidates].
* The value is null if the response contains no valid text [candidates].
*
* Any part that's marked as a thought will be ignored. Learn more about
* [thinking](https://firebase.google.com/docs/ai-logic/thinking?api=dev).
*/
public val text: String? by lazy {
candidates.firstOrNull()?.content?.parts?.filterIsInstance<TextPart>()?.joinToString(" ") {
it.text
}
val parts = candidates.firstOrNull()?.nonThoughtParts()?.filterIsInstance<TextPart>()
if (parts.isNullOrEmpty()) return@lazy null
parts.joinToString(" ") { it.text }
}

/**
* Convenience field to list all the [FunctionCallPart]s in the response.
*
* The value is an empty list if the response contains no [candidates].
*
* Any part that's marked as a thought will be ignored. Learn more about
* [thinking](https://firebase.google.com/docs/ai-logic/thinking?api=dev).
*/
public val functionCalls: List<FunctionCallPart> by lazy {
candidates.firstOrNull()?.content?.parts?.filterIsInstance<FunctionCallPart>().orEmpty()
candidates.firstOrNull()?.nonThoughtParts()?.filterIsInstance<FunctionCallPart>().orEmpty()
}

/**
* Convenience field representing all the text parts in the response that are marked as thoughts
* as a single string, if they exists.
*
* Learn more about [thinking](https://firebase.google.com/docs/ai-logic/thinking?api=dev).
*/
public val thoughtSummary: String? by lazy {
candidates.firstOrNull()?.thoughtParts()?.filterIsInstance<TextPart>()?.joinToString(" ") {
it.text
}
}

/**
* Convenience field representing all the [InlineDataPart]s in the first candidate, if they exist.
* Convenience field representing all the [InlineDataPart]s in the first candidate.
*
* 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].
*
* Any part that's marked as a thought will be ignored. Learn more about
* [thinking](https://firebase.google.com/docs/ai-logic/thinking?api=dev).
*/
public val inlineDataParts: List<InlineDataPart> by lazy {
candidates
.firstOrNull()
?.content
?.parts
?.nonThoughtParts()
?.let { parts ->
parts.filterIsInstance<ImagePart>().map { it.toInlineDataPart() } +
parts.filterIsInstance<InlineDataPart>()
}
.orEmpty()
}

private fun Candidate.thoughtParts(): List<Part> = content.parts.filter { it.isThought }

private fun Candidate.nonThoughtParts(): List<Part> = content.parts.filter { !it.isThought }

@Serializable
internal data class Internal(
val candidates: List<Candidate.Internal>? = null,
Expand Down
Loading
Loading