@@ -581,80 +581,6 @@ async def get_weather(location: str, units: Literal["c", "f"]) -> BetaFunctionTo
581581 assert print_obj (message , monkeypatch ) == snapshots ["basic" ]["result" ]
582582
583583
584- @pytest .mark .skipif (PYDANTIC_V1 , reason = "tool runner not supported with pydantic v1" )
585- async def test_async_compaction_filters_tool_use (async_client : AsyncAnthropic ) -> None :
586- """Test that async compaction correctly filters out tool_use blocks.
587-
588- When compaction runs and the last message is an assistant message with only
589- tool_use blocks (no text), the filtering should remove it to avoid API errors
590- about tool_use without corresponding tool_result.
591-
592- This is a regression test for a bug where the async version used
593- self._params["messages"] instead of the filtered local `messages` variable.
594- """
595- from unittest .mock import AsyncMock , MagicMock
596-
597- runner = async_client .beta .messages .tool_runner (
598- model = "claude-sonnet-4-20250514" ,
599- max_tokens = 500 ,
600- tools = [],
601- messages = [{"role" : "user" , "content" : "test" }],
602- compaction_control = {
603- "enabled" : True ,
604- "context_token_threshold" : 100 ,
605- },
606- )
607-
608- # Set up messages ending with assistant containing ONLY tool_use (no text)
609- runner ._params ["messages" ] = [
610- {"role" : "user" , "content" : "What is 2+2?" },
611- {
612- "role" : "assistant" ,
613- "content" : [
614- {
615- "type" : "tool_use" ,
616- "id" : "toolu_test123" ,
617- "name" : "calculator" ,
618- "input" : {"a" : 2 , "b" : 2 }
619- }
620- ]
621- },
622- ]
623-
624- # Mock _get_last_message to return high token usage to trigger compaction
625- mock_message = MagicMock ()
626- mock_message .usage .input_tokens = 500
627- mock_message .usage .output_tokens = 100
628- mock_message .usage .cache_creation_input_tokens = 0
629- mock_message .usage .cache_read_input_tokens = 0
630- runner ._get_last_message = AsyncMock (return_value = mock_message )
631-
632- # Mock the API call for compaction summary
633- mock_response = MagicMock ()
634- mock_response .content = [MagicMock (type = "text" , text = "Summary of conversation" )]
635- mock_response .usage .output_tokens = 50
636- runner ._client .beta .messages .create = AsyncMock (return_value = mock_response )
637-
638- # This should succeed - the tool_use should be filtered out
639- # Before the fix, this would send tool_use without tool_result and fail
640- result = await runner ._check_and_compact ()
641-
642- assert result is True , "Compaction should have run"
643-
644- # Verify the API was called (compaction happened)
645- runner ._client .beta .messages .create .assert_called_once ()
646-
647- # Get the messages that were sent to the API
648- call_kwargs = runner ._client .beta .messages .create .call_args [1 ]
649- sent_messages = call_kwargs ["messages" ]
650-
651- # The tool_use-only assistant message should have been removed
652- # So we should have: [user_message, summary_prompt]
653- assert len (sent_messages ) == 2 , f"Expected 2 messages, got { len (sent_messages )} "
654- assert sent_messages [0 ]["role" ] == "user"
655- assert sent_messages [1 ]["role" ] == "user" # Summary prompt is a user message
656-
657-
658584def _get_weather (location : str , units : Literal ["c" , "f" ]) -> Dict [str , Any ]:
659585 # Simulate a weather API call
660586 print (f"Fetching weather for { location } in { units } " )
0 commit comments