8000 [Bug]: A2A Server (Starlette) Fails with HTTP 503/ReadTimeout when Task Artifact Contains Multiple TextParts · Issue #112 · google-a2a/a2a-python · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[Bug]: A2A Server (Starlette) Fails with HTTP 503/ReadTimeout when Task Artifact Contains Multiple TextParts #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 task done
theailifestyle opened this issue May 26, 2025 · 1 comment

Comments

@theailifestyle
Copy link

What happened?

  • When an A2A agent's AgentExecutor produces a final result that includes an Artifact containing multiple a2a.types.TextPart objects, the A2A server (using A2AStarletteApplication and DefaultRequestHandler) fails to send a successful response to the client. Clients experience an httpx.ReadTimeout or an HTTP 503 error.

  • Server-side logs from the AgentExecutor show that it correctly processes the agent's output and calls TaskUpdater.add_artifact() with a list[Part] (containing multiple TextParts) and TaskUpdater.complete(). No exceptions are observed within the AgentExecutor itself.

  • The issue appears to occur later in the A2A framework, likely during the serialization of the a2a.types.Task object (which includes the multi-part artifact) into a JSON-RPC response, or during the HTTP sending phase by Starlette.

  • The problem is reproducible when an ADK agent using BuiltInCodeExecutor generates code, execution results, and a final text answer, all of which are converted into distinct TextParts for the final artifact.

  • If the AgentExecutor is modified to ensure the final artifact contains only a single TextPart, the client receives a successful response. This points to the handling of multiple TextParts within an artifact as the root cause.

  • Steps to Reproduce:

    1. Set up an A2A agent (like the code_interpreter_agent example) that uses an underlying mechanism (e.g., Google ADK with BuiltInCodeExecutor) which produces multiple distinct pieces of textual information for its final output (e.g., generated code, code output, final summary).
    2. Ensure the AgentExecutor's convert_genai_parts_to_a2a (or equivalent) function converts these distinct pieces of information into multiple a2a.types.TextPart objects.
    3. In the AgentExecutor._process_request method, when the final ADK event is received, create a list[Part] containing these multiple TextParts and pass it to TaskUpdater.add_artifact(). Then call TaskUpdater.complete().
    4. Have an A2A client send a request to this agent.
    5. Observe the client timeout / HTTP 503 error, and the lack of server-side exceptions after the AgentExecutor logs completion of its processing.
  • Expected Behavior: The A2A server should successfully serialize and send a Task object containing an artifact with multiple TextParts.

  • Actual Behavior: Client receives HTTP 503 / ReadTimeout. Server logs from the agent executor are clean, suggesting a failure later in the A2A framework's response pipeline.

Relevant log output

Server side:
NFO:     Started server process [65049]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://localhost:10010 (Press CTRL+C to quit)
INFO:     ::1:55023 - "GET /.well-known/agent.json HTTP/1.1" 200 OK
WARNING:google_genai.types:Warning: there are non-text parts in the response: ['executable_code', 'code_execution_result'],returning concatenated text result from text parts,check out the non text parts for full response from model.
DEBUG:adk_agent_executor:Received ADK event: content=Content(parts=[Part(video_metadata=None, thought=None, inline_data=None, code_execution_result=None, executable_code=ExecutableCode(code='print((5 + 7) * 3)\n', language=<Language.PYTHON: 'PYTHON'>), file_data=None, function_call=None, function_response=None, text=None), Part(video_metadata=None, thought=None, inline_data=None, code_execution_result=CodeExecutionResult(outcome=<Outcome.OUTCOME_OK: 'OUTCOME_OK'>, output='36\n'), executable_code=None, file_data=None, function_call=None, function_response=None, text=None), Part(video_metadata=None, thought=None, inline_data=None, code_execution_result=None, executable_code=None, file_data=None, function_call=None, function_response=None, text='36\n')], role='model') grounding_metadata=None partial=None turn_complete=None error_code=None error_message=None interrupted=None custom_metadata=None usage_metadata=GenerateContentResponseUsageMetadata(cache_tokens_details=None, cached_content_token_count=None, candidates_token_count=18, candidates_tokens_details=[ModalityTokenCount(modality=<MediaModality.TEXT: 'TEXT'>, token_count=18)], prompt_token_count=95, prompt_tokens_details=[ModalityTokenCount(modality=<MediaModality.TEXT: 'TEXT'>, token_count=95)], thoughts_token_count=None, tool_use_prompt_token_count=110, tool_use_prompt_tokens_details=[ModalityTokenCount(modality=<MediaModality.TEXT: 'TEXT'>, token_count=110)], total_token_count=223, traffic_type=None) invocation_id='e-b5922d8e-65f8-431d-915e-e4c65b540dbb' author='code_interpreter_agent_adk' actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}) long_running_tool_ids=None branch=None id='W4p38vWh' timestamp=1748281635.866301
DEBUG:adk_agent_executor:Agent generated code:

print((5 + 7) * 3)


DEBUG:adk_agent_executor:Code Execution Result: Outcome.OUTCOME_OK - Output:
36

DEBUG:adk_agent_executor:Text: '36'
DEBUG:adk_agent_executor:Yielding final response: [TextPart(kind='text', metadata=None, text='[Generated Code Placeholder]'), TextPart(kind='text', metadata=None, text='[Code Result Placeholder: Outcome.OUTCOME_OK - 36]'), TextPart(kind='text', metadata=None, text='36')]
DEBUG:adk_agent_executor:Yielding final response parts: [TextPart(kind='text', metadata=None, text='[Generated Code Placeholder]'), TextPart(kind='text', metadata=None, text='[Code Result Placeholder: Outcome.OUTCOME_OK - 36]'), TextPart(kind='text', metadata=None, text='36')]

Client side:
<user> % uv run test_client.py
INFO:__main__:Attempting to fetch public agent card from: http://localhost:10010/.well-known/agent.json
INFO:httpx:HTTP Request: GET http://localhost:10010/.well-known/agent.json "HTTP/1.1 200 OK"
INFO:a2a.client.client:Successfully fetched agent card data from http://localhost:10010/.well-known/agent.json: {'capabilities': {'streaming': True}, 'defaultInputModes': ['text'], 'defaultOutputModes': ['text'], 'description': 'I can execute Python code to perform calculations and data manipulation.', 'name': 'ADK Code Interpreter Agent', 'skills': [{'description': 'Executes Python code to perform calculations and data manipulation.', 'examples': ['Calculate the value of (5 + 7) * 3', 'What is 10 factorial?', 'Solve for x: 2x + 5 = 15'], 'id': 'code_interpreter', 'name': 'Code Interpreter', 'tags': ['code', 'calculation', 'data manipulation']}], 'url': 'http://localhost:10010/', 'version': '1.0.0'}
INFO:__main__:Successfully fetched public agent card:
INFO:__main__:{
  "capabilities": {
    "streaming": true
  },
  "defaultInputModes": [
    "text"
  ],
  "defaultOutputModes": [
    "text"
  ],
  "description": "I can execute Python code to perform calculations and data manipulation.",
  "name": "ADK Code Interpreter Agent",
  "skills": [
    {
      "description": "Executes Python code to perform calculations and data manipulation.",
      "examples": [
        "Calculate the value of (5 + 7) * 3",
        "What is 10 factorial?",
        "Solve for x: 2x + 5 = 15"
      ],
      "id": "code_interpreter",
      "name": "Code Interpreter",
      "tags": [
        "code",
        "calculation",
        "data manipulation"
      ]
    }
  ],
  "url": "http://localhost:10010/",
  "version": "1.0.0"
}
INFO:__main__:
Using PUBLIC agent card for client initialization.
INFO:__main__:A2AClient initialized.

--- Sending query: 'Calculate the value of (5 + 7) * 3' ---
Traceback (most recent call last):
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_transports/default.py", line 101, in map_httpcore_exceptions
    yield
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_transports/default.py", line 394, in handle_async_request
    resp = await self._pool.handle_async_request(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpcore/_async/connection_pool.py", line 256, in handle_async_request
    raise exc from None
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpcore/_async/connection_pool.py", line 236, in handle_async_request
    response = await connection.handle_async_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        pool_request.request
        ^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpcore/_async/connection.py", line 103, in handle_async_request
    return await self._connection.handle_async_request(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpcore/_async/http11.py", line 136, in handle_async_request
    raise exc
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpcore/_async/http11.py", line 106, in handle_async_request
    ) = await self._receive_response_headers(**kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpcore/_async/http11.py", line 177, in _receive_response_headers
    event = await self._receive_event(timeout=timeout)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpcore/_async/http11.py", line 217, in _receive_event
    data = await self._network_stream.read(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        self.READ_NUM_BYTES, timeout=timeout
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpcore/_backends/anyio.py", line 32, in read
    with map_exceptions(exc_map):
         ~~~~~~~~~~~~~~^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 162, in __exit__
    self.gen.throw(value)
    ~~~~~~~~~~~~~~^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpcore/_exceptions.py", line 14, in map_exceptions
    raise to_exc(exc) from exc
httpcore.ReadTimeout

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/<user>/a2asdkpython/a2a-python/src/a2a/client/client.py", line 279, in _send_request
    response = await self.httpx_client.post(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        self.url, json=rpc_request_payload, **(http_kwargs or {})
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1859, in post
    return await self.request(
           ^^^^^^^^^^^^^^^^^^^
    ...<13 lines>...
    )
    ^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1540, in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1629, in send
    response = await self._send_handling_auth(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<4 lines>...
    )
    ^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
    response = await self._send_handling_redirects(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<3 lines>...
    )
    ^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
    response = await self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_client.py", line 1730, in _send_single_request
    response = await transport.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_transports/default.py", line 393, in handle_async_request
    with map_httpcore_exceptions():
         ~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 162, in __exit__
    self.gen.throw(value)
    ~~~~~~~~~~~~~~^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/.venv/lib/python3.13/site-packages/httpx/_transports/default.py", line 118, in map_httpcore_exceptions
    raise mapped_exc(message) from exc
httpx.ReadTimeout

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/<user>/a2asdkpython/a2a-python/examples/google_adk/code_interpreter_agent/test_client.py", line 87, in <module>
    asyncio.run(main())
    ~~~~~~~~~~~^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 195, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 719, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/Users/<user>/a2asdkpython/a2a-python/examples/google_adk/code_interpreter_agent/test_client.py", line 69, in main
    response = await client.send_message(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/src/a2a/utils/telemetry.py", line 158, in async_wrapper
    result = await func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/src/a2a/client/client.py", line 199, in send_message
    **await self._send_request(
      ^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<2 lines>...
    )
    ^
  File "/Users/<user>/a2asdkpython/a2a-python/src/a2a/utils/telemetry.py", line 158, in async_wrapper
    result = await func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/<user>/a2asdkpython/a2a-python/src/a2a/client/client.py", line 289, in _send_request
    raise A2AClientHTTPError(
        503, f'Network communication error: {e}'
    ) from e
a2a.client.errors.A2AClientHTTPError: HTTP Error 503: Network communication error:

Code of Conduct

  • I agree to follow this project's Code of Conduct
@kthota-g
Copy link
Collaborator

Hi,
I tried to replicate the issue with a simple test case. But I can't reproduce it. Here is my example.

from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.utils import new_task
from collections.abc import  AsyncGenerator
import asyncio
from a2a.server.tasks import TaskUpdater
from a2a.types import Part, TextPart

class HelloWorldAgent:
    """Hello World Agent."""

    async def stream(self) -> AsyncGenerator[str, None]:
        yield 'Hello '
        await asyncio.sleep(2)
        yield 'World '
        await asyncio.sleep(2)
        yield 'again!'


class HelloWorldAgentExecutor(AgentExecutor):
    """Test AgentProxy Implementation."""

    def __init__(self):
        self.agent = HelloWorldAgent()

    async def execute(
        self,
        context: RequestContext,
        event_queue: EventQueue,
    ) -> None:
        
        task = context.current_task
        if not task and context.message:
            task = new_task(context.message)
            event_queue.enqueue_event(task)
        
        updater = TaskUpdater(event_queue, task.id, task.contextId)

        parts: list[Part] = []
        async for content in self.agent.stream():
            parts.append(Part(TextPart(text=content)))

        updater.add_artifact(
                    parts,
                    name='result',
                )   
        updater.complete()

    async def cancel(
        self, context: RequestContext, event_queue: EventQueue
    ) -> None:
        raise Exception('cancel not supported')

Here is the response from the server.

data: {"id":33,"jsonrpc":"2.0","result":{"contextId":"310b7d7b-1893-4e9c-b1fd-21d44077f17d","history":[{"contextId":"310b7d7b-1893-4e9c-b1fd-21d44077f17d","kind":"message","messageId":"foo1","parts":[{"kind":"text","text":"hello"}],"role":"user","taskId":"a163df2a-2ba4-4c56-9eaa-aeaa4d500cb7"}],"id":"a163df2a-2ba4-4c56-9eaa-aeaa4d500cb7","kind":"task","status":{"state":"submitted"}}}

data: {"id":33,"jsonrpc":"2.0","result":{"artifact":{"artifactId":"6986a263-3a13-4ddd-aef2-caf6a783b371","name":"result","parts":[{"kind":"text","text":"Hello "},{"kind":"text","text":"World "},{"kind":"text","text":"again!"}]},"contextId":"310b7d7b-1893-4e9c-b1fd-21d44077f17d","kind":"artifact-update","taskId":"a163df2a-2ba4-4c56-9eaa-aeaa4d500cb7"}}

data: {"id":33,"jsonrpc":"2.0","result":{"contextId":"310b7d7b-1893-4e9c-b1fd-21d44077f17d","final":true,"kind":"status-update","status":{"state":"completed"},"taskId":"a163df2a-2ba4-4c56-9eaa-aeaa4d500cb7"}}

Just wanted to confirm if this snippet correctly represents the scenario you described?

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

No branches or pull requests

2 participants
0