@@ -614,4 +614,161 @@ def test_store_batch_managed_object_success(
614614 )
615615
616616 # Verify managed files hook was called
617- mock_proxy_logging_obj .get_proxy_hook .assert_called_once_with ("managed_files" )
617+ mock_proxy_logging_obj .get_proxy_hook .assert_called_once_with ("managed_files" )
618+
619+
620+ class TestAnthropicStreamingMetadataPreservation :
621+ """Test that metadata (user_api_key_hash, team_id, etc.) is preserved in streaming kwargs."""
622+
623+ def setup_method (self ):
624+ self .start_time = datetime .now ()
625+ self .end_time = datetime .now ()
626+ self .mock_chunks = [
627+ '{"type": "message_start", "message": {"id": "msg_123", "model": "claude-3-sonnet-20240229"}}' ,
628+ '{"type": "content_block_delta", "delta": {"text": "Hello"}}' ,
629+ '{"type": "message_stop"}' ,
630+ ]
631+ self .mock_metadata = {
632+ "user_api_key_hash" : "sk-test-hash-1234" ,
633+ "user_api_key_alias" : "my-test-key" ,
634+ "user_api_key_team_id" : "team-abc" ,
635+ "user_api_key_org_id" : "org-xyz" ,
636+ "user_api_key_user_id" : "user-123" ,
637+ }
638+
639+ def _create_mock_logging_obj_with_metadata (self ):
640+ mock_logging_obj = MagicMock ()
641+ mock_logging_obj .model_call_details = {
642+ "model" : "claude-3-sonnet-20240229" ,
643+ "litellm_params" : {
644+ "metadata" : self .mock_metadata .copy (),
645+ },
646+ "passthrough_logging_payload" : {
647+ "url" : "https://api.anthropic.com/v1/messages" ,
648+ "request_body" : {
649+ "model" : "claude-3-sonnet-20240229" ,
650+ "messages" : [{"role" : "user" , "content" : "Hello" }],
651+ },
652+ },
653+ }
654+ mock_logging_obj .litellm_call_id = "test-call-id"
655+ return mock_logging_obj
656+
657+ @patch .object (
658+ AnthropicPassthroughLoggingHandler , "_build_complete_streaming_response"
659+ )
660+ @patch .object (
661+ AnthropicPassthroughLoggingHandler ,
662+ "_create_anthropic_response_logging_payload" ,
663+ )
664+ def test_streaming_handler_passes_litellm_params_in_kwargs (
665+ self , mock_create_payload , mock_build_response
666+ ):
667+ """Verify that litellm_params with metadata is passed to _create_anthropic_response_logging_payload."""
668+ mock_build_response .return_value = MagicMock ()
669+ mock_create_payload .return_value = {
670+ "response_cost" : 0.001 ,
671+ "model" : "claude-3-sonnet-20240229" ,
672+ "litellm_params" : {"metadata" : self .mock_metadata .copy ()},
673+ }
674+ logging_obj = self ._create_mock_logging_obj_with_metadata ()
675+
676+ result = AnthropicPassthroughLoggingHandler ._handle_logging_anthropic_collected_chunks (
677+ litellm_logging_obj = logging_obj ,
678+ passthrough_success_handler_obj = MagicMock (),
679+ url_route = "/anthropic/v1/messages" ,
680+ request_body = {"model" : "claude-3-sonnet-20240229" },
681+ endpoint_type = "messages" ,
682+ start_time = self .start_time ,
683+ all_chunks = self .mock_chunks ,
684+ end_time = self .end_time ,
685+ )
686+
687+ # Verify _create_anthropic_response_logging_payload was called with kwargs containing litellm_params
688+ mock_create_payload .assert_called_once ()
689+ call_kwargs = mock_create_payload .call_args [1 ]["kwargs" ]
690+ assert "litellm_params" in call_kwargs
691+ assert "metadata" in call_kwargs ["litellm_params" ]
692+ assert (
693+ call_kwargs ["litellm_params" ]["metadata" ]["user_api_key_hash" ]
694+ == "sk-test-hash-1234"
695+ )
696+ assert (
697+ call_kwargs ["litellm_params" ]["metadata" ]["user_api_key_alias" ]
698+ == "my-test-key"
699+ )
700+ assert (
701+ call_kwargs ["litellm_params" ]["metadata" ]["user_api_key_team_id" ]
702+ == "team-abc"
703+ )
704+
705+ @patch .object (
706+ AnthropicPassthroughLoggingHandler , "_build_complete_streaming_response"
707+ )
708+ @patch .object (
709+ AnthropicPassthroughLoggingHandler ,
710+ "_create_anthropic_response_logging_payload" ,
711+ )
712+ def test_streaming_handler_passes_passthrough_logging_payload (
713+ self , mock_create_payload , mock_build_response
714+ ):
715+ """Verify that passthrough_logging_payload is included in kwargs when present."""
716+ mock_build_response .return_value = MagicMock ()
717+ mock_create_payload .return_value = {"response_cost" : 0.001 }
718+ logging_obj = self ._create_mock_logging_obj_with_metadata ()
719+
720+ AnthropicPassthroughLoggingHandler ._handle_logging_anthropic_collected_chunks (
721+ litellm_logging_obj = logging_obj ,
722+ passthrough_success_handler_obj = MagicMock (),
723+ url_route = "/anthropic/v1/messages" ,
724+ request_body = {"model" : "claude-3-sonnet-20240229" },
725+ endpoint_type = "messages" ,
726+ start_time = self .start_time ,
727+ all_chunks = self .mock_chunks ,
728+ end_time = self .end_time ,
729+ )
730+
731+ call_kwargs = mock_create_payload .call_args [1 ]["kwargs" ]
732+ assert "passthrough_logging_payload" in call_kwargs
733+ assert (
734+ call_kwargs ["passthrough_logging_payload" ]["url" ]
735+ == "https://api.anthropic.com/v1/messages"
736+ )
737+
738+ @patch .object (
739+ AnthropicPassthroughLoggingHandler , "_build_complete_streaming_response"
740+ )
741+ @patch .object (
742+ AnthropicPassthroughLoggingHandler ,
743+ "_create_anthropic_response_logging_payload" ,
744+ )
745+ def test_streaming_handler_without_passthrough_logging_payload (
746+ self , mock_create_payload , mock_build_response
747+ ):
748+ """Verify kwargs still contain litellm_params even when passthrough_logging_payload is absent."""
749+ mock_build_response .return_value = MagicMock ()
750+ mock_create_payload .return_value = {"response_cost" : 0.001 }
751+
752+ logging_obj = MagicMock ()
753+ logging_obj .model_call_details = {
754+ "model" : "claude-3-sonnet-20240229" ,
755+ "litellm_params" : {
756+ "metadata" : self .mock_metadata .copy (),
757+ },
758+ }
759+ logging_obj .litellm_call_id = "test-call-id"
760+
761+ AnthropicPassthroughLoggingHandler ._handle_logging_anthropic_collected_chunks (
762+ litellm_logging_obj = logging_obj ,
763+ passthrough_success_handler_obj = MagicMock (),
764+ url_route = "/anthropic/v1/messages" ,
765+ request_body = {"model" : "claude-3-sonnet-20240229" },
766+ endpoint_type = "messages" ,
767+ start_time = self .start_time ,
768+ all_chunks = self .mock_chunks ,
769+ end_time = self .end_time ,
770+ )
771+
772+ call_kwargs = mock_create_payload .call_args [1 ]["kwargs" ]
773+ assert "litellm_params" in call_kwargs
774+ assert "passthrough_logging_payload" not in call_kwargs
0 commit comments