@@ -84,10 +84,6 @@ def _run_patch_behaviour_tests(self):
8484 self ._test_unpatched_botocore_propagator ()
8585 self ._test_unpatched_gevent_instrumentation ()
8686 self ._test_unpatched_starlette_instrumentation ()
87- # TODO: remove these tests once we bump botocore instrumentation version to 0.56b0
88- # Bedrock Runtime tests
89- self ._test_unpatched_converse_stream_wrapper ()
90- self ._test_unpatched_extract_tool_calls ()
9187
9288 # Apply patches
9389 apply_instrumentation_patches ()
@@ -178,6 +174,16 @@ def _test_unpatched_botocore_instrumentation(self):
178174 # DynamoDB
179175 self .assertTrue ("dynamodb" in _KNOWN_EXTENSIONS , "Upstream has removed a DynamoDB extension" )
180176
177+ # Bedrock Runtime tests
178+ # TODO: remove these tests once we bump botocore instrumentation version to 0.56b0
179+ self ._test_unpatched_converse_stream_wrapper ()
180+ self ._test_unpatched_extract_tool_calls ()
181+
182+ # TODO: remove these tests once we bump botocore instrumentation version to 0.60b0
183+ self ._test_unpatched_process_anthropic_claude_chunk ({"location" : "Seattle" }, {"location" : "Seattle" })
184+ self ._test_unpatched_process_anthropic_claude_chunk (None , None )
185+ self ._test_unpatched_process_anthropic_claude_chunk ({}, {})
186+
181187 def _test_unpatched_gevent_instrumentation (self ):
182188 self .assertFalse (gevent .monkey .is_module_patched ("os" ), "gevent os module has been patched" )
183189 self .assertFalse (gevent .monkey .is_module_patched ("thread" ), "gevent thread module has been patched" )
@@ -223,10 +229,14 @@ def _test_patched_botocore_instrumentation(self):
223229 # Bedrock Agent Operation
224230 self ._test_patched_bedrock_agent_instrumentation ()
225231
226- # TODO: remove these tests once we bump botocore instrumentation version to 0.56b0
227232 # Bedrock Runtime
233+ # TODO: remove these tests once we bump botocore instrumentation version to 0.56b0
228234 self ._test_patched_converse_stream_wrapper ()
229235 self ._test_patched_extract_tool_calls ()
236+ # TODO: remove these tests once we bump botocore instrumentation version to 0.60b0
237+ self ._test_patched_process_anthropic_claude_chunk ({"location" : "Seattle" }, {"location" : "Seattle" })
238+ self ._test_patched_process_anthropic_claude_chunk (None , None )
239+ self ._test_patched_process_anthropic_claude_chunk ({}, {})
230240
231241 # Bedrock Agent Runtime
232242 self .assertTrue ("bedrock-agent-runtime" in _KNOWN_EXTENSIONS )
@@ -600,6 +610,95 @@ def _test_patched_extract_tool_calls(self):
600610 result = bedrock_utils .extract_tool_calls (message_with_string_content , True )
601611 self .assertIsNone (result )
602612
613+ # Test with toolUse format to exercise the for loop
614+ message_with_tool_use = {"role" : "assistant" , "content" : [{"toolUse" : {"toolUseId" : "id1" , "name" : "func1" }}]}
615+ result = bedrock_utils .extract_tool_calls (message_with_tool_use , True )
616+ self .assertEqual (len (result ), 1 )
617+
618+ # Test with tool_use format to exercise the for loop
619+ message_with_type_tool_use = {
620+ "role" : "assistant" ,
621+ "content" : [{"type" : "tool_use" , "id" : "id2" , "name" : "func2" }],
622+ }
623+ result = bedrock_utils .extract_tool_calls (message_with_type_tool_use , True )
624+ self .assertEqual (len (result ), 1 )
625+
626+ def _test_patched_process_anthropic_claude_chunk (
627+ self , input_value : Dict [str , str ], expected_output : Dict [str , str ]
628+ ):
629+ self ._test_process_anthropic_claude_chunk (input_value , expected_output , False )
630+
631+ def _test_unpatched_process_anthropic_claude_chunk (
632+ self , input_value : Dict [str , str ], expected_output : Dict [str , str ]
633+ ):
634+ self ._test_process_anthropic_claude_chunk (input_value , expected_output , True )
635+
636+ def _test_process_anthropic_claude_chunk (
637+ self , input_value : Dict [str , str ], expected_output : Dict [str , str ], expect_exception : bool
638+ ):
639+ """Test that _process_anthropic_claude_chunk handles various tool_use input formats."""
640+ wrapper = bedrock_utils .InvokeModelWithResponseStreamWrapper (
641+ stream = MagicMock (),
642+ stream_done_callback = MagicMock ,
643+ stream_error_callback = MagicMock ,
644+ model_id = "anthropic.claude-3-5-sonnet-20240620-v1:0" ,
645+ )
646+
647+ # Simulate message_start
648+ wrapper ._process_anthropic_claude_chunk (
649+ {
650+ "type" : "message_start" ,
651+ "message" : {
652+ "role" : "assistant" ,
653+ "content" : [],
654+ },
655+ }
656+ )
657+
658+ # Simulate content_block_start with specified input
659+ content_block = {
660+ "type" : "tool_use" ,
661+ "id" : "test_id" ,
662+ "name" : "test_tool" ,
663+ }
664+ if input_value is not None :
665+ content_block ["input" ] = input_value
666+
667+ wrapper ._process_anthropic_claude_chunk (
668+ {
669+ "type" : "content_block_start" ,
670+ "index" : 0 ,
671+ "content_block" : content_block ,
672+ }
673+ )
674+
675+ # Simulate content_block_stop
676+ try :
677+ wrapper ._process_anthropic_claude_chunk ({"type" : "content_block_stop" , "index" : 0 })
678+ except TypeError :
679+ if expect_exception :
680+ return
681+ else :
682+ raise
683+
684+ # Verify the message content
685+ self .assertEqual (len (wrapper ._message ["content" ]), 1 )
686+ tool_block = wrapper ._message ["content" ][0 ]
687+ self .assertEqual (tool_block ["type" ], "tool_use" )
688+ self .assertEqual (tool_block ["id" ], "test_id" )
689+ self .assertEqual (tool_block ["name" ], "test_tool" )
690+
691+ if expected_output is not None :
692+ self .assertEqual (tool_block ["input" ], expected_output )
693+ self .assertIsInstance (tool_block ["input" ], dict )
694+ else :
695+ self .assertNotIn ("input" , tool_block )
696+
697+ # Just adding this to do basic sanity checks and increase code coverage
698+ wrapper ._process_anthropic_claude_chunk ({"type" : "content_block_delta" , "index" : 0 })
699+ wrapper ._process_anthropic_claude_chunk ({"type" : "message_delta" })
700+ wrapper ._process_anthropic_claude_chunk ({"type" : "message_stop" })
701+
603702 def _test_patched_bedrock_agent_instrumentation (self ):
604703 """For bedrock-agent service, both extract_attributes and on_success provides attributes,
605704 the attributes depend on the API being invoked."""
0 commit comments