8000 fix(firebase): send the response schema and penalties to Firebase by GCSimba · Pull Request #856 · google/adk-kotlin · GitHub
[go: up one dir, main page]

Skip to content

fix(firebase): send the response schema and penalties to Firebase - #856

Open
GCSimba wants to merge 1 commit into
google:mainfrom
GCSimba:fix/firebase-response-schema
Open

GCSimba wants to merge 1 commit into
google:mainfrom
GCSimba:fix/firebase-response-schema

Conversation

@GCSimba
@GCSimba GCSimba commented Nov 2, 2026 •
Copy link
Copy Markdown
Contributor

An agent's outputSchema reaches the model as GenerateContentConfig.responseSchema, but the Firebase backend never set it on Firebase's GenerationConfig, so the request kept only the JSON MIME type and the model could answer with any JSON shape. presencePenalty and frequencyPenalty were dropped the same way, although Firebase's GenerationConfig.Builder has all three fields and the Gemini backend sends them. With a Gemini 3 model canUseOutputSchemaWithTools is true, so even an agent with tools relies on the response schema rather than set_model_response.

Set the two penalties directly and convert the response schema with the existing toFirebaseSchema, which keeps required through optionalProperties and warns about constraints Firebase cannot carry. One exception: an object that declares no properties is left out. toFirebaseSchema turns it into a string carrying JSON, which suits a tool argument but would make the model answer with a quoted string; the JSON MIME type alone still asks for an object. Both functions use the same convertsToFreeFormObject() check to recognize such an object.

Tests in ConversionsTest:

  • the existing generation config test now covers both penalties;
  • mapsResponseSchema maps an object schema with a required field;
  • freeFormResponseSchema_isLeftOut leaves out OBJECT with no or empty properties, TYPE_UNSPECIFIED, and a schema without a type;
  • untypedUnionAndEnumResponseSchemas_areKept keeps an untyped anyOf union and an untyped enum.

All but the free-form test fail without the fix.

Validation on Ubuntu 24.04 (WSL2), JDK 21, at 5ddf267 (rebased onto b961c9e):

  • ConversionsTest: 78 passed. With Conversions.kt from main it has 3 failures: mapsResponseSchema (expected: OBJECT), returnsBuilder (expected: 0.6) and untypedUnionAndEnumResponseSchemas_areKept.
  • :google-adk-kotlin-firebase:build and :google-adk-kotlin-examples-android:build, the only module that depends on Firebase, passed. The Firebase unit tests ran 113 with no failures; the 8 skipped are the live integration tests, which need Firebase credentials. The build left the working tree clean.
  • ktfmt --google-style (0.65) leaves the new source lines unchanged. In the test file it would only add trailing commas, as it would throughout the existing file. git diff --check passed.

After rebasing onto e863fac (now fc22165, same diff), ConversionsTest passed again on the new base: 78/78.

The Gradle runs used -PjdkVersion=21, -Dorg.gradle.jvmargs="-Xmx3g -XX:MaxMetaspaceSize=1g", -Dorg.gradle.parallel=false, --max-workers=2, --no-daemon, --stacktrace, and --continue. No request reached a live Firebase backend, and the JDK 17/25 matrix was not run locally.

This change was drafted with AI assistance (GitHub Copilot); I reviewed the diff and the validation results above.

@google-cla
google-cla Bot commented Nov 2, 2026
Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@GCSimba
GCSimba force-pushed the fix/firebase-response-schema branch from 8e5e303 to a1cdb25 Compare November 2, 2026 15:15
@aravindmurali22 aravindmurali22 self-assigned this Nov 2, 2026
@aravindmurali22 aravindmurali22 added the need attention New issue awaiting maintainer review or initial triage label Nov 2, 2026
Comment on lines +786 to +790
/** Whether [toFirebaseSchema] converts this schema with [freeFormObject]. */
private fun Schema.convertsToFreeFormObject(): Boolean =
firebaseBranch() == FirebaseBranch.TYPED &&
(type == Type.OBJECT || type == Type.TYPE_UNSPECIFIED || type == null) &&
properties.isNullOrEmpty()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider reusing convertsToFreeFormObject() inside toFirebaseSchema's Type.OBJECT / Type.TYPE_UNSPECIFIED / null branch (lines 671–676) so the free-form object predicate is defined in one place and cannot drift between toFirebaseSchema and toFirebaseResponseSchema.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5ddf267: the Type.OBJECT / Type.TYPE_UNSPECIFIED / null branch of toFirebaseSchema now calls convertsToFreeFormObject(), so toFirebaseSchema and toFirebaseResponseSchema share one definition of the free-form object case.

Comment on lines +805 to +822

