mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
feat(fal_ai): bill flat per-video fal.ai models with output_cost_per_video (#51225)
* fix(fal_ai): support flat per-video output pricing Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fal_ai): handle flat-priced video catalog rows Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * style(rust): format flat video pricing parser test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
b251f125a7
commit
f298400060
16 changed files with 1009 additions and 19 deletions
|
|
@ -2070,25 +2070,21 @@ def completion_cost(
|
|||
litellm_logging_obj, custom_pricing, router_model_id
|
||||
)
|
||||
|
||||
usage_obj = getattr(completion_response, "usage", None)
|
||||
_video_usage_obj: object = attribute_or_none(completion_response, "usage")
|
||||
duration_seconds: float | None = None
|
||||
video_resolution: str | None = None
|
||||
provider_reported_cost: float | None = None
|
||||
if completion_response is not None and usage_obj:
|
||||
# Handle both dict and Pydantic Usage object
|
||||
if isinstance(usage_obj, dict):
|
||||
duration_seconds = usage_obj.get("duration_seconds", None)
|
||||
_vr = usage_obj.get("video_resolution", None)
|
||||
provider_reported_cost = usage_obj.get("provider_reported_cost_usd", None)
|
||||
_vc = usage_obj.get("video_count", None)
|
||||
else:
|
||||
duration_seconds = getattr(usage_obj, "duration_seconds", None)
|
||||
_vr = getattr(usage_obj, "video_resolution", None)
|
||||
provider_reported_cost = getattr(usage_obj, "provider_reported_cost_usd", None)
|
||||
_vc = getattr(usage_obj, "video_count", None)
|
||||
if _vr is not None:
|
||||
video_resolution = str(_vr).strip().lower()
|
||||
video_count = _vc if isinstance(_vc, int) and not isinstance(_vc, bool) and _vc > 1 else 1
|
||||
if completion_response is not None and _video_usage_obj:
|
||||
duration_seconds = _VIDEO_USAGE_NUMBER.validate_python(
|
||||
_video_usage_value(_video_usage_obj, "duration_seconds")
|
||||
)
|
||||
provider_reported_cost = _VIDEO_USAGE_NUMBER.validate_python(
|
||||
_video_usage_value(_video_usage_obj, "provider_reported_cost_usd")
|
||||
)
|
||||
if (
|
||||
video_resolution_value := _video_usage_value(_video_usage_obj, "video_resolution")
|
||||
) is not None:
|
||||
video_resolution = str(video_resolution_value).strip().lower()
|
||||
|
||||
if _video_model_info is None and provider_reported_cost is not None:
|
||||
return float(provider_reported_cost)
|
||||
|
|
@ -2105,7 +2101,7 @@ def completion_cost(
|
|||
custom_llm_provider=custom_llm_provider,
|
||||
model_info=_video_model_info,
|
||||
video_resolution=video_resolution,
|
||||
) * video_count + _video_input_image_cost(
|
||||
) * _video_count(_video_usage_obj) + _video_input_image_cost(
|
||||
requested_model or model,
|
||||
custom_llm_provider,
|
||||
_video_model_info,
|
||||
|
|
@ -2118,7 +2114,7 @@ def completion_cost(
|
|||
custom_llm_provider=custom_llm_provider,
|
||||
model_info=_video_model_info,
|
||||
video_resolution=video_resolution,
|
||||
)
|
||||
) * _video_count(_video_usage_obj)
|
||||
elif call_type in _SPEECH_CALL_TYPES:
|
||||
prompt_characters = _speech_billed_characters(
|
||||
completion_response if isinstance(completion_response, HttpxBinaryResponseContent) else None,
|
||||
|
|
@ -2631,6 +2627,19 @@ def _speech_billed_characters(speech_response: HttpxBinaryResponseContent | None
|
|||
return reported if isinstance(reported, int) else litellm.utils.count_characters(text=prompt)
|
||||
|
||||
|
||||
_VIDEO_USAGE_NUMBER: Final[TypeAdapter[float | None]] = TypeAdapter(float | None)
|
||||
|
||||
|
||||
def _video_usage_value(usage: object, field: str) -> object:
|
||||
fields: Final = as_str_mapping(usage)
|
||||
return fields.get(field) if fields is not None else attribute_or_none(usage, field)
|
||||
|
||||
|
||||
def _video_count(usage: object) -> int:
|
||||
count: Final = _video_usage_value(usage, "video_count")
|
||||
return count if type(count) is int and count > 1 else 1
|
||||
|
||||
|
||||
def _video_input_image_cost(
|
||||
model: str,
|
||||
custom_llm_provider: str | None,
|
||||
|
|
@ -3184,6 +3193,10 @@ def default_video_cost_calculator(
|
|||
if cost_info is None:
|
||||
raise Exception(f"Model not found in cost map for model={model}")
|
||||
|
||||
video_cost: Final = cost_info.get("output_cost_per_video")
|
||||
if video_cost is not None:
|
||||
return video_cost
|
||||
|
||||
# Check for video-specific cost per second first
|
||||
video_cost_per_second: Final = cost_info.get("output_cost_per_video_per_second")
|
||||
if video_cost_per_second is not None:
|
||||
|
|
|
|||
|
|
@ -189,6 +189,9 @@ def fal_ai_passthrough_cost(model: str, request_body: Mapping[str, object]) -> f
|
|||
entry: Final = _entry(f"{litellm.LlmProviders.FAL_AI.value}/{model}")
|
||||
if entry is None or "output_cost_per_billable_unit" in entry:
|
||||
return None
|
||||
cost_per_video: Final = entry.get("output_cost_per_video")
|
||||
if isinstance(cost_per_video, (int, float)):
|
||||
return float(cost_per_video)
|
||||
cost_per_image: Final = _passthrough_cost_per_image(entry, request_body)
|
||||
if cost_per_image is None:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -162,6 +162,10 @@ def video_generation_cost(
|
|||
if model_info is None:
|
||||
model_info = get_model_info(model=model, custom_llm_provider=custom_llm_provider or "openai")
|
||||
|
||||
video_cost: Final = model_info.get("output_cost_per_video")
|
||||
if video_cost is not None:
|
||||
return video_cost
|
||||
|
||||
# Check for video-specific cost per second
|
||||
video_cost_per_second: Final = model_info.get("output_cost_per_video_per_second")
|
||||
if video_cost_per_second is not None:
|
||||
|
|
|
|||
|
|
@ -95168,6 +95168,438 @@
|
|||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/auto-caption": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.1,
|
||||
"source": "https://fal.ai/models/fal-ai/auto-caption",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/hunyuan-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.4,
|
||||
"source": "https://fal.ai/models/fal-ai/hunyuan-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/hunyuan-video-image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.4,
|
||||
"source": "https://fal.ai/models/fal-ai/hunyuan-video-image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/infinity-star/text-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.07,
|
||||
"source": "https://fal.ai/models/fal-ai/infinity-star/text-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.02,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video-13b-distilled": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.04,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video-13b-distilled",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video-13b-distilled/extend": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.04,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video-13b-distilled/extend",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video-13b-distilled/multiconditioning": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.04,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video-13b-distilled/multiconditioning",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.02,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/hailuo-2.3-fast/pro/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.33,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/hailuo-2.3-fast/pro/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-director": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-director",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-director/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-director/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-live": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-live",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-live/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-live/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-subject-reference": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-subject-reference",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ovi": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.2,
|
||||
"source": "https://fal.ai/models/fal-ai/ovi",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ovi/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.2,
|
||||
"source": "https://fal.ai/models/fal-ai/ovi/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan-i2v-lora": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.75,
|
||||
"source": "https://fal.ai/models/fal-ai/wan-i2v-lora",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan-pro/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.8,
|
||||
"source": "https://fal.ai/models/fal-ai/wan-pro/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan-t2v-lora": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.75,
|
||||
"source": "https://fal.ai/models/fal-ai/wan-t2v-lora",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan/v2.2-5b/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.15,
|
||||
"source": "https://fal.ai/models/fal-ai/wan/v2.2-5b/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan/v2.2-5b/text-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.15,
|
||||
"source": "https://fal.ai/models/fal-ai/wan/v2.2-5b/text-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/moonvalley/marey/motion-transfer": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 2,
|
||||
"source": "https://fal.ai/models/moonvalley/marey/motion-transfer",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/moonvalley/marey/pose-transfer": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 2,
|
||||
"source": "https://fal.ai/models/moonvalley/marey/pose-transfer",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/mureka/api/generate/lyrics-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.1,
|
||||
"source": "https://fal.ai/models/mureka/api/generate/lyrics-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan-22-vace-fun-a14b/inpainting": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"metadata": {"comment":"per second of generated video by resolution; the page's resolution default is auto or unstated, so output_cost_per_second is the highest tier (720p); tiers without a catalog field: 580p $0.075"},
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ pub struct ModelInfoFields {
|
|||
pub output_cost_per_second_768p: Option<f64>,
|
||||
pub output_cost_per_second_2k: Option<f64>,
|
||||
pub output_cost_per_second_4k: Option<f64>,
|
||||
pub output_cost_per_video: Option<f64>,
|
||||
pub output_cost_per_video_per_second: Option<f64>,
|
||||
pub output_cost_per_image: Option<f64>,
|
||||
pub output_cost_per_billable_unit: Option<f64>,
|
||||
|
|
|
|||
|
|
@ -869,6 +869,7 @@ class LiteLLMParamsTypedDict(TypedDict, total=False):
|
|||
cost_per_second: ReadOnly[float | None]
|
||||
input_cost_per_second: float | None
|
||||
input_cost_per_text_message: ReadOnly[float | None]
|
||||
output_cost_per_video: ReadOnly[float | None]
|
||||
output_cost_per_second: float | None
|
||||
output_cost_per_second_480p: ReadOnly[float | None]
|
||||
output_cost_per_second_720p: ReadOnly[float | None]
|
||||
|
|
|
|||
|
|
@ -463,6 +463,7 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False):
|
|||
output_cost_per_reasoning_token: float | None
|
||||
output_cost_per_reasoning_token_flex: float | None
|
||||
output_cost_per_reasoning_token_priority: float | None
|
||||
output_cost_per_video: ReadOnly[float | None]
|
||||
output_cost_per_video_per_second: float | None # only for vertex ai models
|
||||
output_cost_per_audio_per_second: float | None # only for vertex ai models
|
||||
output_cost_per_second: float | None # for OpenAI Speech models
|
||||
|
|
@ -4268,6 +4269,7 @@ class CustomPricingLiteLLMParams(MirroredPricingParams):
|
|||
output_cost_per_reasoning_token: float | None = None
|
||||
output_cost_per_reasoning_token_flex: float | None = None
|
||||
output_cost_per_reasoning_token_priority: float | None = None
|
||||
output_cost_per_video: float | None = None
|
||||
output_cost_per_video_per_second: float | None = None
|
||||
output_cost_per_audio_per_second: float | None = None
|
||||
search_context_cost_per_query: dict[str, Any] | None = None
|
||||
|
|
|
|||
4
litellm/ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
4
litellm/ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -37621,6 +37621,8 @@ export interface components {
|
|||
output_cost_per_token_priority?: number | null;
|
||||
/** Output Cost Per Token Ultrafast */
|
||||
output_cost_per_token_ultrafast?: number | null;
|
||||
/** Output Cost Per Video */
|
||||
output_cost_per_video?: number | null;
|
||||
/** Output Cost Per Video Per Second */
|
||||
output_cost_per_video_per_second?: number | null;
|
||||
/** Output Cost Per Video Token */
|
||||
|
|
@ -54107,6 +54109,8 @@ export interface components {
|
|||
output_cost_per_token_priority?: number | null;
|
||||
/** Output Cost Per Token Ultrafast */
|
||||
output_cost_per_token_ultrafast?: number | null;
|
||||
/** Output Cost Per Video */
|
||||
output_cost_per_video?: number | null;
|
||||
/** Output Cost Per Video Per Second */
|
||||
output_cost_per_video_per_second?: number | null;
|
||||
/** Output Cost Per Video Token */
|
||||
|
|
|
|||
|
|
@ -7485,6 +7485,7 @@ def get_model_info_helper(
|
|||
output_cost_per_second_768p=_model_info.get("output_cost_per_second_768p", None),
|
||||
output_cost_per_second_2k=_model_info.get("output_cost_per_second_2k", None),
|
||||
output_cost_per_second_4k=_model_info.get("output_cost_per_second_4k", None),
|
||||
output_cost_per_video=_model_info.get("output_cost_per_video", 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),
|
||||
output_cost_per_image_1K=_model_info.get("output_cost_per_image_1K", None),
|
||||
|
|
@ -7667,6 +7668,7 @@ def get_model_info(
|
|||
] # only for vertex ai models
|
||||
output_cost_per_image: Optional[float]
|
||||
output_vector_size: Optional[int]
|
||||
output_cost_per_video: Optional[float]
|
||||
output_cost_per_video_per_second: Optional[float] # only for vertex ai models
|
||||
output_cost_per_audio_per_second: Optional[float] # only for vertex ai models
|
||||
litellm_provider: Required[str]
|
||||
|
|
|
|||
|
|
@ -95168,6 +95168,438 @@
|
|||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/auto-caption": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.1,
|
||||
"source": "https://fal.ai/models/fal-ai/auto-caption",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/hunyuan-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.4,
|
||||
"source": "https://fal.ai/models/fal-ai/hunyuan-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/hunyuan-video-image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.4,
|
||||
"source": "https://fal.ai/models/fal-ai/hunyuan-video-image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/infinity-star/text-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.07,
|
||||
"source": "https://fal.ai/models/fal-ai/infinity-star/text-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.02,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video-13b-distilled": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.04,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video-13b-distilled",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video-13b-distilled/extend": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.04,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video-13b-distilled/extend",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video-13b-distilled/multiconditioning": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.04,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video-13b-distilled/multiconditioning",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ltx-video/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.02,
|
||||
"source": "https://fal.ai/models/fal-ai/ltx-video/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/hailuo-2.3-fast/pro/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.33,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/hailuo-2.3-fast/pro/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-director": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-director",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-director/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-director/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-live": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-live",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-live/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-live/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01-subject-reference": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01-subject-reference",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/minimax/video-01/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.5,
|
||||
"source": "https://fal.ai/models/fal-ai/minimax/video-01/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ovi": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.2,
|
||||
"source": "https://fal.ai/models/fal-ai/ovi",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/ovi/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.2,
|
||||
"source": "https://fal.ai/models/fal-ai/ovi/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan-i2v-lora": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.75,
|
||||
"source": "https://fal.ai/models/fal-ai/wan-i2v-lora",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan-pro/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.8,
|
||||
"source": "https://fal.ai/models/fal-ai/wan-pro/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan-t2v-lora": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.75,
|
||||
"source": "https://fal.ai/models/fal-ai/wan-t2v-lora",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan/v2.2-5b/image-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.15,
|
||||
"source": "https://fal.ai/models/fal-ai/wan/v2.2-5b/image-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan/v2.2-5b/text-to-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.15,
|
||||
"source": "https://fal.ai/models/fal-ai/wan/v2.2-5b/text-to-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/moonvalley/marey/motion-transfer": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 2,
|
||||
"source": "https://fal.ai/models/moonvalley/marey/motion-transfer",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/moonvalley/marey/pose-transfer": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 2,
|
||||
"source": "https://fal.ai/models/moonvalley/marey/pose-transfer",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/mureka/api/generate/lyrics-video": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"mode": "video_generation",
|
||||
"output_cost_per_video": 0.1,
|
||||
"source": "https://fal.ai/models/mureka/api/generate/lyrics-video",
|
||||
"supported_endpoints": [
|
||||
"/v1/videos"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"video"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"video"
|
||||
]
|
||||
},
|
||||
"fal_ai/fal-ai/wan-22-vace-fun-a14b/inpainting": {
|
||||
"litellm_provider": "fal_ai",
|
||||
"metadata": {"comment":"per second of generated video by resolution; the page's resolution default is auto or unstated, so output_cost_per_second is the highest tier (720p); tiers without a catalog field: 580p $0.075"},
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ from typing import Final
|
|||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.cost_calculator import default_video_cost_calculator
|
||||
from litellm.litellm_core_utils.core_helpers import set_provider_response_headers_in_hidden_params
|
||||
from litellm.litellm_core_utils.llm_cost_calc.utils import CostCalculatorUtils
|
||||
from litellm.llms.fal_ai.cost_calculator import FAL_BILLABLE_UNITS_HEADER, cost_calculator, fal_ai_passthrough_cost
|
||||
from litellm.llms.openai.cost_calculation import video_generation_cost
|
||||
from litellm.types.utils import ImageObject, ImageResponse
|
||||
|
||||
|
||||
|
|
@ -34,6 +36,20 @@ def _image_response_with_dimensions(dimensions: tuple[tuple[int, int], ...]) ->
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("duration", [0.0, 8.0])
|
||||
def test_catalog_flat_video_price_reaches_both_video_calculators(
|
||||
monkeypatch: pytest.MonkeyPatch, duration: float
|
||||
) -> None:
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost,
|
||||
"fal_ai/fal-ai/test-flat-video",
|
||||
{"litellm_provider": "fal_ai", "mode": "video_generation", "output_cost_per_video": 0.37},
|
||||
)
|
||||
assert video_generation_cost("fal-ai/test-flat-video", duration, "fal_ai") == pytest.approx(0.37)
|
||||
assert default_video_cost_calculator("fal-ai/test-flat-video", duration, "fal_ai") == pytest.approx(0.37)
|
||||
assert fal_ai_passthrough_cost("fal-ai/test-flat-video", {}) == pytest.approx(0.37)
|
||||
|
||||
|
||||
GPT_IMAGE_25_MODELS = (
|
||||
"openai/gpt-image-2.5/flare/text-to-image",
|
||||
"openai/gpt-image-2.5/flare/edit",
|
||||
|
|
|
|||
|
|
@ -722,7 +722,8 @@ class TestFalAIVideoTransformation:
|
|||
assert isinstance(provider_config, FalAIVideoConfig)
|
||||
assert isinstance(self.config.get_error_class("bad key", 401, {}), FalAIVideoError)
|
||||
|
||||
def test_video_cost_uses_tiered_rows(self):
|
||||
@pytest.mark.parametrize(("duration", "resolution"), [(0, None), (5, "720p"), (17, "9999p")])
|
||||
def test_video_cost_uses_tiered_rows(self, duration: float, resolution: str | None) -> None:
|
||||
rows = {
|
||||
model: row
|
||||
for model, row in litellm.model_cost.items()
|
||||
|
|
@ -730,6 +731,9 @@ class TestFalAIVideoTransformation:
|
|||
}
|
||||
assert rows
|
||||
for model, row in rows.items():
|
||||
if (flat_cost := row.get("output_cost_per_video")) is not None:
|
||||
assert default_video_cost_calculator(model, duration, "fal_ai", video_resolution=resolution) == flat_cost
|
||||
continue
|
||||
for key, value in row.items():
|
||||
if key.startswith("output_cost_per_second_") and value is not None:
|
||||
tier = key.removeprefix("output_cost_per_second_")
|
||||
|
|
|
|||
|
|
@ -87,6 +87,22 @@ def test_handler_charges_nothing_and_names_the_model_for_queue_status_and_result
|
|||
assert logging_obj.model_call_details["response_cost"] is None
|
||||
|
||||
|
||||
def test_handler_charges_flat_video_queue_submit(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost,
|
||||
"fal_ai/fal-ai/test-flat-video",
|
||||
{"litellm_provider": "fal_ai", "mode": "video_generation", "output_cost_per_video": 0.37},
|
||||
)
|
||||
result: Final = FalAIPassthroughLoggingHandler().fal_ai_passthrough_handler(
|
||||
response_body={"request_id": "req-1", "status": "IN_QUEUE"},
|
||||
request_body={},
|
||||
logging_obj=_logging_obj(),
|
||||
url_route="https://queue.fal.run/fal-ai/test-flat-video",
|
||||
kwargs={},
|
||||
)
|
||||
assert result["kwargs"]["response_cost"] == pytest.approx(0.37)
|
||||
|
||||
|
||||
def test_handler_charges_for_queue_submit():
|
||||
handler_result = FalAIPassthroughLoggingHandler().fal_ai_passthrough_handler(
|
||||
response_body={"request_id": "req-1", "status": "IN_QUEUE"},
|
||||
|
|
|
|||
|
|
@ -10663,6 +10663,20 @@ class TestFalAIPassthroughRoute:
|
|||
assert sent.headers["authorization"] == "Key fal-test-key"
|
||||
assert json.loads(sent.content or b"{}") == body
|
||||
|
||||
def test_flat_video_submit_is_accepted(self, client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost,
|
||||
"fal_ai/fal-ai/test-flat-video",
|
||||
{"litellm_provider": "fal_ai", "mode": "video_generation", "output_cost_per_video": 0.37},
|
||||
)
|
||||
with respx.mock(assert_all_called=True) as upstream:
|
||||
upstream.post("https://queue.fal.run/fal-ai/test-flat-video").mock(
|
||||
return_value=httpx.Response(200, json={"request_id": "req-flat", "status": "IN_QUEUE"})
|
||||
)
|
||||
response: Final = client.post("/fal_ai/fal-ai/test-flat-video", json={"prompt": "A lake"})
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"request_id": "req-flat", "status": "IN_QUEUE"}
|
||||
|
||||
def test_status_get_forwards_to_queue_fal_run(self, client: TestClient) -> None:
|
||||
with respx.mock(assert_all_called=True) as upstream:
|
||||
route = upstream.get("https://queue.fal.run/fal-ai/trellis-2/requests/req-1/status").mock(
|
||||
|
|
|
|||
|
|
@ -1183,6 +1183,7 @@ _COST_MAP_ROW_SCHEMA: Final = {
|
|||
"output_cost_per_token_above_512k_tokens": {"type": "number"},
|
||||
"output_cost_per_token_batches": {"type": "number"},
|
||||
"output_cost_per_reasoning_token": {"type": "number"},
|
||||
"output_cost_per_video": {"type": "number"},
|
||||
"output_cost_per_video_per_second": {"type": "number"},
|
||||
"output_db_cost_per_token": {"type": "number"},
|
||||
"output_dbu_cost_per_token": {"type": "number"},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import asyncio
|
|||
import io
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Final
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
|
|
@ -19,6 +20,8 @@ from litellm.llms.gemini.videos.transformation import GeminiVideoConfig
|
|||
from litellm.llms.openai.videos.transformation import OpenAIVideoConfig
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.types.videos.main import VideoObject, VideoResponse
|
||||
from litellm.types.utils import ModelInfo
|
||||
from litellm.llms.openai.cost_calculation import video_generation_cost
|
||||
from litellm.videos import main as videos_main
|
||||
from litellm.videos.main import (
|
||||
avideo_generation,
|
||||
|
|
@ -29,6 +32,48 @@ from litellm.videos.main import (
|
|||
from tests.unit.proxy.router_entry_point_stubs import route_entry_points_to_stubs
|
||||
|
||||
|
||||
@pytest.mark.parametrize("duration", [0.0, 5.0, 30.0])
|
||||
@pytest.mark.parametrize("price", [0.0, 0.37])
|
||||
def test_flat_video_price_takes_precedence_over_duration(duration: float, price: float) -> None:
|
||||
pricing: Final[ModelInfo] = {"output_cost_per_video": price, "output_cost_per_video_per_second": 0.12}
|
||||
assert video_generation_cost("test-flat-video", duration, model_info=pricing) == price
|
||||
assert default_video_cost_calculator("test-flat-video", duration, model_info=pricing) == price
|
||||
|
||||
|
||||
@pytest.mark.parametrize("duration", [None, 0.0, 12.0])
|
||||
@pytest.mark.parametrize("count", [1, 3])
|
||||
def test_completion_cost_flat_video_deployment_override_counts_videos(
|
||||
monkeypatch: pytest.MonkeyPatch, duration: float | None, count: int
|
||||
) -> None:
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost,
|
||||
"openai/test-flat-video",
|
||||
{"litellm_provider": "openai", "mode": "video_generation", "output_cost_per_video": 0.91},
|
||||
)
|
||||
response: Final = VideoObject(
|
||||
id="vid-flat", object="video", status="completed", usage={"duration_seconds": duration, "video_count": count}
|
||||
)
|
||||
logging_obj: Final = LitellmLogging(
|
||||
model="openai/test-flat-video",
|
||||
messages=[],
|
||||
stream=False,
|
||||
call_type="create_video",
|
||||
start_time=datetime(2026, 1, 1),
|
||||
litellm_call_id="flat-video",
|
||||
function_id="flat-video",
|
||||
)
|
||||
logging_obj.litellm_params = {"metadata": {"model_info": {"output_cost_per_video": 0.37}}}
|
||||
cost: Final = litellm.completion_cost(
|
||||
completion_response=response,
|
||||
model="openai/test-flat-video",
|
||||
call_type="create_video",
|
||||
custom_llm_provider="openai",
|
||||
custom_pricing=True,
|
||||
litellm_logging_obj=logging_obj,
|
||||
)
|
||||
assert cost == pytest.approx(0.37 * count)
|
||||
|
||||
|
||||
class TestVideoGeneration:
|
||||
"""Test suite for video generation functionality."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue