Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c5e4e51
fix(event_loop): raise dedicated exception when encountering max toke…
dbschmigelski Jul 30, 2025
6703819
fix: update integ tests
dbschmigelski Jul 30, 2025
c94b74e
fix: rename exception message, add to exception, move earlier in cycle
dbschmigelski Jul 31, 2025
36dd0f9
Update tests_integ/test_max_tokens_reached.py
dbschmigelski Jul 31, 2025
e04c73d
Update tests_integ/test_max_tokens_reached.py
dbschmigelski Jul 31, 2025
cca2f86
linting
dbschmigelski Jul 31, 2025
f647baa
Merge branch 'strands-agents:main' into fix-max-tokens
dbschmigelski Jul 31, 2025
78c5a91
Merge branch 'strands-agents:main' into fix-max-tokens
dbschmigelski Aug 1, 2025
a208496
Merge branch 'strands-agents:main' into fix-max-tokens
dbschmigelski Aug 4, 2025
2e2d4df
feat: add builtin hook provider to address max tokens reached truncation
dbschmigelski Aug 4, 2025
447d147
tests: modify integ test to inspect message history
dbschmigelski Aug 4, 2025
564895d
fix: fix linting errors
dbschmigelski Aug 4, 2025
2f118fb
fix: linting
dbschmigelski Aug 4, 2025
e5fc51a
refactor: switch from hook approach to conversation manager
dbschmigelski Aug 5, 2025
5906fc2
linting
dbschmigelski Aug 5, 2025
87445a3
fix: test contained incorrect assertions
dbschmigelski Aug 6, 2025
924fea9
fix: add event emission
dbschmigelski Aug 6, 2025
104f6b4
feat: move to async
dbschmigelski Aug 6, 2025
11b91f4
feat: add additional error case where no tool uses were fixed
dbschmigelski Aug 6, 2025
1da9ba7
feat: add max tokens reached test
dbschmigelski Aug 6, 2025
623f3c7
linting
dbschmigelski Aug 6, 2025
66c4c07
feat: add max tokens reached test
dbschmigelski Aug 6, 2025
4b5c5a7
feat: switch to a default behavior to recover from max tokens reached
dbschmigelski Aug 7, 2025
83ad822
fix: all tool uses now must be replaced
dbschmigelski Aug 8, 2025
faa4618
fix: boolean
dbschmigelski Aug 8, 2025
fa8195f
remove todo
dbschmigelski Aug 8, 2025
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
38 changes: 18 additions & 20 deletions src/strands/event_loop/_recover_message_on_max_tokens_reached.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,44 @@


def recover_message_on_max_tokens_reached(message: Message) -> Message:
"""Recover and clean up incomplete messages when max token limits are reached.
"""Recover and clean up messages when max token limits are reached.

When a model response is truncated due to maximum token limits, tool use blocks may be
incomplete or malformed. This function inspects the message content and:
When a model response is truncated due to maximum token limits, all tool use blocks
should be replaced with informative error messages since they may be incomplete or
unreliable. This function inspects the message content and:

1. Identifies incomplete tool use blocks (missing name, input, or toolUseId)
2. Replaces incomplete tool uses with informative error messages
3. Preserves all valid content blocks (text and complete tool uses)
1. Identifies all tool use blocks (regardless of validity)
2. Replaces all tool uses with informative error messages
3. Preserves all non-tool content blocks (text, images, etc.)
4. Returns a cleaned message suitable for conversation history

This recovery mechanism ensures that the conversation can continue gracefully even when
model responses are truncated, providing clear feedback about what happened.
model responses are truncated, providing clear feedback about what happened and preventing
potentially incomplete or corrupted tool executions.

TODO: after https://github.com/strands-agents/sdk-python/issues/561 is completed, only the verifiable
invalid tool_use content blocks need to be replaced.

Args:
message: The potentially incomplete message from the model that was truncated
due to max token limits.

Returns:
A cleaned Message with incomplete tool uses replaced by explanatory text content.
A cleaned Message with all tool uses replaced by explanatory text content.
The returned message maintains the same role as the input message.

Example:
If a message contains an incomplete tool use like:
If a message contains any tool use (complete or incomplete):
```
{"toolUse": {"name": "calculator"}} # missing input and toolUseId
{"toolUse": {"name": "calculator", "input": {"expression": "2+2"}, "toolUseId": "123"}}
```

It will be replaced with:
```
{"text": "The selected tool calculator's tool use was incomplete due to maximum token limits being reached."}
```
"""
logger.info("handling max_tokens stop reason - inspecting incomplete message for invalid tool uses")
logger.info("handling max_tokens stop reason - replacing all tool uses with error messages")

valid_content: list[ContentBlock] = []
for content in message["content"] or []:
Expand All @@ -55,15 +60,8 @@ def recover_message_on_max_tokens_reached(message: Message) -> Message:
valid_content.append(content)
continue

# Check if tool use is incomplete (missing or empty required fields)
tool_name = tool_use.get("name")
if tool_name and tool_use.get("input") and tool_use.get("toolUseId"):
# As far as we can tell, tool use is valid if this condition is true
valid_content.append(content)
continue

# Tool use is incomplete due to max_tokens truncation
display_name = tool_name if tool_name else "<unknown>"
# Replace all tool uses with error messages when max_tokens is reached
display_name = tool_use.get("name") or "<unknown>"
logger.warning("tool_name=<%s> | replacing with error message due to max_tokens truncation.", display_name)

valid_content.append(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def test_recover_message_on_max_tokens_reached_with_missing_tool_use_id():


def test_recover_message_on_max_tokens_reached_with_valid_tool_use():
"""Test that valid tool uses are preserved unchanged."""
"""Test that even valid tool uses are replaced with error messages."""
complete_message: Message = {
"role": "assistant",
"content": [
Expand All @@ -106,13 +106,15 @@ def test_recover_message_on_max_tokens_reached_with_valid_tool_use():

result = recover_message_on_max_tokens_reached(complete_message)

# Should preserve the message exactly as-is
# Should replace even valid tool uses with error messages
assert result["role"] == "assistant"
assert len(result["content"]) == 2
assert result["content"][0] == {"text": "I'll help you with that."}
assert result["content"][1] == {
"toolUse": {"name": "calculator", "input": {"expression": "2+2"}, "toolUseId": "123"}
}

# Valid tool use should also be replaced with error message
assert "text" in result["content"][1]
assert "calculator" in result["content"][1]["text"]
assert "incomplete due to maximum token limits" in result["content"][1]["text"]


def test_recover_message_on_max_tokens_reached_with_empty_content():
Expand Down
Loading