fix(a2a_executor): remove shadowing local Part import that broke streaming

Python scoping rule: any name assigned anywhere in a function body
is local for the entire body. The outbound-files block at ~L442
had `from a2a.types import ... Part ...`, which made `Part` a local
name throughout the execute() function. The astream_events loop at
L358 — which runs BEFORE that import — then raised:

  UnboundLocalError: cannot access local variable 'Part' where it
  is not associated with a value

Every streaming A2A reply died with "Agent error: cannot access
local variable 'Part' where it is not associated with a value"
instead of the actual agent text. 5 tests caught it:
  - test_streaming_plain_string_content
  - test_streaming_anthropic_content_blocks
  - test_non_stream_events_ignored
  - test_core_execute_on_chat_model_end_captures_last_ai_message
  - test_core_execute_pii_redaction_when_pii_found

Fix: drop `Part` from the function-scope import (it is already
imported at module level on line 42) and leave a comment pinning
the rationale so a future refactor doesn't re-introduce the shadow.

All 43 test_a2a_executor tests pass locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hongming Wang 2026-04-24 14:21:04 -07:00
parent 425df5e5a9
commit a34121d451

View File

@ -439,7 +439,14 @@ class LangGraphA2AExecutor(AgentExecutor):
# deepagents, future ReAct variants) inherits it.
_outbound = collect_outbound_files(final_text)
if _outbound:
from a2a.types import FilePart, FileWithUri, Message, Part, Role, TextPart
# NOTE: do NOT re-import `Part` here. It is already imported
# at module scope (line 42). A function-scope `from a2a.types
# import ... Part ...` would mark `Part` as a local name
# throughout this function under Python's scoping rules,
# making the earlier `Part(text=text)` call (line ~358, inside
# the astream_events loop) raise UnboundLocalError because
# the local binding is not yet in scope at that point.
from a2a.types import FilePart, FileWithUri, Message, Role, TextPart
_parts: list[Part] = [Part(root=TextPart(text=final_text))] if final_text else []
for f in _outbound:
_parts.append(Part(root=FilePart(file=FileWithUri(