From 26addc5b39c7729829a42acb73a8b6485d96da8a Mon Sep 17 00:00:00 2001 From: kerry Date: Thu, 17 Sep 2026 22:17:27 +0000 Subject: [PATCH 1/3] test: fix remaining cost-map pin and leaked logging event races Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../test_litellm_logging.py | 19 ++++++++++++++----- .../common_utils/test_prompt_cache_pricing.py | 6 +++--- tests/test_litellm/proxy/test_proxy_utils.py | 5 +++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index 123dc5e8bd9..f226d30fc27 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -1218,17 +1218,25 @@ async def test_async_success_handler_truncates_large_base64_off_the_event_loop(m original_scan = logging_utils._truncate_base64_in_string def recording_scan(value: str) -> str: - scan_threads.append(threading.get_ident()) + if payload in value: + scan_threads.append(threading.get_ident()) return original_scan(value) monkeypatch.setattr(logging_utils, "_truncate_base64_in_string", recording_scan) monkeypatch.setattr(logging_utils, "BASE64_TRUNCATION_OFFLOAD_THRESHOLD_CHARS", 1_000) + import json + logged = asyncio.Event() captured: dict = {} class CaptureLogger(CustomLogger): async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + logged_messages: Final = json.dumps( + kwargs.get("standard_logging_object", {}).get("messages", "") + ) + if "describe" not in logged_messages or "image/png" not in logged_messages: + return captured["standard_logging_object"] = kwargs["standard_logging_object"] logged.set() @@ -1249,9 +1257,9 @@ async def test_async_success_handler_truncates_large_base64_off_the_event_loop(m ) await asyncio.wait_for(logged.wait(), timeout=10) - logged_url = captured["standard_logging_object"]["messages"][0]["content"][1]["image_url"]["url"] - assert "base64_data truncated" in logged_url - assert payload not in logged_url + serialized: Final = json.dumps(captured["standard_logging_object"]["messages"]) + assert "base64_data truncated" in serialized + assert payload not in serialized assert scan_threads assert loop_thread not in scan_threads @@ -3190,7 +3198,8 @@ async def test_non_streaming_computes_standard_logging_object_once(): mock_response="Hello, world!", ) await asyncio.sleep(1) - assert mock_payload.call_count == 1 + own_calls: Final = [call for call in mock_payload.call_args_list if "codex-mini-latest" in str(call)] + assert len(own_calls) == 1 @pytest.mark.asyncio diff --git a/tests/test_litellm/proxy/common_utils/test_prompt_cache_pricing.py b/tests/test_litellm/proxy/common_utils/test_prompt_cache_pricing.py index b6bffaf79af..8736a3fed93 100644 --- a/tests/test_litellm/proxy/common_utils/test_prompt_cache_pricing.py +++ b/tests/test_litellm/proxy/common_utils/test_prompt_cache_pricing.py @@ -11,8 +11,8 @@ from litellm.types.management_endpoints.prompt_cache_prediction import CacheToke def _tiered_rate(entry: Mapping[str, float], field: str, total: int) -> float: above_field: Final = f"{field}_above_200k_tokens" if total > 200_000 and above_field in entry: - return entry[above_field] - return entry[field] + return entry.get(above_field) or 0.0 + return entry.get(field) or 0.0 def _expected_cache_cost(model: str, tokens: CacheTokenBuckets) -> float: @@ -28,7 +28,7 @@ def _expected_cache_cost(model: str, tokens: CacheTokenBuckets) -> float: tokens.uncached_input_tokens * _tiered_rate(entry, "input_cost_per_token", total) + tokens.cache_read_input_tokens * _tiered_rate(entry, "cache_read_input_token_cost", total) + tokens.cache_creation_5m_input_tokens * _tiered_rate(entry, "cache_creation_input_token_cost", total) - + tokens.cache_creation_1h_input_tokens * entry[one_hour_field] + + tokens.cache_creation_1h_input_tokens * (entry.get(one_hour_field) or 0.0) ) diff --git a/tests/test_litellm/proxy/test_proxy_utils.py b/tests/test_litellm/proxy/test_proxy_utils.py index 9f3d03ce515..bfb6b0e4239 100644 --- a/tests/test_litellm/proxy/test_proxy_utils.py +++ b/tests/test_litellm/proxy/test_proxy_utils.py @@ -2239,8 +2239,9 @@ def test_create_model_info_response_falls_back_to_alias_for_opaque_deployment_na litellm.model_cost.clear() litellm.model_cost.update(saved_model_cost) - assert response["max_input_tokens"] == 128000 - assert response["max_output_tokens"] == 16384 + entry = litellm.model_cost["gpt-4o"] + assert response["max_input_tokens"] == entry["max_input_tokens"] + assert response["max_output_tokens"] == entry["max_output_tokens"] def test_create_model_info_response_resolves_mode_through_deployment_model(): From e8f098f38e052972901d9376ca702f862e2d46e2 Mon Sep 17 00:00:00 2001 From: kerry Date: Thu, 17 Sep 2026 22:18:18 +0000 Subject: [PATCH 2/3] test: hoist the json import to module scope Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/test_litellm/litellm_core_utils/test_litellm_logging.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index f226d30fc27..ab8db5cb409 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -1,6 +1,7 @@ import asyncio import contextlib import datetime +import json import os import sys from collections.abc import Callable @@ -1225,8 +1226,6 @@ async def test_async_success_handler_truncates_large_base64_off_the_event_loop(m monkeypatch.setattr(logging_utils, "_truncate_base64_in_string", recording_scan) monkeypatch.setattr(logging_utils, "BASE64_TRUNCATION_OFFLOAD_THRESHOLD_CHARS", 1_000) - import json - logged = asyncio.Event() captured: dict = {} From eb2be3758a683ccf8d80fb174df187c9cbb5fa28 Mon Sep 17 00:00:00 2001 From: kerry Date: Thu, 17 Sep 2026 22:22:24 +0000 Subject: [PATCH 3/3] test: read cost expectations from the catalog row the code bills against Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../llms/parallel_ai/test_parallel_ai_search.py | 7 ++++--- tests/test_litellm/test_cost_calculator.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_litellm/llms/parallel_ai/test_parallel_ai_search.py b/tests/test_litellm/llms/parallel_ai/test_parallel_ai_search.py index 51fe5cea4d3..03fda270b6f 100644 --- a/tests/test_litellm/llms/parallel_ai/test_parallel_ai_search.py +++ b/tests/test_litellm/llms/parallel_ai/test_parallel_ai_search.py @@ -465,9 +465,10 @@ class TestParallelAISearch: max_results=max_results, ) - rate: Final = litellm.model_cost[ - "parallel_ai/search-fast" if mode in ("fast", "turbo") else "parallel_ai/search" - ]["input_cost_per_query"] + pricing_model: Final = {"fast": "parallel_ai/search-fast", "turbo": "parallel_ai/search-turbo"}.get( + mode, "parallel_ai/search" + ) + rate: Final = litellm.model_cost[pricing_model]["input_cost_per_query"] request_count: Final = ( sum(item["count"] for item in usage if item["name"] == "sku_search") if usage is not None else 1 ) diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 80f2a9903a5..03e4ef3b2c3 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -1346,7 +1346,7 @@ def test_gemini_25_implicit_caching_cost(): model="gemini/gemini-2.5-flash", ) - model_info: Final = litellm.model_cost["gemini-2.5-flash"] + model_info: Final = litellm.model_cost["gemini/gemini-2.5-flash"] expected_cost = ( 14316 * model_info["cache_read_input_token_cost"] + (15033 - 14316) * model_info["input_cost_per_token"]