feat(provider/google): support JSON Schema for structured outputs via useResponseJsonSchema - #18325
feat(provider/google): support JSON Schema for structured outputs via useResponseJsonSchema#18325onatozmenn wants to merge 2 commits into
useResponseJsonSchema#18325Conversation
|
The routing here looks right to me — mutual exclusivity with One thing I'd suggest adding to the two Google's
Absent from that list, and accepted by the narrow So for a schema like z.object({ slug: z.string().min(3).max(50).regex(/^[a-z-]+$/) })flipping Two smaller notes: The new tests pass either way. Both fixtures use bare "That subset does not cover unions" is slightly narrower than it reads. Caveat on that last point, because it cuts both ways: the pre-auth check is a proto-shape oracle. It catches unknown field names and bad enum values, and it runs before the semantic checks, so the |
|
Thanks, this is a good catch, and it's in both One thing shifted while I was checking it though. I ran your keyword list through the SDK's own converter before writing the note, and most of those never reach Gemini on the default path either. So You're right that a unit test can't catch Gemini ignoring a keyword. It can catch the SDK changing which keywords it sends, which is the half that's ours, so the two new tests snapshot a constraint-bearing schema on both paths instead of the bare On unions, agreed, and I've reworded that sentence. I get the same results you do: Investigated with AI assistance. |
Send structured output schemas via generationConfig.responseJsonSchema instead of the OpenAPI 3.0 subset, enabling unions, records and recursive schemas on Gemini 2.5+.
…de-off The two schema fields support different subsets, so useResponseJsonSchema is not a superset of the default path. Gemini's supported keyword list for responseJsonSchema has no pattern, minLength or maxLength, and unsupported keywords are ignored rather than rejected, so a z.string().min(3) constraint stops being enforced with no error. Going the other way, the OpenAPI conversion drops minimum, maximum, minItems and maxItems, which responseJsonSchema keeps. Two request-body tests pin both halves with a constraint-bearing schema, since the existing fixtures use bare string properties and read the same either way.
cb85d21 to
6091b9b
Compare
Background
Fixes #6494.
Gemini structured outputs currently go out as
generationConfig.responseSchema, which is a subset of the OpenAPI 3.0 spec rather than JSON Schema. That subset trips on unions and records:anyOfitself is accepted by the API, but the schema this provider builds for az.unioncarries notypeon the union node, and the API requires one. Soz.union/z.recordschemas either fail with errors likeresponse_schema.properties[occupation].type: must be specifiedor force users to disable structured outputs entirely (structuredOutputs: false), losing constrained decoding.Gemini 2.5 and later support submitting JSON Schema through
generationConfig.responseJsonSchema, which is mutually exclusive withresponseSchemaand requiresresponseMimeType(API reference, guide).Summary
Adds a
useResponseJsonSchemaprovider option to@ai-sdk/google:true, the response format schema is sent unchanged asgenerationConfig.responseJsonSchemainstead of being converted to the OpenAPI subset forgenerationConfig.responseSchemaresponseMimeTypekeeps being set as beforefalse, so existing requests are unchangedstructuredOutputs: falsestill wins and suppresses both fieldsThis unblocks unions, records and recursive schemas for object generation on Gemini 2.5+ without giving up structured outputs.
The schema is forwarded as-is (including
$schema), matching how the officialjs-genaiSDK routes JSON Schema payloads to this field.What the flag costs
The two fields support different subsets, so this is a trade-off rather than an upgrade, and the docs now say so. Gemini's supported keyword list for
responseJsonSchemahas nopattern,minLengthormaxLength, and unsupported keywords there are ignored rather than rejected. Of those,minLengthis the only one the OpenAPI conversion forwards today, so it is the one that goes from enforced to silently ignored. In the other direction the conversion dropsminimum,maximum,minItemsandmaxItems, whichresponseJsonSchemakeeps.Contributor Credit
End-to-End Verification
Verified through the provider request-body tests, which assert the exact JSON sent to
:generateContent:useResponseJsonSchema: trueproducesgenerationConfig.responseJsonSchemawith the untouchedanyOfunion schema and noresponseSchemauseResponseJsonSchema: truecombined withstructuredOutputs: falsesends neither schema fieldpnpm --filter @ai-sdk/google exec vitest --config vitest.node.config.js --run→ 739 passed.tsc --buildandoxlinton the touched files are clean.Checklist
pnpm changesetin the project root)Future Work
The Gemini API exposes the same escape hatch for tool inputs via
FunctionDeclaration.parametersJsonSchema. Wiring that up would let tool input schemas use unions and records too; it was left out here to keep this change focused on structured outputs.