Skip to content

Commit 2147920

Browse files
authored
feat: skip model invocation when latest message contains ToolUse (#1068)
* feat: skip model invocation when latest message contains ToolUse - Add _has_tool_use_in_latest_message() helper function to detect ToolUse in latest message - Modify event_loop_cycle() to skip model execution when ToolUse is detected - Set stop_reason='tool_use' and use latest message directly for tool execution - Add comprehensive test coverage with 10 test scenarios - Maintain backward compatibility and existing functionality - No performance impact, minimal overhead for detection Resolves the requirement to skip model calls when the agent should directly execute tools based on existing ToolUse messages in the conversation. 🤖 Assisted by the code-assist agent script * fix: Check messages array size
1 parent 73865d3 commit 2147920

File tree

2 files changed

+46
-2
lines changed

2 files changed

+46
-2
lines changed

src/strands/event_loop/event_loop.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
ToolResultMessageEvent,
3434
TypedEvent,
3535
)
36-
from ..types.content import Message
36+
from ..types.content import Message, Messages
3737
from ..types.exceptions import (
3838
ContextWindowOverflowException,
3939
EventLoopException,
@@ -56,6 +56,26 @@
5656
MAX_DELAY = 240 # 4 minutes
5757

5858

59+
def _has_tool_use_in_latest_message(messages: "Messages") -> bool:
60+
"""Check if the latest message contains any ToolUse content blocks.
61+
62+
Args:
63+
messages: List of messages in the conversation.
64+
65+
Returns:
66+
True if the latest message contains at least one ToolUse content block, False otherwise.
67+
"""
68+
if len(messages) > 0:
69+
latest_message = messages[-1]
70+
content_blocks = latest_message.get("content", [])
71+
72+
for content_block in content_blocks:
73+
if "toolUse" in content_block:
74+
return True
75+
76+
return False
77+
78+
5979
async def event_loop_cycle(
6080
agent: "Agent",
6181
invocation_state: dict[str, Any],
@@ -121,7 +141,10 @@ async def event_loop_cycle(
121141
if agent._interrupt_state.activated:
122142
stop_reason: StopReason = "tool_use"
123143
message = agent._interrupt_state.context["tool_use_message"]
124-
144+
# Skip model invocation if the latest message contains ToolUse
145+
elif _has_tool_use_in_latest_message(agent.messages):
146+
stop_reason = "tool_use"
147+
message = agent.messages[-1]
125148
else:
126149
model_events = _handle_model_execution(
127150
agent, cycle_span, cycle_trace, invocation_state, tracer, structured_output_context

tests/strands/agent/test_agent.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,6 +2063,27 @@ def test_agent_tool_caller_interrupt(user):
20632063
agent.tool.test_tool()
20642064

20652065

2066+
def test_latest_message_tool_use_skips_model_invoke(tool_decorated):
2067+
mock_model = MockedModelProvider([{"role": "assistant", "content": [{"text": "I see the tool result"}]}])
2068+
2069+
messages: Messages = [
2070+
{
2071+
"role": "assistant",
2072+
"content": [
2073+
{"toolUse": {"toolUseId": "123", "name": "tool_decorated", "input": {"random_string": "Hello"}}}
2074+
],
2075+
}
2076+
]
2077+
agent = Agent(model=mock_model, tools=[tool_decorated], messages=messages)
2078+
2079+
agent()
2080+
2081+
assert mock_model.index == 1
2082+
assert len(agent.messages) == 3
2083+
assert agent.messages[1]["content"][0]["toolResult"]["content"][0]["text"] == "Hello"
2084+
assert agent.messages[2]["content"][0]["text"] == "I see the tool result"
2085+
2086+
20662087
def test_agent_del_before_tool_registry_set():
20672088
"""Test that Agent.__del__ doesn't fail if called before tool_registry is set."""
20682089
agent = Agent()

0 commit comments

Comments
 (0)