Add the Kotlin tab for structured input and output schemas - #2151
Open
happyhuman wants to merge 2 commits into
Open
Add the Kotlin tab for structured input and output schemas#2151happyhuman wants to merge 2 commits into
happyhuman wants to merge 2 commits into
Conversation
The structured-data section of agents/llm-agents had Python, TypeScript, Go and Java but no Kotlin, and adk-kotlin 0.8.0 is what makes the tab worth writing: Schema gained the twelve JSON Schema constraint fields, so a constraint can be declared rather than described in the property's description and hoped for. The snippet uses two of them, pattern and minLength, on the capital string. Two conditions come with those fields, both from the upstream KDoc rather than from guessing, and both easy to hit: - Gemini rejects a schema whose `format` is anything but int32/int64 on a number or enum/date-time on a string. - `default` must hold a JSON-native value, and serializing a Schema that sets one needs a Json whose serializersModule has a contextual serializer for `Any`; a plain Json throws. The tab also names the type, because `Schema` is ambiguous in this codebase: `com.google.adk.kt.types.Schema` is the data class LlmAgent takes, while `com.google.adk.kt.tools.Schema` is an unrelated annotation, and the GenAI SDK has a third. `kotlin_api.py sig Schema` prints two of them. Written as a region in CapitalAgent.kt rather than inline as the backlog row proposed. Every other Kotlin tab on this page transcludes from that file even though its Python and Java siblings are inline, so inline Kotlin here would break the page's own convention and give up CI compile coverage. Verified: runner.sh build and lint both PASS (JDK 17), verify_snippets L0-L6 pass, and rendering the page with the repo's markdown extensions shows the target group as [Python, TypeScript, Go, Java, Kotlin].
Review against the v0.8.0 sources found four claims a Kotlin reader would have
acted on and been wrong, and one example that taught the wrong thing.
- "Cannot use tools effectively here", carried over from the Python and Java
tabs, is false for adk-kotlin. LlmAgent.kt:100-107 documents the opposite:
with tools present the schema is applied directly on models that support
both, and models that do not get a set_model_response fallback. The comment is
gone and the tab says what actually happens.
- The pattern in the example was the wrong constraint for the data. Under
full-match semantics `^[A-Z][A-Za-z .'-]*` rejects Bogota, Brasilia,
Reykjavik, San Jose and "Washington, D.C." - correct answers the model would
be marked down for - and under JSON Schema's partial-match default it rejects
nothing at all, since the tail may match zero characters. It now constrains a
countryCode field with ^[A-Z]{2}$, where a pattern is genuinely the right
tool, and the capital carries minLength/maxLength instead. The instruction was
updated to ask for both fields, since it previously disagreed with the schema
it was paired with.
- "A constraint can be declared instead of described" implied ADK enforces the
constraints. It does not: SchemaUtils reads none of the twelve fields, and its
validation covers type, required, nullable, anyOf and items only. They are
forwarded to the model, and the tab now says so.
- With outputSchema set, outputKey does not hold text. LlmAgent.kt:360-374
stores the parsed Map, and on a validation failure logs and stores the raw
string under the same key - so `state["found_capital"] as String` throws on
the happy path, and nothing but the runtime type distinguishes the two
outcomes. The page's bullets above the group say the text content is saved.
- Only a top-level object schema is accepted, so the list of new fields, which
includes minItems and maxItems, could have led a reader to a top-level array
that silently fails validation.
Also scoped the format note to Type.INTEGER as well as Type.NUMBER, and the
default note to a hand-rolled Json, since ADK's own registers a contextual Any
serializer (Serializers.kt:138).
Verified: runner.sh build and lint both PASS (JDK 17), verify_snippets L0-L6
pass, and the rendered page shows the group as [Python, TypeScript, Go, Java,
Kotlin] with the new bullets inside the Kotlin tab.
✅ Deploy Preview for adk-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The structured-data section of
agents/llm-agentshad Python, TypeScript, Go andJava tabs but no Kotlin. This adds one, and adk-kotlin 0.8.0 is what makes it
worth writing:
com.google.adk.kt.types.Schemagained the twelve JSON Schemaconstraint fields —
pattern,minLength,maxLength,minimum,maximum,minItems,maxItems,format,nullable,default,anyOfandtitle. At0.7.0 the class had six properties and none of these.
The snippet declares an output schema with
minLength/maxLengthon the capitalname and
^[A-Z]{2}$on an ISO 3166-1 country code, then wires it intoLlmAgent(outputSchema = …, outputKey = …).Four corrections to what this page implies for Kotlin
Most of the review effort went here rather than into the snippet. The page's
surrounding bullets and warning describe Python behaviour, and a bare Kotlin tab
would have inherited all of it. Each of these is checked against the pinned
sources, not inferred:
SchemaUtilsreadsnone of the twelve fields; its validation covers
type,required,nullable,anyOfanditemsonly. They are forwarded to the API. A readerwould otherwise assume
minLengthguarantees a non-empty value in state.outputKeyreceives aMap, not text. WithoutputSchemaset,LlmAgent.kt:360-374stores the parsed object; on a validation failure itlogs and stores the raw string under the same key. So
state["found_capital"] as Stringthrows on the happy path, and the value'sruntime type is the only signal of which outcome occurred — while the page
bullet directly above the tab group says the text content is saved.
minItems/maxItemswithout this would point readers at a top-level arraythat fails validation and, per the previous point, fails silently.
output_schemawith tools is only supported by specific models. Kotlin applies the schema
directly on models that support both and falls back to a
set_model_responsetool on those that don't (
LlmAgent.kt:100-107) — so a Kotlin reader shouldnot restructure into sub-agents to dodge a limitation they don't have.
Two of the new fields carry conditions of their own, both from the upstream
KDoc: Gemini rejects a
formatother thanint32/int64onType.INTEGERorType.NUMBER, orenum/date-timeonType.STRING; anddefaultmust hold aJSON-native value, which ADK's own
Jsonserializes but a hand-rolled onewithout a contextual
Anyserializer does not.Review notes
A sceptical pass over the first commit caught a bad example, fixed in
6c0242b4:the original demonstrated
patternwith^[A-Z][A-Za-z .'-]*on a capital-cityname. Under full-match semantics that rejects Bogotá, Brasília, Reykjavík, San
José and "Washington, D.C." — correct answers the model would be penalised for.
Under JSON Schema's partial-match default it rejects nothing at all, since the
tail can match zero characters. Either way it was the wrong constraint to teach
for that data, so
patternmoved to the country code, where it is exactly right,and the instruction was updated to match the schema it is paired with.
The same pass removed a
// Cannot use tools effectively here.comment copiedfrom the Python and Java tabs, which is false for adk-kotlin.
Not inline, unlike the backlog row
KT-22 specifies inline Kotlin. Every other Kotlin tab on this page transcludes a
region from
CapitalAgent.kt, even though its Python and Java siblings areinline, so inline here would break the page's convention and give up CI compile
coverage. It is a
schema_exampleregion instead.Verification
./tools/kotlin-snippets/runner.sh build …/CapitalAgent.kt→ PASS (JDK 17;the command CI runs).
lint→ PASS.verify_snippets.py→ L0 symbols, L1 compile, L2 ktlint, L3 transclusions,L5 registration, L6 badge all PASS. L4 skips — no
runSnippetstask here.mkdocs.yml: the target groupcomes back as
['Python', 'TypeScript', 'Go', 'Java', 'Kotlin']and the newbullets render inside the Kotlin tab.
Every claim is grounded in the
v0.8.0tag ofgoogle/adk-kotlin— the versionexamples/kotlin/build.gradle.ktspins — never the working tree, which iscurrently 13 commits ahead of it.