|
1 | 1 | """ |
2 | | -Sample tests for AgentEx ACP agent. |
| 2 | +Tests for s010-multiturn (sync agent) |
3 | 3 |
|
4 | | -This test suite demonstrates how to test the main AgentEx API functions: |
5 | | -- Non-streaming message sending |
6 | | -- Streaming message sending |
7 | | -- Task creation via RPC |
| 4 | +This test suite demonstrates testing a multi-turn sync agent using the AgentEx testing framework. |
8 | 5 |
|
9 | | -To run these tests: |
10 | | -1. Make sure the agent is running (via docker-compose or `agentex agents run`) |
11 | | -2. Set the AGENTEX_API_BASE_URL environment variable if not using default |
12 | | -3. Run: pytest test_agent.py -v |
| 6 | +Test coverage: |
| 7 | +- Multi-turn non-streaming conversation |
| 8 | +- Multi-turn streaming conversation |
| 9 | +- State management across turns |
13 | 10 |
|
14 | | -Configuration: |
15 | | -- AGENTEX_API_BASE_URL: Base URL for the AgentEx server (default: http://localhost:5003) |
16 | | -- AGENT_NAME: Name of the agent to test (default: s010-multiturn) |
17 | | -""" |
18 | | - |
19 | | -import os |
20 | | - |
21 | | -import pytest |
22 | | -from test_utils.sync import validate_text_in_string, collect_streaming_response |
23 | | - |
24 | | -from agentex import Agentex |
25 | | -from agentex.types import TextContent, TextContentParam |
26 | | -from agentex.types.agent_rpc_params import ParamsCreateTaskRequest, ParamsSendMessageRequest |
27 | | -from agentex.lib.sdk.fastacp.base.base_acp_server import uuid |
28 | | - |
29 | | -# Configuration from environment variables |
30 | | -AGENTEX_API_BASE_URL = os.environ.get("AGENTEX_API_BASE_URL", "http://localhost:5003") |
31 | | -AGENT_NAME = os.environ.get("AGENT_NAME", "s010-multiturn") |
| 11 | +Prerequisites: |
| 12 | + - AgentEx services running (make dev) |
| 13 | + - Agent running: agentex agents run --manifest manifest.yaml |
32 | 14 |
|
| 15 | +Run tests: |
| 16 | + pytest tests/test_agent.py -v |
| 17 | +""" |
33 | 18 |
|
34 | | -@pytest.fixture |
35 | | -def client(): |
36 | | - """Create an AgentEx client instance for testing.""" |
37 | | - return Agentex(base_url=AGENTEX_API_BASE_URL) |
38 | | - |
| 19 | +from agentex.lib.testing import ( |
| 20 | + test_sync_agent, |
| 21 | + collect_streaming_deltas, |
| 22 | + assert_valid_agent_response, |
| 23 | + assert_agent_response_contains, |
| 24 | +) |
39 | 25 |
|
40 | | -@pytest.fixture |
41 | | -def agent_name(): |
42 | | - """Return the agent name for testing.""" |
43 | | - return AGENT_NAME |
| 26 | +AGENT_NAME = "s010-multiturn" |
44 | 27 |
|
45 | 28 |
|
46 | | -@pytest.fixture |
47 | | -def agent_id(client, agent_name): |
48 | | - """Retrieve the agent ID based on the agent name.""" |
49 | | - agents = client.agents.list() |
50 | | - for agent in agents: |
51 | | - if agent.name == agent_name: |
52 | | - return agent.id |
53 | | - raise ValueError(f"Agent with name {agent_name} not found.") |
| 29 | +def test_multiturn_conversation(): |
| 30 | + """Test multi-turn conversation with non-streaming messages.""" |
| 31 | + with test_sync_agent(agent_name=AGENT_NAME) as test: |
| 32 | + messages = [ |
| 33 | + "Hello, can you tell me a little bit about tennis? I want to you make sure you use the word 'tennis' in each response.", |
| 34 | + "Pick one of the things you just mentioned, and dive deeper into it.", |
| 35 | + "Can you now output a summary of this conversation", |
| 36 | + ] |
54 | 37 |
|
| 38 | + for msg in messages: |
| 39 | + response = test.send_message(msg) |
55 | 40 |
|
56 | | -class TestNonStreamingMessages: |
57 | | - """Test non-streaming message sending.""" |
| 41 | + # Validate response |
| 42 | + assert_valid_agent_response(response) |
58 | 43 |
|
59 | | - def test_send_message(self, client: Agentex, agent_name: str, agent_id: str): |
60 | | - task_response = client.agents.create_task(agent_id, params=ParamsCreateTaskRequest(name=uuid.uuid1().hex)) |
61 | | - task = task_response.result |
| 44 | + # Validate "tennis" appears in response (per agent's behavior) |
| 45 | + assert_agent_response_contains(response, "tennis") |
62 | 46 |
|
63 | | - assert task is not None |
| 47 | + # Verify conversation history |
| 48 | + history = test.get_conversation_history() |
| 49 | + assert len(history) >= 6, f"Expected >= 6 messages (3 user + 3 agent), got {len(history)}" |
64 | 50 |
|
65 | | - messages = [ |
66 | | - "Hello, can you tell me a litle bit about tennis? I want to you make sure you use the word 'tennis' in each response.", |
67 | | - "Pick one of the things you just mentioned, and dive deeper into it.", |
68 | | - "Can you now output a summary of this conversation", |
69 | | - ] |
70 | 51 |
|
71 | | - for i, msg in enumerate(messages): |
72 | | - response = client.agents.send_message( |
73 | | - agent_name=agent_name, |
74 | | - params=ParamsSendMessageRequest( |
75 | | - content=TextContentParam( |
76 | | - author="user", |
77 | | - content=msg, |
78 | | - type="text", |
79 | | - ), |
80 | | - task_id=task.id, |
81 | | - ), |
82 | | - ) |
83 | | - assert response is not None and response.result is not None |
84 | | - result = response.result |
85 | | - |
86 | | - for message in result: |
87 | | - content = message.content |
88 | | - assert content is not None |
89 | | - assert isinstance(content, TextContent) and isinstance(content.content, str) |
90 | | - validate_text_in_string("tennis", content.content) |
91 | | - |
92 | | - states = client.states.list(agent_id=agent_id, task_id=task.id) |
93 | | - assert len(states) == 1 |
94 | | - |
95 | | - state = states[0] |
96 | | - assert state.state is not None |
97 | | - assert state.state.get("system_prompt", None) == "You are a helpful assistant that can answer questions." |
98 | | - |
99 | | - message_history = client.messages.list( |
100 | | - task_id=task.id, |
101 | | - ) |
102 | | - assert len(message_history) == (i + 1) * 2 # user + agent messages |
103 | | - |
104 | | - |
105 | | -class TestStreamingMessages: |
106 | | - """Test streaming message sending.""" |
107 | | - |
108 | | - def test_stream_message(self, client: Agentex, agent_name: str, agent_id: str): |
109 | | - """Test streaming messages in a multi-turn conversation.""" |
110 | | - |
111 | | - # create a task for this specific conversation |
112 | | - task_response = client.agents.create_task(agent_id, params=ParamsCreateTaskRequest(name=uuid.uuid1().hex)) |
113 | | - task = task_response.result |
114 | | - |
115 | | - assert task is not None |
| 52 | +def test_multiturn_streaming(): |
| 53 | + """Test multi-turn conversation with streaming messages.""" |
| 54 | + with test_sync_agent(agent_name=AGENT_NAME) as test: |
116 | 55 | messages = [ |
117 | 56 | "Hello, can you tell me a little bit about tennis? I want you to make sure you use the word 'tennis' in each response.", |
118 | 57 | "Pick one of the things you just mentioned, and dive deeper into it.", |
119 | 58 | "Can you now output a summary of this conversation", |
120 | 59 | ] |
121 | 60 |
|
122 | | - for i, msg in enumerate(messages): |
123 | | - stream = client.agents.send_message_stream( |
124 | | - agent_name=agent_name, |
125 | | - params=ParamsSendMessageRequest( |
126 | | - content=TextContentParam( |
127 | | - author="user", |
128 | | - content=msg, |
129 | | - type="text", |
130 | | - ), |
131 | | - task_id=task.id, |
132 | | - ), |
133 | | - ) |
| 61 | + for msg in messages: |
| 62 | + # Get streaming response |
| 63 | + response_gen = test.send_message_streaming(msg) |
134 | 64 |
|
135 | | - # Collect the streaming response |
136 | | - aggregated_content, chunks = collect_streaming_response(stream) |
| 65 | + # Collect streaming deltas |
| 66 | + aggregated_content, chunks = collect_streaming_deltas(response_gen) |
137 | 67 |
|
138 | | - assert len(chunks) == 1 |
139 | | - # Get the actual content (prefer full_content if available, otherwise use aggregated) |
| 68 | + # Validate we got content |
| 69 | + assert len(chunks) > 0, "Should receive chunks" |
| 70 | + assert len(aggregated_content) > 0, "Should receive content" |
140 | 71 |
|
141 | | - # Validate that "tennis" appears in the response because that is what our model does |
142 | | - validate_text_in_string("tennis", aggregated_content) |
| 72 | + # Validate "tennis" appears in response |
| 73 | + assert "tennis" in aggregated_content.lower(), f"Expected 'tennis' in: {aggregated_content[:100]}" |
143 | 74 |
|
144 | | - states = client.states.list(task_id=task.id) |
145 | | - assert len(states) == 1 |
146 | | - |
147 | | - message_history = client.messages.list( |
148 | | - task_id=task.id, |
149 | | - ) |
150 | | - assert len(message_history) == (i + 1) * 2 # user + agent messages |
| 75 | + # Verify conversation history |
| 76 | + history = test.get_conversation_history() |
| 77 | + assert len(history) >= 6, f"Expected >= 6 messages, got {len(history)}" |
151 | 78 |
|
152 | 79 |
|
153 | 80 | if __name__ == "__main__": |
| 81 | + import pytest |
| 82 | + |
154 | 83 | pytest.main([__file__, "-v"]) |
0 commit comments