From bae85cd7eeb760517018983b329d979301c1b72a Mon Sep 17 00:00:00 2001 From: Roxanne Farhad Date: Thu, 18 Dec 2025 13:08:42 +0000 Subject: [PATCH 1/4] making testing better --- .github/workflows/agentex-tutorials-test.yml | 9 +- .../00_sync/020_streaming/tests/test_agent.py | 1 - .../00_base/000_hello_acp/tests/test_agent.py | 98 ++++++--- .../00_base/010_multiturn/tests/test_agent.py | 61 ++++-- .../00_base/020_streaming/tests/test_agent.py | 115 ++++++---- .../040_other_sdks/tests/test_agent.py | 199 +++++++++++------- .../080_batch_events/tests/test_agent.py | 69 +++--- .../tests/test_agent.py | 62 +++--- .../000_hello_acp/tests/test_agent.py | 98 ++++++--- .../010_agent_chat/tests/test_agent.py | 167 +++++++++------ .../020_state_machine/tests/test_agent.py | 68 +++--- .../tests/test_agent.py | 86 +++++--- .../tests/test_agent.py | 104 +++++---- .../tests/test_agent.py | 38 ++-- examples/tutorials/test_utils/async_utils.py | 2 + src/agentex/types/__init__.py | 4 +- uv.lock | 2 +- 17 files changed, 734 insertions(+), 449 deletions(-) diff --git a/.github/workflows/agentex-tutorials-test.yml b/.github/workflows/agentex-tutorials-test.yml index eb6da4762..9c19bdbef 100644 --- a/.github/workflows/agentex-tutorials-test.yml +++ b/.github/workflows/agentex-tutorials-test.yml @@ -20,20 +20,17 @@ jobs: id: get-tutorials run: | cd examples/tutorials - # Find all tutorials and exclude specific temporal ones + # Find all tutorials with a manifest.yaml all_tutorials=$(find . -name "manifest.yaml" -exec dirname {} \; | sort | sed 's|^\./||') - # Filter out the specified temporal tutorials that are being updated - filtered_tutorials=$(echo "$all_tutorials" | grep -v -E "(temporal)") + # Include all tutorials (temporal tutorials are now included) + filtered_tutorials="$all_tutorials" # Convert to JSON array tutorials=$(echo "$filtered_tutorials" | jq -R -s -c 'split("\n") | map(select(length > 0))') echo "tutorials=$tutorials" >> $GITHUB_OUTPUT echo "All tutorials found: $(echo "$all_tutorials" | wc -l)" - echo "Filtered tutorials: $(echo "$filtered_tutorials" | wc -l)" - echo "Excluded tutorials:" - echo "$all_tutorials" | grep -E "(10_temporal/050_|10_temporal/070_|10_temporal/080_)" || echo " (none matched exclusion pattern)" echo "Final tutorial list: $tutorials" test-tutorial: diff --git a/examples/tutorials/00_sync/020_streaming/tests/test_agent.py b/examples/tutorials/00_sync/020_streaming/tests/test_agent.py index 7a649f2d3..5bde7d6b2 100644 --- a/examples/tutorials/00_sync/020_streaming/tests/test_agent.py +++ b/examples/tutorials/00_sync/020_streaming/tests/test_agent.py @@ -151,4 +151,3 @@ def test_send_stream_message(self, client: Agentex, agent_name: str, agent_id: s if __name__ == "__main__": pytest.main([__file__, "-v"]) - diff --git a/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py b/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py index ba3444109..bbd261642 100644 --- a/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py @@ -73,31 +73,55 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): assert task is not None # Poll for the initial task creation message - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your task" in message.content.content - break + task_creation_message_found = False + + async def poll_for_task_creation() -> None: + nonlocal task_creation_message_found + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your task" in message.content.content + task_creation_message_found = True + break + + try: + await asyncio.wait_for(poll_for_task_creation(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for task creation message") + + assert task_creation_message_found, "Task creation message not found" # Send an event and poll for response user_message = "Hello, this is a test message!" - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your task" in message.content.content - break + agent_response_found = False + + async def poll_for_response() -> None: + nonlocal agent_response_found + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your message" in message.content.content + agent_response_found = True + break + + try: + await asyncio.wait_for(poll_for_response(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for agent response") + + assert agent_response_found, "Agent response not found" class TestStreamingEvents: @@ -111,18 +135,26 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): assert task is not None task_creation_found = False + # Poll for the initial task creation message - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your task" in message.content.content - task_creation_found = True - break + async def poll_for_task_creation() -> None: + nonlocal task_creation_found + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your task" in message.content.content + task_creation_found = True + break + + try: + await asyncio.wait_for(poll_for_task_creation(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for task creation message") assert task_creation_found, "Task creation message not found in poll" diff --git a/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py b/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py index 4da1745c6..fcc28810a 100644 --- a/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py @@ -89,25 +89,48 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): user_message = "Hello! Here is my test message" messages = [] - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - messages.append(message) - if len(messages) == 1: - assert message.content == TextContent( - author="user", - content=user_message, - type="text", - ) - else: - assert message.content is not None - assert message.content.author == "agent" - break + + # Flags to track what we've received + user_message_found = False + agent_response_found = False + + async def poll_for_messages() -> None: + nonlocal user_message_found, agent_response_found + + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + messages.append(message) + + # Validate messages as they arrive + if message.content and hasattr(message.content, 'author'): + if message.content.author == "user" and message.content.content == user_message: + assert message.content == TextContent( + author="user", + content=user_message, + type="text", + ) + user_message_found = True + elif message.content.author == "agent": + assert user_message_found, "Agent response arrived before user message" + agent_response_found = True + + # Exit early if we've found all expected messages + if user_message_found and agent_response_found: + break + + try: + await asyncio.wait_for(poll_for_messages(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out after 30s waiting for expected messages") + + assert user_message_found, "User message not found" + assert agent_response_found, "Agent response not found" await asyncio.sleep(1) # wait for state to be updated states = await client.states.list(agent_id=agent_id, task_id=task.id) diff --git a/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py b/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py index d863199cd..b82f857df 100644 --- a/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py @@ -28,7 +28,6 @@ ) from agentex import AsyncAgentex -from agentex.types import TaskMessage, TextContent from agentex.types.agent_rpc_params import ParamsCreateTaskRequest from agentex.types.text_content_param import TextContentParam @@ -89,32 +88,48 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): user_message = "Hello! Here is my test message" messages = [] - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - yield_updates=False, - ): - - messages.append(message) - - assert len(messages) > 0 - # the first message should be the agent re-iterating what the user sent - assert isinstance(messages, List) - assert len(messages) == 2 - first_message: TaskMessage = messages[0] - assert first_message.content == TextContent( - author="user", - content=user_message, - type="text", - ) - - second_message: TaskMessage = messages[1] - assert second_message.content is not None - assert second_message.content.author == "agent" + + # Flags to track what we've received + user_message_found = False + agent_response_found = False + + async def poll_for_messages() -> None: + nonlocal user_message_found, agent_response_found + + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + yield_updates=False, + ): + messages.append(message) + + # Validate messages as they come in + if message.content and hasattr(message.content, 'author'): + if message.content.author == "user" and message.content.content == user_message: + user_message_found = True + elif message.content.author == "agent": + # Agent response should come after user message + assert user_message_found, "Agent response arrived before user message" + agent_response_found = True + + # Exit early if we've found all expected messages + if user_message_found and agent_response_found: + break + + # Run polling with timeout + try: + await asyncio.wait_for(poll_for_messages(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out after 30s waiting for expected messages") + + # Validate we received expected messages + assert len(messages) >= 2, "Expected at least 2 messages (user + agent)" + assert user_message_found, "User message not found" + assert agent_response_found, "Agent response not found" # assert the state has been updated await asyncio.sleep(1) # wait for state to be updated @@ -158,7 +173,14 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): # Collect events from stream all_events = [] + # Flags to track what we've received + user_message_found = False + full_agent_message_found = False + delta_messages_found = False + async def stream_messages() -> None: + nonlocal user_message_found, full_agent_message_found, delta_messages_found + async for event in stream_agent_response( client=client, task_id=task.id, @@ -166,33 +188,34 @@ async def stream_messages() -> None: ): all_events.append(event) + # Check events as they arrive + event_type = event.get("type") + if event_type == "full": + content = event.get("content", {}) + if content.get("content") == user_message and content.get("author") == "user": + user_message_found = True + elif content.get("author") == "agent": + full_agent_message_found = True + elif event_type == "delta": + delta_messages_found = True + + # Exit early if we've found all expected messages + if user_message_found and full_agent_message_found and delta_messages_found: + break + stream_task = asyncio.create_task(stream_messages()) event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - # Wait for streaming to complete - await stream_task + # Wait for streaming to complete (with timeout) + try: + await asyncio.wait_for(stream_task, timeout=15) + except asyncio.TimeoutError: + pytest.fail("Stream timed out after 15s waiting for expected messages") # Validate we received events assert len(all_events) > 0, "No events received in streaming response" - - # Check for user message, full agent response, and delta messages - user_message_found = False - full_agent_message_found = False - delta_messages_found = False - - for event in all_events: - event_type = event.get("type") - if event_type == "full": - content = event.get("content", {}) - if content.get("content") == user_message and content.get("author") == "user": - user_message_found = True - elif content.get("author") == "agent": - full_agent_message_found = True - elif event_type == "delta": - delta_messages_found = True - assert user_message_found, "User message not found in stream" assert full_agent_message_found, "Full agent message not found in stream" assert delta_messages_found, "Delta messages not found in stream (streaming response expected)" diff --git a/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py b/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py index 429d8d879..799ca15b5 100644 --- a/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py @@ -94,24 +94,36 @@ async def test_send_event_and_poll_simple_query(self, client: AsyncAgentex, agen # Send a simple message that shouldn't require tool use user_message = "Hello! Please introduce yourself briefly." messages = [] - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - messages.append(message) - - if len(messages) == 1: - assert message.content == TextContent( - author="user", - content=user_message, - type="text", - ) - break + user_message_found = False + + async def poll_for_messages() -> None: + nonlocal user_message_found + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + messages.append(message) + + if message.content and message.content.author == "user": + assert message.content == TextContent( + author="user", + content=user_message, + type="text", + ) + user_message_found = True + break + + try: + await asyncio.wait_for(poll_for_messages(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for user message") + + assert user_message_found, "User message not found" # Verify state has been updated by polling the states for 10 seconds for i in range(20): @@ -141,26 +153,33 @@ async def test_send_event_and_poll_with_tool_use(self, client: AsyncAgentex, age tool_response_found = False has_final_agent_response = False - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=60, # Longer timeout for tool use - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "tool_request": - tool_request_found = True - assert message.content.author == "agent" - assert hasattr(message.content, "name") - assert hasattr(message.content, "tool_call_id") - elif message.content and message.content.type == "tool_response": - tool_response_found = True - assert message.content.author == "agent" - elif message.content and message.content.type == "text" and message.content.author == "agent": - has_final_agent_response = True - break + async def poll_for_tool_response() -> None: + nonlocal tool_request_found, tool_response_found, has_final_agent_response + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=60, # Longer timeout for tool use + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "tool_request": + tool_request_found = True + assert message.content.author == "agent" + assert hasattr(message.content, "name") + assert hasattr(message.content, "tool_call_id") + elif message.content and message.content.type == "tool_response": + tool_response_found = True + assert message.content.author == "agent" + elif message.content and message.content.type == "text" and message.content.author == "agent": + has_final_agent_response = True + break + + try: + await asyncio.wait_for(poll_for_tool_response(), timeout=60) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for tool response") assert has_final_agent_response, "Did not receive final agent text response" assert tool_request_found, "Did not see tool request message" @@ -176,24 +195,37 @@ async def test_multi_turn_conversation_with_state(self, client: AsyncAgentex, ag # ensure the task is created before we send the first event await asyncio.sleep(1) + # First turn user_message_1 = "My favorite color is blue." - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message_1, - timeout=20, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if ( - message.content - and message.content.type == "text" - and message.content.author == "agent" - and message.content.content + first_turn_response_found = False + + async def poll_for_first_turn() -> None: + nonlocal first_turn_response_found + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message_1, + timeout=20, + sleep_interval=1.0, ): - break + assert isinstance(message, TaskMessage) + if ( + message.content + and message.content.type == "text" + and message.content.author == "agent" + and message.content.content + ): + first_turn_response_found = True + break + + try: + await asyncio.wait_for(poll_for_first_turn(), timeout=20) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for first turn response") + + assert first_turn_response_found, "First turn response not found" ## keep polling the states for 10 seconds for the input_list and turn_number to be updated for i in range(30): @@ -210,29 +242,38 @@ async def test_multi_turn_conversation_with_state(self, client: AsyncAgentex, ag assert state.get("turn_number") == 1 await asyncio.sleep(1) - found_response = False + # Second turn - reference previous context user_message_2 = "What did I just tell you my favorite color was?" - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message_2, - timeout=30, - sleep_interval=1.0, - ): - if ( - message.content - and message.content.type == "text" - and message.content.author == "agent" - and message.content.content + second_turn_response_found = False + + async def poll_for_second_turn() -> None: + nonlocal second_turn_response_found + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message_2, + timeout=30, + sleep_interval=1.0, ): - response_text = message.content.content.lower() - assert "blue" in response_text - found_response = True - break + if ( + message.content + and message.content.type == "text" + and message.content.author == "agent" + and message.content.content + ): + response_text = message.content.content.lower() + assert "blue" in response_text + second_turn_response_found = True + break + + try: + await asyncio.wait_for(poll_for_second_turn(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for second turn response") - assert found_response, "Did not receive final agent text response" + assert second_turn_response_found, "Did not receive final agent text response" for i in range(10): if i == 9: raise Exception("Timeout waiting for state updates") @@ -296,8 +337,11 @@ async def stream_messages() -> None: event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - # Wait for streaming to complete - await stream_task + # Wait for streaming to complete (with timeout) + try: + await asyncio.wait_for(stream_task, timeout=30) + except asyncio.TimeoutError: + pytest.fail("Stream timed out after 30s waiting for expected messages") assert user_message_found, "User message found in stream" ## keep polling the states for 10 seconds for the input_list and turn_number to be updated for i in range(10): @@ -382,8 +426,11 @@ async def stream_messages() -> None: event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - # Wait for streaming to complete - await stream_task + # Wait for streaming to complete (with timeout) + try: + await asyncio.wait_for(stream_task, timeout=60) + except asyncio.TimeoutError: + pytest.fail("Stream timed out after 60s waiting for expected messages") # Verify we saw tool usage (if the agent decided to use tools) # Note: The agent may or may not use tools depending on its reasoning diff --git a/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py b/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py index 6ccad7d2f..1d6c26524 100644 --- a/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py @@ -75,19 +75,31 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Send an event and poll for response using the helper function # there should only be one message returned about batching - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message="Process this single event", - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - assert isinstance(message.content, TextContent) - assert "Processed event IDs" in message.content.content - assert message.content.author == "agent" - break + agent_response_found = False + + async def poll_for_response() -> None: + nonlocal agent_response_found + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message="Process this single event", + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.author == "agent": + assert isinstance(message.content, TextContent) + assert "Processed event IDs" in message.content.content + agent_response_found = True + break + + try: + await asyncio.wait_for(poll_for_response(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for agent response") + + assert agent_response_found, "Agent response not found" @pytest.mark.asyncio async def test_send_multiple_events_batched(self, client: AsyncAgentex, agent_id: str): @@ -109,19 +121,26 @@ async def test_send_multiple_events_batched(self, client: AsyncAgentex, agent_id ## there should be at least 2 agent responses to ensure that not all of the events are processed ## in the same message agent_messages = [] - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message="Process this single event", - timeout=30, - sleep_interval=1.0, - ): - if message.content and message.content.author == "agent": - agent_messages.append(message) - if len(agent_messages) == 2: - break + async def poll_for_batch_responses() -> None: + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message="Process this single event", + timeout=30, + sleep_interval=1.0, + ): + if message.content and message.content.author == "agent": + agent_messages.append(message) + + if len(agent_messages) == 2: + break + + try: + await asyncio.wait_for(poll_for_batch_responses(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for batch responses") assert len(agent_messages) > 0, "Should have received at least one agent response" diff --git a/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py b/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py index d4c1dd7dd..950156002 100644 --- a/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py @@ -92,39 +92,47 @@ async def test_multi_agent_workflow_complete(self, client: AsyncAgentex, agent_i } all_agents_done = False - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=json.dumps(request_json), - timeout=120, # Longer timeout for multi-agent workflow - sleep_interval=2.0, - ): - messages.append(message) - # Print messages as they arrive to show real-time progress - if message.content and message.content.content: - # Track agent participation as messages arrive - content = message.content.content.lower() - if "starting content workflow" in content: - workflow_markers["orchestrator_started"] = True + async def poll_for_workflow() -> None: + nonlocal all_agents_done + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=json.dumps(request_json), + timeout=120, # Longer timeout for multi-agent workflow + sleep_interval=2.0, + ): + messages.append(message) + # Print messages as they arrive to show real-time progress + if message.content and message.content.content: + # Track agent participation as messages arrive + content = message.content.content.lower() + + if "starting content workflow" in content: + workflow_markers["orchestrator_started"] = True - if "creator output" in content: - workflow_markers["creator_called"] = True + if "creator output" in content: + workflow_markers["creator_called"] = True - if "critic feedback" in content or "content approved by critic" in content: - workflow_markers["critic_called"] = True + if "critic feedback" in content or "content approved by critic" in content: + workflow_markers["critic_called"] = True - if "calling formatter agent" in content: - workflow_markers["formatter_called"] = True + if "calling formatter agent" in content: + workflow_markers["formatter_called"] = True - if "workflow complete" in content or "content creation complete" in content: - workflow_markers["workflow_completed"] = True + if "workflow complete" in content or "content creation complete" in content: + workflow_markers["workflow_completed"] = True + + # Check if all agents have participated + all_agents_done = all(workflow_markers.values()) + if all_agents_done: + break - # Check if all agents have participated - all_agents_done = all(workflow_markers.values()) - if all_agents_done: - break + try: + await asyncio.wait_for(poll_for_workflow(), timeout=120) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for multi-agent workflow completion") # Assert all agents participated assert workflow_markers["orchestrator_started"], "Orchestrator did not start workflow" diff --git a/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py index 9150afaa2..5aa182c48 100644 --- a/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py @@ -72,35 +72,57 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): task_response = await client.agents.create_task(agent_id, params=ParamsCreateTaskRequest(name=uuid.uuid1().hex)) task = task_response.result assert task is not None + task_creation_found = False + # Poll for the initial task creation message - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your task" in message.content.content - task_creation_found = True - break + async def poll_for_task_creation() -> None: + nonlocal task_creation_found + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your task" in message.content.content + task_creation_found = True + break + + try: + await asyncio.wait_for(poll_for_task_creation(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for task creation message") assert task_creation_found, "Task creation message not found in poll" await asyncio.sleep(1.5) + # Send an event and poll for response user_message = "Hello, this is a test message!" - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your message" in message.content.content - break + agent_response_found = False + + async def poll_for_response() -> None: + nonlocal agent_response_found + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your message" in message.content.content + agent_response_found = True + break + + try: + await asyncio.wait_for(poll_for_response(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for agent response") + + assert agent_response_found, "Agent response not found" class TestStreamingEvents: @@ -114,17 +136,25 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): assert task is not None task_creation_found = False - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your task" in message.content.content - task_creation_found = True - break + + async def poll_for_task_creation() -> None: + nonlocal task_creation_found + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your task" in message.content.content + task_creation_found = True + break + + try: + await asyncio.wait_for(poll_for_task_creation(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for task creation message") assert task_creation_found, "Task creation message not found in poll" @@ -182,8 +212,6 @@ async def collect_stream_events(): #noqa: ANN101 assert user_echo_found, "User message echo not found in stream" assert agent_response_found, "Agent response not found in stream" - # Wait for streaming to complete - await stream_task if __name__ == "__main__": pytest.main([__file__, "-v"]) \ No newline at end of file diff --git a/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py index 6eb03f728..e532397ca 100644 --- a/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py @@ -87,24 +87,36 @@ async def test_send_event_and_poll_simple_query(self, client: AsyncAgentex, agen # Send a simple message that shouldn't require tool use user_message = "Hello! Please introduce yourself briefly." messages = [] - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - messages.append(message) - - if len(messages) == 1: - assert message.content == TextContent( - author="user", - content=user_message, - type="text", - ) - break + user_message_found = False + + async def poll_for_user_message() -> None: + nonlocal user_message_found + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + messages.append(message) + + if message.content and message.content.author == "user": + assert message.content == TextContent( + author="user", + content=user_message, + type="text", + ) + user_message_found = True + break + + try: + await asyncio.wait_for(poll_for_user_message(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for user message") + + assert user_message_found, "User message not found" @pytest.mark.asyncio async def test_send_event_and_poll_with_calculator(self, client: AsyncAgentex, agent_id: str): @@ -121,20 +133,27 @@ async def test_send_event_and_poll_with_calculator(self, client: AsyncAgentex, a user_message = "What is 15 multiplied by 37?" has_final_agent_response = False - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=60, # Longer timeout for tool use - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - # Check that the answer contains 555 (15 * 37) - if "555" in message.content.content: - has_final_agent_response = True - break + async def poll_for_calculator_response() -> None: + nonlocal has_final_agent_response + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=60, # Longer timeout for tool use + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + # Check that the answer contains 555 (15 * 37) + if "555" in message.content.content: + has_final_agent_response = True + break + + try: + await asyncio.wait_for(poll_for_calculator_response(), timeout=60) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for calculator response") assert has_final_agent_response, "Did not receive final agent text response with correct answer" @@ -151,22 +170,34 @@ async def test_multi_turn_conversation(self, client: AsyncAgentex, agent_id: str # First turn user_message_1 = "My favorite color is blue." - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message_1, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if ( - message.content - and message.content.type == "text" - and message.content.author == "agent" - and message.content.content + first_turn_found = False + + async def poll_for_first_turn() -> None: + nonlocal first_turn_found + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message_1, + timeout=30, + sleep_interval=1.0, ): - break + assert isinstance(message, TaskMessage) + if ( + message.content + and message.content.type == "text" + and message.content.author == "agent" + and message.content.content + ): + first_turn_found = True + break + + try: + await asyncio.wait_for(poll_for_first_turn(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for first turn response") + + assert first_turn_found, "First turn response not found" # Wait a bit for state to update await asyncio.sleep(2) @@ -174,24 +205,32 @@ async def test_multi_turn_conversation(self, client: AsyncAgentex, agent_id: str # Second turn - reference previous context found_response = False user_message_2 = "What did I just tell you my favorite color was?" - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message_2, - timeout=30, - sleep_interval=1.0, - ): - if ( - message.content - and message.content.type == "text" - and message.content.author == "agent" - and message.content.content + + async def poll_for_second_turn() -> None: + nonlocal found_response + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message_2, + timeout=30, + sleep_interval=1.0, ): - response_text = message.content.content.lower() - assert "blue" in response_text, f"Expected 'blue' in response but got: {response_text}" - found_response = True - break + if ( + message.content + and message.content.type == "text" + and message.content.author == "agent" + and message.content.content + ): + response_text = message.content.content.lower() + assert "blue" in response_text, f"Expected 'blue' in response but got: {response_text}" + found_response = True + break + + try: + await asyncio.wait_for(poll_for_second_turn(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for second turn response") assert found_response, "Did not receive final agent text response with context recall" diff --git a/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py index 5c458fe89..0a27c69b1 100644 --- a/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py @@ -86,18 +86,26 @@ async def test_send_event_and_poll_simple_query(self, client: AsyncAgentex, agen user_message = "Hello! Please tell me the latest news about AI and AI startups." messages = [] found_agent_message = False - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - ## we should expect to get a question from the agent - if message.content.type == "text" and message.content.author == "agent": - found_agent_message = True - break + + async def poll_for_agent_message() -> None: + nonlocal found_agent_message + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + ## we should expect to get a question from the agent + if message.content.type == "text" and message.content.author == "agent": + found_agent_message = True + break + + try: + await asyncio.wait_for(poll_for_agent_message(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for agent message") assert found_agent_message, "Did not find an agent message" @@ -106,20 +114,28 @@ async def test_send_event_and_poll_simple_query(self, client: AsyncAgentex, agen next_user_message = "I want to know what viral news came up and which startups failed, got acquired, or became very successful or popular in the last 3 months" starting_deep_research_message = False uses_tool_requests = False - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=next_user_message, - timeout=30, - sleep_interval=1.0, - ): - if message.content.type == "text" and message.content.author == "agent": - if "starting deep research" in message.content.content.lower(): - starting_deep_research_message = True - if isinstance(message.content, ToolRequestContent): - uses_tool_requests = True - break + + async def poll_for_research_response() -> None: + nonlocal starting_deep_research_message, uses_tool_requests + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=next_user_message, + timeout=30, + sleep_interval=1.0, + ): + if message.content.type == "text" and message.content.author == "agent": + if "starting deep research" in message.content.content.lower(): + starting_deep_research_message = True + if isinstance(message.content, ToolRequestContent): + uses_tool_requests = True + break + + try: + await asyncio.wait_for(poll_for_research_response(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for research response") assert starting_deep_research_message, "Did not start deep research" assert uses_tool_requests, "Did not use tool requests" diff --git a/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py index d571e0e7a..e8e1557eb 100644 --- a/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py @@ -17,6 +17,7 @@ import os import uuid +import asyncio import pytest import pytest_asyncio @@ -69,18 +70,30 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): assert task is not None # Poll for the initial task creation message - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - # Check for the Haiku Assistant welcome message - assert "Haiku Assistant" in message.content.content - assert "Temporal" in message.content.content - break + task_creation_found = False + + async def poll_for_task_creation() -> None: + nonlocal task_creation_found + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + # Check for the Haiku Assistant welcome message + assert "Haiku Assistant" in message.content.content + assert "Temporal" in message.content.content + task_creation_found = True + break + + try: + await asyncio.wait_for(poll_for_task_creation(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for task creation message") + + assert task_creation_found, "Task creation message not found" # Send event and poll for response with streaming updates user_message = "Hello how is life?" @@ -88,26 +101,34 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Use yield_updates=True to get all streaming chunks as they're written final_message = None - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - yield_updates=True, # Get updates as streaming writes chunks - ): - if message.content and message.content.type == "text" and message.content.author == "agent": - print( - f"[DEBUG 060 POLL] Received update - Status: {message.streaming_status}, " - f"Content length: {len(message.content.content)}" - ) - final_message = message - - # Stop polling once we get a DONE message - if message.streaming_status == "DONE": - print(f"[DEBUG 060 POLL] Streaming complete!") - break + + async def poll_for_haiku() -> None: + nonlocal final_message + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + yield_updates=True, # Get updates as streaming writes chunks + ): + if message.content and message.content.type == "text" and message.content.author == "agent": + print( + f"[DEBUG 060 POLL] Received update - Status: {message.streaming_status}, " + f"Content length: {len(message.content.content)}" + ) + final_message = message + + # Stop polling once we get a DONE message + if message.streaming_status == "DONE": + print(f"[DEBUG 060 POLL] Streaming complete!") + break + + try: + await asyncio.wait_for(poll_for_haiku(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for haiku response") # Verify the final message has content (the haiku) assert final_message is not None, "Should have received an agent message" @@ -116,7 +137,6 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): print(f"[DEBUG 060 POLL] ✅ Successfully received haiku response!") print(f"[DEBUG 060 POLL] Final haiku:\n{final_message.content.content}") - pass class TestStreamingEvents: diff --git a/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py index d6fdc6ff8..3e6edb346 100644 --- a/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py @@ -17,6 +17,7 @@ import os import uuid +import asyncio import pytest import pytest_asyncio @@ -71,18 +72,30 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Poll for the initial task creation message print(f"[DEBUG 070 POLL] Polling for initial task creation message...") - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - # Check for the initial acknowledgment message - print(f"[DEBUG 070 POLL] Initial message: {message.content.content[:100]}") - assert "task" in message.content.content.lower() or "received" in message.content.content.lower() - break + task_creation_found = False + + async def poll_for_task_creation() -> None: + nonlocal task_creation_found + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + # Check for the initial acknowledgment message + print(f"[DEBUG 070 POLL] Initial message: {message.content.content[:100]}") + assert "task" in message.content.content.lower() or "received" in message.content.content.lower() + task_creation_found = True + break + + try: + await asyncio.wait_for(poll_for_task_creation(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for task creation message") + + assert task_creation_found, "Task creation message not found" # Send an event asking about the weather in NYC and poll for response with streaming user_message = "What is the weather in New York City?" @@ -93,37 +106,44 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): seen_tool_response = False final_message = None - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=60, - sleep_interval=1.0 + async def poll_for_weather_response() -> None: + nonlocal seen_tool_request, seen_tool_response, final_message + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=60, + sleep_interval=1.0 ): - assert isinstance(message, TaskMessage) - print(f"[DEBUG 070 POLL] Received message - Type: {message.content.type if message.content else 'None'}, Author: {message.content.author if message.content else 'None'}, Status: {message.streaming_status}") - - # Track tool_request messages (agent calling get_weather) - if message.content and message.content.type == "tool_request": - print(f"[DEBUG 070 POLL] ✅ Saw tool_request - agent is calling get_weather tool") - seen_tool_request = True - - # Track tool_response messages (get_weather result) - if message.content and message.content.type == "tool_response": - print(f"[DEBUG 070 POLL] ✅ Saw tool_response - get_weather returned result") - seen_tool_response = True - - # Track agent text messages and their streaming updates - if message.content and message.content.type == "text" and message.content.author == "agent": - content_length = len(message.content.content) if message.content.content else 0 - print(f"[DEBUG 070 POLL] Agent text update - Status: {message.streaming_status}, Length: {content_length}") - final_message = message - - # Stop when we get DONE status - if message.streaming_status == "DONE" and content_length > 0: - print(f"[DEBUG 070 POLL] ✅ Streaming complete!") - break + assert isinstance(message, TaskMessage) + print(f"[DEBUG 070 POLL] Received message - Type: {message.content.type if message.content else 'None'}, Author: {message.content.author if message.content else 'None'}, Status: {message.streaming_status}") + + # Track tool_request messages (agent calling get_weather) + if message.content and message.content.type == "tool_request": + print(f"[DEBUG 070 POLL] ✅ Saw tool_request - agent is calling get_weather tool") + seen_tool_request = True + + # Track tool_response messages (get_weather result) + if message.content and message.content.type == "tool_response": + print(f"[DEBUG 070 POLL] ✅ Saw tool_response - get_weather returned result") + seen_tool_response = True + + # Track agent text messages and their streaming updates + if message.content and message.content.type == "text" and message.content.author == "agent": + content_length = len(message.content.content) if message.content.content else 0 + print(f"[DEBUG 070 POLL] Agent text update - Status: {message.streaming_status}, Length: {content_length}") + final_message = message + + # Stop when we get DONE status + if message.streaming_status == "DONE" and content_length > 0: + print(f"[DEBUG 070 POLL] ✅ Streaming complete!") + break + + try: + await asyncio.wait_for(poll_for_weather_response(), timeout=60) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for weather response") # Verify we got all the expected pieces assert seen_tool_request, "Expected to see tool_request message (agent calling get_weather)" diff --git a/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py index 5b0c2f74c..cf8f78e84 100644 --- a/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py @@ -88,18 +88,30 @@ async def test_send_event_and_poll_with_human_approval(self, client: AsyncAgente # Poll for the initial task creation message print(f"[DEBUG 080 POLL] Polling for initial task creation message...") - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - # Check for the initial acknowledgment message - print(f"[DEBUG 080 POLL] Initial message: {message.content.content[:100]}") - assert "task" in message.content.content.lower() or "received" in message.content.content.lower() - break + task_creation_found = False + + async def poll_for_task_creation() -> None: + nonlocal task_creation_found + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + # Check for the initial acknowledgment message + print(f"[DEBUG 080 POLL] Initial message: {message.content.content[:100]}") + assert "task" in message.content.content.lower() or "received" in message.content.content.lower() + task_creation_found = True + break + + try: + await asyncio.wait_for(poll_for_task_creation(), timeout=30) + except asyncio.TimeoutError: + pytest.fail("Polling timed out waiting for task creation message") + + assert task_creation_found, "Task creation message not found" # Send an event asking to confirm an order (triggers human-in-the-loop) user_message = "Please confirm my order" @@ -172,8 +184,8 @@ async def poll_and_detect(): try: await asyncio.wait_for(polling_task, timeout=60) except asyncio.TimeoutError: - print(f"[DEBUG 080 POLL] ⚠️ Polling timed out - workflow may still be waiting") polling_task.cancel() + pytest.fail("Polling timed out waiting for human-in-the-loop workflow to complete") # Verify that we saw the complete flow: tool_request -> human approval -> tool_response -> final answer assert seen_tool_request, "Expected to see tool_request message (agent calling wait_for_confirmation)" diff --git a/examples/tutorials/test_utils/async_utils.py b/examples/tutorials/test_utils/async_utils.py index effe87789..ff3dbd57c 100644 --- a/examples/tutorials/test_utils/async_utils.py +++ b/examples/tutorials/test_utils/async_utils.py @@ -221,8 +221,10 @@ async def stream_agent_response( except asyncio.TimeoutError: print(f"[DEBUG] Stream timed out after {timeout}s") + raise except Exception as e: print(f"[DEBUG] Stream error: {e}") + raise async def stream_task_messages( diff --git a/src/agentex/types/__init__.py b/src/agentex/types/__init__.py index 34f5fb902..140acd92f 100644 --- a/src/agentex/types/__init__.py +++ b/src/agentex/types/__init__.py @@ -62,9 +62,9 @@ from .task_message_content_param import TaskMessageContentParam as TaskMessageContentParam from .tool_request_content_param import ToolRequestContentParam as ToolRequestContentParam from .tool_response_content_param import ToolResponseContentParam as ToolResponseContentParam -from .deployment_history_list_params import DeploymentHistoryListParams as DeploymentHistoryListParams -from .deployment_history_list_response import DeploymentHistoryListResponse as DeploymentHistoryListResponse from .task_retrieve_by_name_params import TaskRetrieveByNameParams as TaskRetrieveByNameParams from .message_list_paginated_params import MessageListPaginatedParams as MessageListPaginatedParams +from .deployment_history_list_params import DeploymentHistoryListParams as DeploymentHistoryListParams from .task_retrieve_by_name_response import TaskRetrieveByNameResponse as TaskRetrieveByNameResponse from .message_list_paginated_response import MessageListPaginatedResponse as MessageListPaginatedResponse +from .deployment_history_list_response import DeploymentHistoryListResponse as DeploymentHistoryListResponse diff --git a/uv.lock b/uv.lock index 0a0242aed..391297102 100644 --- a/uv.lock +++ b/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "agentex-sdk" -version = "0.6.7" +version = "0.8.0" source = { editable = "." } dependencies = [ { name = "aiohttp" }, From 14a32a683bbce91febe32dac4a646523baff5742 Mon Sep 17 00:00:00 2001 From: Roxanne Farhad Date: Thu, 18 Dec 2025 13:50:24 +0000 Subject: [PATCH 2/4] fixing the temporal tests --- .github/workflows/agentex-tutorials-test.yml | 16 ++- .../00_base/000_hello_acp/tests/test_agent.py | 6 +- .../project/workflow.py | 121 +++++++----------- .../manifest.yaml | 1 - .../manifest.yaml | 1 - .../manifest.yaml | 1 - .../tests/test_agent.py | 2 +- 7 files changed, 61 insertions(+), 87 deletions(-) diff --git a/.github/workflows/agentex-tutorials-test.yml b/.github/workflows/agentex-tutorials-test.yml index 9c19bdbef..bff4a1a2f 100644 --- a/.github/workflows/agentex-tutorials-test.yml +++ b/.github/workflows/agentex-tutorials-test.yml @@ -55,8 +55,20 @@ jobs: - name: Pull latest AgentEx image run: | echo "🐳 Pulling latest Scale AgentEx Docker image..." - docker pull ghcr.io/scaleapi/scale-agentex/agentex:latest - echo "✅ Successfully pulled AgentEx Docker image" + max_attempts=3 + attempt=1 + while [ $attempt -le $max_attempts ]; do + echo "Attempt $attempt of $max_attempts..." + if docker pull ghcr.io/scaleapi/scale-agentex/agentex:latest; then + echo "✅ Successfully pulled AgentEx Docker image" + exit 0 + fi + echo "❌ Pull failed, waiting before retry..." + sleep $((attempt * 10)) + attempt=$((attempt + 1)) + done + echo "❌ Failed to pull image after $max_attempts attempts" + exit 1 - name: Checkout scale-agentex repo uses: actions/checkout@v4 diff --git a/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py b/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py index bbd261642..85e61306e 100644 --- a/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py @@ -112,9 +112,9 @@ async def poll_for_response() -> None: ): assert isinstance(message, TaskMessage) if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your message" in message.content.content - agent_response_found = True - break + if "Hello! I've received your message" in message.content.content: + agent_response_found = True + break try: await asyncio.wait_for(poll_for_response(), timeout=30) diff --git a/examples/tutorials/10_async/10_temporal/050_agent_chat_guardrails/project/workflow.py b/examples/tutorials/10_async/10_temporal/050_agent_chat_guardrails/project/workflow.py index c6d2f11ff..d1110bc1f 100644 --- a/examples/tutorials/10_async/10_temporal/050_agent_chat_guardrails/project/workflow.py +++ b/examples/tutorials/10_async/10_temporal/050_agent_chat_guardrails/project/workflow.py @@ -15,12 +15,12 @@ from agentex.lib import adk from agentex.lib.types.acp import SendEventParams, CreateTaskParams -from agentex.lib.adk.models import ModelSettings from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent from agentex.lib.utils.model_utils import BaseModel -from agentex.lib.core.base.run_context import RunContextWrapper + +from agents import ModelSettings, RunContextWrapper from agentex.lib.environment_variables import EnvironmentVariables from agentex.lib.core.temporal.types.workflow import SignalName from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow @@ -36,6 +36,7 @@ class GuardrailFunctionOutput(BaseModel): """Output from a guardrail function.""" + output_info: Dict[str, Any] tripwire_triggered: bool @@ -99,10 +100,7 @@ async def calculator(context: RunContextWrapper, args: str) -> str: # noqa: ARG b = parsed_args.get("b") if operation is None or a is None or b is None: - return ( - "Error: Missing required parameters. " - "Please provide 'operation', 'a', and 'b'." - ) + return "Error: Missing required parameters. Please provide 'operation', 'a', and 'b'." # Convert to numbers try: @@ -124,10 +122,7 @@ async def calculator(context: RunContextWrapper, args: str) -> str: # noqa: ARG result = a / b else: supported_ops = "add, subtract, multiply, divide" - return ( - f"Error: Unknown operation '{operation}'. " - f"Supported operations: {supported_ops}." - ) + return f"Error: Unknown operation '{operation}'. Supported operations: {supported_ops}." # Format the result nicely if result == int(result): @@ -160,9 +155,7 @@ async def calculator(context: RunContextWrapper, args: str) -> str: # noqa: ARG # Define the spaghetti guardrail function async def check_spaghetti_guardrail( - ctx: RunContextWrapper[None], - agent: Agent, - input: str | list + ctx: RunContextWrapper[None], agent: Agent, input: str | list ) -> GuardrailFunctionOutput: """ A simple guardrail that checks if 'spaghetti' is mentioned in the input. @@ -185,25 +178,22 @@ async def check_spaghetti_guardrail( return GuardrailFunctionOutput( output_info={ "contains_spaghetti": contains_spaghetti, - "checked_text": ( - input_text[:200] + "..." - if len(input_text) > 200 else input_text - ), + "checked_text": (input_text[:200] + "..." if len(input_text) > 200 else input_text), "rejection_message": ( "I'm sorry, but I cannot process messages about spaghetti. " "This guardrail was put in place for demonstration purposes. " "Please ask me about something else!" - ) if contains_spaghetti else None + ) + if contains_spaghetti + else None, }, - tripwire_triggered=contains_spaghetti + tripwire_triggered=contains_spaghetti, ) # Define soup input guardrail function async def check_soup_guardrail( - ctx: RunContextWrapper[None], - agent: Agent, - input: str | list + ctx: RunContextWrapper[None], agent: Agent, input: str | list ) -> GuardrailFunctionOutput: """ A guardrail that checks if 'soup' is mentioned in the input. @@ -226,44 +216,33 @@ async def check_soup_guardrail( return GuardrailFunctionOutput( output_info={ "contains_soup": contains_soup, - "checked_text": ( - input_text[:200] + "..." - if len(input_text) > 200 else input_text - ), + "checked_text": (input_text[:200] + "..." if len(input_text) > 200 else input_text), "rejection_message": ( "I'm sorry, but I cannot process messages about soup. " "This is a demonstration guardrail for testing purposes. " "Please ask about something other than soup!" - ) if contains_soup else None + ) + if contains_soup + else None, }, - tripwire_triggered=contains_soup + tripwire_triggered=contains_soup, ) # Create the input guardrails -SPAGHETTI_GUARDRAIL = TemporalInputGuardrail( - guardrail_function=check_spaghetti_guardrail, - name="spaghetti_guardrail" -) +SPAGHETTI_GUARDRAIL = TemporalInputGuardrail(guardrail_function=check_spaghetti_guardrail, name="spaghetti_guardrail") -SOUP_GUARDRAIL = TemporalInputGuardrail( - guardrail_function=check_soup_guardrail, - name="soup_guardrail" -) +SOUP_GUARDRAIL = TemporalInputGuardrail(guardrail_function=check_soup_guardrail, name="soup_guardrail") # Define pizza output guardrail function -async def check_pizza_guardrail( - ctx: RunContextWrapper[None], - agent: Agent, - output: str -) -> GuardrailFunctionOutput: +async def check_pizza_guardrail(ctx: RunContextWrapper[None], agent: Agent, output: str) -> GuardrailFunctionOutput: """ An output guardrail that prevents mentioning pizza. """ output_text = output.lower() if isinstance(output, str) else "" contains_pizza = "pizza" in output_text - + return GuardrailFunctionOutput( output_info={ "contains_pizza": contains_pizza, @@ -271,24 +250,22 @@ async def check_pizza_guardrail( "I cannot provide this response as it mentions pizza. " "Due to content policies, I need to avoid discussing pizza. " "Let me provide a different response." - ) if contains_pizza else None + ) + if contains_pizza + else None, }, - tripwire_triggered=contains_pizza + tripwire_triggered=contains_pizza, ) # Define sushi output guardrail function -async def check_sushi_guardrail( - ctx: RunContextWrapper[None], - agent: Agent, - output: str -) -> GuardrailFunctionOutput: +async def check_sushi_guardrail(ctx: RunContextWrapper[None], agent: Agent, output: str) -> GuardrailFunctionOutput: """ An output guardrail that prevents mentioning sushi. """ output_text = output.lower() if isinstance(output, str) else "" contains_sushi = "sushi" in output_text - + return GuardrailFunctionOutput( output_info={ "contains_sushi": contains_sushi, @@ -296,29 +273,23 @@ async def check_sushi_guardrail( "I cannot mention sushi in my response. " "This guardrail prevents discussions about sushi for demonstration purposes. " "Please let me provide information about other topics." - ) if contains_sushi else None + ) + if contains_sushi + else None, }, - tripwire_triggered=contains_sushi + tripwire_triggered=contains_sushi, ) # Create the output guardrails -PIZZA_GUARDRAIL = TemporalOutputGuardrail( - guardrail_function=check_pizza_guardrail, - name="pizza_guardrail" -) +PIZZA_GUARDRAIL = TemporalOutputGuardrail(guardrail_function=check_pizza_guardrail, name="pizza_guardrail") -SUSHI_GUARDRAIL = TemporalOutputGuardrail( - guardrail_function=check_sushi_guardrail, - name="sushi_guardrail" -) +SUSHI_GUARDRAIL = TemporalOutputGuardrail(guardrail_function=check_sushi_guardrail, name="sushi_guardrail") # Example output guardrail function (kept for reference) async def check_output_length_guardrail( - ctx: RunContextWrapper[None], - agent: Agent, - output: str + ctx: RunContextWrapper[None], agent: Agent, output: str ) -> GuardrailFunctionOutput: """ A simple output guardrail that checks if the response is too long. @@ -326,7 +297,7 @@ async def check_output_length_guardrail( # Check the length of the output max_length = 1000 # Maximum allowed characters is_too_long = len(output) > max_length if isinstance(output, str) else False - + return GuardrailFunctionOutput( output_info={ "output_length": len(output) if isinstance(output, str) else 0, @@ -336,9 +307,11 @@ async def check_output_length_guardrail( f"I'm sorry, but my response is too long ({len(output)} characters). " f"Please ask a more specific question so I can provide a concise answer " f"(max {max_length} characters)." - ) if is_too_long else None + ) + if is_too_long + else None, }, - tripwire_triggered=is_too_long + tripwire_triggered=is_too_long, ) @@ -353,10 +326,7 @@ async def check_output_length_guardrail( # Create the calculator tool CALCULATOR_TOOL = FunctionTool( name="calculator", - description=( - "Performs basic arithmetic operations (add, subtract, multiply, " - "divide) on two numbers." - ), + description=("Performs basic arithmetic operations (add, subtract, multiply, divide) on two numbers."), params_json_schema={ "type": "object", "properties": { @@ -390,16 +360,13 @@ def __init__(self): @workflow.signal(name=SignalName.RECEIVE_EVENT) @override async def on_task_event_send(self, params: SendEventParams) -> None: - if not params.event.content: return if params.event.content.type != "text": raise ValueError(f"Expected text message, got {params.event.content.type}") if params.event.content.author != "user": - raise ValueError( - f"Expected user message, got {params.event.content.author}" - ) + raise ValueError(f"Expected user message, got {params.event.content.author}") if self._state is None: raise ValueError("State is not initialized") @@ -407,9 +374,7 @@ async def on_task_event_send(self, params: SendEventParams) -> None: # Increment the turn number self._state.turn_number += 1 # Add the new user message to the message history - self._state.input_list.append( - {"role": "user", "content": params.event.content.content} - ) + self._state.input_list.append({"role": "user", "content": params.event.content.content}) async with adk.tracing.span( trace_id=params.task.id, @@ -475,7 +440,7 @@ async def on_task_event_send(self, params: SendEventParams) -> None: input_guardrails=[SPAGHETTI_GUARDRAIL, SOUP_GUARDRAIL], output_guardrails=[PIZZA_GUARDRAIL, SUSHI_GUARDRAIL], ) - + # Update state with the final input list from result if self._state and result: final_list = getattr(result, "final_input_list", None) diff --git a/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/manifest.yaml b/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/manifest.yaml index 773d5ba44..b339542d5 100644 --- a/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/manifest.yaml +++ b/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/manifest.yaml @@ -104,7 +104,6 @@ agent: # Optional: Set Environment variables for running your agent locally as well # as for deployment later on env: - OPENAI_API_KEY: "" # OPENAI_BASE_URL: "" OPENAI_ORG_ID: "" diff --git a/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/manifest.yaml b/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/manifest.yaml index 40bf9e78a..d28da57b6 100644 --- a/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/manifest.yaml +++ b/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/manifest.yaml @@ -102,7 +102,6 @@ agent: # Optional: Set Environment variables for running your agent locally as well # as for deployment later on env: - OPENAI_API_KEY: "" # OPENAI_BASE_URL: "" OPENAI_ORG_ID: "" diff --git a/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/manifest.yaml b/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/manifest.yaml index 09f49c3e5..f6fc7e9ca 100644 --- a/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/manifest.yaml +++ b/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/manifest.yaml @@ -104,7 +104,6 @@ agent: # Optional: Set Environment variables for running your agent locally as well # as for deployment later on env: - OPENAI_API_KEY: "" # OPENAI_BASE_URL: "" OPENAI_ORG_ID: "" diff --git a/examples/tutorials/10_async/10_temporal/090_claude_agents_sdk_mvp/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/090_claude_agents_sdk_mvp/tests/test_agent.py index aae1cb916..9b93b1b76 100644 --- a/examples/tutorials/10_async/10_temporal/090_claude_agents_sdk_mvp/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/090_claude_agents_sdk_mvp/tests/test_agent.py @@ -18,7 +18,7 @@ # Configuration from environment variables AGENTEX_API_BASE_URL = os.environ.get("AGENTEX_API_BASE_URL", "http://localhost:5003") -AGENT_NAME = os.environ.get("AGENT_NAME", "claude_") +AGENT_NAME = os.environ.get("AGENT_NAME", "claude-mvp-agent") @pytest_asyncio.fixture From 0f0b8b949f946ac27c349cb7d836cdae0e7dd96e Mon Sep 17 00:00:00 2001 From: Roxanne Farhad Date: Mon, 22 Dec 2025 14:37:09 +0000 Subject: [PATCH 3/4] revert the nesting --- .../00_base/000_hello_acp/tests/test_agent.py | 163 ++++----- .../00_base/010_multiturn/tests/test_agent.py | 121 +++---- .../00_base/020_streaming/tests/test_agent.py | 111 +++---- .../040_other_sdks/tests/test_agent.py | 311 +++++++----------- .../080_batch_events/tests/test_agent.py | 67 ++-- .../tests/test_agent.py | 67 ++-- .../000_hello_acp/tests/test_agent.py | 162 ++++----- .../010_agent_chat/tests/test_agent.py | 265 +++++++-------- .../020_state_machine/tests/test_agent.py | 131 +++----- .../tests/test_agent.py | 83 ++--- .../tests/test_agent.py | 116 +++---- .../tests/test_agent.py | 151 ++++----- 12 files changed, 714 insertions(+), 1034 deletions(-) diff --git a/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py b/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py index 85e61306e..1a98158d8 100644 --- a/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py @@ -17,7 +17,6 @@ import os import uuid -import asyncio import pytest import pytest_asyncio @@ -75,24 +74,17 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Poll for the initial task creation message task_creation_message_found = False - async def poll_for_task_creation() -> None: - nonlocal task_creation_message_found - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your task" in message.content.content - task_creation_message_found = True - break - - try: - await asyncio.wait_for(poll_for_task_creation(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for task creation message") + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your task" in message.content.content + task_creation_message_found = True + break assert task_creation_message_found, "Task creation message not found" @@ -100,30 +92,21 @@ async def poll_for_task_creation() -> None: user_message = "Hello, this is a test message!" agent_response_found = False - async def poll_for_response() -> None: - nonlocal agent_response_found - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - if "Hello! I've received your message" in message.content.content: - agent_response_found = True - break - - try: - await asyncio.wait_for(poll_for_response(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for agent response") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your task" in message.content.content + agent_response_found = True + break assert agent_response_found, "Agent response not found" - - class TestStreamingEvents: """Test streaming event sending.""" @@ -136,27 +119,19 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): assert task is not None task_creation_found = False - # Poll for the initial task creation message - async def poll_for_task_creation() -> None: - nonlocal task_creation_found - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your task" in message.content.content - task_creation_found = True - break - - try: - await asyncio.wait_for(poll_for_task_creation(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for task creation message") - - assert task_creation_found, "Task creation message not found in poll" + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your task" in message.content.content + task_creation_found = True + break + + assert task_creation_found, "Task creation message not found" user_message = "Hello, this is a test message!" stream_timeout = 10 @@ -168,48 +143,36 @@ async def poll_for_task_creation() -> None: user_echo_found = False agent_response_found = False - async def collect_stream_events() -> None: - nonlocal user_echo_found, agent_response_found - - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=stream_timeout, - ): - all_events.append(event) - # Check events as they arrive - event_type = event.get("type") - if event_type == "full": - content = event.get("content", {}) - if content.get("content") is None: - continue # Skip empty content - if content.get("type") == "text" and content.get("author") == "agent": - # Check for agent response to user message - if "Hello! I've received your message" in content.get("content", ""): - # Agent response should come after user echo - assert user_echo_found, "Agent response arrived before user message echo (incorrect order)" - agent_response_found = True - elif content.get("type") == "text" and content.get("author") == "user": - # Check for user message echo - if content.get("content") == user_message: - user_echo_found = True - - # Exit early if we've found all expected messages - if user_echo_found and agent_response_found: - break - - # Start streaming task - stream_task = asyncio.create_task(collect_stream_events()) - # Send the event event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - # Wait for the stream to complete (with timeout) - try: - await asyncio.wait_for(stream_task, timeout=stream_timeout) - except asyncio.TimeoutError: - pytest.fail(f"Stream timed out after {stream_timeout}s waiting for expected messages") + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=stream_timeout, + ): + all_events.append(event) + # Check events as they arrive + event_type = event.get("type") + if event_type == "full": + content = event.get("content", {}) + if content.get("content") is None: + continue # Skip empty content + if content.get("type") == "text" and content.get("author") == "agent": + # Check for agent response to user message + if "Hello! I've received your message" in content.get("content", ""): + # Agent response should come after user echo + assert user_echo_found, "Agent response arrived before user message echo (incorrect order)" + agent_response_found = True + elif content.get("type") == "text" and content.get("author") == "user": + # Check for user message echo + if content.get("content") == user_message: + user_echo_found = True + + # Exit early if we've found all expected messages + if user_echo_found and agent_response_found: + break # Verify all expected messages were received (fail if stream ended without finding them) assert user_echo_found, "User message echo not found in stream" diff --git a/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py b/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py index fcc28810a..5fa3ce70a 100644 --- a/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py @@ -93,41 +93,33 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Flags to track what we've received user_message_found = False agent_response_found = False - - async def poll_for_messages() -> None: - nonlocal user_message_found, agent_response_found - - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - messages.append(message) - - # Validate messages as they arrive - if message.content and hasattr(message.content, 'author'): - if message.content.author == "user" and message.content.content == user_message: - assert message.content == TextContent( - author="user", - content=user_message, - type="text", - ) - user_message_found = True - elif message.content.author == "agent": - assert user_message_found, "Agent response arrived before user message" - agent_response_found = True - - # Exit early if we've found all expected messages - if user_message_found and agent_response_found: - break - - try: - await asyncio.wait_for(poll_for_messages(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out after 30s waiting for expected messages") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + messages.append(message) + + # Validate messages as they arrive + if message.content and hasattr(message.content, "author"): + msg_text = getattr(message.content, "content", None) + if message.content.author == "user" and msg_text == user_message: + assert message.content == TextContent( + author="user", + content=user_message, + type="text", + ) + user_message_found = True + elif message.content.author == "agent": + assert user_message_found, "Agent response arrived before user message" + agent_response_found = True + + # Exit early if we've found all expected messages + if user_message_found and agent_response_found: + break assert user_message_found, "User message not found" assert agent_response_found, "Agent response not found" @@ -175,44 +167,31 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): # Flags to track what we've received user_message_found = False agent_response_found = False - - async def stream_messages(): - nonlocal user_message_found, agent_response_found - - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=15, - ): - all_events.append(event) - - # Check events as they arrive - event_type = event.get("type") - if event_type == "full": - content = event.get("content", {}) - if content.get("content") == user_message and content.get("author") == "user": - # User message should come before agent response - assert not agent_response_found, "User message arrived after agent response (incorrect order)" - user_message_found = True - elif content.get("author") == "agent": - # Agent response should come after user message - assert user_message_found, "Agent response arrived before user message (incorrect order)" - agent_response_found = True - - # Exit early if we've found both messages - if user_message_found and agent_response_found: - break - - stream_task = asyncio.create_task(stream_messages()) - event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - - # Wait for streaming to complete (with timeout) - try: - await asyncio.wait_for(stream_task, timeout=15) - except asyncio.TimeoutError: - pytest.fail("Stream timed out after 15s waiting for expected messages") + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=15, + ): + all_events.append(event) + + # Check events as they arrive + event_type = event.get("type") + if event_type == "full": + content = event.get("content", {}) + if content.get("content") == user_message and content.get("author") == "user": + # User message should come before agent response + assert not agent_response_found, "User message arrived after agent response (incorrect order)" + user_message_found = True + elif content.get("author") == "agent": + # Agent response should come after user message + assert user_message_found, "Agent response arrived before user message (incorrect order)" + agent_response_found = True + + # Exit early if we've found both messages + if user_message_found and agent_response_found: + break # Validate we received events assert len(all_events) > 0, "No events received in streaming response" diff --git a/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py b/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py index b82f857df..cc5ef20f6 100644 --- a/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py @@ -92,39 +92,29 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Flags to track what we've received user_message_found = False agent_response_found = False - - async def poll_for_messages() -> None: - nonlocal user_message_found, agent_response_found - - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - yield_updates=False, - ): - messages.append(message) - - # Validate messages as they come in - if message.content and hasattr(message.content, 'author'): - if message.content.author == "user" and message.content.content == user_message: - user_message_found = True - elif message.content.author == "agent": - # Agent response should come after user message - assert user_message_found, "Agent response arrived before user message" - agent_response_found = True - - # Exit early if we've found all expected messages - if user_message_found and agent_response_found: - break - - # Run polling with timeout - try: - await asyncio.wait_for(poll_for_messages(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out after 30s waiting for expected messages") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + yield_updates=False, + ): + messages.append(message) + + # Validate messages as they come in + if message.content and hasattr(message.content, "author"): + if message.content.author == "user" and message.content.content == user_message: + user_message_found = True + elif message.content.author == "agent": + # Agent response should come after user message + assert user_message_found, "Agent response arrived before user message" + agent_response_found = True + + # Exit early if we've found all expected messages + if user_message_found and agent_response_found: + break # Validate we received expected messages assert len(messages) >= 2, "Expected at least 2 messages (user + agent)" @@ -177,42 +167,29 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): user_message_found = False full_agent_message_found = False delta_messages_found = False - - async def stream_messages() -> None: - nonlocal user_message_found, full_agent_message_found, delta_messages_found - - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=15, - ): - all_events.append(event) - - # Check events as they arrive - event_type = event.get("type") - if event_type == "full": - content = event.get("content", {}) - if content.get("content") == user_message and content.get("author") == "user": - user_message_found = True - elif content.get("author") == "agent": - full_agent_message_found = True - elif event_type == "delta": - delta_messages_found = True - - # Exit early if we've found all expected messages - if user_message_found and full_agent_message_found and delta_messages_found: - break - - stream_task = asyncio.create_task(stream_messages()) - event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - - # Wait for streaming to complete (with timeout) - try: - await asyncio.wait_for(stream_task, timeout=15) - except asyncio.TimeoutError: - pytest.fail("Stream timed out after 15s waiting for expected messages") + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=15, + ): + all_events.append(event) + + # Check events as they arrive + event_type = event.get("type") + if event_type == "full": + content = event.get("content", {}) + if content.get("content") == user_message and content.get("author") == "user": + user_message_found = True + elif content.get("author") == "agent": + full_agent_message_found = True + elif event_type == "delta": + delta_messages_found = True + + # Exit early if we've found all expected messages + if user_message_found and full_agent_message_found and delta_messages_found: + break # Validate we received events assert len(all_events) > 0, "No events received in streaming response" diff --git a/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py b/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py index 799ca15b5..0f62cc06d 100644 --- a/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py @@ -95,33 +95,25 @@ async def test_send_event_and_poll_simple_query(self, client: AsyncAgentex, agen user_message = "Hello! Please introduce yourself briefly." messages = [] user_message_found = False - - async def poll_for_messages() -> None: - nonlocal user_message_found - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - messages.append(message) - - if message.content and message.content.author == "user": - assert message.content == TextContent( - author="user", - content=user_message, - type="text", - ) - user_message_found = True - break - - try: - await asyncio.wait_for(poll_for_messages(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for user message") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + messages.append(message) + + if message.content and message.content.author == "user": + assert message.content == TextContent( + author="user", + content=user_message, + type="text", + ) + user_message_found = True + break assert user_message_found, "User message not found" @@ -152,34 +144,26 @@ async def test_send_event_and_poll_with_tool_use(self, client: AsyncAgentex, age tool_request_found = False tool_response_found = False has_final_agent_response = False - - async def poll_for_tool_response() -> None: - nonlocal tool_request_found, tool_response_found, has_final_agent_response - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=60, # Longer timeout for tool use - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "tool_request": - tool_request_found = True - assert message.content.author == "agent" - assert hasattr(message.content, "name") - assert hasattr(message.content, "tool_call_id") - elif message.content and message.content.type == "tool_response": - tool_response_found = True - assert message.content.author == "agent" - elif message.content and message.content.type == "text" and message.content.author == "agent": - has_final_agent_response = True - break - - try: - await asyncio.wait_for(poll_for_tool_response(), timeout=60) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for tool response") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=60, # Longer timeout for tool use + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "tool_request": + tool_request_found = True + assert message.content.author == "agent" + assert hasattr(message.content, "name") + assert hasattr(message.content, "tool_call_id") + elif message.content and message.content.type == "tool_response": + tool_response_found = True + assert message.content.author == "agent" + elif message.content and message.content.type == "text" and message.content.author == "agent": + has_final_agent_response = True + break assert has_final_agent_response, "Did not receive final agent text response" assert tool_request_found, "Did not see tool request message" @@ -199,31 +183,23 @@ async def test_multi_turn_conversation_with_state(self, client: AsyncAgentex, ag # First turn user_message_1 = "My favorite color is blue." first_turn_response_found = False - - async def poll_for_first_turn() -> None: - nonlocal first_turn_response_found - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message_1, - timeout=20, - sleep_interval=1.0, + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message_1, + timeout=20, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if ( + message.content + and message.content.type == "text" + and message.content.author == "agent" + and message.content.content ): - assert isinstance(message, TaskMessage) - if ( - message.content - and message.content.type == "text" - and message.content.author == "agent" - and message.content.content - ): - first_turn_response_found = True - break - - try: - await asyncio.wait_for(poll_for_first_turn(), timeout=20) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for first turn response") + first_turn_response_found = True + break assert first_turn_response_found, "First turn response not found" @@ -246,32 +222,24 @@ async def poll_for_first_turn() -> None: # Second turn - reference previous context user_message_2 = "What did I just tell you my favorite color was?" second_turn_response_found = False - - async def poll_for_second_turn() -> None: - nonlocal second_turn_response_found - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message_2, - timeout=30, - sleep_interval=1.0, + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message_2, + timeout=30, + sleep_interval=1.0, + ): + if ( + message.content + and message.content.type == "text" + and message.content.author == "agent" + and message.content.content ): - if ( - message.content - and message.content.type == "text" - and message.content.author == "agent" - and message.content.content - ): - response_text = message.content.content.lower() - assert "blue" in response_text - second_turn_response_found = True - break - - try: - await asyncio.wait_for(poll_for_second_turn(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for second turn response") + response_text = message.content.content.lower() + assert "blue" in response_text + second_turn_response_found = True + break assert second_turn_response_found, "Did not receive final agent text response" for i in range(10): @@ -312,36 +280,24 @@ async def test_send_event_and_stream_simple(self, client: AsyncAgentex, agent_id # Collect events from stream # Check for user message and delta messages user_message_found = False - - async def stream_messages() -> None: - nonlocal user_message_found - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=20, - ): - msg_type = event.get("type") - # For full messages, content is at the top level - # For delta messages, we need to check parent_task_message - if msg_type == "full": - if ( - event.get("content", {}).get("type") == "text" - and event.get("content", {}).get("author") == "user" - ): - user_message_found = True - elif msg_type == "done": - break - - stream_task = asyncio.create_task(stream_messages()) - event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - - # Wait for streaming to complete (with timeout) - try: - await asyncio.wait_for(stream_task, timeout=30) - except asyncio.TimeoutError: - pytest.fail("Stream timed out after 30s waiting for expected messages") + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=20, + ): + msg_type = event.get("type") + # For full messages, content is at the top level + # For delta messages, we need to check parent_task_message + if msg_type == "full": + if ( + event.get("content", {}).get("type") == "text" + and event.get("content", {}).get("author") == "user" + ): + user_message_found = True + elif msg_type == "done": + break assert user_message_found, "User message found in stream" ## keep polling the states for 10 seconds for the input_list and turn_number to be updated for i in range(10): @@ -377,60 +333,47 @@ async def test_send_event_and_stream_with_tools(self, client: AsyncAgentex, agen tool_requests_seen = [] tool_responses_seen = [] text_deltas_seen = [] - - async def stream_messages() -> None: - nonlocal tool_requests_seen, tool_responses_seen, text_deltas_seen - - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=45, - ): - msg_type = event.get("type") - - # For full messages, content is at the top level - # For delta messages, we need to check parent_task_message - if msg_type == "delta": - parent_msg = event.get("parent_task_message", {}) - content = parent_msg.get("content", {}) - delta = event.get("delta", {}) - content_type = content.get("type") - - if content_type == "text": - text_deltas_seen.append(delta.get("text_delta", "")) - elif msg_type == "full": - # For full messages - content = event.get("content", {}) - content_type = content.get("type") - - if content_type == "tool_request": - tool_requests_seen.append( - { - "name": content.get("name"), - "tool_call_id": content.get("tool_call_id"), - "streaming_type": msg_type, - } - ) - elif content_type == "tool_response": - tool_responses_seen.append( - { - "tool_call_id": content.get("tool_call_id"), - "streaming_type": msg_type, - } - ) - elif msg_type == "done": - break - - stream_task = asyncio.create_task(stream_messages()) - event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - - # Wait for streaming to complete (with timeout) - try: - await asyncio.wait_for(stream_task, timeout=60) - except asyncio.TimeoutError: - pytest.fail("Stream timed out after 60s waiting for expected messages") + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=45, + ): + msg_type = event.get("type") + + # For full messages, content is at the top level + # For delta messages, we need to check parent_task_message + if msg_type == "delta": + parent_msg = event.get("parent_task_message", {}) + content = parent_msg.get("content", {}) + delta = event.get("delta", {}) + content_type = content.get("type") + + if content_type == "text": + text_deltas_seen.append(delta.get("text_delta", "")) + elif msg_type == "full": + # For full messages + content = event.get("content", {}) + content_type = content.get("type") + + if content_type == "tool_request": + tool_requests_seen.append( + { + "name": content.get("name"), + "tool_call_id": content.get("tool_call_id"), + "streaming_type": msg_type, + } + ) + elif content_type == "tool_response": + tool_responses_seen.append( + { + "tool_call_id": content.get("tool_call_id"), + "streaming_type": msg_type, + } + ) + elif msg_type == "done": + break # Verify we saw tool usage (if the agent decided to use tools) # Note: The agent may or may not use tools depending on its reasoning diff --git a/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py b/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py index 1d6c26524..103b614e3 100644 --- a/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py @@ -76,28 +76,20 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Send an event and poll for response using the helper function # there should only be one message returned about batching agent_response_found = False - - async def poll_for_response() -> None: - nonlocal agent_response_found - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message="Process this single event", - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.author == "agent": - assert isinstance(message.content, TextContent) - assert "Processed event IDs" in message.content.content - agent_response_found = True - break - - try: - await asyncio.wait_for(poll_for_response(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for agent response") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message="Process this single event", + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.author == "agent": + assert isinstance(message.content, TextContent) + assert "Processed event IDs" in message.content.content + agent_response_found = True + break assert agent_response_found, "Agent response not found" @@ -121,26 +113,19 @@ async def test_send_multiple_events_batched(self, client: AsyncAgentex, agent_id ## there should be at least 2 agent responses to ensure that not all of the events are processed ## in the same message agent_messages = [] + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message="Process this single event", + timeout=30, + sleep_interval=1.0, + ): + if message.content and message.content.author == "agent": + agent_messages.append(message) - async def poll_for_batch_responses() -> None: - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message="Process this single event", - timeout=30, - sleep_interval=1.0, - ): - if message.content and message.content.author == "agent": - agent_messages.append(message) - - if len(agent_messages) == 2: - break - - try: - await asyncio.wait_for(poll_for_batch_responses(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for batch responses") + if len(agent_messages) == 2: + break assert len(agent_messages) > 0, "Should have received at least one agent response" diff --git a/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py b/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py index 950156002..f11c17ed8 100644 --- a/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py @@ -18,8 +18,8 @@ import os import uuid -# import pytest -# import pytest_asyncio +import pytest +import pytest_asyncio from test_utils.async_utils import ( stream_agent_response, send_event_and_poll_yielding, @@ -92,47 +92,40 @@ async def test_multi_agent_workflow_complete(self, client: AsyncAgentex, agent_i } all_agents_done = False + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=json.dumps(request_json), + timeout=120, # Longer timeout for multi-agent workflow + sleep_interval=2.0, + ): + messages.append(message) + # Print messages as they arrive to show real-time progress + msg_text = getattr(message.content, "content", None) if message.content else None + if isinstance(msg_text, str) and msg_text: + # Track agent participation as messages arrive + content = msg_text.lower() - async def poll_for_workflow() -> None: - nonlocal all_agents_done - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=json.dumps(request_json), - timeout=120, # Longer timeout for multi-agent workflow - sleep_interval=2.0, - ): - messages.append(message) - # Print messages as they arrive to show real-time progress - if message.content and message.content.content: - # Track agent participation as messages arrive - content = message.content.content.lower() - - if "starting content workflow" in content: - workflow_markers["orchestrator_started"] = True + if "starting content workflow" in content: + workflow_markers["orchestrator_started"] = True - if "creator output" in content: - workflow_markers["creator_called"] = True + if "creator output" in content: + workflow_markers["creator_called"] = True - if "critic feedback" in content or "content approved by critic" in content: - workflow_markers["critic_called"] = True + if "critic feedback" in content or "content approved by critic" in content: + workflow_markers["critic_called"] = True - if "calling formatter agent" in content: - workflow_markers["formatter_called"] = True + if "calling formatter agent" in content: + workflow_markers["formatter_called"] = True - if "workflow complete" in content or "content creation complete" in content: - workflow_markers["workflow_completed"] = True - - # Check if all agents have participated - all_agents_done = all(workflow_markers.values()) - if all_agents_done: - break + if "workflow complete" in content or "content creation complete" in content: + workflow_markers["workflow_completed"] = True - try: - await asyncio.wait_for(poll_for_workflow(), timeout=120) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for multi-agent workflow completion") + # Check if all agents have participated + all_agents_done = all(workflow_markers.values()) + if all_agents_done: + break # Assert all agents participated assert workflow_markers["orchestrator_started"], "Orchestrator did not start workflow" diff --git a/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py index 5aa182c48..60894814e 100644 --- a/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py @@ -74,53 +74,36 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): assert task is not None task_creation_found = False - - # Poll for the initial task creation message - async def poll_for_task_creation() -> None: - nonlocal task_creation_found - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your task" in message.content.content - task_creation_found = True - break - - try: - await asyncio.wait_for(poll_for_task_creation(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for task creation message") - - assert task_creation_found, "Task creation message not found in poll" + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your task" in message.content.content + task_creation_found = True + break + + assert task_creation_found, "Task creation message not found" await asyncio.sleep(1.5) # Send an event and poll for response user_message = "Hello, this is a test message!" agent_response_found = False - - async def poll_for_response() -> None: - nonlocal agent_response_found - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your message" in message.content.content - agent_response_found = True - break - - try: - await asyncio.wait_for(poll_for_response(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for agent response") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your message" in message.content.content + agent_response_found = True + break assert agent_response_found, "Agent response not found" @@ -136,27 +119,19 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): assert task is not None task_creation_found = False - - async def poll_for_task_creation() -> None: - nonlocal task_creation_found - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - assert "Hello! I've received your task" in message.content.content - task_creation_found = True - break - - try: - await asyncio.wait_for(poll_for_task_creation(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for task creation message") - - assert task_creation_found, "Task creation message not found in poll" + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + assert "Hello! I've received your task" in message.content.content + task_creation_found = True + break + + assert task_creation_found, "Task creation message not found" user_message = "Hello, this is a test message!" @@ -167,46 +142,35 @@ async def poll_for_task_creation() -> None: user_echo_found = False agent_response_found = False stream_timeout = 30 - async def collect_stream_events(): #noqa: ANN101 - nonlocal user_echo_found, agent_response_found - - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=stream_timeout, - ): - # Check events as they arrive - event_type = event.get("type") - if event_type == "full": - content = event.get("content", {}) - if content.get("content") is None: - continue # Skip empty content - if content.get("type") == "text" and content.get("author") == "agent": - # Check for agent response to user message - if "Hello! I've received your message" in content.get("content", ""): - # Agent response should come after user echo - assert user_echo_found, "Agent response arrived before user message echo (incorrect order)" - agent_response_found = True - elif content.get("type") == "text" and content.get("author") == "user": - # Check for user message echo - if content.get("content") == user_message: - user_echo_found = True - - # Exit early if we've found all expected messages - if user_echo_found and agent_response_found: - break - # Start streaming task - stream_task = asyncio.create_task(collect_stream_events()) # Send the event event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - - # Wait for the stream to complete (with timeout) - try: - await asyncio.wait_for(stream_task, timeout=stream_timeout) - except asyncio.TimeoutError: - pytest.fail(f"Stream timed out after {stream_timeout}s waiting for expected messages") + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=stream_timeout, + ): + # Check events as they arrive + event_type = event.get("type") + if event_type == "full": + content = event.get("content", {}) + if content.get("content") is None: + continue # Skip empty content + if content.get("type") == "text" and content.get("author") == "agent": + # Check for agent response to user message + if "Hello! I've received your message" in content.get("content", ""): + # Agent response should come after user echo + assert user_echo_found, "Agent response arrived before user message echo (incorrect order)" + agent_response_found = True + elif content.get("type") == "text" and content.get("author") == "user": + # Check for user message echo + if content.get("content") == user_message: + user_echo_found = True + + # Exit early if we've found all expected messages + if user_echo_found and agent_response_found: + break # Verify all expected messages were received (fail if stream ended without finding them) diff --git a/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py index e532397ca..e486daebd 100644 --- a/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py @@ -88,33 +88,25 @@ async def test_send_event_and_poll_simple_query(self, client: AsyncAgentex, agen user_message = "Hello! Please introduce yourself briefly." messages = [] user_message_found = False - - async def poll_for_user_message() -> None: - nonlocal user_message_found - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - messages.append(message) - - if message.content and message.content.author == "user": - assert message.content == TextContent( - author="user", - content=user_message, - type="text", - ) - user_message_found = True - break - - try: - await asyncio.wait_for(poll_for_user_message(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for user message") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + messages.append(message) + + if message.content and message.content.author == "user": + assert message.content == TextContent( + author="user", + content=user_message, + type="text", + ) + user_message_found = True + break assert user_message_found, "User message not found" @@ -132,28 +124,20 @@ async def test_send_event_and_poll_with_calculator(self, client: AsyncAgentex, a # Send a message that could trigger the calculator tool (though with reasoning, it may not need it) user_message = "What is 15 multiplied by 37?" has_final_agent_response = False - - async def poll_for_calculator_response() -> None: - nonlocal has_final_agent_response - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=60, # Longer timeout for tool use - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - # Check that the answer contains 555 (15 * 37) - if "555" in message.content.content: - has_final_agent_response = True - break - - try: - await asyncio.wait_for(poll_for_calculator_response(), timeout=60) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for calculator response") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=60, # Longer timeout for tool use + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + # Check that the answer contains 555 (15 * 37) + if "555" in message.content.content: + has_final_agent_response = True + break assert has_final_agent_response, "Did not receive final agent text response with correct answer" @@ -171,31 +155,23 @@ async def test_multi_turn_conversation(self, client: AsyncAgentex, agent_id: str # First turn user_message_1 = "My favorite color is blue." first_turn_found = False - - async def poll_for_first_turn() -> None: - nonlocal first_turn_found - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message_1, - timeout=30, - sleep_interval=1.0, + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message_1, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if ( + message.content + and message.content.type == "text" + and message.content.author == "agent" + and message.content.content ): - assert isinstance(message, TaskMessage) - if ( - message.content - and message.content.type == "text" - and message.content.author == "agent" - and message.content.content - ): - first_turn_found = True - break - - try: - await asyncio.wait_for(poll_for_first_turn(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for first turn response") + first_turn_found = True + break assert first_turn_found, "First turn response not found" @@ -205,32 +181,24 @@ async def poll_for_first_turn() -> None: # Second turn - reference previous context found_response = False user_message_2 = "What did I just tell you my favorite color was?" - - async def poll_for_second_turn() -> None: - nonlocal found_response - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message_2, - timeout=30, - sleep_interval=1.0, + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message_2, + timeout=30, + sleep_interval=1.0, + ): + if ( + message.content + and message.content.type == "text" + and message.content.author == "agent" + and message.content.content ): - if ( - message.content - and message.content.type == "text" - and message.content.author == "agent" - and message.content.content - ): - response_text = message.content.content.lower() - assert "blue" in response_text, f"Expected 'blue' in response but got: {response_text}" - found_response = True - break - - try: - await asyncio.wait_for(poll_for_second_turn(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for second turn response") + response_text = message.content.content.lower() + assert "blue" in response_text, f"Expected 'blue' in response but got: {response_text}" + found_response = True + break assert found_response, "Did not receive final agent text response with context recall" @@ -255,66 +223,53 @@ async def test_send_event_and_stream_with_reasoning(self, client: AsyncAgentex, user_message_found = False agent_response_found = False reasoning_found = False - - async def stream_messages() -> None: # noqa: ANN101 - nonlocal user_message_found, agent_response_found, reasoning_found - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=90, # Increased timeout for CI environments - ): - msg_type = event.get("type") - if msg_type == "full": - task_message_update = StreamTaskMessageFull.model_validate(event) - if task_message_update.parent_task_message and task_message_update.parent_task_message.id: - finished_message = await client.messages.retrieve(task_message_update.parent_task_message.id) - if ( - finished_message.content - and finished_message.content.type == "text" - and finished_message.content.author == "user" - ): - user_message_found = True - elif ( - finished_message.content - and finished_message.content.type == "text" - and finished_message.content.author == "agent" - ): - agent_response_found = True - elif finished_message.content and finished_message.content.type == "reasoning": - reasoning_found = True - - # Exit early if we have what we need - if user_message_found and agent_response_found: - break - - elif msg_type == "done": - task_message_update = StreamTaskMessageDone.model_validate(event) - if task_message_update.parent_task_message and task_message_update.parent_task_message.id: - finished_message = await client.messages.retrieve(task_message_update.parent_task_message.id) - if finished_message.content and finished_message.content.type == "reasoning": - reasoning_found = True - elif ( - finished_message.content - and finished_message.content.type == "text" - and finished_message.content.author == "agent" - ): - agent_response_found = True - - # Exit early if we have what we need - if user_message_found and agent_response_found: - break - - stream_task = asyncio.create_task(stream_messages()) - event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=90, # Increased timeout for CI environments + ): + msg_type = event.get("type") + if msg_type == "full": + task_message_update = StreamTaskMessageFull.model_validate(event) + if task_message_update.parent_task_message and task_message_update.parent_task_message.id: + finished_message = await client.messages.retrieve(task_message_update.parent_task_message.id) + if ( + finished_message.content + and finished_message.content.type == "text" + and finished_message.content.author == "user" + ): + user_message_found = True + elif ( + finished_message.content + and finished_message.content.type == "text" + and finished_message.content.author == "agent" + ): + agent_response_found = True + elif finished_message.content and finished_message.content.type == "reasoning": + reasoning_found = True + + # Exit early if we have what we need + if user_message_found and agent_response_found: + break - # Wait for streaming to complete with timeout - try: - await asyncio.wait_for(stream_task, timeout=120) # Overall timeout for CI - except asyncio.TimeoutError: - stream_task.cancel() - pytest.fail("Test timed out waiting for streaming response") + elif msg_type == "done": + task_message_update_done = StreamTaskMessageDone.model_validate(event) + if task_message_update_done.parent_task_message and task_message_update_done.parent_task_message.id: + finished_message = await client.messages.retrieve(task_message_update_done.parent_task_message.id) + if finished_message.content and finished_message.content.type == "reasoning": + reasoning_found = True + elif ( + finished_message.content + and finished_message.content.type == "text" + and finished_message.content.author == "agent" + ): + agent_response_found = True + + # Exit early if we have what we need + if user_message_found and agent_response_found: + break assert user_message_found, "User message not found in stream" assert agent_response_found, "Agent response not found in stream" diff --git a/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py index 0a27c69b1..d2eff03b6 100644 --- a/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py @@ -86,26 +86,19 @@ async def test_send_event_and_poll_simple_query(self, client: AsyncAgentex, agen user_message = "Hello! Please tell me the latest news about AI and AI startups." messages = [] found_agent_message = False - - async def poll_for_agent_message() -> None: - nonlocal found_agent_message - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - ): - ## we should expect to get a question from the agent - if message.content.type == "text" and message.content.author == "agent": - found_agent_message = True - break - - try: - await asyncio.wait_for(poll_for_agent_message(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for agent message") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + ): + messages.append(message) + ## we should expect to get a question from the agent + if message.content.type == "text" and message.content.author == "agent": + found_agent_message = True + break assert found_agent_message, "Did not find an agent message" @@ -114,28 +107,20 @@ async def poll_for_agent_message() -> None: next_user_message = "I want to know what viral news came up and which startups failed, got acquired, or became very successful or popular in the last 3 months" starting_deep_research_message = False uses_tool_requests = False - - async def poll_for_research_response() -> None: - nonlocal starting_deep_research_message, uses_tool_requests - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=next_user_message, - timeout=30, - sleep_interval=1.0, - ): - if message.content.type == "text" and message.content.author == "agent": - if "starting deep research" in message.content.content.lower(): - starting_deep_research_message = True - if isinstance(message.content, ToolRequestContent): - uses_tool_requests = True - break - - try: - await asyncio.wait_for(poll_for_research_response(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for research response") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=next_user_message, + timeout=30, + sleep_interval=1.0, + ): + if message.content.type == "text" and message.content.author == "agent": + if "starting deep research" in message.content.content.lower(): + starting_deep_research_message = True + if isinstance(message.content, ToolRequestContent): + uses_tool_requests = True + break assert starting_deep_research_message, "Did not start deep research" assert uses_tool_requests, "Did not use tool requests" @@ -151,52 +136,38 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): assert task is not None found_agent_message = False - async def poll_message_in_background() -> None: - nonlocal found_agent_message - async for message in stream_task_messages( - client=client, - task_id=task.id, - timeout=30, - ): - if message.content.type == "text" and message.content.author == "agent": - found_agent_message = True - break - - assert found_agent_message, "Did not find an agent message" - - poll_task = asyncio.create_task(poll_message_in_background()) - # create the first user_message = "Hello! Please tell me the latest news about AI and AI startups." await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": TextContentParam(type="text", author="user", content=user_message)}) - - await poll_task + async for message in stream_task_messages( + client=client, + task_id=task.id, + timeout=30, + ): + if message.content.type == "text" and message.content.author == "agent": + found_agent_message = True + break + assert found_agent_message, "Did not find an agent message" await asyncio.sleep(2) starting_deep_research_message = False uses_tool_requests = False - async def poll_message_in_background_2() -> None: - nonlocal starting_deep_research_message, uses_tool_requests - async for message in stream_task_messages( - client=client, - task_id=task.id, - timeout=30, - ): - # can you add the same checks as we did in the non-streaming events test? - if message.content.type == "text" and message.content.author == "agent": - if "starting deep research" in message.content.content.lower(): - starting_deep_research_message = True - if isinstance(message.content, ToolRequestContent): - uses_tool_requests = True - break - - assert starting_deep_research_message, "Did not start deep research" - assert uses_tool_requests, "Did not use tool requests" - - poll_task_2 = asyncio.create_task(poll_message_in_background_2()) - next_user_message = "I want to know what viral news came up and which startups failed, got acquired, or became very successful or popular in the last 3 months" await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": TextContentParam(type="text", author="user", content=next_user_message)}) - await poll_task_2 + async for message in stream_task_messages( + client=client, + task_id=task.id, + timeout=30, + ): + # can you add the same checks as we did in the non-streaming events test? + if message.content.type == "text" and message.content.author == "agent": + if "starting deep research" in message.content.content.lower(): + starting_deep_research_message = True + if isinstance(message.content, ToolRequestContent): + uses_tool_requests = True + break + + assert starting_deep_research_message, "Did not start deep research" + assert uses_tool_requests, "Did not use tool requests" if __name__ == "__main__": diff --git a/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py index e8e1557eb..78da470d4 100644 --- a/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py @@ -17,7 +17,6 @@ import os import uuid -import asyncio import pytest import pytest_asyncio @@ -71,27 +70,19 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Poll for the initial task creation message task_creation_found = False - - async def poll_for_task_creation() -> None: - nonlocal task_creation_found - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - # Check for the Haiku Assistant welcome message - assert "Haiku Assistant" in message.content.content - assert "Temporal" in message.content.content - task_creation_found = True - break - - try: - await asyncio.wait_for(poll_for_task_creation(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for task creation message") + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + # Check for the Haiku Assistant welcome message + assert "Haiku Assistant" in message.content.content + assert "Temporal" in message.content.content + task_creation_found = True + break assert task_creation_found, "Task creation message not found" @@ -101,34 +92,26 @@ async def poll_for_task_creation() -> None: # Use yield_updates=True to get all streaming chunks as they're written final_message = None - - async def poll_for_haiku() -> None: - nonlocal final_message - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=30, - sleep_interval=1.0, - yield_updates=True, # Get updates as streaming writes chunks - ): - if message.content and message.content.type == "text" and message.content.author == "agent": - print( - f"[DEBUG 060 POLL] Received update - Status: {message.streaming_status}, " - f"Content length: {len(message.content.content)}" - ) - final_message = message - - # Stop polling once we get a DONE message - if message.streaming_status == "DONE": - print(f"[DEBUG 060 POLL] Streaming complete!") - break - - try: - await asyncio.wait_for(poll_for_haiku(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for haiku response") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=30, + sleep_interval=1.0, + yield_updates=True, # Get updates as streaming writes chunks + ): + if message.content and message.content.type == "text" and message.content.author == "agent": + print( + f"[DEBUG 060 POLL] Received update - Status: {message.streaming_status}, " + f"Content length: {len(message.content.content)}" + ) + final_message = message + + # Stop polling once we get a DONE message + if message.streaming_status == "DONE": + print(f"[DEBUG 060 POLL] Streaming complete!") + break # Verify the final message has content (the haiku) assert final_message is not None, "Should have received an agent message" diff --git a/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py index 3e6edb346..c1376e532 100644 --- a/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py @@ -17,7 +17,6 @@ import os import uuid -import asyncio import pytest import pytest_asyncio @@ -73,27 +72,19 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Poll for the initial task creation message print(f"[DEBUG 070 POLL] Polling for initial task creation message...") task_creation_found = False - - async def poll_for_task_creation() -> None: - nonlocal task_creation_found - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - # Check for the initial acknowledgment message - print(f"[DEBUG 070 POLL] Initial message: {message.content.content[:100]}") - assert "task" in message.content.content.lower() or "received" in message.content.content.lower() - task_creation_found = True - break - - try: - await asyncio.wait_for(poll_for_task_creation(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for task creation message") + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + # Check for the initial acknowledgment message + print(f"[DEBUG 070 POLL] Initial message: {message.content.content[:100]}") + assert "task" in message.content.content.lower() or "received" in message.content.content.lower() + task_creation_found = True + break assert task_creation_found, "Task creation message not found" @@ -105,56 +96,53 @@ async def poll_for_task_creation() -> None: seen_tool_request = False seen_tool_response = False final_message = None - - async def poll_for_weather_response() -> None: - nonlocal seen_tool_request, seen_tool_response, final_message - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=60, - sleep_interval=1.0 - ): - assert isinstance(message, TaskMessage) - print(f"[DEBUG 070 POLL] Received message - Type: {message.content.type if message.content else 'None'}, Author: {message.content.author if message.content else 'None'}, Status: {message.streaming_status}") - - # Track tool_request messages (agent calling get_weather) - if message.content and message.content.type == "tool_request": - print(f"[DEBUG 070 POLL] ✅ Saw tool_request - agent is calling get_weather tool") - seen_tool_request = True - - # Track tool_response messages (get_weather result) - if message.content and message.content.type == "tool_response": - print(f"[DEBUG 070 POLL] ✅ Saw tool_response - get_weather returned result") - seen_tool_response = True - - # Track agent text messages and their streaming updates - if message.content and message.content.type == "text" and message.content.author == "agent": - content_length = len(message.content.content) if message.content.content else 0 - print(f"[DEBUG 070 POLL] Agent text update - Status: {message.streaming_status}, Length: {content_length}") - final_message = message - - # Stop when we get DONE status - if message.streaming_status == "DONE" and content_length > 0: - print(f"[DEBUG 070 POLL] ✅ Streaming complete!") - break - - try: - await asyncio.wait_for(poll_for_weather_response(), timeout=60) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for weather response") + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=60, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + print( + f"[DEBUG 070 POLL] Received message - Type: {message.content.type if message.content else 'None'}, " + f"Author: {message.content.author if message.content else 'None'}, Status: {message.streaming_status}" + ) + + # Track tool_request messages (agent calling get_weather) + if message.content and message.content.type == "tool_request": + print(f"[DEBUG 070 POLL] ✅ Saw tool_request - agent is calling get_weather tool") + seen_tool_request = True + + # Track tool_response messages (get_weather result) + if message.content and message.content.type == "tool_response": + print(f"[DEBUG 070 POLL] ✅ Saw tool_response - get_weather returned result") + seen_tool_response = True + + # Track agent text messages and their streaming updates + if message.content and message.content.type == "text" and message.content.author == "agent": + agent_text = getattr(message.content, "content", "") or "" + content_length = len(str(agent_text)) + print(f"[DEBUG 070 POLL] Agent text update - Status: {message.streaming_status}, Length: {content_length}") + final_message = message + + # Stop when we get DONE status + if message.streaming_status == "DONE" and content_length > 0: + print(f"[DEBUG 070 POLL] ✅ Streaming complete!") + break # Verify we got all the expected pieces assert seen_tool_request, "Expected to see tool_request message (agent calling get_weather)" assert seen_tool_response, "Expected to see tool_response message (get_weather result)" assert final_message is not None, "Expected to see final agent text message" - assert final_message.content is not None and len(final_message.content.content) > 0, "Final message should have content" + final_text = getattr(final_message.content, "content", None) if final_message.content else None + assert isinstance(final_text, str) and len(final_text) > 0, "Final message should have content" # Check that the response contains the temperature (22 degrees) # The get_weather activity returns "The weather in New York City is 22 degrees Celsius" - print(f"[DEBUG 070 POLL] Final response: {final_message.content.content}") - assert "22" in final_message.content.content, "Expected weather response to contain temperature (22 degrees)" + print(f"[DEBUG 070 POLL] Final response: {final_text}") + assert "22" in final_text, "Expected weather response to contain temperature (22 degrees)" class TestStreamingEvents: diff --git a/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py index cf8f78e84..2425b8099 100644 --- a/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py @@ -89,27 +89,19 @@ async def test_send_event_and_poll_with_human_approval(self, client: AsyncAgente # Poll for the initial task creation message print(f"[DEBUG 080 POLL] Polling for initial task creation message...") task_creation_found = False - - async def poll_for_task_creation() -> None: - nonlocal task_creation_found - async for message in poll_messages( - client=client, - task_id=task.id, - timeout=30, - sleep_interval=1.0, - ): - assert isinstance(message, TaskMessage) - if message.content and message.content.type == "text" and message.content.author == "agent": - # Check for the initial acknowledgment message - print(f"[DEBUG 080 POLL] Initial message: {message.content.content[:100]}") - assert "task" in message.content.content.lower() or "received" in message.content.content.lower() - task_creation_found = True - break - - try: - await asyncio.wait_for(poll_for_task_creation(), timeout=30) - except asyncio.TimeoutError: - pytest.fail("Polling timed out waiting for task creation message") + async for message in poll_messages( + client=client, + task_id=task.id, + timeout=30, + sleep_interval=1.0, + ): + assert isinstance(message, TaskMessage) + if message.content and message.content.type == "text" and message.content.author == "agent": + # Check for the initial acknowledgment message + print(f"[DEBUG 080 POLL] Initial message: {message.content.content[:100]}") + assert "task" in message.content.content.lower() or "received" in message.content.content.lower() + task_creation_found = True + break assert task_creation_found, "Task creation message not found" @@ -121,71 +113,58 @@ async def poll_for_task_creation() -> None: seen_tool_request = False seen_tool_response = False found_final_response = False - child_workflow_detected = False - - # Start polling for messages in the background - async def poll_and_detect(): - nonlocal seen_tool_request, seen_tool_response, found_final_response, child_workflow_detected - - async for message in send_event_and_poll_yielding( - client=client, - agent_id=agent_id, - task_id=task.id, - user_message=user_message, - timeout=120, # Longer timeout for human-in-the-loop - sleep_interval=1.0, - yield_updates=True, # Get all streaming chunks - ): - assert isinstance(message, TaskMessage) - print(f"[DEBUG 080 POLL] Received message - Type: {message.content.type if message.content else 'None'}, Author: {message.content.author if message.content else 'None'}, Status: {message.streaming_status}") - - # Track tool_request messages (agent calling wait_for_confirmation) - if message.content and message.content.type == "tool_request": - print(f"[DEBUG 080 POLL] ✅ Saw tool_request - agent is calling wait_for_confirmation tool") - print(f"[DEBUG 080 POLL] 🔔 Child workflow should be spawned - will signal it to approve") - seen_tool_request = True - child_workflow_detected = True - - # Track tool_response messages (child workflow completion) - if message.content and message.content.type == "tool_response": - print(f"[DEBUG 080 POLL] ✅ Saw tool_response - child workflow completed after approval") - seen_tool_response = True - - # Track agent text messages and their streaming updates - if message.content and message.content.type == "text" and message.content.author == "agent": - content_length = len(message.content.content) if message.content.content else 0 - print(f"[DEBUG 080 POLL] Agent text update - Status: {message.streaming_status}, Length: {content_length}") - - # Stop when we get DONE status with actual content - if message.streaming_status == "DONE" and content_length > 0: - print(f"[DEBUG 080 POLL] ✅ Streaming complete!") - found_final_response = True - break - - # Start polling task - polling_task = asyncio.create_task(poll_and_detect()) - - # Wait a bit for the child workflow to be created - print(f"[DEBUG 080 POLL] Waiting for child workflow to spawn...") - await asyncio.sleep(5) - - # Send signal to child workflow to approve the order - # The child workflow ID is fixed as "child-workflow-id" (see tools.py) - try: - print(f"[DEBUG 080 POLL] Sending approval signal to child workflow...") - handle = temporal_client.get_workflow_handle("child-workflow-id") - await handle.signal("fulfill_order_signal", True) - print(f"[DEBUG 080 POLL] ✅ Approval signal sent successfully!") - except Exception as e: - print(f"[DEBUG 080 POLL] ⚠️ Warning: Could not send signal to child workflow: {e}") - print(f"[DEBUG 080 POLL] This may be expected if workflow completed before signal could be sent") - - # Wait for polling to complete - try: - await asyncio.wait_for(polling_task, timeout=60) - except asyncio.TimeoutError: - polling_task.cancel() - pytest.fail("Polling timed out waiting for human-in-the-loop workflow to complete") + approval_signal_sent = False + + async for message in send_event_and_poll_yielding( + client=client, + agent_id=agent_id, + task_id=task.id, + user_message=user_message, + timeout=120, # Longer timeout for human-in-the-loop + sleep_interval=1.0, + yield_updates=True, # Get all streaming chunks + ): + assert isinstance(message, TaskMessage) + print( + f"[DEBUG 080 POLL] Received message - Type: {message.content.type if message.content else 'None'}, " + f"Author: {message.content.author if message.content else 'None'}, Status: {message.streaming_status}" + ) + + # Track tool_request messages (agent calling wait_for_confirmation) + if message.content and message.content.type == "tool_request": + print(f"[DEBUG 080 POLL] ✅ Saw tool_request - agent is calling wait_for_confirmation tool") + seen_tool_request = True + + if not approval_signal_sent: + # Send signal to child workflow to approve the order + # The child workflow ID is fixed as "child-workflow-id" (see tools.py) + # Give Temporal a brief moment to materialize the child workflow + await asyncio.sleep(1) + try: + print(f"[DEBUG 080 POLL] Sending approval signal to child workflow...") + handle = temporal_client.get_workflow_handle("child-workflow-id") + await handle.signal("fulfill_order_signal", True) + approval_signal_sent = True + print(f"[DEBUG 080 POLL] ✅ Approval signal sent successfully!") + except Exception as e: + print(f"[DEBUG 080 POLL] ⚠️ Warning: Could not send signal to child workflow: {e}") + print(f"[DEBUG 080 POLL] This may be expected if workflow completed before signal could be sent") + + # Track tool_response messages (child workflow completion) + if message.content and message.content.type == "tool_response": + print(f"[DEBUG 080 POLL] ✅ Saw tool_response - child workflow completed after approval") + seen_tool_response = True + + # Track agent text messages and their streaming updates + if message.content and message.content.type == "text" and message.content.author == "agent": + content_length = len(message.content.content) if message.content.content else 0 + print(f"[DEBUG 080 POLL] Agent text update - Status: {message.streaming_status}, Length: {content_length}") + + # Stop when we get DONE status with actual content + if message.streaming_status == "DONE" and content_length > 0: + print(f"[DEBUG 080 POLL] ✅ Streaming complete!") + found_final_response = True + break # Verify that we saw the complete flow: tool_request -> human approval -> tool_response -> final answer assert seen_tool_request, "Expected to see tool_request message (agent calling wait_for_confirmation)" From b80074a682afe9b116c3e039938dd5ee6d63cc72 Mon Sep 17 00:00:00 2001 From: Roxanne Farhad Date: Mon, 22 Dec 2025 14:51:48 +0000 Subject: [PATCH 4/4] reverting those changes --- .../00_base/000_hello_acp/tests/test_agent.py | 62 +++++---- .../00_base/010_multiturn/tests/test_agent.py | 53 ++++---- .../00_base/020_streaming/tests/test_agent.py | 49 ++++--- .../040_other_sdks/tests/test_agent.py | 122 ++++++++++-------- .../080_batch_events/tests/test_agent.py | 50 +++---- .../tests/test_agent.py | 84 ++++++------ .../000_hello_acp/tests/test_agent.py | 58 +++++---- .../010_agent_chat/tests/test_agent.py | 92 ++++++------- .../020_state_machine/tests/test_agent.py | 63 +++++---- .../project/workflow.py | 3 +- .../tests/test_agent.py | 9 -- .../tests/test_agent.py | 13 +- .../tests/test_agent.py | 19 +-- examples/tutorials/test_utils/async_utils.py | 56 ++++++-- 14 files changed, 402 insertions(+), 331 deletions(-) diff --git a/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py b/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py index 1a98158d8..c57cec448 100644 --- a/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/000_hello_acp/tests/test_agent.py @@ -17,6 +17,7 @@ import os import uuid +import asyncio import pytest import pytest_asyncio @@ -143,36 +144,43 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): user_echo_found = False agent_response_found = False + async def stream_messages() -> None: + nonlocal user_echo_found, agent_response_found + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=stream_timeout, + ): + all_events.append(event) + # Check events as they arrive + event_type = event.get("type") + if event_type == "full": + content = event.get("content", {}) + if content.get("content") is None: + continue # Skip empty content + if content.get("type") == "text" and content.get("author") == "agent": + # Check for agent response to user message + if "Hello! I've received your message" in content.get("content", ""): + # Agent response should come after user echo + assert user_echo_found, "Agent response arrived before user message echo (incorrect order)" + agent_response_found = True + elif content.get("type") == "text" and content.get("author") == "user": + # Check for user message echo + if content.get("content") == user_message: + user_echo_found = True + elif event_type == "done": + break + + # Exit early if we've found all expected messages + if user_echo_found and agent_response_found: + break + + stream_task = asyncio.create_task(stream_messages()) + # Send the event event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=stream_timeout, - ): - all_events.append(event) - # Check events as they arrive - event_type = event.get("type") - if event_type == "full": - content = event.get("content", {}) - if content.get("content") is None: - continue # Skip empty content - if content.get("type") == "text" and content.get("author") == "agent": - # Check for agent response to user message - if "Hello! I've received your message" in content.get("content", ""): - # Agent response should come after user echo - assert user_echo_found, "Agent response arrived before user message echo (incorrect order)" - agent_response_found = True - elif content.get("type") == "text" and content.get("author") == "user": - # Check for user message echo - if content.get("content") == user_message: - user_echo_found = True - - # Exit early if we've found all expected messages - if user_echo_found and agent_response_found: - break + await stream_task # Verify all expected messages were received (fail if stream ended without finding them) assert user_echo_found, "User message echo not found in stream" diff --git a/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py b/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py index 5fa3ce70a..33d831858 100644 --- a/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/010_multiturn/tests/test_agent.py @@ -167,31 +167,38 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): # Flags to track what we've received user_message_found = False agent_response_found = False + async def stream_messages() -> None: + nonlocal user_message_found, agent_response_found + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=15, + ): + all_events.append(event) + + # Check events as they arrive + event_type = event.get("type") + if event_type == "full": + content = event.get("content", {}) + if content.get("content") == user_message and content.get("author") == "user": + # User message should come before agent response + assert not agent_response_found, "User message arrived after agent response (incorrect order)" + user_message_found = True + elif content.get("author") == "agent": + # Agent response should come after user message + assert user_message_found, "Agent response arrived before user message (incorrect order)" + agent_response_found = True + elif event_type == "done": + break + + # Exit early if we've found both messages + if user_message_found and agent_response_found: + break + + stream_task = asyncio.create_task(stream_messages()) event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=15, - ): - all_events.append(event) - - # Check events as they arrive - event_type = event.get("type") - if event_type == "full": - content = event.get("content", {}) - if content.get("content") == user_message and content.get("author") == "user": - # User message should come before agent response - assert not agent_response_found, "User message arrived after agent response (incorrect order)" - user_message_found = True - elif content.get("author") == "agent": - # Agent response should come after user message - assert user_message_found, "Agent response arrived before user message (incorrect order)" - agent_response_found = True - - # Exit early if we've found both messages - if user_message_found and agent_response_found: - break + await stream_task # Validate we received events assert len(all_events) > 0, "No events received in streaming response" diff --git a/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py b/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py index cc5ef20f6..c55525191 100644 --- a/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/020_streaming/tests/test_agent.py @@ -167,29 +167,36 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): user_message_found = False full_agent_message_found = False delta_messages_found = False + async def stream_messages() -> None: + nonlocal user_message_found, full_agent_message_found, delta_messages_found + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=15, + ): + all_events.append(event) + + # Check events as they arrive + event_type = event.get("type") + if event_type == "full": + content = event.get("content", {}) + if content.get("content") == user_message and content.get("author") == "user": + user_message_found = True + elif content.get("author") == "agent": + full_agent_message_found = True + elif event_type == "delta": + delta_messages_found = True + elif event_type == "done": + break + + # Exit early if we've found all expected messages + if user_message_found and full_agent_message_found and delta_messages_found: + break + + stream_task = asyncio.create_task(stream_messages()) event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=15, - ): - all_events.append(event) - - # Check events as they arrive - event_type = event.get("type") - if event_type == "full": - content = event.get("content", {}) - if content.get("content") == user_message and content.get("author") == "user": - user_message_found = True - elif content.get("author") == "agent": - full_agent_message_found = True - elif event_type == "delta": - delta_messages_found = True - - # Exit early if we've found all expected messages - if user_message_found and full_agent_message_found and delta_messages_found: - break + await stream_task # Validate we received events assert len(all_events) > 0, "No events received in streaming response" diff --git a/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py b/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py index 0f62cc06d..72a5cf120 100644 --- a/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/040_other_sdks/tests/test_agent.py @@ -280,24 +280,32 @@ async def test_send_event_and_stream_simple(self, client: AsyncAgentex, agent_id # Collect events from stream # Check for user message and delta messages user_message_found = False + async def stream_messages() -> None: + nonlocal user_message_found + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=20, + ): + msg_type = event.get("type") + # For full messages, content is at the top level + # For delta messages, we need to check parent_task_message + if msg_type == "full": + if ( + event.get("content", {}).get("type") == "text" + and event.get("content", {}).get("author") == "user" + ): + user_message_found = True + elif msg_type == "done": + break + + if user_message_found: + break + + stream_task = asyncio.create_task(stream_messages()) event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=20, - ): - msg_type = event.get("type") - # For full messages, content is at the top level - # For delta messages, we need to check parent_task_message - if msg_type == "full": - if ( - event.get("content", {}).get("type") == "text" - and event.get("content", {}).get("author") == "user" - ): - user_message_found = True - elif msg_type == "done": - break + await stream_task assert user_message_found, "User message found in stream" ## keep polling the states for 10 seconds for the input_list and turn_number to be updated for i in range(10): @@ -333,47 +341,51 @@ async def test_send_event_and_stream_with_tools(self, client: AsyncAgentex, agen tool_requests_seen = [] tool_responses_seen = [] text_deltas_seen = [] + async def stream_messages() -> None: + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=45, + ): + msg_type = event.get("type") + + # For full messages, content is at the top level + # For delta messages, we need to check parent_task_message + if msg_type == "delta": + parent_msg = event.get("parent_task_message", {}) + content = parent_msg.get("content", {}) + delta = event.get("delta", {}) + content_type = content.get("type") + + if content_type == "text": + text_deltas_seen.append(delta.get("text_delta", "")) + elif msg_type == "full": + # For full messages + content = event.get("content", {}) + content_type = content.get("type") + + if content_type == "tool_request": + tool_requests_seen.append( + { + "name": content.get("name"), + "tool_call_id": content.get("tool_call_id"), + "streaming_type": msg_type, + } + ) + elif content_type == "tool_response": + tool_responses_seen.append( + { + "tool_call_id": content.get("tool_call_id"), + "streaming_type": msg_type, + } + ) + elif msg_type == "done": + break + + stream_task = asyncio.create_task(stream_messages()) event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=45, - ): - msg_type = event.get("type") - - # For full messages, content is at the top level - # For delta messages, we need to check parent_task_message - if msg_type == "delta": - parent_msg = event.get("parent_task_message", {}) - content = parent_msg.get("content", {}) - delta = event.get("delta", {}) - content_type = content.get("type") - - if content_type == "text": - text_deltas_seen.append(delta.get("text_delta", "")) - elif msg_type == "full": - # For full messages - content = event.get("content", {}) - content_type = content.get("type") - - if content_type == "tool_request": - tool_requests_seen.append( - { - "name": content.get("name"), - "tool_call_id": content.get("tool_call_id"), - "streaming_type": msg_type, - } - ) - elif content_type == "tool_response": - tool_responses_seen.append( - { - "tool_call_id": content.get("tool_call_id"), - "streaming_type": msg_type, - } - ) - elif msg_type == "done": - break + await stream_task # Verify we saw tool usage (if the agent decided to use tools) # Note: The agent may or may not use tools depending on its reasoning diff --git a/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py b/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py index 103b614e3..1dea1300b 100644 --- a/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/080_batch_events/tests/test_agent.py @@ -163,6 +163,33 @@ async def test_send_twenty_events_batched_streaming(self, client: AsyncAgentex, task = task_response.result assert task is not None + # Stream the responses and collect agent messages + print("\nStreaming batch responses...") + + # We'll collect all agent messages from the stream + agent_messages = [] + stream_timeout = 90 # Longer timeout for 20 events + async def stream_messages() -> None: + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=stream_timeout, + ): + # Collect agent text messages + if event.get("type") == "full": + content = event.get("content", {}) + if content.get("type") == "text" and content.get("author") == "agent": + msg_content = content.get("content", "") + if msg_content and msg_content.strip(): + agent_messages.append(msg_content) + elif event.get("type") == "done": + break + + if len(agent_messages) >= 2: + break + + stream_task = asyncio.create_task(stream_messages()) + # Send 10 events in quick succession (should be batched) num_events = 10 print(f"\nSending {num_events} events in quick succession...") @@ -171,28 +198,7 @@ async def test_send_twenty_events_batched_streaming(self, client: AsyncAgentex, await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) await asyncio.sleep(0.1) # Small delay to ensure ordering - # Stream the responses and collect agent messages - print("\nStreaming batch responses...") - - # We'll collect all agent messages from the stream - agent_messages = [] - stream_timeout = 90 # Longer timeout for 20 events - - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=stream_timeout, - ): - # Collect agent text messages - if event.get("type") == "full": - content = event.get("content", {}) - if content.get("type") == "text" and content.get("author") == "agent": - msg_content = content.get("content", "") - if msg_content and msg_content.strip(): - agent_messages.append(msg_content) - - if len(agent_messages) >= 2: - break + await stream_task print(f"\nSent {num_events} events") print(f"Received {len(agent_messages)} agent response(s)") diff --git a/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py b/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py index f11c17ed8..8af941a81 100644 --- a/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py +++ b/examples/tutorials/10_async/00_base/090_multi_agent_non_temporal/tests/test_agent.py @@ -17,6 +17,7 @@ import os import uuid +import asyncio import pytest import pytest_asyncio @@ -179,47 +180,54 @@ async def test_multi_agent_workflow_streaming(self, client: AsyncAgentex, agent_ creator_iterations = 0 critic_feedback_count = 0 + async def stream_messages() -> None: + nonlocal creator_iterations, critic_feedback_count + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=120, + ): + # Handle different event types + if event.get("type") == "full": + content = event.get("content", {}) + if content.get("type") == "text" and content.get("author") == "agent": + message_text = content.get("content", "") + all_messages.append(message_text) + + # Track agent participation + content_lower = message_text.lower() + + if "starting content workflow" in content_lower: + workflow_markers["orchestrator_started"] = True + + if "creator output" in content_lower: + creator_iterations += 1 + workflow_markers["creator_called"] = True + + if "critic feedback" in content_lower or "content approved by critic" in content_lower: + if "critic feedback" in content_lower: + critic_feedback_count += 1 + workflow_markers["critic_called"] = True + + if "calling formatter agent" in content_lower: + workflow_markers["formatter_called"] = True + + if "workflow complete" in content_lower or "content creation complete" in content_lower: + workflow_markers["workflow_completed"] = True + + if event.get("type") == "done": + break + + # Check if all agents have participated + if all(workflow_markers.values()): + break + + stream_task = asyncio.create_task(stream_messages()) + # Send the event to trigger the agent workflow event_content = TextContentParam(type="text", author="user", content=json.dumps(request_json)) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=120, - ): - # Handle different event types - if event.get("type") == "full": - content = event.get("content", {}) - if content.get("type") == "text" and content.get("author") == "agent": - message_text = content.get("content", "") - all_messages.append(message_text) - - # Track agent participation - content_lower = message_text.lower() - - if "starting content workflow" in content_lower: - workflow_markers["orchestrator_started"] = True - - if "creator output" in content_lower: - creator_iterations += 1 - workflow_markers["creator_called"] = True - - if "critic feedback" in content_lower or "content approved by critic" in content_lower: - if "critic feedback" in content_lower: - critic_feedback_count += 1 - workflow_markers["critic_called"] = True - - if "calling formatter agent" in content_lower: - workflow_markers["formatter_called"] = True - - if "workflow complete" in content_lower or "content creation complete" in content_lower: - workflow_markers["workflow_completed"] = True - - # Check if all agents have participated - all_agents_done = all(workflow_markers.values()) - if all_agents_done: - break + await stream_task # Validate we got streaming responses assert len(all_messages) > 0, "No messages received from streaming" diff --git a/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py index 60894814e..65204ac36 100644 --- a/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/000_hello_acp/tests/test_agent.py @@ -143,34 +143,42 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): agent_response_found = False stream_timeout = 30 + async def stream_messages() -> None: + nonlocal user_echo_found, agent_response_found + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=stream_timeout, + ): + # Check events as they arrive + event_type = event.get("type") + if event_type == "full": + content = event.get("content", {}) + if content.get("content") is None: + continue # Skip empty content + if content.get("type") == "text" and content.get("author") == "agent": + # Check for agent response to user message + if "Hello! I've received your message" in content.get("content", ""): + # Agent response should come after user echo + assert user_echo_found, "Agent response arrived before user message echo (incorrect order)" + agent_response_found = True + elif content.get("type") == "text" and content.get("author") == "user": + # Check for user message echo + if content.get("content") == user_message: + user_echo_found = True + elif event_type == "done": + break + + # Exit early if we've found all expected messages + if user_echo_found and agent_response_found: + break + + stream_task = asyncio.create_task(stream_messages()) + # Send the event event_content = TextContentParam(type="text", author="user", content=user_message) await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=stream_timeout, - ): - # Check events as they arrive - event_type = event.get("type") - if event_type == "full": - content = event.get("content", {}) - if content.get("content") is None: - continue # Skip empty content - if content.get("type") == "text" and content.get("author") == "agent": - # Check for agent response to user message - if "Hello! I've received your message" in content.get("content", ""): - # Agent response should come after user echo - assert user_echo_found, "Agent response arrived before user message echo (incorrect order)" - agent_response_found = True - elif content.get("type") == "text" and content.get("author") == "user": - # Check for user message echo - if content.get("content") == user_message: - user_echo_found = True - - # Exit early if we've found all expected messages - if user_echo_found and agent_response_found: - break + await stream_task # Verify all expected messages were received (fail if stream ended without finding them) diff --git a/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py index e486daebd..6e7f46e74 100644 --- a/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/010_agent_chat/tests/test_agent.py @@ -223,53 +223,55 @@ async def test_send_event_and_stream_with_reasoning(self, client: AsyncAgentex, user_message_found = False agent_response_found = False reasoning_found = False - event_content = TextContentParam(type="text", author="user", content=user_message) - await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) - async for event in stream_agent_response( - client=client, - task_id=task.id, - timeout=90, # Increased timeout for CI environments - ): - msg_type = event.get("type") - if msg_type == "full": - task_message_update = StreamTaskMessageFull.model_validate(event) - if task_message_update.parent_task_message and task_message_update.parent_task_message.id: - finished_message = await client.messages.retrieve(task_message_update.parent_task_message.id) - if ( - finished_message.content - and finished_message.content.type == "text" - and finished_message.content.author == "user" - ): - user_message_found = True - elif ( - finished_message.content - and finished_message.content.type == "text" - and finished_message.content.author == "agent" - ): - agent_response_found = True - elif finished_message.content and finished_message.content.type == "reasoning": - reasoning_found = True - - # Exit early if we have what we need - if user_message_found and agent_response_found: + async def stream_messages() -> None: + nonlocal user_message_found, agent_response_found, reasoning_found + async for event in stream_agent_response( + client=client, + task_id=task.id, + timeout=90, # Increased timeout for CI environments + ): + msg_type = event.get("type") + if msg_type == "full": + task_message_update = StreamTaskMessageFull.model_validate(event) + if task_message_update.parent_task_message and task_message_update.parent_task_message.id: + finished_message = await client.messages.retrieve(task_message_update.parent_task_message.id) + if ( + finished_message.content + and finished_message.content.type == "text" + and finished_message.content.author == "user" + ): + user_message_found = True + elif ( + finished_message.content + and finished_message.content.type == "text" + and finished_message.content.author == "agent" + ): + agent_response_found = True + elif finished_message.content and finished_message.content.type == "reasoning": + reasoning_found = True + + # Exit early if we have what we need + if user_message_found and agent_response_found: + break + + elif msg_type == "done": + task_message_update_done = StreamTaskMessageDone.model_validate(event) + if task_message_update_done.parent_task_message and task_message_update_done.parent_task_message.id: + finished_message = await client.messages.retrieve(task_message_update_done.parent_task_message.id) + if finished_message.content and finished_message.content.type == "reasoning": + reasoning_found = True + elif ( + finished_message.content + and finished_message.content.type == "text" + and finished_message.content.author == "agent" + ): + agent_response_found = True break - elif msg_type == "done": - task_message_update_done = StreamTaskMessageDone.model_validate(event) - if task_message_update_done.parent_task_message and task_message_update_done.parent_task_message.id: - finished_message = await client.messages.retrieve(task_message_update_done.parent_task_message.id) - if finished_message.content and finished_message.content.type == "reasoning": - reasoning_found = True - elif ( - finished_message.content - and finished_message.content.type == "text" - and finished_message.content.author == "agent" - ): - agent_response_found = True - - # Exit early if we have what we need - if user_message_found and agent_response_found: - break + stream_task = asyncio.create_task(stream_messages()) + event_content = TextContentParam(type="text", author="user", content=user_message) + await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": event_content}) + await stream_task assert user_message_found, "User message not found in stream" assert agent_response_found, "Agent response not found in stream" diff --git a/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py index d2eff03b6..fac8605aa 100644 --- a/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/020_state_machine/tests/test_agent.py @@ -137,34 +137,53 @@ async def test_send_event_and_stream(self, client: AsyncAgentex, agent_id: str): found_agent_message = False user_message = "Hello! Please tell me the latest news about AI and AI startups." - await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": TextContentParam(type="text", author="user", content=user_message)}) - async for message in stream_task_messages( - client=client, - task_id=task.id, - timeout=30, - ): - if message.content.type == "text" and message.content.author == "agent": - found_agent_message = True - break + async def stream_first_turn() -> None: + nonlocal found_agent_message + async for message in stream_task_messages( + client=client, + task_id=task.id, + timeout=30, + ): + if message.content.type == "text" and message.content.author == "agent": + found_agent_message = True + break + + stream_task = asyncio.create_task(stream_first_turn()) + await client.agents.send_event( + agent_id=agent_id, + params={"task_id": task.id, "content": TextContentParam(type="text", author="user", content=user_message)}, + ) + await stream_task assert found_agent_message, "Did not find an agent message" await asyncio.sleep(2) starting_deep_research_message = False uses_tool_requests = False next_user_message = "I want to know what viral news came up and which startups failed, got acquired, or became very successful or popular in the last 3 months" - await client.agents.send_event(agent_id=agent_id, params={"task_id": task.id, "content": TextContentParam(type="text", author="user", content=next_user_message)}) - async for message in stream_task_messages( - client=client, - task_id=task.id, - timeout=30, - ): - # can you add the same checks as we did in the non-streaming events test? - if message.content.type == "text" and message.content.author == "agent": - if "starting deep research" in message.content.content.lower(): - starting_deep_research_message = True - if isinstance(message.content, ToolRequestContent): - uses_tool_requests = True - break + async def stream_second_turn() -> None: + nonlocal starting_deep_research_message, uses_tool_requests + async for message in stream_task_messages( + client=client, + task_id=task.id, + timeout=30, + ): + # can you add the same checks as we did in the non-streaming events test? + if message.content.type == "text" and message.content.author == "agent": + if "starting deep research" in message.content.content.lower(): + starting_deep_research_message = True + if isinstance(message.content, ToolRequestContent): + uses_tool_requests = True + break + + stream_task = asyncio.create_task(stream_second_turn()) + await client.agents.send_event( + agent_id=agent_id, + params={ + "task_id": task.id, + "content": TextContentParam(type="text", author="user", content=next_user_message), + }, + ) + await stream_task assert starting_deep_research_message, "Did not start deep research" assert uses_tool_requests, "Did not use tool requests" diff --git a/examples/tutorials/10_async/10_temporal/050_agent_chat_guardrails/project/workflow.py b/examples/tutorials/10_async/10_temporal/050_agent_chat_guardrails/project/workflow.py index d1110bc1f..b54c8fade 100644 --- a/examples/tutorials/10_async/10_temporal/050_agent_chat_guardrails/project/workflow.py +++ b/examples/tutorials/10_async/10_temporal/050_agent_chat_guardrails/project/workflow.py @@ -6,6 +6,7 @@ from typing import Any, Dict, List, override from mcp import StdioServerParameters +from agents import ModelSettings, RunContextWrapper from dotenv import load_dotenv # Simple guardrail output model for this example @@ -19,8 +20,6 @@ from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent from agentex.lib.utils.model_utils import BaseModel - -from agents import ModelSettings, RunContextWrapper from agentex.lib.environment_variables import EnvironmentVariables from agentex.lib.core.temporal.types.workflow import SignalName from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow diff --git a/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py index 78da470d4..73cf60db9 100644 --- a/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/060_open_ai_agents_sdk_hello_world/tests/test_agent.py @@ -88,7 +88,6 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Send event and poll for response with streaming updates user_message = "Hello how is life?" - print(f"[DEBUG 060 POLL] Sending message: '{user_message}'") # Use yield_updates=True to get all streaming chunks as they're written final_message = None @@ -102,15 +101,10 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): yield_updates=True, # Get updates as streaming writes chunks ): if message.content and message.content.type == "text" and message.content.author == "agent": - print( - f"[DEBUG 060 POLL] Received update - Status: {message.streaming_status}, " - f"Content length: {len(message.content.content)}" - ) final_message = message # Stop polling once we get a DONE message if message.streaming_status == "DONE": - print(f"[DEBUG 060 POLL] Streaming complete!") break # Verify the final message has content (the haiku) @@ -118,9 +112,6 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): assert final_message.content is not None, "Final message should have content" assert len(final_message.content.content) > 0, "Final message should have haiku content" - print(f"[DEBUG 060 POLL] ✅ Successfully received haiku response!") - print(f"[DEBUG 060 POLL] Final haiku:\n{final_message.content.content}") - class TestStreamingEvents: """Test streaming event sending (backend verification via polling).""" diff --git a/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py index c1376e532..8bf59cafc 100644 --- a/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/070_open_ai_agents_sdk_tools/tests/test_agent.py @@ -70,7 +70,7 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): assert task is not None # Poll for the initial task creation message - print(f"[DEBUG 070 POLL] Polling for initial task creation message...") + task_creation_found = False async for message in poll_messages( client=client, @@ -81,7 +81,6 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): assert isinstance(message, TaskMessage) if message.content and message.content.type == "text" and message.content.author == "agent": # Check for the initial acknowledgment message - print(f"[DEBUG 070 POLL] Initial message: {message.content.content[:100]}") assert "task" in message.content.content.lower() or "received" in message.content.content.lower() task_creation_found = True break @@ -90,7 +89,6 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Send an event asking about the weather in NYC and poll for response with streaming user_message = "What is the weather in New York City?" - print(f"[DEBUG 070 POLL] Sending message: '{user_message}'") # Track what we've seen to ensure tool calls happened seen_tool_request = False @@ -105,31 +103,23 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): sleep_interval=1.0, ): assert isinstance(message, TaskMessage) - print( - f"[DEBUG 070 POLL] Received message - Type: {message.content.type if message.content else 'None'}, " - f"Author: {message.content.author if message.content else 'None'}, Status: {message.streaming_status}" - ) # Track tool_request messages (agent calling get_weather) if message.content and message.content.type == "tool_request": - print(f"[DEBUG 070 POLL] ✅ Saw tool_request - agent is calling get_weather tool") seen_tool_request = True # Track tool_response messages (get_weather result) if message.content and message.content.type == "tool_response": - print(f"[DEBUG 070 POLL] ✅ Saw tool_response - get_weather returned result") seen_tool_response = True # Track agent text messages and their streaming updates if message.content and message.content.type == "text" and message.content.author == "agent": agent_text = getattr(message.content, "content", "") or "" content_length = len(str(agent_text)) - print(f"[DEBUG 070 POLL] Agent text update - Status: {message.streaming_status}, Length: {content_length}") final_message = message # Stop when we get DONE status if message.streaming_status == "DONE" and content_length > 0: - print(f"[DEBUG 070 POLL] ✅ Streaming complete!") break # Verify we got all the expected pieces @@ -141,7 +131,6 @@ async def test_send_event_and_poll(self, client: AsyncAgentex, agent_id: str): # Check that the response contains the temperature (22 degrees) # The get_weather activity returns "The weather in New York City is 22 degrees Celsius" - print(f"[DEBUG 070 POLL] Final response: {final_text}") assert "22" in final_text, "Expected weather response to contain temperature (22 degrees)" diff --git a/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py b/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py index 2425b8099..3377c1ea8 100644 --- a/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py +++ b/examples/tutorials/10_async/10_temporal/080_open_ai_agents_sdk_human_in_the_loop/tests/test_agent.py @@ -87,7 +87,6 @@ async def test_send_event_and_poll_with_human_approval(self, client: AsyncAgente assert task is not None # Poll for the initial task creation message - print(f"[DEBUG 080 POLL] Polling for initial task creation message...") task_creation_found = False async for message in poll_messages( client=client, @@ -98,7 +97,6 @@ async def test_send_event_and_poll_with_human_approval(self, client: AsyncAgente assert isinstance(message, TaskMessage) if message.content and message.content.type == "text" and message.content.author == "agent": # Check for the initial acknowledgment message - print(f"[DEBUG 080 POLL] Initial message: {message.content.content[:100]}") assert "task" in message.content.content.lower() or "received" in message.content.content.lower() task_creation_found = True break @@ -107,7 +105,6 @@ async def test_send_event_and_poll_with_human_approval(self, client: AsyncAgente # Send an event asking to confirm an order (triggers human-in-the-loop) user_message = "Please confirm my order" - print(f"[DEBUG 080 POLL] Sending message: '{user_message}'") # Track what we've seen to ensure human-in-the-loop flow happened seen_tool_request = False @@ -125,14 +122,9 @@ async def test_send_event_and_poll_with_human_approval(self, client: AsyncAgente yield_updates=True, # Get all streaming chunks ): assert isinstance(message, TaskMessage) - print( - f"[DEBUG 080 POLL] Received message - Type: {message.content.type if message.content else 'None'}, " - f"Author: {message.content.author if message.content else 'None'}, Status: {message.streaming_status}" - ) # Track tool_request messages (agent calling wait_for_confirmation) if message.content and message.content.type == "tool_request": - print(f"[DEBUG 080 POLL] ✅ Saw tool_request - agent is calling wait_for_confirmation tool") seen_tool_request = True if not approval_signal_sent: @@ -141,28 +133,23 @@ async def test_send_event_and_poll_with_human_approval(self, client: AsyncAgente # Give Temporal a brief moment to materialize the child workflow await asyncio.sleep(1) try: - print(f"[DEBUG 080 POLL] Sending approval signal to child workflow...") handle = temporal_client.get_workflow_handle("child-workflow-id") await handle.signal("fulfill_order_signal", True) approval_signal_sent = True - print(f"[DEBUG 080 POLL] ✅ Approval signal sent successfully!") except Exception as e: - print(f"[DEBUG 080 POLL] ⚠️ Warning: Could not send signal to child workflow: {e}") - print(f"[DEBUG 080 POLL] This may be expected if workflow completed before signal could be sent") + # It's okay if the workflow completed before we could signal it. + _ = e # Track tool_response messages (child workflow completion) if message.content and message.content.type == "tool_response": - print(f"[DEBUG 080 POLL] ✅ Saw tool_response - child workflow completed after approval") seen_tool_response = True # Track agent text messages and their streaming updates if message.content and message.content.type == "text" and message.content.author == "agent": content_length = len(message.content.content) if message.content.content else 0 - print(f"[DEBUG 080 POLL] Agent text update - Status: {message.streaming_status}, Length: {content_length}") # Stop when we get DONE status with actual content if message.streaming_status == "DONE" and content_length > 0: - print(f"[DEBUG 080 POLL] ✅ Streaming complete!") found_final_response = True break @@ -171,8 +158,6 @@ async def test_send_event_and_poll_with_human_approval(self, client: AsyncAgente assert seen_tool_response, "Expected to see tool_response message (child workflow completion after approval)" assert found_final_response, "Expected to see final text response after human approval" - print(f"[DEBUG 080 POLL] ✅ Human-in-the-loop workflow completed successfully!") - class TestStreamingEvents: """Test streaming event sending (backend verification via polling).""" diff --git a/examples/tutorials/test_utils/async_utils.py b/examples/tutorials/test_utils/async_utils.py index ff3dbd57c..2187e98d8 100644 --- a/examples/tutorials/test_utils/async_utils.py +++ b/examples/tutorials/test_utils/async_utils.py @@ -8,6 +8,7 @@ import json import time import asyncio +import contextlib from typing import Optional, AsyncGenerator from datetime import datetime, timezone @@ -94,7 +95,7 @@ async def poll_messages( # Keep track of messages we've already yielded seen_message_ids = set() # Track message content hashes to detect updates (for streaming) - message_content_hashes = {} + message_content_hashes: dict[str, int] = {} start_time = datetime.now() # Poll continuously until timeout @@ -120,6 +121,10 @@ async def poll_messages( if msg_timestamp < messages_created_after: continue + # Some message objects may not have an ID; skip them since we use IDs for dedupe. + if not message.id: + continue + # Check if this is a new message or an update to existing message is_new_message = message.id not in seen_message_ids @@ -177,18 +182,45 @@ async def send_event_and_stream( Raises: Exception: If streaming fails """ - # Send the event - event_content = TextContentParam(type="text", author="user", content=user_message) + queue: asyncio.Queue[dict[str, object] | None] = asyncio.Queue() + stream_exc: BaseException | None = None + + async def consume_stream() -> None: + nonlocal stream_exc + try: + async for event in stream_agent_response( + client=client, + task_id=task_id, + timeout=timeout, + ): + await queue.put(event) + if event.get("type") == "done": + break + except BaseException as e: # noqa: BLE001 - propagate after draining + stream_exc = e + finally: + await queue.put(None) + + # Start consuming the stream *before* sending the event, so we don't block waiting for the first message. + stream_task = asyncio.create_task(consume_stream()) - await client.agents.send_event(agent_id=agent_id, params={"task_id": task_id, "content": event_content}) + try: + event_content = TextContentParam(type="text", author="user", content=user_message) + await client.agents.send_event(agent_id=agent_id, params={"task_id": task_id, "content": event_content}) - # Stream the response using stream_agent_response and yield events up the stack - async for event in stream_agent_response( - client=client, - task_id=task_id, - timeout=timeout, - ): - yield event + while True: + item = await queue.get() + if item is None: + break + yield item + + if stream_exc is not None: + raise stream_exc + finally: + if not stream_task.done(): + stream_task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await stream_task async def stream_agent_response( @@ -220,10 +252,8 @@ async def stream_agent_response( yield event except asyncio.TimeoutError: - print(f"[DEBUG] Stream timed out after {timeout}s") raise except Exception as e: - print(f"[DEBUG] Stream error: {e}") raise