mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
Merge pull request #31884 from BerriAI/litellm_add-claude-sonnet-5-pricing
fix(pricing): rolling model registry update: Bedrock gpt-6-astra, gpt-image-2.5, Cohere rerank 4, Vertex Grok 4.3/4.6/4.20, Gemini 3.5 audio, OpenAI web search fee, xAI Imagine video, Lyria 3.5, Voyage, ChatGPT GPT-5.5/5.6, Bedrock Mantle, Scaleway dates
This commit is contained in:
commit
0e088337a2
18 changed files with 1542 additions and 200 deletions
|
|
@ -4,6 +4,7 @@ Translating between OpenAI's `/chat/completion` format and Amazon's `/converse`
|
|||
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import types
|
||||
from collections.abc import Mapping
|
||||
|
|
@ -293,6 +294,10 @@ class AmazonConverseConfig(BaseConfig):
|
|||
llm_provider="bedrock",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _is_openai_gpt_reasoning_model(model: str) -> bool:
|
||||
return re.search(r"openai\.gpt-\d", model) is not None
|
||||
|
||||
def _is_nova_2_model(self, model: str) -> bool:
|
||||
"""
|
||||
Check if the model is a Nova 2 model that supports reasoningConfig.
|
||||
|
|
@ -423,14 +428,14 @@ class AmazonConverseConfig(BaseConfig):
|
|||
Handle the reasoning_effort parameter based on the model type.
|
||||
|
||||
- GPT-OSS models: passed through unchanged via additionalModelRequestFields.
|
||||
- OpenAI GPT-5.x models: mapped to ``reasoning.effort`` via additionalModelRequestFields.
|
||||
- OpenAI GPT-5.x and GPT-6 models: mapped to ``reasoning.effort`` via additionalModelRequestFields.
|
||||
- Nova 2 models: transformed to reasoningConfig.
|
||||
- Anthropic models: mapped to ``thinking`` (and ``output_config.effort`` on
|
||||
adaptive Claude 4.6 / 4.7).
|
||||
"""
|
||||
if "gpt-oss" in model:
|
||||
optional_params["reasoning_effort"] = reasoning_effort
|
||||
elif "openai.gpt-5" in model:
|
||||
elif self._is_openai_gpt_reasoning_model(model):
|
||||
reasoning: Final[BedrockConverseGptReasoningEffortBlock] = {"effort": reasoning_effort}
|
||||
optional_params["reasoning"] = reasoning
|
||||
elif self._is_nova_2_model(model):
|
||||
|
|
@ -564,7 +569,11 @@ class AmazonConverseConfig(BaseConfig):
|
|||
# only anthropic and mistral support tool choice config. otherwise (E.g. cohere) will fail the call - https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ToolChoice.html
|
||||
supported_params.append("tool_choice")
|
||||
|
||||
if "gpt-oss" in model or "openai.gpt-5" in model or "openai.gpt-5" in base_model:
|
||||
if (
|
||||
"gpt-oss" in model
|
||||
or self._is_openai_gpt_reasoning_model(model)
|
||||
or self._is_openai_gpt_reasoning_model(base_model)
|
||||
):
|
||||
supported_params.append("reasoning_effort")
|
||||
elif self._is_nova_2_model(model):
|
||||
# Nova 2 models support reasoning_effort (transformed to reasoningConfig)
|
||||
|
|
@ -920,7 +929,7 @@ class AmazonConverseConfig(BaseConfig):
|
|||
optional_params["_parallel_tool_use_config"] = {
|
||||
"tool_choice": {"type": "auto", "disable_parallel_tool_use": not value}
|
||||
}
|
||||
if param == "thinking" and "openai.gpt-5" not in model:
|
||||
if param == "thinking" and not self._is_openai_gpt_reasoning_model(model):
|
||||
if (
|
||||
isinstance(value, dict)
|
||||
and value.get("type") == "adaptive"
|
||||
|
|
@ -1805,6 +1814,7 @@ class AmazonConverseConfig(BaseConfig):
|
|||
data=request_data,
|
||||
messages=messages,
|
||||
encoding=encoding,
|
||||
json_mode=json_mode,
|
||||
)
|
||||
|
||||
def _transform_reasoning_content(self, reasoning_content_blocks: list[BedrockConverseReasoningContentBlock]) -> str:
|
||||
|
|
@ -2237,6 +2247,7 @@ class AmazonConverseConfig(BaseConfig):
|
|||
data: dict | str,
|
||||
messages: list,
|
||||
encoding,
|
||||
json_mode: bool | None = None,
|
||||
) -> ModelResponse:
|
||||
## LOGGING
|
||||
if logging_obj is not None:
|
||||
|
|
@ -2247,7 +2258,9 @@ class AmazonConverseConfig(BaseConfig):
|
|||
additional_args={"complete_input_dict": data},
|
||||
)
|
||||
|
||||
json_mode: Final[bool | None] = optional_params.get("json_mode", None)
|
||||
resolved_json_mode: Final[bool | None] = (
|
||||
json_mode if json_mode is not None else optional_params.get("json_mode", None)
|
||||
)
|
||||
## RESPONSE OBJECT
|
||||
try:
|
||||
completion_response: Final = ConverseResponseBlock(**response.json())
|
||||
|
|
@ -2339,7 +2352,7 @@ class AmazonConverseConfig(BaseConfig):
|
|||
chat_completion_message["thinking_blocks"] = self._transform_thinking_blocks(reasoningContentBlocks)
|
||||
chat_completion_message["content"] = content_str
|
||||
filtered_tools: Final = self._filter_json_mode_tools(
|
||||
json_mode=json_mode,
|
||||
json_mode=resolved_json_mode,
|
||||
tools=tools,
|
||||
chat_completion_message=chat_completion_message,
|
||||
)
|
||||
|
|
@ -2363,7 +2376,7 @@ class AmazonConverseConfig(BaseConfig):
|
|||
# When json_mode filtered out all synthetic tool calls the response
|
||||
# is plain content, not a pending tool invocation. Fix finish_reason
|
||||
# so callers (e.g. OpenAI SDK) don't misinterpret it.
|
||||
if json_mode and not filtered_tools and tools:
|
||||
if resolved_json_mode and not filtered_tools and tools:
|
||||
initial_finish_reason = "stop"
|
||||
|
||||
(
|
||||
|
|
|
|||
|
|
@ -340,6 +340,7 @@ class AmazonInvokeConfig(BaseConfig, BaseAWSLLM):
|
|||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
encoding=encoding,
|
||||
json_mode=json_mode,
|
||||
)
|
||||
elif provider == "twelvelabs":
|
||||
return litellm.AmazonTwelveLabsPegasusConfig().transform_response(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -525,6 +525,7 @@ class LiteLLMParamsTypedDict(TypedDict, total=False):
|
|||
input_cost_per_second: float | None
|
||||
output_cost_per_second: float | None
|
||||
output_cost_per_second_480p: ReadOnly[float | None]
|
||||
output_cost_per_second_720p: ReadOnly[float | None]
|
||||
output_cost_per_second_1080p: float | None
|
||||
output_cost_per_second_4k: ReadOnly[float | None]
|
||||
num_retries: int | None
|
||||
|
|
|
|||
|
|
@ -318,6 +318,7 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False):
|
|||
float | None
|
||||
) # video_generation tier: key output_cost_per_second_<resolution> (e.g. 1080p, 720p)
|
||||
output_cost_per_second_480p: ReadOnly[float | None]
|
||||
output_cost_per_second_720p: ReadOnly[float | None]
|
||||
output_cost_per_second_4k: ReadOnly[float | None]
|
||||
ocr_cost_per_page: float | None # for OCR models
|
||||
ocr_cost_per_credit: float | None # for OCR models priced by credit
|
||||
|
|
@ -3522,6 +3523,7 @@ class CustomPricingLiteLLMParams(MirroredPricingParams):
|
|||
output_cost_per_second: float | None = None
|
||||
output_cost_per_second_1080p: float | None = None
|
||||
output_cost_per_second_480p: float | None = None
|
||||
output_cost_per_second_720p: float | None = None
|
||||
output_cost_per_second_4k: float | None = None
|
||||
input_cost_per_pixel: float | None = None
|
||||
output_cost_per_pixel: float | None = None
|
||||
|
|
|
|||
|
|
@ -5913,6 +5913,7 @@ def _get_model_info_helper(
|
|||
output_cost_per_second=_model_info.get("output_cost_per_second", None),
|
||||
output_cost_per_second_1080p=_model_info.get("output_cost_per_second_1080p", None),
|
||||
output_cost_per_second_480p=_model_info.get("output_cost_per_second_480p", None),
|
||||
output_cost_per_second_720p=_model_info.get("output_cost_per_second_720p", None),
|
||||
output_cost_per_second_4k=_model_info.get("output_cost_per_second_4k", None),
|
||||
output_cost_per_video_per_second=_model_info.get("output_cost_per_video_per_second", None),
|
||||
output_cost_per_image=_model_info.get("output_cost_per_image", None),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -478,6 +478,10 @@
|
|||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"output_cost_per_second_720p": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"output_cost_per_token": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import json
|
||||
from collections.abc import Mapping, Sequence
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -11,8 +12,6 @@ from litellm.types.llms.openai import FileSearchTool, ResponsesAPIResponse, WebS
|
|||
from litellm.types.utils import ModelResponse, StandardBuiltInToolsParams
|
||||
|
||||
|
||||
|
||||
|
||||
def test_web_search_cost_low():
|
||||
web_search_options = WebSearchOptions(search_context_size="low")
|
||||
model_info = litellm.get_model_info("gpt-4o-search-preview")
|
||||
|
|
@ -683,12 +682,13 @@ def test_web_search_provider_prefix_fallback_does_not_misprice_non_gemini_model(
|
|||
|
||||
|
||||
def _openai_responses_with_web_search_calls(model, num_calls):
|
||||
from litellm.types.llms.openai import ResponsesAPIResponse
|
||||
from openai.types.responses.response_function_web_search import (
|
||||
ActionSearch,
|
||||
ResponseFunctionWebSearch,
|
||||
)
|
||||
|
||||
from litellm.types.llms.openai import ResponsesAPIResponse
|
||||
|
||||
output = [
|
||||
ResponseFunctionWebSearch(
|
||||
id=f"ws_{i}",
|
||||
|
|
@ -859,11 +859,62 @@ def test_dated_search_preview_entries_carry_search_pricing(local_model_cost_map)
|
|||
custom_llm_provider="openai",
|
||||
standard_built_in_tools_params=None,
|
||||
)
|
||||
assert cost == pytest.approx(0.035), (
|
||||
f"dated search-preview id must bill the $0.035 search fee, got ${cost}"
|
||||
assert cost == pytest.approx(0.025), (
|
||||
f"dated search-preview id must bill the $0.025 search fee, got ${cost}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"web_search_options",
|
||||
[
|
||||
None,
|
||||
WebSearchOptions(search_context_size="low"),
|
||||
WebSearchOptions(search_context_size="medium"),
|
||||
WebSearchOptions(search_context_size="high"),
|
||||
],
|
||||
)
|
||||
def test_gpt_4o_mini_snapshot_bills_web_search_like_its_alias(
|
||||
web_search_options: WebSearchOptions | None, local_model_cost_map: None
|
||||
) -> None:
|
||||
alias_info = litellm.get_model_info("gpt-4o-mini")
|
||||
snapshot_info = litellm.get_model_info("gpt-4o-mini-2024-07-18")
|
||||
|
||||
assert not snapshot_info["supports_web_search"]
|
||||
assert not alias_info["supports_web_search"]
|
||||
|
||||
snapshot_cost = StandardBuiltInToolCostTracking.get_cost_for_web_search(
|
||||
web_search_options=web_search_options, model_info=snapshot_info
|
||||
)
|
||||
alias_cost = StandardBuiltInToolCostTracking.get_cost_for_web_search(
|
||||
web_search_options=web_search_options, model_info=alias_info
|
||||
)
|
||||
|
||||
assert snapshot_cost == alias_cost == 0.025
|
||||
|
||||
|
||||
def test_gpt_4o_mini_web_search_price_matches_in_both_cost_maps():
|
||||
repo_root = Path(__file__).parents[4]
|
||||
cost_maps = tuple(
|
||||
json.loads((repo_root / path).read_text(encoding="utf-8"))
|
||||
for path in (
|
||||
"model_prices_and_context_window.json",
|
||||
"litellm/model_prices_and_context_window_backup.json",
|
||||
)
|
||||
)
|
||||
canonical, backup = cost_maps
|
||||
expected_search_price = {
|
||||
"search_context_size_low": 0.025,
|
||||
"search_context_size_medium": 0.025,
|
||||
"search_context_size_high": 0.025,
|
||||
}
|
||||
for model_name in ("gpt-4o-mini", "gpt-4o-mini-2024-07-18"):
|
||||
canonical_entry = canonical[model_name]
|
||||
backup_entry = backup[model_name]
|
||||
assert canonical_entry["search_context_cost_per_query"] == expected_search_price
|
||||
assert backup_entry["search_context_cost_per_query"] == expected_search_price
|
||||
assert canonical_entry == backup_entry
|
||||
|
||||
|
||||
# Note: File search integration test removed due to complex annotation detection logic
|
||||
# The unit tests in test_azure_assistant_cost_tracking.py provide comprehensive coverage
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import json
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
@ -190,3 +192,45 @@ def test_get_error_class_preserves_provider_headers():
|
|||
assert isinstance(error, BedrockError)
|
||||
assert error.headers == {"x-amzn-RequestId": "req-invoke-500"}
|
||||
assert error.response.headers["x-amzn-requestid"] == "req-invoke-500"
|
||||
|
||||
|
||||
def test_transform_response_hands_json_mode_to_nova():
|
||||
"""The invoke dispatcher forwards its json_mode argument to Nova instead of dropping it."""
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
response_json = {
|
||||
"output": {
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"toolUse": {
|
||||
"toolUseId": "tooluse_nova_json",
|
||||
"name": "json_tool_call",
|
||||
"input": {"city": "Paris", "temperature": 21},
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
"stopReason": "tool_use",
|
||||
"usage": {"inputTokens": 5, "outputTokens": 4, "totalTokens": 9},
|
||||
}
|
||||
raw_response = httpx.Response(200, json=response_json, request=httpx.Request("POST", "https://bedrock"))
|
||||
|
||||
result = AmazonInvokeConfig().transform_response(
|
||||
model="invoke/amazon.nova-lite-v1:0",
|
||||
raw_response=raw_response,
|
||||
model_response=ModelResponse(),
|
||||
logging_obj=MagicMock(),
|
||||
request_data={},
|
||||
messages=[{"role": "user", "content": "weather"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
encoding=None,
|
||||
api_key=None,
|
||||
json_mode=True,
|
||||
)
|
||||
|
||||
assert result.choices[0].message.tool_calls is None
|
||||
assert json.loads(result.choices[0].message.content) == {"city": "Paris", "temperature": 21}
|
||||
|
|
|
|||
|
|
@ -382,6 +382,8 @@ def test_reasoning_with_forced_tool_choice_switches_to_auto():
|
|||
"us.openai.gpt-5.6-sol",
|
||||
"global.openai.gpt-5.6-terra",
|
||||
"bedrock/converse/us.openai.gpt-5.6-luna",
|
||||
"us.openai.gpt-6-astra",
|
||||
"bedrock/converse/global.openai.gpt-6-astra",
|
||||
],
|
||||
)
|
||||
def test_reasoning_effort_maps_to_reasoning_effort_for_openai_gpt5_converse(model, local_model_cost_map):
|
||||
|
|
@ -412,6 +414,7 @@ def test_reasoning_effort_maps_to_reasoning_effort_for_openai_gpt5_converse(mode
|
|||
[
|
||||
"us.openai.gpt-5.6-sol",
|
||||
"bedrock/converse/global.openai.gpt-5.6-luna",
|
||||
"us.openai.gpt-6-astra",
|
||||
],
|
||||
)
|
||||
def test_openai_gpt5_converse_never_forwards_thinking(model, local_model_cost_map):
|
||||
|
|
@ -6727,3 +6730,41 @@ def test_forced_tool_choice_forwarded_on_converse_models_that_support_it(
|
|||
)
|
||||
|
||||
assert result == {"any": {}}
|
||||
|
||||
|
||||
def test_transform_response_honors_json_mode_kwarg_when_optional_params_lack_it():
|
||||
response_json = {
|
||||
"metrics": {"latencyMs": 900},
|
||||
"output": {
|
||||
"message": {
|
||||
"content": [
|
||||
{
|
||||
"toolUse": {
|
||||
"input": {"city": "Paris", "population": 2100000},
|
||||
"name": "json_tool_call",
|
||||
"toolUseId": "tooluse_invoke_nova_json",
|
||||
}
|
||||
}
|
||||
],
|
||||
"role": "assistant",
|
||||
}
|
||||
},
|
||||
"stopReason": "tool_use",
|
||||
"usage": {"inputTokens": 40, "outputTokens": 20, "totalTokens": 60},
|
||||
}
|
||||
raw_response = httpx.Response(200, json=response_json, request=httpx.Request("POST", "https://bedrock.test"))
|
||||
logging_obj = MagicMock()
|
||||
result = AmazonConverseConfig().transform_response(
|
||||
model="bedrock/invoke/us.amazon.nova-micro-v1:0",
|
||||
raw_response=raw_response,
|
||||
model_response=ModelResponse(),
|
||||
logging_obj=logging_obj,
|
||||
request_data={},
|
||||
messages=[],
|
||||
optional_params={"tools": [{"type": "function", "function": {"name": "json_tool_call", "parameters": {}}}]},
|
||||
litellm_params={},
|
||||
encoding=None,
|
||||
json_mode=True,
|
||||
)
|
||||
assert result.choices[0].message.tool_calls is None
|
||||
assert json.loads(result.choices[0].message.content) == {"city": "Paris", "population": 2100000}
|
||||
|
|
|
|||
|
|
@ -10,18 +10,23 @@ from unittest.mock import MagicMock, patch
|
|||
import httpx
|
||||
import pytest
|
||||
|
||||
|
||||
import litellm
|
||||
from litellm.llms.chatgpt.responses.transformation import ChatGPTResponsesAPIConfig
|
||||
from litellm.llms.openai.common_utils import OpenAIError
|
||||
from litellm.main import responses_api_bridge_check
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.utils import LlmProviders
|
||||
from litellm.utils import ProviderConfigManager
|
||||
from litellm.llms.chatgpt.responses.transformation import ChatGPTResponsesAPIConfig
|
||||
|
||||
|
||||
class TestChatGPTResponsesAPITransformation:
|
||||
@pytest.mark.parametrize(
|
||||
"model_name",
|
||||
[
|
||||
"chatgpt/gpt-5.5",
|
||||
"chatgpt/gpt-5.6-luna",
|
||||
"chatgpt/gpt-5.6-sol",
|
||||
"chatgpt/gpt-5.6-terra",
|
||||
"chatgpt/gpt-5.4",
|
||||
"chatgpt/gpt-5.4-pro",
|
||||
"chatgpt/gpt-5.3-chat-latest",
|
||||
|
|
@ -40,6 +45,52 @@ class TestChatGPTResponsesAPITransformation:
|
|||
assert isinstance(config, ChatGPTResponsesAPIConfig)
|
||||
assert config.custom_llm_provider == LlmProviders.CHATGPT
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_name",
|
||||
[
|
||||
"chatgpt/gpt-5.5",
|
||||
"chatgpt/gpt-5.6-luna",
|
||||
"chatgpt/gpt-5.6-sol",
|
||||
"chatgpt/gpt-5.6-terra",
|
||||
],
|
||||
)
|
||||
def test_chatgpt_responses_model_metadata(self, model_name: str, local_model_cost_map: None) -> None:
|
||||
model_info = litellm.get_model_info(model_name)
|
||||
|
||||
assert model_info["litellm_provider"] == "chatgpt"
|
||||
assert model_info["mode"] == "responses"
|
||||
assert model_info["supported_endpoints"] == [
|
||||
"/v1/chat/completions",
|
||||
"/v1/responses",
|
||||
]
|
||||
assert model_info["max_input_tokens"] == 1050000
|
||||
assert model_info["max_output_tokens"] == 128000
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_name",
|
||||
[
|
||||
"gpt-5.5",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-5.6-sol",
|
||||
"gpt-5.6-terra",
|
||||
],
|
||||
)
|
||||
def test_chatgpt_models_bridge_chat_completions_to_responses(
|
||||
self, model_name: str, local_model_cost_map: None
|
||||
) -> None:
|
||||
"""A chat completions request for these models must take the Responses bridge.
|
||||
|
||||
`gpt-5.6-*` also exists as an openai chat model, so an unregistered
|
||||
chatgpt model resolves to mode "chat" here and never reaches the bridge.
|
||||
"""
|
||||
model_info, resolved_model = responses_api_bridge_check(
|
||||
model=model_name,
|
||||
custom_llm_provider="chatgpt",
|
||||
)
|
||||
|
||||
assert model_info["mode"] == "responses"
|
||||
assert resolved_model == model_name
|
||||
|
||||
@patch("litellm.llms.chatgpt.responses.transformation.Authenticator")
|
||||
def test_chatgpt_responses_endpoint_url(self, mock_authenticator_class):
|
||||
mock_auth_instance = MagicMock()
|
||||
|
|
|
|||
|
|
@ -322,8 +322,8 @@ class TestModelCostEntry:
|
|||
entry = json.load(f)["vertex_ai/gemini-3.5-transcribe-preview"]
|
||||
assert entry["mode"] == "audio_transcription"
|
||||
assert entry["litellm_provider"] == "vertex_ai"
|
||||
assert entry["input_cost_per_audio_token"] == pytest.approx(2.5e-06)
|
||||
assert entry["input_cost_per_token"] == pytest.approx(2.5e-06)
|
||||
assert entry["input_cost_per_audio_token"] == pytest.approx(2e-06)
|
||||
assert entry["input_cost_per_token"] == pytest.approx(2e-06)
|
||||
assert entry["output_cost_per_token"] == pytest.approx(1.2e-05)
|
||||
assert entry["supported_endpoints"] == ["/v1/audio/transcriptions"]
|
||||
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ class TestSuggesterRejectsModelsWithoutToolCalling:
|
|||
|
||||
def test_a_model_without_forced_tool_choice_support_remains_eligible(self, local_model_cost_map):
|
||||
supported_params = litellm.get_supported_openai_params(
|
||||
model="amazon.nova-pro-v1:0",
|
||||
model="meta.llama4-scout-17b-instruct-v1:0",
|
||||
custom_llm_provider="bedrock",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@ from litellm.utils import (
|
|||
# Adds the parent directory to the system path
|
||||
|
||||
|
||||
def test_cloudflare_model_info_includes_rpm(local_model_cost_map: None) -> None:
|
||||
assert litellm.get_model_info("cloudflare/@cf/meta/llama-3.1-8b-instruct-fp8")["rpm"] == 300
|
||||
assert litellm.get_model_info("cloudflare/@cf/moonshotai/kimi-k2.6")["rpm"] == 20
|
||||
assert litellm.get_model_info("cloudflare/@cf/openai/whisper-large-v3-turbo")["rpm"] == 720
|
||||
|
||||
|
||||
def test_get_utc_datetime_returns_current_aware_utc_time() -> None:
|
||||
before: Final = datetime.now(timezone.utc)
|
||||
result: Final = litellm.utils.get_utc_datetime()
|
||||
|
|
@ -810,6 +816,7 @@ def validate_model_cost_values(model_data, exceptions=None):
|
|||
"input_cost_per_second",
|
||||
"output_cost_per_second",
|
||||
"output_cost_per_second_480p",
|
||||
"output_cost_per_second_720p",
|
||||
"output_cost_per_second_1080p",
|
||||
"output_cost_per_second_4k",
|
||||
"input_cost_per_query",
|
||||
|
|
@ -1033,6 +1040,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
|
|||
"output_cost_per_pixel": {"type": "number"},
|
||||
"output_cost_per_second": {"type": "number"},
|
||||
"output_cost_per_second_480p": {"type": "number"},
|
||||
"output_cost_per_second_720p": {"type": "number"},
|
||||
"output_cost_per_second_1080p": {"type": "number"},
|
||||
"output_cost_per_second_4k": {"type": "number"},
|
||||
"output_cost_per_token": {"type": "number"},
|
||||
|
|
@ -1405,23 +1413,35 @@ def test_supports_tool_choice_simple_tests():
|
|||
is True
|
||||
)
|
||||
|
||||
assert (
|
||||
litellm.utils.supports_tool_choice(model="us.amazon.nova-micro-v1:0") is False
|
||||
)
|
||||
assert (
|
||||
litellm.utils.supports_tool_choice(model="bedrock/us.amazon.nova-micro-v1:0")
|
||||
is False
|
||||
)
|
||||
assert (
|
||||
litellm.utils.supports_tool_choice(
|
||||
model="us.amazon.nova-micro-v1:0", custom_llm_provider="bedrock_converse"
|
||||
)
|
||||
is False
|
||||
)
|
||||
|
||||
assert litellm.utils.supports_tool_choice(model="perplexity/sonar") is False
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("local_model_cost_map")
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
[
|
||||
"amazon.nova-lite-v1:0",
|
||||
"amazon.nova-micro-v1:0",
|
||||
"amazon.nova-pro-v1:0",
|
||||
"apac.amazon.nova-lite-v1:0",
|
||||
"apac.amazon.nova-micro-v1:0",
|
||||
"apac.amazon.nova-pro-v1:0",
|
||||
"bedrock/us-gov-east-1/amazon.nova-pro-v1:0",
|
||||
"bedrock/us-gov-west-1/amazon.nova-lite-v1:0",
|
||||
"bedrock/us-gov-west-1/amazon.nova-micro-v1:0",
|
||||
"bedrock/us-gov-west-1/amazon.nova-pro-v1:0",
|
||||
"eu.amazon.nova-lite-v1:0",
|
||||
"eu.amazon.nova-micro-v1:0",
|
||||
"eu.amazon.nova-pro-v1:0",
|
||||
"us.amazon.nova-lite-v1:0",
|
||||
"us.amazon.nova-micro-v1:0",
|
||||
"us.amazon.nova-pro-v1:0",
|
||||
],
|
||||
)
|
||||
def test_amazon_nova_v1_understanding_models_support_tool_choice(model: str) -> None:
|
||||
assert litellm.utils.supports_tool_choice(model=model) is True
|
||||
|
||||
|
||||
def test_check_provider_match():
|
||||
"""
|
||||
Test the _check_provider_match function for various provider scenarios
|
||||
|
|
|
|||
|
|
@ -532,6 +532,32 @@ class TestVideoGeneration:
|
|||
assert abs(cost_for("runwayml/seedance2_5", "480p", 8.0) - 1.6) < 0.001
|
||||
assert abs(cost_for("runwayml/gen4.5", None, 8.0) - 0.96) < 0.001
|
||||
|
||||
def test_completion_cost_xai_imagine_video_720p_tier_from_cost_map(self, monkeypatch):
|
||||
"""720p xAI Imagine Video requests bill the published 720p rate, not the 480p base rate."""
|
||||
from litellm.cost_calculator import completion_cost
|
||||
|
||||
local_map_path = os.path.join(
|
||||
os.path.dirname(__file__), "..", "..", "model_prices_and_context_window.json"
|
||||
)
|
||||
with open(local_map_path, "r") as f:
|
||||
monkeypatch.setattr(litellm, "model_cost", json.load(f))
|
||||
|
||||
def cost_for(model: str, resolution: str, duration: float) -> float:
|
||||
mock_response = MagicMock()
|
||||
mock_response.usage = {"duration_seconds": duration, "video_resolution": resolution}
|
||||
type(mock_response)._hidden_params = {}
|
||||
return completion_cost(
|
||||
completion_response=mock_response,
|
||||
model=model,
|
||||
call_type="create_video",
|
||||
custom_llm_provider="xai",
|
||||
)
|
||||
|
||||
assert abs(cost_for("xai/grok-imagine-video", "720p", 10.0) - 0.7) < 0.001
|
||||
assert abs(cost_for("xai/grok-imagine-video-1.5", "720p", 10.0) - 1.4) < 0.001
|
||||
assert abs(cost_for("xai/grok-imagine-video-1.5", "480p", 10.0) - 0.8) < 0.001
|
||||
assert abs(cost_for("xai/grok-imagine-video-1.5", "1080p", 10.0) - 2.5) < 0.001
|
||||
|
||||
def test_completion_cost_veo_31_tiers_pin_published_rates(self, monkeypatch):
|
||||
"""The gemini and vertex_ai veo 3.1 entries bill Google's published per-second tier rates."""
|
||||
from litellm.cost_calculator import completion_cost
|
||||
|
|
|
|||
4
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
4
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -29741,6 +29741,8 @@ export interface components {
|
|||
output_cost_per_second_480p?: number | null;
|
||||
/** Output Cost Per Second 4K */
|
||||
output_cost_per_second_4k?: number | null;
|
||||
/** Output Cost Per Second 720P */
|
||||
output_cost_per_second_720p?: number | null;
|
||||
/** Output Cost Per Token */
|
||||
output_cost_per_token?: number | null;
|
||||
/** Output Cost Per Token Above 128K Tokens */
|
||||
|
|
@ -39932,6 +39934,8 @@ export interface components {
|
|||
output_cost_per_second_480p?: number | null;
|
||||
/** Output Cost Per Second 4K */
|
||||
output_cost_per_second_4k?: number | null;
|
||||
/** Output Cost Per Second 720P */
|
||||
output_cost_per_second_720p?: number | null;
|
||||
/** Output Cost Per Token */
|
||||
output_cost_per_token?: number | null;
|
||||
/** Output Cost Per Token Above 128K Tokens */
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ ai21.jamba-instruct-v1:0
|
|||
twelvelabs.pegasus-1-2-v1:0
|
||||
us.twelvelabs.pegasus-1-2-v1:0
|
||||
eu.twelvelabs.pegasus-1-2-v1:0
|
||||
global.twelvelabs.pegasus-1-2-v1:0
|
||||
amazon.titan-text-express-v1
|
||||
amazon.titan-text-lite-v1
|
||||
amazon.titan-text-premier-v1:0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue