Skip to content

fix(workflow): preserve explicit null values in typed node inputs - #7430

Open
hulkbig wants to merge 13 commits into
google:mainfrom
hulkbig:tmp/fix-workflow-nullable-input-20261006
Open

hulkbig wants to merge 13 commits into
google:mainfrom
hulkbig:tmp/fix-workflow-nullable-input-20261006

Conversation

@hulkbig

@hulkbig hulkbig commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Describe the bug

Workflow input normalization drops valid nullable fields by serializing Pydantic models with exclude_none=True. A required nullable value becomes missing before the function validates its input again, and explicit null on a field with a non-null default can silently become that default, including in nested models.

Preserve None during input normalization with a keyword-only exclude_none option on validate_node_data. Its existing True default retains output validation and other callers' behavior. Regressions cover required/defaulted/nested nulls, omitted fields, message and predecessor inputs, and model/list/dictionary serialization.

Reproduction

Define a model with value: str | None and a workflow function accepting node_input of that model type. Run Workflow(edges=[(START, consume)]) through InMemoryRunner with a user message containing {"value": null}, or pass {'value': None} from a preceding node. Before the fix, this raises Field required; a non-null field default instead replaces explicit null. After the fix, explicit null reaches the function as None, an omitted field uses its default, and an omitted required nullable field remains invalid.

The seven public-workflow regressions give four failures and three control passes with the pristine base methods; all seven pass with this change. The original built-wheel replay through InMemoryRunner also passes all seven scenarios. No model API is involved.

Current revision and verification

Current head b80d408ba012cdcc7a786ca6e249d832c7a0a3bc normally merges upstream main 7099ce45fa7b55d708c96e1696aa03f15067756d. Relative to main, the diff is the original four-file nullable-input fix plus a five-line GKE test-fixture correction (146 insertions, 3 deletions across five files).

The mocked Kubernetes API now returns a real V1Job with string owner fields. This restores five GKE test scenarios that also fail on pristine main with Kubernetes 37.0.0. The production GKE implementation and dependency declarations are unchanged; no dependency downgrade or test filter is used.

Full supported-version tox matrix

The original pytest tests/unittests command completed on dad5ca17894ad4a0c34962326db118d24e7f0181 in the repository's isolated tox environments:

Python Passed Skipped Xfailed Non-strict xpassed Failed/errors
3.11.17 18,136 90 24 2 0
3.12.15 18,127 91 24 2 0
3.13.11 18,127 91 24 2 0
3.14.8 18,127 91 24 2 0

All use Kubernetes 37.0.0, Pydantic 2.13.5, and the optional Agent Platform / Vertex SDK pair 2.4.0 on macOS arm64. The full command is unfiltered; the two xpasses are non-strict upstream cases and all four tox exit codes are zero.

Final published-head verification

Subsequent normal upstream merges include LiteLLM parsing and the latest A2A, CLI, evaluation-rubric, Runner and cache-analyzer changes through 7099ce45. The authored PR patch, dependency declarations, lock file, tox and hook configurations remain byte-identical to the full-matrix commit.

  • On this final head, the complete workflow, utils, A2A, CLI, evaluation and flow suites, runner tests, Model Armor tests, GKE executor tests and affected LiteLLM module pass on each of Python 3.11–3.14: 6,814 passed, 57 skipped, 8 xfailed, with zero failures/errors. Unaffected full-suite evidence above is reused with its exact source scope; this is scoped validation, not a second full-suite run on the final SHA.
  • The final isolated offline flit wheel builds, passes CRC verification, and contains byte-identical source for all 12 checked runtime modules, including the five latest upstream modules.
  • Mypy 2.4.0 against exact pristine main in the same Python 3.12 dependency environment: both baseline and PR have 808 existing diagnostics, zero new PR errors. This is the local test-extra comparison, not the hosted all-extras/platform matrix.
  • The public seven-case InMemoryRunner reproduction on current main gives four expected failures and three control passes; the fix passes all seven. The original four-file pre-commit proof remains applicable by byte-identical patch/config checks. The new GKE fixture passes pinned Pyink 25.12 / isort 8.0.1, compliance, header and diff checks; current guide links also pass.

Required hosted CI (including other platform/API-version matrices), repository-wide hosted pre-commit and maintainer review remain pending. AI assistance was used for implementation and independent review/validation.

hulkbig added 12 commits October 6, 2026 23:01
AI-assisted implementation and independent AI review. Temporary own-fork review branch, part 1 of 4; not submitted as an upstream contribution. This commit alone is incomplete.

Preserve None only when requested for input normalization. The complete four-file change was tested: 143 focused tests passed; independent InMemoryRunner matrix verified 11 expected behaviors. Broader related run: 1218 passed, 1 skipped, 5 xfailed, 9 setup errors from missing mocker; an A2A-dependent module was excluded.

Formatting checks, full pre-commit, tox/Python-version matrix, complete suite, and clean-wheel E2E remain unverified.
AI-assisted implementation and independent AI review. Temporary own-fork review branch, part 2 of 4; not submitted as an upstream contribution. Regression test files follow.

The complete four-file change passed 143 focused tests and an independent 11-behavior InMemoryRunner matrix. Full formatter/pre-commit checks, complete repository suite, tox/Python-version matrix, and clean-wheel E2E remain unverified.
AI-assisted implementation and independent AI review. Temporary own-fork review branch, part 3 of 4; not submitted as an upstream contribution. Workflow regression test file follows.

The complete four-file change passed 143 focused tests and an independent 11-behavior InMemoryRunner matrix. Full formatter/pre-commit checks, complete repository suite, tox/Python-version matrix, and clean-wheel E2E remain unverified.
Input normalization currently omits validated BaseModel fields whose value is None. Re-validating that normalized input can reject a required nullable field or replace an explicit None with a non-None default. Preserve None only for input normalization and retain the existing output-schema omission behavior.

Add regression coverage for message and dictionary inputs, nullable defaults, nested models, and missing required nullable fields.

AI-assisted implementation and independent AI review. Temporary review branch; formatting checks are incomplete. Not submitted as an upstream contribution. This completes the four reviewed files.

Verification on Python 3.12.14:
- Pristine-baseline new Workflow cases: 4 failed, 3 passed.
- Patched focused tests: 143 passed.
- Independent public InMemoryRunner matrix: 11 expected behaviors verified.
- git diff --check, compileall, and repository compliance checks passed.
- Broader related run: 1218 passed, 1 skipped, 5 xfailed, 9 setup errors from a missing mocker fixture; an A2A-dependent module was excluded.

Limits: full pre-commit/formatter checks, tox/Python-version matrix, complete repository suite, and clean-wheel E2E remain unverified. This input-only change does not alter typed output None omission or other repeated-coercion behavior.
Apply the configured pyink formatting to the two regression test files.
Their ASTs and the production implementation are unchanged.

AI assistance: OpenAI Codex assisted formatting and validation.
Preserve all upstream sync-generator tests and nullable-input regressions.
Related workflow/schema/runner suite: 1285 passed, 1 skipped, 5 xfailed.
Four-file pre-commit framework passed without changing bytes.

AI assistance was used for conflict resolution and validation.
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.

1 participant