Repository navigation
Let the model choose the variant of an anyOf - #320
Merged
Merged
Conversation
Constrained generation always took the first variant of an anyOf, so a nullable union could never produce null and a union of object types always produced the first type (#289). The generator now resolves references and flattens nested unions, then samples once among the tokens that can start each variant and continues with the variants that can start with the sample. Object variants all start with `{`, so the key mask offers the keys of every live variant and each emitted key keeps the variants that declare it; a shared key whose values are string choices (enum case tags, constants) narrows by the chosen value. Variants that start alike but aren't all objects still fall back to the first of them, noted where it happens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node equality includes descriptions, so two object variants whose schemas for a shared key differ only in their descriptions were treated as different: the fallback chose the first and dropped the other, letting declaration order decide the union again. Compare the schemas with descriptions removed, both for the fast path and for narrowing after the sampled value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a shared key's values are all objects, generate the value with the multi-variant object generator and keep the variants whose nested object still matches, instead of always keeping the first. Resolve references when comparing a shared key's schemas, stopping at a reference already being resolved so recursive definitions terminate. Keep the live variants when a generated choice matches none of the choices, as when a token decodes with a leading space, so the object does not close before the remaining required keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the tokens sampled for a string choice decode to text that is not exactly one of the choices (a piece with a leading space, say), the live variants used to stay as they were. A variant that the choice ruled out could then let the object close before the chosen case's required keys. generateChoice now reports which candidate the sampled tokens spell, and the value path narrows by that candidate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A union that refers to the same definition twice, at each level, made `flattenedVariants` double its result per level: the depth guard capped the chain length, not the size. Each definition is now expanded once per flattening, which also drops the depth guard. `withoutDescriptions` had the same shape for definitions that refer to another one twice. It now resolves at most 256 references per call and leaves the rest as references, which can only make two schemas compare unequal sooner. Properties are visited in key order, so the cut-off is the same for equal schemas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mattt
added a commit
that referenced
this pull request
Oct 13, 2026
Resolve the conflict in StructuredGeneration.swift by keeping #320's variant selection, where the keys the model writes choose among the objects of a union. When those objects are the cases of an enumeration with associated values, each with a different constant type, the object now starts with type, and #320's string choice narrowing picks the case.
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.
This PR lands #309 by @2570165831, with one fix on top. When a nested object in a union closes, generation now keeps only the variants that the object completes. Before, a nested union could produce JSON that matched none of its variants.
Fixes #289