Skip to content

fix(events): preserve subclass validation aliases - #7922

Open
1828703450qqcom-ux wants to merge 1 commit into
google:mainfrom
1828703450qqcom-ux:fix/events-respect-validation-aliases
Open

1828703450qqcom-ux wants to merge 1 commit into
google:mainfrom
1828703450qqcom-ux:fix/events-respect-validation-aliases

Conversation

@1828703450qqcom-ux

@1828703450qqcom-ux 1828703450qqcom-ux commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Link to Issue or Description of Change

No existing issue; bug report and reproduction are included below.

Problem:

Event._accept_convenience_kwargs preserves subclass field names and alias, but does not account for Pydantic's validation_alias. It consumes message, state, route, or node_path before subclass field validation can read them.

For example, the following event should store domain data in destination, but instead raises a missing-field ValidationError and treats the input as actions.route:

from google.adk.events import Event
from pydantic import Field

class DomainEvent(Event):
    destination: str = Field(validation_alias="route")

event = DomainEvent(route="domain_destination")
assert event.destination == "domain_destination"
assert event.actions.route is None

The same issue affects AliasChoices and AliasPath, including paths nested inside choices. This contradicts the validator's existing contract that subclass fields and their aliases retain normal validation behavior.

Solution:

Reserve string validation aliases and the top-level keys consumed by AliasPath / AliasChoices before routing convenience kwargs. A nested leaf such as AliasPath("payload", "route") does not reserve the unrelated top-level route kwarg. Existing field-name and regular-alias handling is retained.

Testing Plan

Unit Tests:

  • Added regression coverage for all four convenience kwargs with string aliases, choices, paths, and choices containing paths (16 cases), plus a nested-leaf boundary case.

  • Added a real Runner/Workflow regression proving an aliased domain field does not divert execution away from the next node.

  • All 16 parameterized cases fail against the original implementation and pass with this fix.

  • pytest tests/unittests/events tests/unittests/workflow/test_workflow.py -q: 323 passed on Python 3.13 (5 existing warnings).

  • Supported-version matrix: tox -q --override 'testenv.commands=pytest tests/unittests/events tests/unittests/workflow/test_workflow.py -q' passes on Python 3.11, 3.12, 3.13, and 3.14, with 323 passing tests per version.

  • mypy src/google/adk/events/event.py: Success: no issues found in 1 source file.

  • Formatting, Ruff, isort, pyink, and codespell checks pass. Three repository-local hooks cannot launch through their Unix shebangs on Windows (/bin/bash missing / WinError 1920); their equivalent Python entry points were run directly and passed: scripts/check_new_py_files.py --new-dir ., scripts/compliance_checks.py with the three changed files, and scripts/check_docs.py.

  • Full-suite attempt: tox collected 22,656 tests on Python 3.11 and encountered an unrelated failure in TestRemoteA2aAgentResolution.test_ensure_resolved_uses_custom_factory (CustomFactory has no _httpx_client, line 643). Running that test against unchanged main at 95d8610ea3445ce9c6d0cf70aed1a0071ddc1e2f reproduces the identical failure. The full run was stopped; no full-suite pass is claimed.

  • Additional compatibility check in a disposable checkout combining this PR at fc4e1ff with the original workflow route fix fix(workflow): preserve routes when flushing and delegating output #7931 at 868a2c6: all event and workflow tests report 1545 passed, 2 failed, 1 xfailed on Python 3.13. Both failures are the same Windows-only ExecuteBashTool approval cases that reproduce on unchanged main (the tool explicitly supports POSIX only). This is an integration check, not a dependency; no integration merge commits are included in this PR.

Manual End-to-End (E2E) Tests:

Run the following script from an installed checkout (uv run python /path/to/repro.py). It uses a real Runner, Workflow, and in-memory session service; no model or cloud credentials are needed.

import asyncio
import json

from google.adk.events import Event
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.workflow import START, Workflow
from google.genai import types
from pydantic import AliasChoices, AliasPath, Field


class DomainEvent(Event):
    destination: str = Field(
        validation_alias=AliasChoices(
            "destination_value", AliasPath("route", "target")
        )
    )


async def emit_event():
    yield DomainEvent(route={"target": "domain_destination"}, output="ready")


def consume(node_input: str) -> str:
    return f"{node_input} processed"


async def main():
    sessions = InMemorySessionService()
    session = await sessions.create_session(app_name="alias_check", user_id="user")
    workflow = Workflow(name="alias_flow", edges=[(START, emit_event, consume)])
    runner = Runner(app_name="alias_check", node=workflow, session_service=sessions)
    events = [
        event
        async for event in runner.run_async(
            user_id="user",
            session_id=session.id,
            new_message=types.Content(role="user", parts=[types.Part(text="start")]),
        )
    ]
    outputs = [event.output for event in events if event.output is not None]
    routes = [event.actions.route for event in events if event.actions.route is not None]
    errors = [event.error_code for event in events if event.error_code]
    print(json.dumps({"outputs": outputs, "routes": routes, "errors": errors}))
    assert "ready processed" in outputs
    assert routes == []
    assert errors == []


asyncio.run(main())

Original main fails with ValidationError (invalid actions.route and missing destination_value). With the fix, the script completes and prints:

{"outputs": ["ready", "ready processed"], "routes": [], "errors": []}

Checklist

  • I have read CONTRIBUTING.md.
  • I have performed a self-review of my own code.
  • I have commented the top-level validation-alias handling.
  • I have added tests that prove my fix is effective.
  • New and existing tests in the affected modules pass locally.
  • I have manually tested my changes end-to-end.
  • No dependent downstream changes are required.

AI assistance: Codex investigated, implemented, and verified this change.

Convenience kwargs consumed validation aliases before subclass validation, causing missing fields and unintended workflow routes. Reserve validation alias inputs, including the top-level keys of alias paths and choices, before routing convenience kwargs.
@google-cla

google-cla Bot commented Nov 1, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

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.

2 participants