Skip to content

Python: Exclude ClassVar annotations from generated JSON schemas - #14528

Open
Pushpak Siva Sai (Pushpak731) wants to merge 3 commits into
microsoft:mainfrom
Pushpak731:python-fix-classvar-schema
Open

Pushpak Siva Sai (Pushpak731) wants to merge 3 commits into
microsoft:mainfrom
Pushpak731:python-fix-classvar-schema

Conversation

@Pushpak731

Copy link
Copy Markdown

Fixes #14527

Description

KernelJsonSchemaBuilder.build_model_schema builds the schema from get_type_hints, which includes ClassVar annotations, so a class constant became a required object-valued property in the generated schema. Pydantic excludes ClassVars from model_fields and from serialized instances, so the resulting schema rejected valid serialized payloads ({'value': 1} failed because marker was required). The same wrong schema reached KernelParameterMetadata, generated function-call tool definitions and structured_output=True schemas.

The fix skips ClassVar-annotated hints before the required/property loop — covering the parameterized form (ClassVar[str]), the bare form (ClassVar, whose get_origin is None), and inherited ClassVars, for KernelBaseModel subclasses, plain annotated classes and model instances alike.

class Payload(KernelBaseModel):
    marker: ClassVar[str] = "constant"
    value: int

KernelParameterMetadata(name="payload", type_object=Payload).schema_data
# before: properties include marker (type object) and required: [marker, value]
# after:  {'properties': {'value': ...}, 'required': ['value']}

Contribution Checklist

  • I have read the contribution guidelines.
  • The code builds clean without any errors or warnings
  • The PR follows the SK Contribution Guidelines and the pre-submit framework
  • Tests are included and pass locally: tests/unit/schema/ (45 passed) and tests/unit/functions/test_kernel_parameter_metadata.py (7 passed) — 10 new regression tests cover pydantic models, bare/inherited ClassVar, plain classes, instances, structured_output, descriptions, optionals and the serialized-payload contract
  • ruff check and ruff format --check are clean on both changed files
  • Documentation updates are not needed (no public API change; behavior now matches model_dump)

KernelJsonSchemaBuilder.build_model_schema builds the instance schema
from get_type_hints, which includes ClassVar annotations, so a class
constant became a required object-valued property. Pydantic excludes
ClassVars from model fields and serialized instances, so the generated
schema rejected valid payloads (a serialized {'value': 1} failed
validation because 'marker' was required). The builder now skips
ClassVar-annotated hints (both the parameterized and bare forms,
including inherited ones), for KernelBaseModel subclasses, plain
annotated classes and instances, covering KernelParameterMetadata,
generated tool definitions and structured_output schemas.

Fixes microsoft#14527
Copilot AI balanced review requested due to automatic review settings October 2, 2026 18:46
@Pushpak731
Pushpak Siva Sai (Pushpak731) requested a review from a team as a code owner October 2, 2026 18:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Pushpak731

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@semantic-kernel-automation semantic-kernel-automation Bot added the python Pull requests for the Python Semantic Kernel label Oct 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Community review. This does not clear the merge gate, it is one reader's check.

I reproduced the bug and checked the fix by running code. I loaded the installed semantic_kernel/schema/kernel_json_schema_builder.py (no ClassVar handling in it), then built a copy with your two added lines applied by string patch, and compared KernelJsonSchemaBuilder.build output for three classes.

  • class M(KernelBaseModel): marker: ClassVar[str] = "c"; value: int
    • current code: properties marker: {"type": "object"} and value, required ["marker", "value"]
    • with the patch: only value, required ["value"]
    • M(value=1).model_dump() is {"value": 1} and M.model_fields is ["value"], so the current schema demands a field the model never serializes, and it also types it wrongly as object.
  • Annotated[ClassVar[int], "x"] on a KernelBaseModel: same result, the patch drops it.
  • A plain (non-pydantic) class with a string annotation "ClassVar[dict]": also dropped, because get_type_hints resolves the string before the check.

The check field_type is ClassVar or get_origin(field_type) is ClassVar covers both the bare ClassVar and the subscripted form, which are the two shapes get_type_hints returns. It also fits with the loop: nothing after the continue depends on the skipped field, so required and properties stay consistent.

Notes:

  • I did not run the repo's test file or the full suite, only the builder on the three classes above. The new tests (inherited ClassVar, structured output with additionalProperties, via-instance) read correctly to me, but I did not execute them.
  • I did not check typing_extensions.ClassVar on older Pythons. It is the same object as typing.ClassVar on current versions, so I expect it to work, but that is unverified.
  • Nit: the ten new tests overlap a lot. Two or three would cover the same branches (bare, subscripted, inherited).

Approving from my side.

@Pushpak731

Copy link
Copy Markdown
Author

PRABHU KIRAN VANDRANKI (@VANDRANKI) thanks again for the approval! The branch had fallen behind main, so I merged main into it (46721b2 — no code changes beyond the merge; our ClassVar fix is untouched and main's new commits are unrelated). Could you re-confirm the approval on the updated head whenever convenient? Happy to make any further changes if the merge surfaced anything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Community re-review on head 46721b2. Still a community review, it does not clear the merge gate.

What I checked on the new head:

  • The merge changes nothing in the PR. git diff cc8a15fa3 46721b2 (cc8a15f is the current main tip) is only the two files, kernel_json_schema_builder.py (+6/-1) and test_schema_builder.py. Both files are byte-identical to the previous head 7139056 (empty diff on each).
  • I ran tests/unit/schema on the PR head: 45 passed. With the same test file against main's builder: 9 failed, 36 passed, so the ClassVar tests do guard the fix. tests/unit/functions, tests/unit/kernel and tests/unit/schema together: 332 passed.
  • I did not run the full suite or the other connector test folders. At the time of checking, only license/cla and add_label had reported on this head, so the Python test jobs had not run on it yet.

My earlier approval stands for this head.

This branch was successfully deployed

1 active deployment
github-app-auth — 46721b28 Deployed Oct 9, 2026 by Pushpak731 via add_label #29307
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests for the Python Semantic Kernel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.Net: Python: Bug: ClassVar annotations become required fields in generated tool schemas

3 participants