Skip to content

fix: support list-form JSON schema types - #1813

Open
nightcityblade wants to merge 1 commit into
anthropics:mainfrom
nightcityblade:fix/issue-1764-type-arrays
Open

fix: support list-form JSON schema types#1813
nightcityblade wants to merge 1 commit into
anthropics:mainfrom
nightcityblade:fix/issue-1764-type-arrays

Conversation

@nightcityblade

Copy link
Copy Markdown

Fixes #1764

Summary

  • canonicalize JSON Schema list-form type values into anyOf branches
  • keep shared metadata at the union level while applying constraints to non-null variants
  • compose type unions with existing anyOf, oneOf, and allOf schemas
  • add regression coverage for top-level and nested nullable type arrays

Tests

  • uv run pytest -n0 tests/lib/_parse/test_transform.py (16 passed)
  • uv run ruff check src/anthropic/lib/_parse/_transform.py tests/lib/_parse/test_transform.py
  • uv run ruff format --check src/anthropic/lib/_parse/_transform.py tests/lib/_parse/test_transform.py
  • uv run pyright src/anthropic/lib/_parse/_transform.py tests/lib/_parse/test_transform.py
  • ./scripts/lint

@nightcityblade
nightcityblade requested a review from a team as a code owner August 9, 2026 03:56
@percymcn

percymcn commented Aug 9, 2026

Copy link
Copy Markdown

I reimplemented this diff verbatim against anthropic==0.121.0 and ran it over a battery before commenting, so everything below is measured rather than read off the diff.

It works, including the case that separates a correct fix from a plausible one. The risk with this bug is a fix that copies type through to every branch — that leaves the per-type dispatch untouched, and an object union quietly loses its properties. This diff moves the type-specific keys onto the non-null variants instead, so:

{"type": ["object", "null"], "properties": {"o": {...}}, "required": ["a"]}
# ->
{"anyOf": [{"type": "object", "properties": {...}, "additionalProperties": False, "required": ["a"]},
           {"type": "null"}]}

$defs survive, format/minItems/minLength land on the right branch and demote correctly, and a 1-element ["string"] no longer raises. All four composition branches (anyOf/oneOf/allOf) produce well-formed allOf output.

One regression, and it changes a loud failure into a silent one. is_list([]) is True, so an empty type list falls into the new path:

{"type": []}   # before: AssertionError
               # after:  {"anyOf": []}

An empty anyOf matches nothing, so the model can never produce a satisfying value — and nothing raises. Today that input is rejected; after this it ships. A guard on the empty case (raise, or treat as untyped) keeps the previous behaviour.

The two new tests don't reach the paths the summary claims. test_nested_type_array_schema does discriminate properly on the null branch, which is good. But the summary lists composition with anyOf, oneOf and allOf, and no test exercises any of the three — nor the ["object", "null"]-carrying-properties case above, which is the one where a future refactor of the dispatch would regress silently. Both are cheap to pin.

Worth knowing on the oneOf branch specifically: the wrapped {"oneOf": ...} is then rewritten to anyOf by the existing transformer, so exclusivity is not preserved. That's pre-existing, not this diff, but "composes with oneOf" reads stronger than what happens.

A pre-existing interaction this makes easier to hit (verified separately with no list-form type, so not caused by this PR): a branch carrying required without its own properties comes out as {"type": "object", "properties": {}, "additionalProperties": False, "required": ["a"]} — unsatisfiable. Composing a type union with an object anyOf is a natural way to write that, and the resulting allOf is then unsatisfiable as a whole.

All of the above is checkable offline — transform_schema needs no API key — so the regression and the composition cases can go straight into tests/lib/_parse/test_transform.py.

@nightcityblade
nightcityblade force-pushed the fix/issue-1764-type-arrays branch from f7ac16c to 6147091 Compare August 10, 2026 03:22
@nightcityblade

Copy link
Copy Markdown
Author

Thanks for the thorough validation. I updated the current head (6147091) to cover the gaps you found:

  • Empty type arrays now raise a clear ValueError instead of producing an unsatisfiable empty anyOf.
  • The regression coverage now includes a single-element array, a nullable object carrying properties and required, and composition with anyOf, oneOf, and allOf.
  • Type-specific constraints continue to move onto the non-null branches before recursive transformation.

Local validation passed: all 17 transformer tests, plus the repository's full lint suite (ruff, dependency-cap check, pyright, mypy, and import check).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

transform_schema raises AssertionError on valid list-form types, e.g. {"type": ["string", "null"]}

2 participants