mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
Merge pull request #35140 from BerriAI/litellm_fix_file_content_placeholder_cost
fix(cost): stop token-pricing the placeholder input on file content calls
This commit is contained in:
commit
972c0d0b04
2 changed files with 71 additions and 4 deletions
|
|
@ -1399,10 +1399,7 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
litellm_params=(self.litellm_params if hasattr(self, "litellm_params") else None)
|
||||
)
|
||||
|
||||
prompt = "" # use for tts cost calc
|
||||
_input: Final = self.model_call_details.get("input", None)
|
||||
if _input is not None and isinstance(_input, str):
|
||||
prompt = _input
|
||||
prompt = self._prompt_for_cost_calculation()
|
||||
|
||||
if cache_hit is None:
|
||||
cache_hit = self.model_call_details.get("cache_hit", False)
|
||||
|
|
@ -1461,6 +1458,19 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
|
||||
return None
|
||||
|
||||
def _prompt_for_cost_calculation(self) -> str:
|
||||
"""
|
||||
The raw input string is only priced directly for text-to-speech, which bills per character.
|
||||
Every other call type gets its billable units from the response usage object, and call types
|
||||
that carry no usage at all (file content retrieval, and anything else `function_setup` cannot
|
||||
build messages for) only have the ``"default-message-value"`` placeholder here, so passing the
|
||||
input along would token-price that placeholder.
|
||||
"""
|
||||
if self.call_type not in (CallTypes.speech.value, CallTypes.aspeech.value):
|
||||
return ""
|
||||
_input = self.model_call_details.get("input", None)
|
||||
return _input if isinstance(_input, str) else ""
|
||||
|
||||
def _generate_content_result_as_model_response(self, result: object) -> ModelResponse | None:
|
||||
"""
|
||||
Native Google :generateContent bodies report token usage under
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ sys.path.insert(
|
|||
|
||||
import time
|
||||
|
||||
import httpx
|
||||
from openai._legacy_response import HttpxBinaryResponseContent
|
||||
|
||||
import litellm
|
||||
from litellm.constants import SENTRY_DENYLIST, SENTRY_PII_DENYLIST
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
|
|
@ -1771,6 +1774,60 @@ def test_response_cost_calculator_does_not_transform_non_generate_content_dict()
|
|||
assert not cost
|
||||
|
||||
|
||||
def _file_content_logging_obj(call_type: str) -> LitellmLogging:
|
||||
logging_obj = LitellmLogging(
|
||||
model="gemini-3-flash-preview",
|
||||
messages="default-message-value",
|
||||
stream=False,
|
||||
call_type=call_type,
|
||||
start_time=time.time(),
|
||||
litellm_call_id=f"file-content-{call_type}",
|
||||
function_id=f"file-content-{call_type}",
|
||||
)
|
||||
logging_obj.model_call_details["custom_llm_provider"] = "vertex_ai"
|
||||
logging_obj.model_call_details["input"] = "default-message-value"
|
||||
logging_obj.optional_params = {}
|
||||
return logging_obj
|
||||
|
||||
|
||||
@pytest.mark.parametrize("call_type", ["afile_content", "file_content"])
|
||||
def test_file_content_call_is_not_billed(call_type):
|
||||
"""
|
||||
Regression for #35130: file content retrieval has no token usage, but ``function_setup``
|
||||
stores the ``"default-message-value"`` placeholder as the logged input, which the cost
|
||||
calculator then token-priced, billing every call at exactly 3 * input_cost_per_token.
|
||||
"""
|
||||
result = HttpxBinaryResponseContent(httpx.Response(status_code=200, content=b"file contents"))
|
||||
|
||||
cost = _file_content_logging_obj(call_type)._response_cost_calculator(result=result)
|
||||
|
||||
assert cost == 0.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("call_type", ["aspeech", "speech"])
|
||||
def test_speech_call_is_still_priced_from_input_characters(call_type):
|
||||
"""tts bills per input character, so speech call types must keep passing the input along."""
|
||||
logging_obj = LitellmLogging(
|
||||
model="tts-1",
|
||||
messages="the quick brown fox jumped over the lazy dogs",
|
||||
stream=False,
|
||||
call_type=call_type,
|
||||
start_time=time.time(),
|
||||
litellm_call_id=f"speech-{call_type}",
|
||||
function_id=f"speech-{call_type}",
|
||||
)
|
||||
logging_obj.model_call_details["custom_llm_provider"] = "openai"
|
||||
logging_obj.model_call_details["input"] = "the quick brown fox jumped over the lazy dogs"
|
||||
logging_obj.optional_params = {}
|
||||
|
||||
result = HttpxBinaryResponseContent(httpx.Response(status_code=200, content=b"audio bytes"))
|
||||
|
||||
cost = logging_obj._response_cost_calculator(result=result)
|
||||
|
||||
assert cost is not None
|
||||
assert cost > 0
|
||||
|
||||
|
||||
def test_sentry_event_scrubber_initialization(monkeypatch):
|
||||
# Step 1: Create a fake sentry_sdk.scrubber module
|
||||
mock_event_scrubber_instance = MagicMock()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue