From ef1cde433ea7c6dd1515de06c6d0d748fae4a197 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 20 Aug 2026 18:22:14 -0700 Subject: [PATCH 1/6] fix: add moonshot/kimi-k3 to the cost map models.litellm.ai and released litellm versions read model_prices_and_context_window.json from main at runtime, so Kimi K3 is missing from the hosted catalog even though the entry is in review for litellm_internal_staging in #37552. This copies that entry onto main so the catalog picks it up on its next fetch. Data only: the cost map and its backup copy, no code changes. Pricing matches Moonshot's published rates ($3/M input, $0.30/M cache read, $15/M output, 1,048,576-token context). The fireworks_ai and Azure Foundry kimi-k3 variants are separate work in #37512 and #37658; neither touches the native moonshot/kimi-k3 key. --- .../model_prices_and_context_window_backup.json | 17 +++++++++++++++++ model_prices_and_context_window.json | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 07f9027313b..53d069c4a71 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -30025,6 +30025,23 @@ "supports_video_input": true, "supports_vision": true }, + "moonshot/kimi-k3": { + "cache_read_input_token_cost": 3e-07, + "input_cost_per_token": 3e-06, + "litellm_provider": "moonshot", + "max_input_tokens": 1048576, + "max_output_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 1.5e-05, + "source": "https://platform.kimi.ai/docs/pricing/chat-k3", + "supports_function_calling": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_video_input": true, + "supports_vision": true + }, "moonshot/kimi-latest": { "cache_read_input_token_cost": 1.5e-07, "deprecation_date": "2026-01-28", diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 07f9027313b..53d069c4a71 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -30025,6 +30025,23 @@ "supports_video_input": true, "supports_vision": true }, + "moonshot/kimi-k3": { + "cache_read_input_token_cost": 3e-07, + "input_cost_per_token": 3e-06, + "litellm_provider": "moonshot", + "max_input_tokens": 1048576, + "max_output_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 1.5e-05, + "source": "https://platform.kimi.ai/docs/pricing/chat-k3", + "supports_function_calling": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_video_input": true, + "supports_vision": true + }, "moonshot/kimi-latest": { "cache_read_input_token_cost": 1.5e-07, "deprecation_date": "2026-01-28", From 39b23da802fc0264e9b7f818bcebbd480485c223 Mon Sep 17 00:00:00 2001 From: Gyanu Date: Sun, 23 Aug 2026 12:35:00 +0530 Subject: [PATCH 2/6] fix(proxy): settle unpriced success at the reserved cost --- .../proxy/hooks/proxy_track_cost_callback.py | 60 ++++- .../hooks/test_proxy_track_cost_callback.py | 248 +++++++++++++----- 2 files changed, 243 insertions(+), 65 deletions(-) diff --git a/litellm/proxy/hooks/proxy_track_cost_callback.py b/litellm/proxy/hooks/proxy_track_cost_callback.py index 99d0c94d11b..902eefcbf76 100644 --- a/litellm/proxy/hooks/proxy_track_cost_callback.py +++ b/litellm/proxy/hooks/proxy_track_cost_callback.py @@ -318,22 +318,52 @@ class _ProxyDBLogger(CustomLogger): elif budget_reservation is not None: await _release_budget_reservation(budget_reservation=budget_reservation) else: - await _release_budget_reservation(budget_reservation=budget_reservation) # Non-model call types (health checks, afile_delete) have no model or standard_logging_object. # Use .get() for "stream" to avoid KeyError on health checks. # WS session wrappers (_aresponses_websocket, _arealtime) also reach here with # result=None; their per-turn costs are tracked on the inner aresponses/realtime calls. - if sl_object is None and ( + skippable_non_model_call = sl_object is None and ( not kwargs.get("model") or kwargs.get("call_type") in ("_aresponses_websocket", "_arealtime") - ): + ) + completed_call = kwargs.get("stream") is not True or ( + kwargs.get("stream") is True + and ("complete_streaming_response" in kwargs or "async_complete_streaming_response" in kwargs) + ) + if skippable_non_model_call: + await _release_budget_reservation(budget_reservation=budget_reservation) verbose_proxy_logger.warning( "Cost tracking - skipping, no standard_logging_object for call_type=%s", kwargs.get("call_type", "unknown"), ) return - if kwargs.get("stream") is not True or ( - kwargs.get("stream") is True and "complete_streaming_response" in kwargs - ): + if completed_call: + # Releasing to $0 treats the call as free. Leaving the hold + # open is also wrong: the next priced request only + # reconciles its own reservation, so this one would keep + # blocking shared counters until TTL. Settle at the + # admission estimate instead. No spend-log row — there is + # no real cost to write. + reserved_cost = float((budget_reservation or {}).get("reserved_cost") or 0.0) + try: + await _reconcile_budget_reservation( + budget_reservation=budget_reservation, + actual_cost=reserved_cost, + ) + except Exception: # noqa: BLE001 # settle can fail on cache/redis; still raise cost-tracking after invalidating + verbose_proxy_logger.exception( + "Failed to settle budget reservation after unpriced successful call" + ) + try: + await _invalidate_budget_reservation_counters( + budget_reservation=budget_reservation, + ) + except Exception: # noqa: BLE001 # invalidate is best-effort so the outer cost-tracking error still surfaces + verbose_proxy_logger.exception( + "Failed to invalidate budget reservation counters after settle failed" + ) + finally: + if budget_reservation is not None: + budget_reservation["finalized"] = True if sl_object is not None: cost_tracking_failure_debug_info: dict | str = ( sl_object["response_cost_failure_debug_info"] @@ -345,6 +375,7 @@ class _ProxyDBLogger(CustomLogger): raise Exception( f"Cost tracking failed for model={model}.\nDebug info - {cost_tracking_failure_debug_info}\nAdd custom pricing - https://docs.litellm.ai/docs/proxy/custom_pricing" ) + await _release_budget_reservation(budget_reservation=budget_reservation) except Exception as e: error_msg = f"Error in tracking cost callback - {e}\n Traceback:{traceback.format_exc()}" model = kwargs.get("model", "") @@ -600,6 +631,23 @@ async def _release_budget_reservation(budget_reservation: dict | None) -> None: ) +async def _reconcile_budget_reservation( + budget_reservation: dict | None, + actual_cost: float, +) -> None: + if budget_reservation is None: + return + + from litellm.proxy.spend_tracking.budget_reservation import ( + reconcile_budget_reservation, + ) + + await reconcile_budget_reservation( + budget_reservation=budget_reservation, + actual_cost=actual_cost, + ) + + async def _invalidate_budget_reservation_counters( budget_reservation: dict | None, ) -> None: diff --git a/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py b/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py index 871f4b4bcd1..846bacac224 100644 --- a/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py +++ b/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py @@ -1,4 +1,3 @@ - import pytest @@ -70,9 +69,7 @@ async def test_async_post_call_failure_hook(): # Check that metadata was properly updated assert "litellm_params" in call_args["kwargs"] - assert call_args["kwargs"]["litellm_params"]["proxy_server_request"] == { - "request_id": "test_request_id" - } + assert call_args["kwargs"]["litellm_params"]["proxy_server_request"] == {"request_id": "test_request_id"} metadata = call_args["kwargs"]["litellm_params"]["metadata"] assert metadata["user_api_key"] == "test_api_key" assert metadata["status"] == "failure" @@ -336,9 +333,7 @@ async def test_should_continue_failure_tracking_when_budget_release_fails(): ) assert mock_invalidate_budget_reservation_counters.await_count == 1 assert ( - mock_invalidate_budget_reservation_counters.await_args.kwargs[ - "budget_reservation" - ] + mock_invalidate_budget_reservation_counters.await_args.kwargs["budget_reservation"] is user_api_key_dict.budget_reservation ) assert user_api_key_dict.budget_reservation["finalized"] is True @@ -383,7 +378,13 @@ async def test_track_cost_callback_releases_budget_reservation_when_spend_tracki @pytest.mark.asyncio -async def test_track_cost_callback_releases_budget_reservation_when_response_cost_missing(): +async def test_track_cost_callback_settles_budget_reservation_when_response_cost_missing(): + """A successful unpriced model call must not be refunded to $0. + + Settling at the admission estimate converts the hold into budget spend + without inventing a spend-log row. Health checks still release, covered + separately. + """ logger = _ProxyDBLogger() budget_reservation = {"reserved_cost": 0.5, "entries": []} user_api_key_auth = UserAPIKeyAuth(budget_reservation=budget_reservation) @@ -412,6 +413,14 @@ async def test_track_cost_callback_releases_budget_reservation_when_response_cos "litellm.proxy.spend_tracking.budget_reservation.release_budget_reservation", new_callable=AsyncMock, ) as mock_release_budget_reservation, + patch( + "litellm.proxy.spend_tracking.budget_reservation.reconcile_budget_reservation", + new_callable=AsyncMock, + ) as mock_reconcile_budget_reservation, + patch( + "litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter.update_database", + new_callable=AsyncMock, + ) as mock_update_database, ): mock_proxy_logging.failed_tracking_alert = AsyncMock() @@ -422,6 +431,164 @@ async def test_track_cost_callback_releases_budget_reservation_when_response_cos end_time=datetime.now(), ) + mock_release_budget_reservation.assert_not_awaited() + mock_reconcile_budget_reservation.assert_awaited_once_with( + budget_reservation=budget_reservation, + actual_cost=0.5, + ) + mock_update_database.assert_not_called() + mock_proxy_logging.failed_tracking_alert.assert_called() + + +@pytest.mark.asyncio +async def test_track_cost_callback_settles_async_stream_when_response_cost_missing(): + """Async streams record completion on async_complete_streaming_response.""" + logger = _ProxyDBLogger() + budget_reservation = {"reserved_cost": 0.5, "entries": []} + user_api_key_auth = UserAPIKeyAuth(budget_reservation=budget_reservation) + + kwargs = { + "model": "gpt-4", + "call_type": "acompletion", + "stream": True, + "async_complete_streaming_response": {"usage": {"total_tokens": 10}}, + "litellm_params": { + "metadata": { + "user_api_key_auth": user_api_key_auth, + }, + }, + "standard_logging_object": { + "response_cost": None, + "response_cost_failure_debug_info": "missing custom price", + "request_tags": None, + }, + } + + with ( + patch( + "litellm.proxy.proxy_server.proxy_logging_obj", + ) as mock_proxy_logging, + patch( + "litellm.proxy.spend_tracking.budget_reservation.release_budget_reservation", + new_callable=AsyncMock, + ) as mock_release_budget_reservation, + patch( + "litellm.proxy.spend_tracking.budget_reservation.reconcile_budget_reservation", + new_callable=AsyncMock, + ) as mock_reconcile_budget_reservation, + patch( + "litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter.update_database", + new_callable=AsyncMock, + ) as mock_update_database, + ): + mock_proxy_logging.failed_tracking_alert = AsyncMock() + + await logger._PROXY_track_cost_callback( + kwargs=kwargs, + completion_response=None, + start_time=datetime.now(), + end_time=datetime.now(), + ) + + mock_release_budget_reservation.assert_not_awaited() + mock_reconcile_budget_reservation.assert_awaited_once_with( + budget_reservation=budget_reservation, + actual_cost=0.5, + ) + mock_update_database.assert_not_called() + mock_proxy_logging.failed_tracking_alert.assert_called() + + +@pytest.mark.asyncio +async def test_track_cost_callback_invalidates_reservation_when_settle_fails(): + """A failed settle must not leave the hold pinning later traffic until TTL.""" + logger = _ProxyDBLogger() + budget_reservation = {"reserved_cost": 0.5, "entries": []} + user_api_key_auth = UserAPIKeyAuth(budget_reservation=budget_reservation) + + kwargs = { + "model": "gpt-4", + "call_type": "acompletion", + "litellm_params": { + "metadata": { + "user_api_key_auth": user_api_key_auth, + }, + }, + "standard_logging_object": { + "response_cost": None, + "response_cost_failure_debug_info": "missing custom price", + "request_tags": None, + }, + "stream": False, + } + + with ( + patch( + "litellm.proxy.proxy_server.proxy_logging_obj", + ) as mock_proxy_logging, + patch( + "litellm.proxy.spend_tracking.budget_reservation.release_budget_reservation", + new_callable=AsyncMock, + ) as mock_release_budget_reservation, + patch( + "litellm.proxy.spend_tracking.budget_reservation.reconcile_budget_reservation", + new_callable=AsyncMock, + side_effect=RuntimeError("redis down"), + ), + patch( + "litellm.proxy.spend_tracking.budget_reservation.invalidate_budget_reservation_counters", + new_callable=AsyncMock, + ) as mock_invalidate_budget_reservation_counters, + patch( + "litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter.update_database", + new_callable=AsyncMock, + ) as mock_update_database, + ): + mock_proxy_logging.failed_tracking_alert = AsyncMock() + + await logger._PROXY_track_cost_callback( + kwargs=kwargs, + completion_response=None, + start_time=datetime.now(), + end_time=datetime.now(), + ) + + mock_release_budget_reservation.assert_not_awaited() + mock_invalidate_budget_reservation_counters.assert_awaited_once() + settled = mock_invalidate_budget_reservation_counters.await_args.kwargs["budget_reservation"] + assert settled["reserved_cost"] == 0.5 + assert settled["finalized"] is True + mock_update_database.assert_not_called() + mock_proxy_logging.failed_tracking_alert.assert_called() + + +@pytest.mark.asyncio +async def test_track_cost_callback_releases_budget_reservation_for_non_model_calls(): + logger = _ProxyDBLogger() + budget_reservation = {"reserved_cost": 0.5, "entries": []} + user_api_key_auth = UserAPIKeyAuth(budget_reservation=budget_reservation) + + kwargs = { + "call_type": "health", + "litellm_params": { + "metadata": { + "user_api_key_auth": user_api_key_auth, + }, + }, + "stream": False, + } + + with patch( + "litellm.proxy.spend_tracking.budget_reservation.release_budget_reservation", + new_callable=AsyncMock, + ) as mock_release_budget_reservation: + await logger._PROXY_track_cost_callback( + kwargs=kwargs, + completion_response=None, + start_time=datetime.now(), + end_time=datetime.now(), + ) + mock_release_budget_reservation.assert_awaited_once_with( budget_reservation=budget_reservation, ) @@ -433,36 +600,21 @@ def test_get_budget_reservation_from_metadata_handles_dict_auth_object(): "entries": [{"counter_key": "spend:key:test_api_key"}], } + assert _get_budget_reservation_from_metadata(metadata={"user_api_key_auth": dict(UserAPIKeyAuth())}) is None assert ( _get_budget_reservation_from_metadata( - metadata={"user_api_key_auth": dict(UserAPIKeyAuth())} - ) - is None - ) - assert ( - _get_budget_reservation_from_metadata( - metadata={ - "user_api_key_auth": UserAPIKeyAuth( - budget_reservation=budget_reservation - ) - } + metadata={"user_api_key_auth": UserAPIKeyAuth(budget_reservation=budget_reservation)} ) == budget_reservation ) assert ( _get_budget_reservation_from_metadata( - metadata={ - "user_api_key_auth": dict( - UserAPIKeyAuth(budget_reservation=budget_reservation) - ) - } + metadata={"user_api_key_auth": dict(UserAPIKeyAuth(budget_reservation=budget_reservation))} ) == budget_reservation ) assert ( - _get_budget_reservation_from_metadata( - metadata={"user_api_key_budget_reservation": budget_reservation} - ) + _get_budget_reservation_from_metadata(metadata={"user_api_key_budget_reservation": budget_reservation}) is budget_reservation ) @@ -470,9 +622,7 @@ def test_get_budget_reservation_from_metadata_handles_dict_auth_object(): @pytest.mark.asyncio async def test_update_database_and_spend_counters_releases_reservation_when_db_update_fails(): proxy_logging_obj = MagicMock() - proxy_logging_obj.db_spend_update_writer.update_database = AsyncMock( - side_effect=Exception("db unavailable") - ) + proxy_logging_obj.db_spend_update_writer.update_database = AsyncMock(side_effect=Exception("db unavailable")) increment_spend_counters = AsyncMock() budget_reservation = {"reserved_cost": 0.5, "entries": []} @@ -508,9 +658,7 @@ async def test_update_database_and_spend_counters_releases_reservation_when_db_u async def test_update_database_and_spend_counters_preserves_db_exception_when_release_fails(): proxy_logging_obj = MagicMock() db_exception = RuntimeError("db unavailable") - proxy_logging_obj.db_spend_update_writer.update_database = AsyncMock( - side_effect=db_exception - ) + proxy_logging_obj.db_spend_update_writer.update_database = AsyncMock(side_effect=db_exception) increment_spend_counters = AsyncMock() budget_reservation = {"reserved_cost": 0.5, "entries": []} @@ -554,12 +702,8 @@ async def test_update_database_and_spend_counters_preserves_db_exception_when_re budget_reservation=budget_reservation, ) assert mock_log_exception.call_count == 2 - mock_log_exception.assert_any_call( - "Failed to release budget reservation after database update failed" - ) - mock_log_exception.assert_any_call( - "Failed to invalidate budget reservation counters after release failed" - ) + mock_log_exception.assert_any_call("Failed to release budget reservation after database update failed") + mock_log_exception.assert_any_call("Failed to invalidate budget reservation counters after release failed") increment_spend_counters.assert_not_awaited() @@ -785,10 +929,7 @@ async def test_async_post_call_failure_hook_propagates_trace_id_from_logging_obj # standard_logging_object should have been propagated from logging obj assert call_kwargs.get("standard_logging_object") is not None - assert ( - call_kwargs["standard_logging_object"]["trace_id"] - == "trace-id-from-logging-obj" - ) + assert call_kwargs["standard_logging_object"]["trace_id"] == "trace-id-from-logging-obj" # litellm_trace_id should also be propagated as a fallback assert call_kwargs.get("litellm_trace_id") == "trace-id-from-logging-obj" @@ -1375,9 +1516,7 @@ async def test_async_post_call_failure_hook_records_recovered_partial_spend(): "metadata": {}, "proxy_server_request": {"request_id": "rid"}, "response_cost": 3.5e-05, - "combined_usage_object": Usage( - prompt_tokens=30, completion_tokens=1, total_tokens=31 - ), + "combined_usage_object": Usage(prompt_tokens=30, completion_tokens=1, total_tokens=31), } with patch( @@ -1456,15 +1595,10 @@ async def test_track_cost_callback_enriches_user_id_for_mcp_style_metadata(): assert mock_increment.call_args.kwargs["team_id"] == "team-123" assert mock_increment.call_args.kwargs["org_id"] == "org-456" - update_kwargs = ( - mock_proxy_logging.db_spend_update_writer.update_database.await_args.kwargs - ) + update_kwargs = mock_proxy_logging.db_spend_update_writer.update_database.await_args.kwargs assert update_kwargs["user_id"] == "mcp-user@example.com" assert update_kwargs["team_id"] == "team-123" - assert ( - kwargs["litellm_params"]["metadata"]["user_api_key_user_id"] - == "mcp-user@example.com" - ) + assert kwargs["litellm_params"]["metadata"]["user_api_key_user_id"] == "mcp-user@example.com" @pytest.mark.parametrize( @@ -1512,9 +1646,7 @@ def test_should_track_cost_callback_pass_through_without_owner(call_type, expect ], ) @pytest.mark.asyncio -async def test_track_cost_callback_logs_unauthenticated_pass_through_request( - call_type, expect_spend_log -): +async def test_track_cost_callback_logs_unauthenticated_pass_through_request(call_type, expect_spend_log): """Regression for LIT-3782: a pass-through request with auth=false reaches the cost callback with no key/user/team/end-user. Before the fix the spend-log write was skipped and the request never appeared in request/usage logs. It @@ -1560,6 +1692,4 @@ async def test_track_cost_callback_logs_unauthenticated_pass_through_request( end_time=datetime.now(), ) - assert mock_proxy_logging.db_spend_update_writer.update_database.await_count == ( - 1 if expect_spend_log else 0 - ) + assert mock_proxy_logging.db_spend_update_writer.update_database.await_count == (1 if expect_spend_log else 0) From 3122e13e6ca17d304428daba867541db66ee0cc9 Mon Sep 17 00:00:00 2001 From: Gyanu Date: Sun, 23 Aug 2026 14:21:38 +0530 Subject: [PATCH 3/6] fix(proxy): drop new mutable-collection hits in unpriced settle The empty-dict fallback was a LIT002 construction, and the new helper's dict annotation was a LIT001. --- litellm/proxy/hooks/proxy_track_cost_callback.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/hooks/proxy_track_cost_callback.py b/litellm/proxy/hooks/proxy_track_cost_callback.py index 902eefcbf76..e4df5444844 100644 --- a/litellm/proxy/hooks/proxy_track_cost_callback.py +++ b/litellm/proxy/hooks/proxy_track_cost_callback.py @@ -343,7 +343,7 @@ class _ProxyDBLogger(CustomLogger): # blocking shared counters until TTL. Settle at the # admission estimate instead. No spend-log row — there is # no real cost to write. - reserved_cost = float((budget_reservation or {}).get("reserved_cost") or 0.0) + reserved_cost = float(budget_reservation.get("reserved_cost") or 0.0) if budget_reservation else 0.0 try: await _reconcile_budget_reservation( budget_reservation=budget_reservation, @@ -632,7 +632,7 @@ async def _release_budget_reservation(budget_reservation: dict | None) -> None: async def _reconcile_budget_reservation( - budget_reservation: dict | None, + budget_reservation: dict | None, # mutable-ok: same reservation payload _release_budget_reservation takes actual_cost: float, ) -> None: if budget_reservation is None: From 44b378c416cf46d8d9959e370ecad8005273d864 Mon Sep 17 00:00:00 2001 From: Gyanu Date: Sun, 23 Aug 2026 14:41:39 +0530 Subject: [PATCH 4/6] Assert reservation payload in unpriced-settle tests. The new tests only inspected mocks, which the test-quality gate treats as mock-echo. --- .../proxy/hooks/test_proxy_track_cost_callback.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py b/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py index 846bacac224..3b9f88900c0 100644 --- a/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py +++ b/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py @@ -438,6 +438,9 @@ async def test_track_cost_callback_settles_budget_reservation_when_response_cost ) mock_update_database.assert_not_called() mock_proxy_logging.failed_tracking_alert.assert_called() + # The hold is converted, not dropped: later traffic still sees the reserved cost. + assert budget_reservation["reserved_cost"] == 0.5 + assert budget_reservation.get("finalized") is not True @pytest.mark.asyncio @@ -497,6 +500,8 @@ async def test_track_cost_callback_settles_async_stream_when_response_cost_missi ) mock_update_database.assert_not_called() mock_proxy_logging.failed_tracking_alert.assert_called() + assert budget_reservation["reserved_cost"] == 0.5 + assert budget_reservation.get("finalized") is not True @pytest.mark.asyncio @@ -592,6 +597,9 @@ async def test_track_cost_callback_releases_budget_reservation_for_non_model_cal mock_release_budget_reservation.assert_awaited_once_with( budget_reservation=budget_reservation, ) + # Health checks refund the hold; they must not stamp it settled. + assert budget_reservation.get("finalized") is not True + assert budget_reservation["reserved_cost"] == 0.5 def test_get_budget_reservation_from_metadata_handles_dict_auth_object(): From 10906088c8e199415832b2a213bfb86b16b437b3 Mon Sep 17 00:00:00 2001 From: Gyanu Date: Mon, 24 Aug 2026 15:37:23 +0530 Subject: [PATCH 5/6] Retrigger CI against litellm_internal_staging. Co-authored-by: Cursor From f049c2da440a2aa02ad180459629a470f47f3236 Mon Sep 17 00:00:00 2001 From: Gyanu Date: Tue, 25 Aug 2026 22:03:24 +0530 Subject: [PATCH 6/6] Mark unpriced-settle test patches for TQ008 and ratchet TQ002. Co-authored-by: Cursor --- test-quality-budget.json | 2 +- .../hooks/test_proxy_track_cost_callback.py | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/test-quality-budget.json b/test-quality-budget.json index 4a7bc7edff2..0350d813743 100644 --- a/test-quality-budget.json +++ b/test-quality-budget.json @@ -3,7 +3,7 @@ "limit": 744 }, "TQ002": { - "limit": 742 + "limit": 741 }, "TQ003": { "limit": 62 diff --git a/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py b/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py index fa080921a27..96b2bf15bb8 100644 --- a/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py +++ b/tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py @@ -406,18 +406,18 @@ async def test_track_cost_callback_settles_budget_reservation_when_response_cost } with ( - patch( + patch( # test-quality-ok: callback reads proxy_logging_obj from the module; no injection seam "litellm.proxy.proxy_server.proxy_logging_obj", ) as mock_proxy_logging, - patch( + patch( # test-quality-ok: assert the hold is settled, not refunded through release_budget_reservation "litellm.proxy.spend_tracking.budget_reservation.release_budget_reservation", new_callable=AsyncMock, ) as mock_release_budget_reservation, - patch( + patch( # test-quality-ok: settle is a proxy-internal reservation call, not an HTTP boundary "litellm.proxy.spend_tracking.budget_reservation.reconcile_budget_reservation", new_callable=AsyncMock, ) as mock_reconcile_budget_reservation, - patch( + patch( # test-quality-ok: unpriced settle must not write a spend-log row "litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter.update_database", new_callable=AsyncMock, ) as mock_update_database, @@ -468,18 +468,18 @@ async def test_track_cost_callback_settles_async_stream_when_response_cost_missi } with ( - patch( + patch( # test-quality-ok: callback reads proxy_logging_obj from the module; no injection seam "litellm.proxy.proxy_server.proxy_logging_obj", ) as mock_proxy_logging, - patch( + patch( # test-quality-ok: async-complete streams must settle, not release "litellm.proxy.spend_tracking.budget_reservation.release_budget_reservation", new_callable=AsyncMock, ) as mock_release_budget_reservation, - patch( + patch( # test-quality-ok: settle is a proxy-internal reservation call, not an HTTP boundary "litellm.proxy.spend_tracking.budget_reservation.reconcile_budget_reservation", new_callable=AsyncMock, ) as mock_reconcile_budget_reservation, - patch( + patch( # test-quality-ok: unpriced settle must not write a spend-log row "litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter.update_database", new_callable=AsyncMock, ) as mock_update_database, @@ -528,23 +528,23 @@ async def test_track_cost_callback_invalidates_reservation_when_settle_fails(): } with ( - patch( + patch( # test-quality-ok: callback reads proxy_logging_obj from the module; no injection seam "litellm.proxy.proxy_server.proxy_logging_obj", ) as mock_proxy_logging, - patch( + patch( # test-quality-ok: a failed settle must not refund through release_budget_reservation "litellm.proxy.spend_tracking.budget_reservation.release_budget_reservation", new_callable=AsyncMock, ) as mock_release_budget_reservation, - patch( + patch( # test-quality-ok: force reconcile to fail so the invalidate path can be observed "litellm.proxy.spend_tracking.budget_reservation.reconcile_budget_reservation", new_callable=AsyncMock, side_effect=RuntimeError("redis down"), ), - patch( + patch( # test-quality-ok: invalidate is the only way to unpin counters after settle fails "litellm.proxy.spend_tracking.budget_reservation.invalidate_budget_reservation_counters", new_callable=AsyncMock, ) as mock_invalidate_budget_reservation_counters, - patch( + patch( # test-quality-ok: failed settle must not write a spend-log row "litellm.proxy.db.db_spend_update_writer.DBSpendUpdateWriter.update_database", new_callable=AsyncMock, ) as mock_update_database, @@ -583,7 +583,7 @@ async def test_track_cost_callback_releases_budget_reservation_for_non_model_cal "stream": False, } - with patch( + with patch( # test-quality-ok: health checks have no cost row; release is the observable contract "litellm.proxy.spend_tracking.budget_reservation.release_budget_reservation", new_callable=AsyncMock, ) as mock_release_budget_reservation: