fix(fal_ai): bill Luma Ray v3.2 videos at fal's 540p default resolution (#51477)

fal's luma/agent/ray/v3.2 endpoints default resolution to 540p, but the fal video transformation had no
Luma profile and fell back to the Seedance profile, whose default is 720p. A request that left
resolution unset was stamped video_resolution 720p and priced from output_cost_per_second_720p, which is
twice the 540p rate on text-to-video, image-to-video and reframe and 1.5x on video-to-video.

A Luma Ray profile (540p, 720p, 1080p, default 540p) stamps the real default, so those requests price
from output_cost_per_second, the 540p rate.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Kerry Lu 2026-11-12 15:34:24 -08:00 • committed by GitHub
parent de3f0690e7
commit 91808ed2cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 33 additions and 0 deletions

View file

@ -75,6 +75,14 @@ _H3_MAX_PROFILE: Final[_ModelProfile] = _ModelProfile(
reference_key="image_url",
reference_as_list=False,
)
_LUMA_RAY_PROFILE: Final[_ModelProfile] = _ModelProfile(
resolutions=frozenset({"540p", "720p", "1080p"}),
resolution_tiers=((540, "540p"), (720, "720p"), (sys.maxsize, "1080p")),
default_resolution="540p",
integer_duration=False,
reference_key="image_url",
reference_as_list=False,
)
_SEEDANCE_REFERENCE_PROFILE: Final[_ModelProfile] = replace(
_SEEDANCE_PROFILE,
reference_key="image_urls",
@ -118,6 +126,8 @@ def _profile_for_model(model: str) -> _ModelProfile:
return _H3_PROFILE
if model.startswith("minimax/h3-max"):
return _H3_MAX_PROFILE
if model.startswith("luma/agent/ray/"):
return _LUMA_RAY_PROFILE
is_seedance_reference: Final = model.startswith("bytedance/seedance-") and model.endswith("/reference-to-video")
return _SEEDANCE_REFERENCE_PROFILE if is_seedance_reference else _SEEDANCE_PROFILE

View file

@ -232,6 +232,29 @@ class TestFalAIVideoTransformation:
assert video.usage == {"duration_seconds": 5.0, "video_resolution": "2K"}
def test_transform_video_create_response_luma_ray_defaults_to_540p(self):
response = Mock(spec=httpx.Response)
response.json.return_value = {"request_id": "abc"}
for model in (
"luma/agent/ray/v3.2/text-to-video",
"luma/agent/ray/v3.2/image-to-video",
"luma/agent/ray/v3.2/reframe",
"luma/agent/ray/v3.2/video-to-video",
):
video = self.config.transform_video_create_response(
model=model,
raw_response=response,
logging_obj=self.logging_obj,
custom_llm_provider="fal_ai",
request_data={"duration": "5"},
)
assert video.usage == {"duration_seconds": 5.0, "video_resolution": "540p"}, model
row = litellm.model_cost[f"fal_ai/{model}"]
assert default_video_cost_calculator(model, 5, "fal_ai", video_resolution="540p") == (
5 * row["output_cost_per_second"]
), model
def test_transform_video_create_response_h3_max_leaves_default_resolution_to_cost_map(
self, local_model_cost_map: None
) -> None: