Skip to content

fix(lib): keep description and title beside $ref in transform_schema - #2017

Open
Parsiffall1 wants to merge 2 commits into
anthropics:mainfrom
Parsiffall1:fix/transform-schema-ref-description
Open

Parsiffall1 wants to merge 2 commits into
anthropics:mainfrom
Parsiffall1:fix/transform-schema-ref-description

Conversation

@Parsiffall1

Copy link
Copy Markdown

Summary

transform_schema early-returned on $ref and dropped sibling keywords. Pydantic puts Field(description=...) (and sometimes title) beside $ref for enum and model fields, so those descriptions never reached the API even though the API accepts and follows them.

This change preserves description and title on the $ref early-return path, consistent with how they are kept for other schema shapes.

Fixes #2016

Test plan

  • pytest tests/lib/_parse/test_transform.py — 16 passed (includes two new regression cases)
  • Manual check: pydantic Field(description=...) on an enum field survives transform_schema(Answer)["properties"]["level"]

@Parsiffall1
Parsiffall1 requested a review from a team as a code owner October 11, 2026 17:03
Pydantic places Field(description=...) next to $ref for enum and model
fields. transform_schema previously early-returned on $ref and dropped
those siblings, so the descriptions never reached the API.

Preserve description and title on the $ref early-return path, matching
how they are handled for other schema shapes. Add regression tests.

@chrikrah chrikrah 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.

@Parsiffall1 I'd merge this. With pydantic 2.12.5 every enum or model field that carries Field(description=...) loses it at b4b7916, and at 6b7bcb8 all three come through.

$ python -I probe2017.py   # Answer model: level, lvl_default (enum), inner (model), maybe (Optional[enum])
# origin/main b4b7916
{"level": {"$ref": "#/$defs/Level"}, "lvl_default": {"$ref": "#/$defs/Level"}, "inner": {"$ref": "#/$defs/Inner"}, "maybe": {"anyOf": [...], "description": "Optional enum.\n\n{default: None}"}}
# PR head 6b7bcb8
{"level": {"$ref": "#/$defs/Level", "description": "Always choose low."}, "lvl_default": {"$ref": "#/$defs/Level", "description": "Defaulted."}, "inner": {"$ref": "#/$defs/Inner", "description": "Nested model."}, "maybe": ...unchanged}

$ pytest tests/lib/_parse/test_transform.py -q
16 passed                                   # PR head
2 failed, 14 passed                         # same tests, _transform.py from b4b7916
FAILED ...::test_ref_schema_preserves_sibling_title
FAILED ...::test_ref_schema_preserves_sibling_description
$ ruff check src/anthropic/lib/_parse tests/lib/_parse   # ruff 0.15.6
All checks passed!
# not run: a live request, so whether the API honours a description beside $ref rests on the PR's word

non-blocking: lvl_default above is pydantic's {"$ref": ..., "default": "low", "description": ...}, and the default still vanishes. Every other branch folds a leftover keyword into the description (maybe gets {default: None}), so the $ref branch is now the one place that drops it without a trace. I'd run that same fold before this return. It can't simply fall through, since the type check below would raise. Tried locally: lvl_default becomes "Defaulted.\n\n{default: low}" and the 16 tests still pass. Fine as a follow-up if you'd keep this diff to the two keys.

@dtmeadows-ant the open call is yours: fold default into this PR, or take it as a separate one after the merge?

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.

transform_schema drops description beside $ref, which the API accepts and follows

2 participants