Skip to content

[Bugfix] Fix mypy errors for StructuredOutputsParams by using stdlib dataclass#34693

Closed
hyeongyun0916 wants to merge 5 commits intovllm-project:mainfrom
moreh-dev:fix-pre-commit-dataclass
Closed

[Bugfix] Fix mypy errors for StructuredOutputsParams by using stdlib dataclass#34693
hyeongyun0916 wants to merge 5 commits intovllm-project:mainfrom
moreh-dev:fix-pre-commit-dataclass

Conversation

@hyeongyun0916
Copy link
Contributor

@hyeongyun0916 hyeongyun0916 commented Feb 17, 2026

Purpose

Fix 4 mypy errors that occur when running pre-commit run --all-files locally.

The mypy-local hook (--follow-imports skip) checks all files together, causing mypy to see the actual StructuredOutputsParams definition in sampling_params.py. Since it uses pydantic.dataclasses.dataclass instead of the stdlib dataclasses.dataclass, mypy does not recognize it as a valid dataclass — which breaks dataclasses.replace() calls and constructor keyword resolution.

StructuredOutputsParams had pydantic's @dataclass applied, but dataclasses.replace() only passes mypy's type check with stdlib dataclasses. Since no pydantic-specific features (validators, etc.) are used, switching to stdlib dataclass has no impact on runtime behavior.

$ pre-commit run --all-files
Run mypy locally for lowest supported Python version...Failed

vllm/entrypoints/openai/chat_completion/protocol.py:466: error: Value of type variable "_DataclassT" of "replace" cannot be "StructuredOutputsParams"  [type-var]
vllm/entrypoints/openai/responses/protocol.py:339: error: Unexpected keyword argument "json" for "StructuredOutputsParams"  [call-arg]
vllm/entrypoints/openai/completion/protocol.py:272: error: Value of type variable "_DataclassT" of "replace" cannot be "StructuredOutputsParams"  [type-var]
vllm/entrypoints/openai/responses/serving.py:505: error: Value of type variable "_DataclassT" of "replace" cannot be "StructuredOutputsParams"  [type-var]
Found 4 errors in 4 files (checked 644 source files)

This was introduced in #22772. During that refactor, the dataclass import was switched from stdlib to pydantic, but StructuredOutputsParams does not use any pydantic-specific features (validators, etc.). If this was an intentional change, please let me know.

Fix: Change from pydantic.dataclasses import dataclass back to from dataclasses import dataclass.

Test Plan

pre-commit run mypy-local --all-files

Test Result

Run mypy locally for lowest supported Python version...Passed

Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.
  • (Optional) Release notes update. If your change is user facing, please update the release notes draft in the Google Doc.

Signed-off-by: HyunKyun Moon <mhg5303@gmail.com>
@mergify mergify bot added the bug Something isn't working label Feb 17, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly resolves mypy errors for StructuredOutputsParams by switching its decorator from pydantic.dataclasses.dataclass to the standard library's dataclasses.dataclass. Since StructuredOutputsParams does not leverage any Pydantic-specific functionality, this change effectively fixes type-checking issues encountered with dataclasses.replace() without impacting runtime behavior. The modification is well-justified and properly implemented.

Copy link
Member

@njhill njhill left a comment

Choose a reason for hiding this comment

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

Thanks @hyeongyun0916 this had also been annoying for me, though I could swear it started much more recently than #22772.

@njhill njhill requested a review from hmellor February 17, 2026 17:51
@njhill njhill added the ready ONLY add when PR is ready to merge/full CI is needed label Feb 17, 2026
Signed-off-by: HyunKyun Moon <mhg5303@gmail.com>
Signed-off-by: HyunKyun Moon <mhg5303@gmail.com>
@njhill njhill mentioned this pull request Feb 18, 2026
5 tasks
Copy link
Contributor

@hickeyma hickeyma left a comment

Choose a reason for hiding this comment

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

Thanks @hyeongyun0916 for pushing the changes. I did not see your PR when I started adding a fix in #34739, sorry about that.

I took another approach. Like you I didn't see any obvious reasons for changing to Pydantic but I didn't want to alter it in case there maybe serialization somewhere and also not knowing the background for the change. Also, it meant overriding the replace() method which I though was overkill.

As Pydantic dataclasses do not satisfy mypy's type system with replace() even though they are fully compatible at runtime, I decided to follow recommended practice which is to ignore that specific error type-var. My reasoning is that its a "known mypy/pydantic interop gap" and has no side effects.

@hyeongyun0916
Copy link
Contributor Author

Thanks @hyeongyun0916 for pushing the changes. I did not see your PR when I started adding a fix in #34739, sorry about that.

I took another approach. Like you I didn't see any obvious reasons for changing to Pydantic but I didn't want to alter it in case there maybe serialization somewhere and also not knowing the background for the change. Also, it meant overriding the replace() method which I though was overkill.

As Pydantic dataclasses do not satisfy mypy's type system with replace() even though they are fully compatible at runtime, I decided to follow recommended practice which is to ignore that specific error type-var. My reasoning is that its a "known mypy/pydantic interop gap" and has no side effects.

No worries at all! I totally agree with your approach. I’ve actually been looking into fixing the CI on my end, and as you mentioned, it was becoming unnecessarily complex. Given it's a known interop gap between mypy and Pydantic, using ignore seems like the most practical and cleanest way to handle it. Thanks for the heads up!

@hickeyma
Copy link
Contributor

No worries at all! I totally agree with your approach. I’ve actually been looking into fixing the CI on my end, and as you mentioned, it was becoming unnecessarily complex. Given it's a known interop gap between mypy and Pydantic, using ignore seems like the most practical and cleanest way to handle it. Thanks for the heads up!

Thanks @hyeongyun0916 for the feedback. How would you like to progress this and get the fix in? Do you want to update this PR and I pick up the changes in #34739 or close this PR in favour of #34739?

@hmellor
Copy link
Member

hmellor commented Feb 19, 2026

FYI we do have a pydantic compatible replace implemented here

vllm/vllm/config/utils.py

Lines 105 to 113 in 1391378

def replace(dataclass_instance: ConfigT, /, **kwargs) -> ConfigT:
"""Like [`dataclasses.replace`](https://docs.python.org/3/library/dataclasses.html#dataclasses.replace),
but compatible with Pydantic dataclasses which use `pydantic.fields.Field` instead
of `dataclasses.field`"""
cls = type(dataclass_instance)
dataclass_dict = dataclass_instance.__dict__
dataclass_dict = {k: v for k, v in dataclass_dict.items() if is_init_field(cls, k)}
dataclass_dict.update(kwargs)
return cls(**dataclass_dict)

If we want to keep the pydantic validation, I'd recommend using this instead of dataclasses.replace

@hickeyma
Copy link
Contributor

If we want to keep the pydantic validation, I'd recommend using this instead of dataclasses.replace

Thanks @hmellor - added in #34739

@hyeongyun0916
Copy link
Contributor Author

No worries at all! I totally agree with your approach. I’ve actually been looking into fixing the CI on my end, and as you mentioned, it was becoming unnecessarily complex. Given it's a known interop gap between mypy and Pydantic, using ignore seems like the most practical and cleanest way to handle it. Thanks for the heads up!

Thanks @hyeongyun0916 for the feedback. How would you like to progress this and get the fix in? Do you want to update this PR and I pick up the changes in #34739 or close this PR in favour of #34739?

I'm closing this PR in favor of that one. 👍

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

Labels

bug Something isn't working ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants