Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/strands/event_loop/event_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
ToolResultMessageEvent,
TypedEvent,
)
from ..types.content import Message
from ..types.content import Message, Messages
from ..types.exceptions import (
ContextWindowOverflowException,
EventLoopException,
Expand All @@ -56,6 +56,26 @@
MAX_DELAY = 240 # 4 minutes


def _has_tool_use_in_latest_message(messages: "Messages") -> bool:
"""Check if the latest message contains any ToolUse content blocks.

Args:
messages: List of messages in the conversation.

Returns:
True if the latest message contains at least one ToolUse content block, False otherwise.
"""
if len(messages) > 0:
latest_message = messages[-1]
content_blocks = latest_message.get("content", [])

for content_block in content_blocks:
if "toolUse" in content_block:
return True

return False


async def event_loop_cycle(
agent: "Agent",
invocation_state: dict[str, Any],
Expand Down Expand Up @@ -121,7 +141,10 @@ async def event_loop_cycle(
if agent._interrupt_state.activated:
stop_reason: StopReason = "tool_use"
message = agent._interrupt_state.context["tool_use_message"]

# Skip model invocation if the latest message contains ToolUse
elif _has_tool_use_in_latest_message(agent.messages):
stop_reason = "tool_use"
message = agent.messages[-1]
else:
model_events = _handle_model_execution(
agent, cycle_span, cycle_trace, invocation_state, tracer, structured_output_context
Expand Down
21 changes: 21 additions & 0 deletions tests/strands/agent/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2063,6 +2063,27 @@ def test_agent_tool_caller_interrupt(user):
agent.tool.test_tool()


def test_latest_message_tool_use_skips_model_invoke(tool_decorated):
mock_model = MockedModelProvider([{"role": "assistant", "content": [{"text": "I see the tool result"}]}])

messages: Messages = [
{
"role": "assistant",
"content": [
{"toolUse": {"toolUseId": "123", "name": "tool_decorated", "input": {"random_string": "Hello"}}}
],
}
]
agent = Agent(model=mock_model, tools=[tool_decorated], messages=messages)

agent()

assert mock_model.index == 1
assert len(agent.messages) == 3
assert agent.messages[1]["content"][0]["toolResult"]["content"][0]["text"] == "Hello"
assert agent.messages[2]["content"][0]["text"] == "I see the tool result"


def test_agent_del_before_tool_registry_set():
"""Test that Agent.__del__ doesn't fail if called before tool_registry is set."""
agent = Agent()
Expand Down