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

Abierto
#6,708 3 comentarios 0 reacciones 1 asignado Reclamado por @surajksharma07 Ver en GitHub
a2a
Lenguaje dominante
Python
Estrellas
21.5k
Forks
4k
Merge medio
1 d 14 h
PR fusionados (30 d)
37

Descripción

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.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.