@@ -129,7 +129,8 @@ async def to_async_iter(items):
129129def mock_llm_agent (mocker ):
130130 """Provides a mock LLM agent."""
131131 mock_llm_registry_cls = mocker .patch (
132- "google.adk.evaluation.simulation.llm_backed_user_simulator.LLMRegistry"
132+ "google.adk.evaluation.simulation.llm_backed_user_simulator.LLMRegistry" ,
133+ autospec = True ,
133134 )
134135 mock_llm_registry = mocker .MagicMock ()
135136 mock_llm_registry_cls .return_value = mock_llm_registry
@@ -207,18 +208,25 @@ async def test_get_llm_response_return_value(
207208 self , simulator , mock_llm_agent , mocker
208209 ):
209210 """Tests that _get_llm_response returns the full response correctly."""
210- mock_llm_response = mocker .MagicMock ()
211+ mock_llm_response = mocker .create_autospec (
212+ types .GenerateContentResponse , instance = True
213+ )
214+ mock_llm_response .error_code = None
211215 mock_llm_response .content = types .Content (
212216 parts = [
213217 types .Part (text = "some thought" , thought = True ),
214218 types .Part (text = "Hello world!" ),
215219 ]
216220 )
221+ mock_llm_response .parts = mock_llm_response .content .parts
217222 mock_llm_agent .generate_content_async .return_value = to_async_iter (
218223 [mock_llm_response ]
219224 )
220- response = await simulator ._get_llm_response (rewritten_dialogue = "" )
225+ response , error_reason = await simulator ._get_llm_response (
226+ rewritten_dialogue = ""
227+ )
221228 assert response == "Hello world!"
229+ assert error_reason is None
222230
223231 @pytest .mark .asyncio
224232 async def test_get_next_user_message_first_invocation (
@@ -257,10 +265,14 @@ async def test_turn_limit_reached(self, conversation_scenario):
257265 @pytest .mark .asyncio
258266 async def test_stop_signal_detected (self , simulator , mock_llm_agent , mocker ):
259267 """Tests get_next_user_message when the stop signal is detected."""
260- mock_llm_response = mocker .MagicMock ()
268+ mock_llm_response = mocker .create_autospec (
269+ types .GenerateContentResponse , instance = True
270+ )
271+ mock_llm_response .error_code = None
261272 mock_llm_response .content = types .Content (
262273 parts = [types .Part (text = "Thanks! Bye!</finished>" )]
263274 )
275+ mock_llm_response .parts = mock_llm_response .content .parts
264276 mock_llm_agent .generate_content_async .return_value = to_async_iter (
265277 [mock_llm_response ]
266278 )
@@ -273,22 +285,84 @@ async def test_stop_signal_detected(self, simulator, mock_llm_agent, mocker):
273285 assert next_user_message .user_message is None
274286
275287 @pytest .mark .asyncio
276- async def test_no_message_generated (self , simulator , mock_llm_agent ):
277- """Tests get_next_user_message when no message is generated."""
288+ async def test_no_message_generated_empty_response (
289+ self , simulator , mock_llm_agent
290+ ):
291+ """Tests get_next_user_message when no message is generated (empty stream)."""
278292 mock_llm_agent .generate_content_async .return_value = to_async_iter ([])
279293
280- with pytest .raises (RuntimeError , match = "Failed to generate a user message" ):
294+ with pytest .raises (
295+ RuntimeError ,
296+ match = "Failed to generate a user message: LLM returned empty response" ,
297+ ):
298+ await simulator .get_next_user_message (events = _INPUT_EVENTS )
299+
300+ @pytest .mark .asyncio
301+ async def test_get_next_user_message_safety_blocked (
302+ self , simulator , mock_llm_agent , mocker
303+ ):
304+ """Tests get_next_user_message when response is safety blocked."""
305+ mock_llm_response = mocker .create_autospec (
306+ types .GenerateContentResponse , instance = True
307+ )
308+ mock_llm_response .content = None
309+ mock_llm_response .error_code = "SAFETY"
310+ mock_llm_response .error_message = "Blocked by safety"
311+ mock_llm_response .parts = []
312+ mock_llm_agent .generate_content_async .return_value = to_async_iter (
313+ [mock_llm_response ]
314+ )
315+
316+ with pytest .raises (
317+ RuntimeError ,
318+ match = (
319+ "Failed to generate a user message: safety filters or other error"
320+ " \\ (code=SAFETY\\ )"
321+ ),
322+ ):
323+ await simulator .get_next_user_message (events = _INPUT_EVENTS )
324+
325+ @pytest .mark .asyncio
326+ async def test_get_next_user_message_thinking_only (
327+ self , simulator , mock_llm_agent , mocker
328+ ):
329+ """Tests get_next_user_message when response contains only thinking tokens."""
330+ mock_llm_response = mocker .create_autospec (
331+ types .GenerateContentResponse , instance = True
332+ )
333+ mock_llm_response .content = types .Content (
334+ parts = [
335+ types .Part (text = "thinking..." , thought = True ),
336+ ]
337+ )
338+ mock_llm_response .error_code = None
339+ mock_llm_response .parts = mock_llm_response .content .parts
340+ mock_llm_agent .generate_content_async .return_value = to_async_iter (
341+ [mock_llm_response ]
342+ )
343+
344+ with pytest .raises (
345+ RuntimeError ,
346+ match = (
347+ "Failed to generate a user message: LLM returned only thinking"
348+ " tokens"
349+ ),
350+ ):
281351 await simulator .get_next_user_message (events = _INPUT_EVENTS )
282352
283353 @pytest .mark .asyncio
284354 async def test_get_next_user_message_success (
285355 self , simulator , mock_llm_agent , mocker
286356 ):
287357 """Tests get_next_user_message when the user message is generated successfully."""
288- mock_llm_response = mocker .MagicMock ()
358+ mock_llm_response = mocker .create_autospec (
359+ types .GenerateContentResponse , instance = True
360+ )
361+ mock_llm_response .error_code = None
289362 mock_llm_response .content = types .Content (
290363 parts = [types .Part (text = "I need to book a flight." )]
291364 )
365+ mock_llm_response .parts = mock_llm_response .content .parts
292366 mock_llm_agent .generate_content_async .return_value = to_async_iter (
293367 [mock_llm_response ]
294368 )
@@ -309,10 +383,14 @@ async def test_get_next_user_message_with_persona_success(
309383 self , simulator_with_persona , mock_llm_agent , mocker
310384 ):
311385 """Tests get_next_user_message when the user message is generated successfully."""
312- mock_llm_response = mocker .MagicMock ()
386+ mock_llm_response = mocker .create_autospec (
387+ types .GenerateContentResponse , instance = True
388+ )
389+ mock_llm_response .error_code = None
313390 mock_llm_response .content = types .Content (
314391 parts = [types .Part (text = "I need to book a flight." )]
315392 )
393+ mock_llm_response .parts = mock_llm_response .content .parts
316394 mock_llm_agent .generate_content_async .return_value = to_async_iter (
317395 [mock_llm_response ]
318396 )
0 commit comments