Skip to content

fix(serialization): preserve validation error messages when deserializing invalid components - #270

Open
fede-kamel wants to merge 1 commit into
oracle:mainfrom
fede-kamel:fix/deserializer-validation-errors
Open

fede-kamel wants to merge 1 commit into
oracle:mainfrom
fede-kamel:fix/deserializer-validation-errors

Conversation

@fede-kamel

Copy link
Copy Markdown
Member

Fixes #269.

Root cause

PydanticComponentDeserializationPlugin.deserialize collects the validation errors of a component and rebuilds a ValidationError from InitErrorDetails(type=e.type, loc=e.loc, input=()). pydantic cannot render error types such as value_error, which is what every @model_validator_with_error_accumulation validator raises, from the type alone: they need the context of the original error. ValidationError.from_exception_data therefore raised TypeError: ValueError: 'error' required in context and the real message (the unreachable end node, the input missing from the prompt, ...) was lost for every from_dict, from_json and from_yaml call on an invalid component.

Changes

  • serialization/pydanticdeserializationplugin.py: wrap the original error type and message in a PydanticCustomError when rebuilding the line errors. This preserves both for every error type without needing the original context. The partial-construction path (from_partial_dict, which returns the collected errors) is unchanged.
  • tests/serialization/test_serialization.py: regression tests covering a Flow with an unreachable EndNode through Component.from_dict, from_json, from_yaml and AgentSpecDeserializer.from_dict, and an Agent declaring an input that is not in its prompt. They assert the ValidationError carries the original message and the value_error type. All fail on main with the TypeError, pass with the fix.
  • Changelog entry under Bug fixes.

Verification

  • SKIP_LLM_TESTS=1 pytest tests --ignore=tests/adapters --ignore=tests/evaluation: 912 passed, 407 skipped (Python 3.13).
  • black, isort, flake8 (the only E501 hits are pre-existing lines) and mypy on the serialization package are clean.

@fede-kamel
fede-kamel requested a review from a team October 1, 2026 15:10
@oracle-contributor-agreement oracle-contributor-agreement Bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Oct 1, 2026
@fede-kamel

Copy link
Copy Markdown
Member Author

LGTM. The fix to wrap validation error types in so messages are preserved is clean and targeted. 912 tests pass. Ready for maintainer review.

@fede-kamel fede-kamel left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Clean fix for the deserialization error regression.

Root cause is clearly identified

InitErrorDetails(type=e.type, loc=e.loc, input=()) loses the error context that pydantic needs for types like value_error. Rebuilding without context causes PydanticCustomError to raise TypeError: ValueError: 'error' required in context, swallowing the real validation message.

Fix is minimal and correct

Wrapping (e.type, e.msg) in PydanticCustomError preserves both the error type and message together, so pydantic can reconstruct the line error without needing the original input context. The partial-construction path (from_partial_dict) is unchanged — good, that one already returns collected errors correctly.

Tests are thorough

All 4 deserialization paths are tested (from_dict, from_json, from_yaml, deserializer_from_dict), covering both custom validator errors (unreachable end node) and standard pydantic errors (extra IO properties, field type mismatches).

No blockers.

…zing invalid components

When a component failed validation during Component.from_dict,
from_json or from_yaml, PydanticComponentDeserializationPlugin.deserialize
rebuilt the ValidationError from the collected error type and location
only. Pydantic error types such as value_error, which is what every
@model_validator_with_error_accumulation validator raises, cannot be
rebuilt without the context of the original error, so the deserializer
crashed with "TypeError: ValueError: 'error' required in context" and
the real message, for instance the end node without incoming control
flow edge, was lost. The rebuilt line errors now wrap the original type
and message in a PydanticCustomError, which keeps the error type and the
message for every error type. The partial construction path
(build_from_partial_config, get_validation_errors) is unchanged.

Fixes oracle#269.
@fede-kamel
fede-kamel force-pushed the fix/deserializer-validation-errors branch from 3e73dd2 to 6107693 Compare October 9, 2026 19:16

@fede-kamel fede-kamel left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review

The fix correctly addresses the root cause: Pydantic error types like value_error cannot be reconstructed from their type alone — they need context to render their message.

Key observations:

  1. PydanticCustomError(e.type, e.msg) — wrapping the type and message preserves both without needing the original error context. This is the right approach for @model_validator_with_error_accumulation validators.

  2. Tests cover all code paths:

    • test_deserialization_reports_custom_validation_errors verifies the flow validation case (unreachable end node) across from_dict, from_json, from_yaml, and Deserializer.from_dict.
    • test_deserialization_reports_extra_io_property_errors checks the Agent I/O property validation error.
    • test_deserialization_reports_field_type_errors confirms standard Pydantic type errors still work.
  3. Changelog entry is clear and actionable for end users.

One question: The PydanticCustomError preserves e.type and e.msg, but PydanticCustomError.__init__ expects (type: str, message: str). Are there any error types where e.type might not be a simple string, or e.msg could be None? Looking at the Pydantic source, ValidationError.error is ErrorDetails which has type: str and msg: str, so this should be safe.

Minor nit: The changelog says @model_validator_with_error_accumulation — this is a pyagentspec decorator, not a Pydantic builtin. Good to mention that this is a custom pyagentspec validator.

Overall, clean fix with thorough test coverage.

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

OCA Verified All contributors have signed the Oracle Contributor Agreement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deserializing an invalid component raises TypeError: ValueError: 'error' required in context instead of the validation message

1 participant