fix: address PR review comments for model_dump_with_preserved_fields

- Restore preserve_fields param for backward compatibility (deprecated)
- Use zip() instead of index-based iteration to prevent IndexError
- Add backward compatibility test
This commit is contained in:
Ryan Crabbe
2026-02-21 10:48:10 -08:00
parent 19122ed271
commit c071e01fb6
2 changed files with 39 additions and 6 deletions
+3 -6
View File
@@ -4565,9 +4565,6 @@ def validate_model_access(
)
# Fields within each choice that must be preserved as null (not stripped)
# for OpenAI API compatibility. Each tuple is (sub_object, field_name).
# To add a new preserved field, just append a tuple here.
_PRESERVED_NONE_FIELDS: List[tuple] = [
("message", "content"), # null when tool_calls present (issue #6677)
("message", "role"), # always required by OpenAI spec
@@ -4577,6 +4574,7 @@ _PRESERVED_NONE_FIELDS: List[tuple] = [
def model_dump_with_preserved_fields(
obj: Any,
preserve_fields: Optional[List[str]] = None,
exclude_unset: bool = True,
) -> Dict[str, Any]:
"""
@@ -4588,6 +4586,7 @@ def model_dump_with_preserved_fields(
Args:
obj: The Pydantic BaseModel instance to serialize
preserve_fields: Deprecated, kept for backward compatibility.
exclude_unset: Whether to exclude fields that were not explicitly set
Returns:
@@ -4600,9 +4599,7 @@ def model_dump_with_preserved_fields(
return result
obj_choices = obj.choices
for i, choice_dict in enumerate(choices):
choice_obj = obj_choices[i]
for choice_obj, choice_dict in zip(obj_choices, choices):
for sub_object, field_name in _PRESERVED_NONE_FIELDS:
sub_dict = choice_dict.get(sub_object)
if sub_dict is None:
@@ -375,3 +375,39 @@ def test_delta_dynamic_attributes_in_model_dump():
delta_no_role = Delta(content=None, role=None)
dump_no_role = delta_no_role.model_dump(exclude_none=True)
assert "role" not in dump_no_role
def test_preserve_fields_param_backward_compat():
"""preserve_fields parameter is accepted (deprecated) without error."""
response = ModelResponse(
choices=[
Choices(
finish_reason="tool_calls",
index=0,
message=Message(
content=None,
role="assistant",
tool_calls=[
{
"id": "call_1",
"type": "function",
"function": {"name": "f", "arguments": "{}"},
}
],
),
)
],
)
result_default = model_dump_with_preserved_fields(response, exclude_unset=True)
result_explicit = model_dump_with_preserved_fields(
response,
preserve_fields=[
"choices.*.message.content",
"choices.*.message.role",
"choices.*.delta.content",
],
exclude_unset=True,
)
assert result_default == result_explicit
assert result_default["choices"][0]["message"]["content"] is None
assert result_default["choices"][0]["message"]["role"] == "assistant"