-
Notifications
You must be signed in to change notification settings - Fork 644
Description
Checks
- I have updated to the lastest minor and patch version of Strands
- I have checked the documentation and this is not expected behavior
- I have searched ./issues and there are no duplicates of my issue
Strands Version
1.25.0
Python Version
3.12.0
Operating System
macOS 15.7
Installation Method
other
Steps to Reproduce
- Create an A2A server:
# server.py
from strands import Agent
from strands.multiagent.a2a import A2AServer
from strands_tools.calculator import calculator
strands_agent = Agent(
name="Calculator Agent",
description="Calculator agent for arithmetic operations.",
tools=[calculator],
callback_handler=None,
)
a2a_server = A2AServer(agent=strands_agent, enable_a2a_compliant_streaming=True)
a2a_server.serve()- Call the agent synchronously or with
invoke_async:
# client.py
from strands.agent.a2a_agent import A2AAgent
a2a_agent = A2AAgent(endpoint="http://localhost:9000")
result = a2a_agent("101 * 11 を計算して")
print(result.message)
#Result
# {'role': 'assistant', 'content': []}Expected Behavior
AgentResult.message["content"] should contain the agent's response text, e.g.:
{'role': 'assistant', 'content': [{'text': '101 × 11 = 1111'}]}Actual Behavior
AgentResult.message["content"] is an empty list:
{'role': 'assistant', 'content': []}The text is correctly received in the stream — TaskArtifactUpdateEvents contain the expected text parts. Only the final AgentResult conversion produces empty content.
The issue is in convert_response_to_agent_result in strands/multiagent/a2a/_converters.py.
The A2A stream ends with the following sequence:
- Multiple
TaskArtifactUpdateEvents with text content (correct) - A final
TaskArtifactUpdateEventwithlastChunk=Trueand emptyparts - A final
TaskStatusUpdateEventwithstate=completedandmessage=None
stream_async selects the last complete event via _is_complete_event(). Since TaskStatusUpdateEvent with state=completed is considered complete, it becomes the last_complete_event — overwriting the previous TaskArtifactUpdateEvent with lastChunk=True.
When convert_response_to_agent_result receives (Task, TaskStatusUpdateEvent):
# Current code (lines 103-107)
elif isinstance(update_event, TaskStatusUpdateEvent):
if update_event.status and hasattr(update_event.status, "message") and update_event.status.message:
for part in update_event.status.message.parts:
...update_event.status.message is None, so no content is extracted.
The fallback to task.artifacts (line 109) only runs when update_event is None:
elif update_event is None and task and hasattr(task, "artifacts") and task.artifacts is not None:
for artifact in task.artifacts:
...The Task object does contain the accumulated text in task.artifacts, but the converter never reaches this branch because update_event is a TaskStatusUpdateEvent, not None.
Possible Solution
Add a fallback to task.artifacts when TaskStatusUpdateEvent.status.message is None:
elif isinstance(update_event, TaskStatusUpdateEvent):
if update_event.status and hasattr(update_event.status, "message") and update_event.status.message:
for part in update_event.status.message.parts:
if hasattr(part, "root") and hasattr(part.root, "text"):
content.append({"text": part.root.text})
# Fallback: extract from task.artifacts when status.message is None
elif task and hasattr(task, "artifacts") and task.artifacts is not None:
for artifact in task.artifacts:
if hasattr(artifact, "parts"):
for part in artifact.parts:
if hasattr(part, "root") and hasattr(part.root, "text"):
content.append({"text": part.root.text})Workaround
Use stream_async and extract text directly from TaskArtifactUpdateEvent:
import asyncio
from strands.agent.a2a_agent import A2AAgent
async def main():
a2a_agent = A2AAgent(endpoint="http://localhost:9000")
async for event in a2a_agent.stream_async("101 * 11 を計算して"):
if event.get("type") == "a2a_stream":
a2a_event = event["event"]
if isinstance(a2a_event, tuple):
task, update = a2a_event
if hasattr(update, "artifact") and update.artifact and update.artifact.parts:
for part in update.artifact.parts:
if hasattr(part, "root") and hasattr(part.root, "text"):
print(part.root.text, end="", flush=True)
elif "result" in event:
print()
asyncio.run(main())Related Issues
- [BUG]
partsofArtifactshould not be empty in A2A #1640 — Server-side:lastChunk=Trueevent sends emptypartsarray (violates A2A spec). This is the server-side counterpart; the current issue is about the client-side conversion logic.