a2aproject / a2aproject/a2a-python

[Feat]: Add RetryTransport for automatic retry with exponential backoff

Abierto
#871 6 comentarios 0 reacciones 1 asignado Asignado a @cchinchilla-dev Ver en GitHub
component: client
Lenguaje dominante
Python
Estrellas
2.1k
Forks
496
Merge medio
4 d 17 h
PR fusionados (30 d)
12

Descripción

### Is your feature request related to a problem? Please describe.

The SDK's transports (`JsonRpcTransport`, `RestTransport`, `GrpcTransport`) raise immediately on transient failures (network errors, timeouts, rate limits, 5xx responses, gRPC `UNAVAILABLE`/`RESOURCE_EXHAUSTED`) with no built-in retry mechanism. Every caller has to reimplement the same retry/backoff loop, and to do it correctly they have to inspect `__cause__` chains because `A2AClientError` doesn't expose HTTP status codes or gRPC codes directly.

### Describe the solution you'd like

A `RetryTransport` decorator wrapping any `ClientTransport`, mirroring the existing `TenantTransportDecorator` pattern:

```python
inner = JsonRpcTransport(httpx_client=client, agent_card=card)
transport = RetryTransport(base=inner, max_retries=3)
```

The default predicate retries on:
- `A2AClientTimeoutError` (always).
- `A2AClientError` chained from `httpx.RequestError` or `httpx.HTTPStatusError(408/429/502/503/504)`.
- `A2AClientError` chained from `grpc.aio.AioRpcError(UNAVAILABLE/RESOURCE_EXHAUSTED)`.
- Domain errors (`TaskNotFoundError`, etc.) are never retried.

`retry_predicate` is configurable for custom logic, and `on_retry` is exposed as a hook for logging/metrics. Streaming methods (`send_message_streaming`, `subscribe`) only retry before the first event is yielded.

### Describe alternatives you've considered

- Retry via `ClientCallInterceptor` — not feasible; `after()` only fires on successful results, so interceptors never see exceptions.
- Transport-specific retry (httpx custom transport, gRPC `retryPolicy` / `UnaryUnaryClientInterceptor`) — operates below the SDK's exception layer, requires two separate configurations, and doesn't cover all three transports uniformly.
- Retry inside each transport implementation — triples the logic across `JsonRpcTransport`, `RestTransport`, `GrpcTransport` and forces every future transport to reimplement it.

### Additional context

Purely additive: new `RetryTransport` class, tests, and one export change. No new dependencies — the `grpc` import is conditional, matching the SDK's existing pattern. `ClientFactory` integration is left as a follow-up.

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

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.