Skip to content

Commit 463bb2c

Browse files
Copilotafarntrog
andcommitted
Refactor _handle_tool_execution method - extract helper functions
Co-authored-by: afarntrog <47332252+afarntrog@users.noreply.github.com>
1 parent 0b752de commit 463bb2c

1 file changed

Lines changed: 164 additions & 64 deletions

File tree

src/strands/event_loop/event_loop.py

Lines changed: 164 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,146 @@ async def recurse_event_loop(agent: "Agent", invocation_state: dict[str, Any]) -
357357
recursive_trace.end()
358358

359359

360+
def _prepare_tools_for_execution(
361+
message: Message,
362+
) -> tuple[list[ToolUse], list[ToolResult], list[str]]:
363+
"""Prepare and validate tools from a message for execution.
364+
365+
Args:
366+
message: The message from the model that may contain tool use requests.
367+
368+
Returns:
369+
A tuple containing:
370+
- List of valid tool uses
371+
- List of tool results for invalid tools
372+
- List of invalid tool use IDs
373+
"""
374+
tool_uses: list[ToolUse] = []
375+
tool_results: list[ToolResult] = []
376+
invalid_tool_use_ids: list[str] = []
377+
378+
validate_and_prepare_tools(message, tool_uses, tool_results, invalid_tool_use_ids)
379+
valid_tool_uses = [tool_use for tool_use in tool_uses if tool_use.get("toolUseId") not in invalid_tool_use_ids]
380+
381+
return valid_tool_uses, tool_results, invalid_tool_use_ids
382+
383+
384+
def _create_tool_result_message(tool_results: list[ToolResult]) -> Message:
385+
"""Create a tool result message for conversation history.
386+
387+
Args:
388+
tool_results: List of tool results to include in the message.
389+
390+
Returns:
391+
A formatted message containing the tool results.
392+
"""
393+
return {
394+
"role": "user",
395+
"content": [{"toolResult": result} for result in tool_results],
396+
}
397+
398+
399+
def _process_tool_result_message(
400+
agent: "Agent",
401+
tool_result_message: Message,
402+
) -> None:
403+
"""Process and add a tool result message to the agent's conversation history.
404+
405+
Args:
406+
agent: The agent to add the message to.
407+
tool_result_message: The tool result message to process.
408+
"""
409+
agent.messages.append(tool_result_message)
410+
agent.hooks.invoke_callbacks(MessageAddedEvent(agent=agent, message=tool_result_message))
411+
412+
413+
def _cleanup_event_loop_cycle(
414+
cycle_span: Any,
415+
cycle_start_time: float,
416+
cycle_trace: Trace,
417+
agent: "Agent",
418+
message: Message,
419+
tool_result_message: Message,
420+
) -> None:
421+
"""Clean up tracing and metrics for an event loop cycle.
422+
423+
Args:
424+
cycle_span: Span object for tracing the cycle.
425+
cycle_start_time: Start time of the current cycle.
426+
cycle_trace: Trace object for the current event loop cycle.
427+
agent: The agent for which the cycle is being cleaned up.
428+
message: The original message from the model.
429+
tool_result_message: The tool result message.
430+
"""
431+
if cycle_span:
432+
tracer = get_tracer()
433+
tracer.end_event_loop_cycle_span(
434+
span=cycle_span,
435+
message=message,
436+
tool_result_message=tool_result_message
437+
)
438+
439+
440+
async def _handle_structured_output(
441+
output_schema: "OutputSchema",
442+
invocation_state: dict[str, Any],
443+
tool_uses: list[ToolUse],
444+
tool_results: list[ToolResult],
445+
stop_reason: StopReason,
446+
message: Message,
447+
agent: "Agent",
448+
cycle_start_time: float,
449+
cycle_trace: Trace,
450+
cycle_span: Any,
451+
) -> AsyncGenerator[tuple[TypedEvent, bool], None]:
452+
"""Handle structured output processing and emit appropriate events.
453+
454+
Args:
455+
output_schema: The output schema to process.
456+
invocation_state: Current invocation state.
457+
tool_uses: List of tool uses.
458+
tool_results: List of tool results.
459+
stop_reason: The reason the model stopped generating.
460+
message: The original message from the model.
461+
agent: The agent processing the structured output.
462+
cycle_start_time: Start time of the current cycle.
463+
cycle_trace: Trace object for the current event loop cycle.
464+
cycle_span: Span object for tracing the cycle.
465+
466+
Yields:
467+
A tuple of (event, should_stop) where should_stop indicates if processing should halt.
468+
"""
469+
from ..tools.structured_output_tool import _extract_structured_output_from_state, _cleanup_structured_outputs
470+
471+
structured_output_result = _extract_structured_output_from_state(
472+
invocation_state, tool_uses, output_schema.type.__name__
473+
)
474+
475+
# Cleanup any remaining structured outputs
476+
tool_use_ids = [str(tool_use.get("toolUseId", "")) for tool_use in tool_uses]
477+
_cleanup_structured_outputs(invocation_state, tool_use_ids)
478+
479+
if structured_output_result is not None:
480+
yield StructuredOutputEvent(structured_output=structured_output_result), False
481+
482+
# Create and process tool result message
483+
tool_result_message = _create_tool_result_message(tool_results)
484+
_process_tool_result_message(agent, tool_result_message)
485+
yield ToolResultMessageEvent(message=tool_result_message), False
486+
487+
# End the event loop cycle with structured output
488+
agent.event_loop_metrics.end_cycle(cycle_start_time, cycle_trace, {})
489+
_cleanup_event_loop_cycle(cycle_span, cycle_start_time, cycle_trace, agent, message, tool_result_message)
490+
491+
yield EventLoopStopEvent(
492+
stop_reason,
493+
message,
494+
agent.event_loop_metrics,
495+
invocation_state["request_state"],
496+
structured_output=structured_output_result
497+
), True
498+
499+
360500
async def _handle_tool_execution(
361501
stop_reason: StopReason,
362502
message: Message,
@@ -385,92 +525,52 @@ async def _handle_tool_execution(
385525
- The updated event loop metrics,
386526
- The updated request state.
387527
"""
388-
tool_uses: list[ToolUse] = []
389-
tool_results: list[ToolResult] = []
390-
invalid_tool_use_ids: list[str] = []
391-
392-
validate_and_prepare_tools(message, tool_uses, tool_results, invalid_tool_use_ids)
393-
tool_uses = [tool_use for tool_use in tool_uses if tool_use.get("toolUseId") not in invalid_tool_use_ids]
528+
# Prepare and validate tools
529+
tool_uses, tool_results, invalid_tool_use_ids = _prepare_tools_for_execution(message)
530+
394531
if not tool_uses:
395532
yield EventLoopStopEvent(stop_reason, message, agent.event_loop_metrics, invocation_state["request_state"], None)
396533
return
397534

398-
# Check for structured output tool calls
399-
output_schema: OutputSchema = invocation_state.get("output_schema")
400-
structured_output_tool_names = set()
401-
if output_schema:
402-
structured_output_tool_names.add(output_schema.type.__name__)
403-
404-
# Execute all tools (including structured output tools) through normal pipeline
535+
# Execute all tools through normal pipeline
405536
tool_events = agent.tool_executor._execute(
406537
agent, tool_uses, tool_results, cycle_trace, cycle_span, invocation_state
407538
)
408539
async for tool_event in tool_events:
409540
yield tool_event
410541

411-
# Check if any structured output tools were executed successfully
412-
structured_output_result = None
413-
if output_schema and structured_output_tool_names:
414-
from ..tools.structured_output_tool import _extract_structured_output_from_state, _cleanup_structured_outputs
415-
416-
structured_output_result = _extract_structured_output_from_state(
417-
invocation_state, tool_uses, output_schema.type.__name__
418-
)
419-
420-
# Cleanup any remaining structured outputs
421-
tool_use_ids = [str(tool_use.get("toolUseId", "")) for tool_use in tool_uses]
422-
_cleanup_structured_outputs(invocation_state, tool_use_ids)
423-
424-
# If we found structured output, emit the event and stop the event loop # TODO I think everything after the ` yield StructuredOutpu...` is extra...
425-
if structured_output_result is not None:
426-
yield StructuredOutputEvent(structured_output=structured_output_result)
427-
428-
# Create tool result message for conversation history
429-
tool_result_message: Message = {
430-
"role": "user",
431-
"content": [{"toolResult": result} for result in tool_results],
432-
}
433-
434-
agent.messages.append(tool_result_message)
435-
agent.hooks.invoke_callbacks(MessageAddedEvent(agent=agent, message=tool_result_message))
436-
yield ToolResultMessageEvent(message=tool_result_message)
437-
438-
# End the event loop cycle with structured output
439-
agent.event_loop_metrics.end_cycle(cycle_start_time, cycle_trace, {})
440-
if cycle_span:
441-
tracer = get_tracer()
442-
tracer.end_event_loop_cycle_span(span=cycle_span, message=message, tool_result_message=tool_result_message)
443-
444-
yield EventLoopStopEvent(
445-
stop_reason,
446-
message,
447-
agent.event_loop_metrics,
448-
invocation_state["request_state"],
449-
structured_output=structured_output_result
450-
)
451-
return
542+
# Handle structured output if present
543+
output_schema: "OutputSchema" = invocation_state.get("output_schema")
544+
if output_schema:
545+
structured_output_tool_names = {output_schema.type.__name__}
546+
if structured_output_tool_names: # Only proceed if we have structured output tool names
547+
async for event, should_stop in _handle_structured_output(
548+
output_schema, invocation_state, tool_uses, tool_results,
549+
stop_reason, message, agent, cycle_start_time, cycle_trace, cycle_span
550+
):
551+
yield event
552+
# Check if we should stop processing
553+
if should_stop:
554+
return
452555

453556
# Store parent cycle ID for the next cycle
454557
invocation_state["event_loop_parent_cycle_id"] = invocation_state["event_loop_cycle_id"]
455558

456-
tool_result_message: Message = {
457-
"role": "user",
458-
"content": [{"toolResult": result} for result in tool_results],
459-
}
460-
461-
agent.messages.append(tool_result_message)
462-
agent.hooks.invoke_callbacks(MessageAddedEvent(agent=agent, message=tool_result_message))
559+
# Create and process tool result message
560+
tool_result_message = _create_tool_result_message(tool_results)
561+
_process_tool_result_message(agent, tool_result_message)
463562
yield ToolResultMessageEvent(message=tool_result_message)
464563

465-
if cycle_span:
466-
tracer = get_tracer()
467-
tracer.end_event_loop_cycle_span(span=cycle_span, message=message, tool_result_message=tool_result_message)
564+
# Cleanup tracing
565+
_cleanup_event_loop_cycle(cycle_span, cycle_start_time, cycle_trace, agent, message, tool_result_message)
468566

567+
# Check if we should stop the event loop
469568
if invocation_state["request_state"].get("stop_event_loop", False):
470569
agent.event_loop_metrics.end_cycle(cycle_start_time, cycle_trace)
471570
yield EventLoopStopEvent(stop_reason, message, agent.event_loop_metrics, invocation_state["request_state"], None)
472571
return
473572

573+
# Continue with recursive event loop processing
474574
events = recurse_event_loop(agent=agent, invocation_state=invocation_state)
475575
async for event in events:
476576
yield event

0 commit comments

Comments
 (0)