@@ -2534,3 +2534,141 @@ def test_get_standard_logging_object_payload_includes_litellm_call_id(logging_ob
25342534
25352535 assert payload is not None
25362536 assert payload ["litellm_call_id" ] == call_id
2537+
2538+
2539+ def _make_dict_logging_obj ():
2540+ """Build a Logging instance configured for a non-streaming dict result."""
2541+ obj = LitellmLogging (
2542+ model = "claude-haiku-4-5@20251001" ,
2543+ messages = [{"role" : "user" , "content" : "hi" }],
2544+ stream = False ,
2545+ call_type = "acompletion" ,
2546+ litellm_call_id = "test-call-id" ,
2547+ start_time = time .time (),
2548+ function_id = "test-fn" ,
2549+ )
2550+ obj .model_call_details = {
2551+ "model" : "claude-haiku-4-5@20251001" ,
2552+ "custom_llm_provider" : "vertex_ai" ,
2553+ "litellm_params" : {"metadata" : {}},
2554+ "response_cost" : None ,
2555+ }
2556+ return obj
2557+
2558+
2559+ def test_success_handler_computes_cost_for_dict_response ():
2560+ """Non-streaming dict responses run through the cost calculator."""
2561+ logging_obj = _make_dict_logging_obj ()
2562+ expected_cost = 0.42
2563+ with (
2564+ patch .object (
2565+ logging_obj ,
2566+ "_response_cost_calculator" ,
2567+ return_value = expected_cost ,
2568+ ) as mock_calc ,
2569+ patch .object (
2570+ logging_obj ,
2571+ "_build_standard_logging_payload" ,
2572+ return_value = {"response_cost" : expected_cost },
2573+ ),
2574+ patch (
2575+ "litellm.litellm_core_utils.litellm_logging.emit_standard_logging_payload"
2576+ ),
2577+ patch .object (
2578+ logging_obj ,
2579+ "_is_recognized_call_type_for_logging" ,
2580+ return_value = False ,
2581+ ),
2582+ patch .object (
2583+ logging_obj ,
2584+ "_transform_usage_objects" ,
2585+ side_effect = lambda result : result ,
2586+ ),
2587+ ):
2588+ logging_obj .success_handler (
2589+ result = {"id" : "msg_1" },
2590+ start_time = time .time (),
2591+ end_time = time .time (),
2592+ )
2593+ mock_calc .assert_called_once ()
2594+ assert logging_obj .model_call_details ["response_cost" ] == expected_cost
2595+
2596+
2597+ def test_success_handler_preserves_precomputed_cost_for_dict_response ():
2598+ """Precomputed response_cost on model_call_details must not be overwritten."""
2599+ logging_obj = _make_dict_logging_obj ()
2600+ precomputed_cost = 1.23
2601+ logging_obj .model_call_details ["response_cost" ] = precomputed_cost
2602+ with (
2603+ patch .object (
2604+ logging_obj ,
2605+ "_response_cost_calculator" ,
2606+ return_value = 9.99 ,
2607+ ) as mock_calc ,
2608+ patch .object (
2609+ logging_obj ,
2610+ "_build_standard_logging_payload" ,
2611+ return_value = {"response_cost" : precomputed_cost },
2612+ ),
2613+ patch (
2614+ "litellm.litellm_core_utils.litellm_logging.emit_standard_logging_payload"
2615+ ),
2616+ patch .object (
2617+ logging_obj ,
2618+ "_is_recognized_call_type_for_logging" ,
2619+ return_value = False ,
2620+ ),
2621+ patch .object (
2622+ logging_obj ,
2623+ "_transform_usage_objects" ,
2624+ side_effect = lambda result : result ,
2625+ ),
2626+ ):
2627+ logging_obj .success_handler (
2628+ result = {"id" : "msg_2" },
2629+ start_time = time .time (),
2630+ end_time = time .time (),
2631+ )
2632+ mock_calc .assert_not_called ()
2633+ assert logging_obj .model_call_details ["response_cost" ] == precomputed_cost
2634+
2635+
2636+ def test_success_handler_unified_helper_runs_for_typed_results ():
2637+ """Recognized typed responses still flow through the unified helper."""
2638+ logging_obj = _make_dict_logging_obj ()
2639+ expected_cost = 0.10
2640+ typed_result = MagicMock ()
2641+ typed_result ._hidden_params = {}
2642+
2643+ with (
2644+ patch .object (
2645+ logging_obj ,
2646+ "_response_cost_calculator" ,
2647+ return_value = expected_cost ,
2648+ ) as mock_calc ,
2649+ patch .object (
2650+ logging_obj ,
2651+ "_build_standard_logging_payload" ,
2652+ return_value = {"response_cost" : expected_cost },
2653+ ),
2654+ patch (
2655+ "litellm.litellm_core_utils.litellm_logging.emit_standard_logging_payload"
2656+ ),
2657+ patch .object (
2658+ logging_obj ,
2659+ "_is_recognized_call_type_for_logging" ,
2660+ return_value = True ,
2661+ ),
2662+ patch .object (
2663+ logging_obj ,
2664+ "_transform_usage_objects" ,
2665+ side_effect = lambda result : result ,
2666+ ),
2667+ ):
2668+ logging_obj .success_handler (
2669+ result = typed_result ,
2670+ start_time = time .time (),
2671+ end_time = time .time (),
2672+ )
2673+ mock_calc .assert_called_once ()
2674+ assert logging_obj .model_call_details ["response_cost" ] == expected_cost
0 commit comments