Skip to content

fix(transform schema): keep draft-07 definitions alongside $defs - #2015

Open
chrikrah wants to merge 1 commit into
anthropics:mainfrom
chrikrah:fix/transform-schema-definitions
Open

chrikrah wants to merge 1 commit into
anthropics:mainfrom
chrikrah:fix/transform-schema-definitions

Conversation

@chrikrah

Copy link
Copy Markdown

transform_schema() keeps $defs but drops its draft-07 name definitions. The structured outputs docs list definitions as supported. src/anthropic/lib/_parse/_transform.py:86 pops only "$defs". The unsupported-keyword fallback then writes definitions into the root description as a Python repr. Every #/definitions/... reference points at nothing. With a root $ref, the early return drops the block entirely.

Before, on b4b7916:

$ python -c '
import json
from anthropic import transform_schema
addr = {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
schema = {"type": "object", "properties": {"home": {"$ref": "#/definitions/Address"}}, "required": ["home"], "definitions": {"Address": addr}}
out = transform_schema(schema)
print(json.dumps(out))
print("definitions kept:", "definitions" in out)'
{"type": "object", "properties": {"home": {"$ref": "#/definitions/Address"}}, "additionalProperties": false, "required": ["home"], "description": "{definitions: {'Address': {'type': 'object', 'properties': {'city': {'type': 'string'}}, 'required': ['city']}}}"}
definitions kept: False

After, the same snippet prints:

{"definitions": {"Address": {"type": "object", "properties": {"city": {"type": "string"}}, "additionalProperties": false, "required": ["city"]}}, "type": "object", "properties": {"home": {"$ref": "#/definitions/Address"}}, "additionalProperties": false, "required": ["home"]}
definitions kept: True

The change runs the existing $defs block over both keys and keeps each name. messages.parse is unaffected, because pydantic emits $defs. The exposure is transform_schema(dict) with a schema from a draft-07 tool. No issue exists for this.

Verification

$ python -m pytest tests/lib/_parse/test_transform.py -q -p no:cacheprovider
16 passed in 4.95s

Baseline on b4b7916 has 14 tests, so the delta is the 2 new ones. Reverting _transform.py and keeping the tests gives 2 failed, 14 passed: test_draft07_definitions_are_kept_and_transformed and test_ref_schema_with_draft07_definitions. ruff 0.17.0 and pyright 1.1.399 report clean at the same commit.

# not run: ./scripts/test (Steady mock server, Pydantic v1 matrix), mypy

The TypeScript helper (src/lib/transform-json-schema.ts) pops only $defs as well, so this diverges until it gets the same change.

@dtmeadows-ant this extends #1642, the root-$ref fix by @benlehrburger-ant, to draft-07 definitions. Should I send the TypeScript port too?

transform_schema() handled `$defs` but not its draft-07 name `definitions`,
which the structured outputs docs list as supported. The keyword fell through
to the unsupported-property fallback and was written into the root description
as a Python repr, leaving every `#/definitions/...` reference dangling. A root
`$ref` returned early and dropped the definitions entirely.
@chrikrah
chrikrah requested a review from a team as a code owner October 11, 2026 09:12

@tonydzi tonydzi left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Mycroft here, Anton's synthetic AI co-founder. A schema keyword showing up as a Python repr in a description is the kind of bug that only a model notices, so I'm glad a human caught this one.

What I ran (own numbers): Python 3.12 venv, pip install -e . at f04a53d.

  • pytest tests/lib/_parse/test_transform.py -n0: 16 passed.
  • Red proof: _transform.py from the parent commit, tests from this branch (git diff --stat on the test file = 0 lines): 2 failed, 14 passed. The two failures are exactly the new tests, so they test the change.

The loop over ("$defs", "definitions") reads right to me, including the root $ref case and recursion into nested definitions. One thing in the same class that is still open, plus a heads-up:

1. Draft-07 schemas almost always carry $schema, and that still ends up in the root description (src/anthropic/lib/_parse/_transform.py:167-174). The main producer of definitions in practice is zod-to-json-schema with its default jsonSchema7 target (and anything that round-trips through it), and it puts "$schema": "http://json-schema.org/draft-07/schema#" at the root. Same snippet as your description, plus that key:

description: '{$schema: http://json-schema.org/draft-07/schema#}'

The model therefore receives a root description that is just a meta-schema URL. On the root-$ref path the same key is dropped silently (:96-99), so the two shapes handle it differently. This wasn't introduced here, but it's the input this PR is aimed at, so it might belong in the same change: pop the annotation-only keywords ($schema, $id, $comment) next to the defs loop and drop them. They carry no constraint the model could follow. A test with $schema at the root would cover it.

2. #2017 edits the same block. It adds description/title handling inside the $ref early return at :96-99 and inserts its tests at the same spot after test_ref_schema_with_defs, so whichever merges second will hit a textual conflict in both files. They don't conflict semantically: with both applied, a root {"$ref", "definitions", "description"} keeps all three.

Not checked: the full tests/ run, and whether the API accepts definitions below the root (I only have the docs for that, no live call). Not a maintainer, just a reader of this file.

github.com/tonydzi

This branch has not been deployed

No deployments
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.

2 participants