google / google/adk-python

Follow-up to #4309: fix for failed A2A tasks needs both _handle_a2a_response/_v2, _compat.TS_FAILED, and error_code — still reproducible in 2.6.2

Ouverte
#6,708 3 commentaires 0 réactions 1 personne assignée Réclamée par @surajksharma07 Voir sur GitHub
a2a
Langage dominant
Python
Étoiles
21.5k
Forks
4k
Merge moyen
1 j 14 h
PR mergées (30 j)
37

Description

Follow-up to #4309 ("Failed A2A task errors leak into conversation history as regular content"), closed for lack of follow-up rather than a fix. Filing separately since we hit this independently on a newer version and found a few things the original report and its proposed workaround don't cover — happy to have this folded into #4309 instead if that's preferred.

**Still present in `google-adk==2.6.2`** (Python 3.12). `convert_a2a_task_to_event()` (`google/adk/a2a/converters/event_converter.py`) never reads `a2a_task.status.state`; it only extracts message content and returns a plain `Event`. Same root cause as reported for 1.1.0 → 1.24.1.

**This is a round-trip asymmetry, not just a missing check.** `convert_event_to_a2a_events()` (used when ADK is the A2A *server*) already does the reverse correctly: if `event.error_code` is set, `_create_error_status_event()` builds a `TaskStatus(state=TASK_STATE_FAILED, ...)`. So ADK already models "failed task" as a first-class A2A state on the way out — it just never reads that same state back in on the way in. Two ADK instances talking A2A to each other already lose this, not only ADK-vs-third-party.

Three things worth folding into whatever fix lands:

1. **Which hook actually fires.** `RemoteA2aAgent` dispatches each response to either `_handle_a2a_response` or `_handle_a2a_response_v2` depending on whether the task carries ADK's own integration-extension marker. A third-party (non-ADK) A2A server never sets that marker, so *every* response goes through `_handle_a2a_response` — `_handle_a2a_response_v2` never fires against it at all. A workaround/fix that only patches `_v2` silently does nothing in that setup.
2. **Streaming vs non-streaming shape.** With `streaming=True` the terminal state arrives as an `A2ATaskStatusUpdateEvent` (`update.status.state`), not on the `Task` itself (`task.status.state`) — already flagged in #4309, restating because both hooks from point 1 need to handle both shapes.
3. **`TaskState.failed` isn't version-safe.** The proposed fix in #4309 compares against `TaskState.failed`, which only exists on the pydantic-enum shape of `a2a-sdk` (0.3.x). On the protobuf shape (1.x, e.g. `a2a-sdk==1.1.2`, bundled with `google-adk==2.6.2`), `TaskState` has no `.failed` attribute at all — the value is `TaskState.Value("TASK_STATE_FAILED")`. ADK already has a shim for exactly this split: `google.adk.a2a._compat.TS_FAILED`. Any fix should compare against that instead of a hardcoded enum member.

Also: the proposed fix sets `error_message` but not `error_code`. That stops the leak into history, but callers that want to tell "remote task failed" apart from other `RemoteA2aAgent` error paths generally key off `error_code` (which exists on `Event` for exactly that). Worth setting a stable code (e.g. `"A2A_TASK_FAILED"`) alongside the message.

Sketch (untested against internals of every version, but shows the shape):

```python
from google.adk.a2a import _compat

if task and task.status and task.status.state == _compat.TS_FAILED:
event = Event(
author=self.name,
error_code="A2A_TASK_FAILED",
error_message=extracted_text_from(event) or "Remote agent task failed",
invocation_id=ctx.invocation_id,
branch=ctx.branch,
)
```

applied in both `_handle_a2a_response` and `_handle_a2a_response_v2`, checking both the `Task` and `TaskStatusUpdateEvent` shapes.

**Environment:** `google-adk` 2.6.2, `a2a-sdk` 1.1.2, Python 3.12.

Happy to close as a duplicate of #4309 if you'd rather track everything there.

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.