@Test
fun requestConverter_generationConfigBuilder_freeFormResponseSchema_isLeftOut() {
// A tool argument with no properties becomes a string carrying JSON, but a response with that
// schema would be a quoted string. The JSON MIME type alone still asks for an object.
val request =
LlmRequest(
config =
GenerateContentConfig(
responseMimeType = "application/json",
responseSchema = Schema(type = Type.OBJECT),
)
)

val builder = Conversions().forRequest(request).generationConfigBuilder()

assertThat(builder.responseSchema).isNull()
assertThat(builder.responseMimeType).isEqualTo("application/json")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add test assertions covering Schema(type = Type.OBJECT, properties = emptyMap()) / Schema(type = Type.TYPE_UNSPECIFIED) being omitted and an untyped anyOf union or enum response schema (type = null, properties = null) being preserved by toFirebaseResponseSchema.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5ddf267: freeFormResponseSchema_isLeftOut now checks that Schema(type = Type.OBJECT), Schema(type = Type.OBJECT, properties = emptyMap()), Schema(type = Type.TYPE_UNSPECIFIED) and Schema() are all left out. The new untypedUnionAndEnumResponseSchemas_areKept checks that an untyped anyOf union and an untyped enum (type = null, properties = null) are kept as the response schema.

@aravindmurali22 aravindmurali22 left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clean fix and tests, @GCSimba! Left two minor comments on reusing convertsToFreeFormObject() in Conversions.kt and covering the remaining convertsToFreeFormObject branches in ConversionsTest.kt. please also rebase onto the latest main.

@aravindmurali22 aravindmurali22 added waiting on author Awaiting response from author. Failing that, maintainers will eventually closed it as stale. and removed need attention New issue awaiting maintainer review or initial triage labels Nov 2, 2026
@GCSimba
GCSimba force-pushed the fix/firebase-response-schema branch from a1cdb25 to 5ddf267 Compare November 3, 2026 05:51
@GCSimba
GCSimba commented Nov 3, 2026
Copy link
Copy Markdown
Contributor Author

Thanks for the review, @aravindmurali22! Rebased onto the latest main and addressed both comments in 5ddf267. I also updated the test and validation notes in the description.

@aravindmurali22
aravindmurali22 commented Nov 3, 2026 •
Copy link
Copy Markdown
Contributor

@GCSimba could you please rebase your branch onto the latest main here as well? Thanks!

@aravindmurali22 aravindmurali22 added google support Escalated to Google internal support or engineering team and removed waiting on author Awaiting response from author. Failing that, maintainers will eventually closed it as stale. labels Nov 3, 2026
@GCSimba
GCSimba force-pushed the fix/firebase-response-schema branch from 5ddf267 to fc22165 Compare November 4, 2026 03:39
@GCSimba
GCSimba commented Nov 4, 2026
Copy link
Copy Markdown
Contributor Author

Rebased onto the latest main (e863fac) in fc22165. No conflicts and the diff is unchanged; ConversionsTest passes again (78/78).

@aravindmurali22
Copy link
Copy Markdown
Contributor

@GCSimba Thanks for the contribution!

A couple of quick items before we proceed:

  • Please check and resolve the failing GitHub Actions checks.
  • Because this repo sees daily commits, please monitor updates and keep your branch rebased against main until merge.

Appreciate your help with this!

An agent's output schema reaches the model as responseSchema, but the
Firebase backend never set it, so the request kept only the JSON MIME
type and the model could answer with any JSON shape. presencePenalty
and frequencyPenalty were dropped too, although Firebase supports all
three and the Gemini backend sends them.
@GCSimba
GCSimba force-pushed the fix/firebase-response-schema branch from fc22165 to 3ee76e1 Compare November 5, 2026 17:53
@GCSimba
GCSimba commented Nov 5, 2026
Copy link
Copy Markdown
Contributor Author

Rebased onto e665796 again; the diff is unchanged, and ConversionsTest passes (78/78).

About the failing check: the last validation run failed only in McpToolsetIntegrationTest.run_unresponsiveServer_timesOutThenThrowsAfterRetries on JDK 21 (JDK 17 and 25 passed), which this PR does not touch. It looks like a flaky test rather than something this PR can fix: of the 85 completed validation runs in this repo since Nov 4, the 21 that failed all failed in one of four timing-sensitive tests, 19 of them on other PRs:

Test Failed runs
LiveRoutesTest.runLive_underTheAppsOwnWebSockets_closesAnOversizedFrameWithTooBig 7
KtorChatCompletionsClientTest.create_timeoutClosesTheConnectionWhileTheResponseStalls 6
McpToolsetIntegrationTest.run_unresponsiveServer_timesOutThenThrowsAfterRetries 4
WorkflowHitlTest.runNode_waitingCallCancelledWithTheFirst_doesNotRunTheChild 4
Failed runs

If the new run hits one of these again, could you re-run the failed job? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

google support Escalated to Google internal support or engineering team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

10B
0