From 8cdc84141dfa18df90747c34a7ae3af9a53cf763 Mon Sep 17 00:00:00 2001 From: Stephen Treacy Date: Fri, 6 Mar 2026 19:51:00 +0000 Subject: [PATCH] fix(event-loop): ensure all cycle metrics include end time and duration --- src/strands/event_loop/event_loop.py | 3 +- tests/strands/event_loop/test_event_loop.py | 48 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/strands/event_loop/event_loop.py b/src/strands/event_loop/event_loop.py index 3b1e2d76a..2e8e4a660 100644 --- a/src/strands/event_loop/event_loop.py +++ b/src/strands/event_loop/event_loop.py @@ -560,8 +560,9 @@ async def _handle_tool_execution( if cycle_span: tracer.end_event_loop_cycle_span(span=cycle_span, message=message, tool_result_message=tool_result_message) + agent.event_loop_metrics.end_cycle(cycle_start_time, cycle_trace) + if invocation_state["request_state"].get("stop_event_loop", False) or structured_output_context.stop_loop: - agent.event_loop_metrics.end_cycle(cycle_start_time, cycle_trace) yield EventLoopStopEvent( stop_reason, message, diff --git a/tests/strands/event_loop/test_event_loop.py b/tests/strands/event_loop/test_event_loop.py index 0cabeaeee..cedca269b 100644 --- a/tests/strands/event_loop/test_event_loop.py +++ b/tests/strands/event_loop/test_event_loop.py @@ -1084,3 +1084,51 @@ async def test_invalid_tool_names_adds_tool_uses(agent, model, alist): ], "role": "user", } + + +@pytest.mark.asyncio +async def test_event_loop_metrics_recorded_before_recursion( + agent, + model, + tool, + agenerator, + alist, +): + model.stream.side_effect = [ + agenerator( + [ + { + "contentBlockStart": { + "start": { + "toolUse": { + "toolUseId": "t1", + "name": tool.tool_spec["name"], + }, + }, + }, + }, + {"contentBlockStop": {}}, + {"messageStop": {"stopReason": "tool_use"}}, + ] + ), + agenerator( + [ + {"contentBlockDelta": {"delta": {"text": "test text"}}}, + {"contentBlockStop": {}}, + ] + ), + ] + + with unittest.mock.patch.object(agent.event_loop_metrics, "end_cycle") as mock_end_cycle: + stream = strands.event_loop.event_loop.event_loop_cycle( + agent=agent, + invocation_state={"request_state": {}}, + ) + events = await alist(stream) + + # Verify end_cycle was called once for tool cycle, once for text cycle + assert mock_end_cycle.call_count == 2 + + # Verify the event loop completed successfully + tru_stop_reason, _, _, _, _, _ = events[-1]["stop"] + assert tru_stop_reason == "end_turn"