diff --git a/litellm/llms/fireworks_ai/cost_calculator.py b/litellm/llms/fireworks_ai/cost_calculator.py index 46026f266d6..17320fe749f 100644 --- a/litellm/llms/fireworks_ai/cost_calculator.py +++ b/litellm/llms/fireworks_ai/cost_calculator.py @@ -77,8 +77,28 @@ def cost_per_token(model: str, usage: Usage) -> Tuple[float, float]: ) ## CALCULATE INPUT COST + input_cost_per_token = model_info["input_cost_per_token"] + prompt_cost: float = usage["prompt_tokens"] * input_cost_per_token - prompt_cost: float = usage["prompt_tokens"] * model_info["input_cost_per_token"] + ## ADJUST FOR CACHE READ TOKENS + cache_read_input_tokens = usage.get("cache_read_input_tokens", None) or 0 + cache_read_cost = model_info.get("cache_read_input_token_cost", None) + if cache_read_input_tokens > 0 and cache_read_cost is not None: + # Cache read tokens were already counted at the full input rate above. + # Subtract the full-price portion and add the discounted cache rate. + prompt_cost += cache_read_input_tokens * ( + cache_read_cost - input_cost_per_token + ) + + ## ADJUST FOR CACHE CREATION TOKENS + cache_creation_input_tokens = ( + usage.get("cache_creation_input_tokens", None) or 0 + ) + cache_creation_cost = model_info.get("cache_creation_input_token_cost", None) + if cache_creation_input_tokens > 0 and cache_creation_cost is not None: + prompt_cost += cache_creation_input_tokens * ( + cache_creation_cost - input_cost_per_token + ) ## CALCULATE OUTPUT COST completion_cost = usage["completion_tokens"] * model_info["output_cost_per_token"] diff --git a/tests/local_testing/test_completion_cost.py b/tests/local_testing/test_completion_cost.py index 618287e1955..c6981b19641 100644 --- a/tests/local_testing/test_completion_cost.py +++ b/tests/local_testing/test_completion_cost.py @@ -1196,6 +1196,54 @@ def test_completion_cost_fireworks_ai(model): cost = completion_cost(completion_response=resp) +def test_fireworks_ai_cache_token_pricing(): + """Test that Fireworks AI cost calculator accounts for cache read/creation tokens. + + Regression test for https://github.com/BerriAI/litellm/issues/24774 + """ + from litellm.llms.fireworks_ai.cost_calculator import cost_per_token + from litellm.types.utils import Usage + + # Simulate usage with cache read tokens + usage_with_cache = Usage( + prompt_tokens=1000, + completion_tokens=500, + cache_read_input_tokens=800, + cache_creation_input_tokens=0, + ) + + # Simulate usage without cache tokens + usage_no_cache = Usage( + prompt_tokens=1000, + completion_tokens=500, + ) + + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + prompt_cost_cached, completion_cost_cached = cost_per_token( + model="fireworks_ai/llama-v3p3-70b-instruct", usage=usage_with_cache + ) + prompt_cost_no_cache, completion_cost_no_cache = cost_per_token( + model="fireworks_ai/llama-v3p3-70b-instruct", usage=usage_no_cache + ) + + # Completion cost should be the same regardless of cache + assert completion_cost_cached == completion_cost_no_cache + + # If the model has cache pricing, the prompt cost with cache should differ + # from the prompt cost without cache (cache read rate is cheaper) + model_info = litellm.get_model_info( + model="fireworks_ai/llama-v3p3-70b-instruct", + custom_llm_provider="fireworks_ai", + ) + if model_info.get("cache_read_input_token_cost") is not None: + assert prompt_cost_cached < prompt_cost_no_cache, ( + "Prompt cost with 800 cache-read tokens should be less than " + "full-price for the same total prompt tokens" + ) + + def test_cost_azure_openai_prompt_caching(): from litellm.utils import Choices, Message, ModelResponse, Usage from litellm.types.utils import (