diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 0793fe20b21..0a52e1d283e 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -1371,6 +1371,7 @@ def route_image_generation_cost_calculator( return fal_ai_image_cost_calculator( model=model, image_response=completion_response, + optional_params=optional_params, ) elif custom_llm_provider == litellm.LlmProviders.RUNWAYML.value: from litellm.llms.runwayml.cost_calculator import ( diff --git a/litellm/llms/fal_ai/cost_calculator.py b/litellm/llms/fal_ai/cost_calculator.py index 8c5ad5a8c64..74848784c5b 100644 --- a/litellm/llms/fal_ai/cost_calculator.py +++ b/litellm/llms/fal_ai/cost_calculator.py @@ -1,25 +1,75 @@ -from typing import Any, Final +from collections.abc import Mapping +from types import MappingProxyType +from typing import Final import litellm from litellm.types.utils import ImageResponse +FAL_KEYED_PRICING_DEFAULT_QUALITY: Final[str] = "high" +FAL_TEXT_TO_IMAGE_DEFAULT_SIZE: Final[str] = "1024-x-768" +FAL_NAMED_IMAGE_SIZES: Final[Mapping[str, str]] = MappingProxyType( + { + "square_hd": "1024-x-1024", + "square": "512-x-512", + "portrait_4_3": "768-x-1024", + "portrait_16_9": "576-x-1024", + "landscape_4_3": "1024-x-768", + "landscape_16_9": "1024-x-576", + } +) + + +def _keyed_size(model: str, optional_params: Mapping[str, object]) -> str | None: + image_size: Final = optional_params.get("image_size") + if image_size is None: + return None if model.endswith("/edit") else FAL_TEXT_TO_IMAGE_DEFAULT_SIZE + if isinstance(image_size, Mapping): + width: Final = image_size.get("width") + height: Final = image_size.get("height") + if isinstance(width, int) and isinstance(height, int): + return f"{width}-x-{height}" + return None + if isinstance(image_size, str): + return FAL_NAMED_IMAGE_SIZES.get(image_size) + return None + + +def _keyed_cost_per_image(model: str, optional_params: Mapping[str, object] | None) -> float | None: + if optional_params is None: + return None + size: Final = _keyed_size(model=model, optional_params=optional_params) + if size is None: + return None + raw_quality: Final = optional_params.get("quality") + quality: Final = ( + raw_quality if isinstance(raw_quality, str) and raw_quality != "auto" else FAL_KEYED_PRICING_DEFAULT_QUALITY + ) + keyed_entry: Final = litellm.model_cost.get(f"fal_ai/{quality}/{size}/{model}") + if keyed_entry is None: + return None + keyed_cost: Final = keyed_entry.get("output_cost_per_image") + return float(keyed_cost) if isinstance(keyed_cost, (int, float)) else None + def cost_calculator( model: str, - image_response: Any, + image_response: object, + optional_params: Mapping[str, object] | None = None, ) -> float: """ fal.ai image generation cost calculator """ + if not isinstance(image_response, ImageResponse): + raise ValueError(f"image_response must be of type ImageResponse got type={type(image_response)}") + # the proxy cost path passes the provider-prefixed model name + model = model.removeprefix(f"{litellm.LlmProviders.FAL_AI.value}/") + num_images: Final[int] = len(image_response.data) if image_response.data else 0 + keyed_cost_per_image: Final = _keyed_cost_per_image(model=model, optional_params=optional_params) + if keyed_cost_per_image is not None: + return keyed_cost_per_image * num_images _model_info: Final = litellm.get_model_info( model=model, custom_llm_provider=litellm.LlmProviders.FAL_AI.value, ) output_cost_per_image: Final[float] = _model_info.get("output_cost_per_image") or 0.0 - num_images: int = 0 - if isinstance(image_response, ImageResponse): - if image_response.data: - num_images = len(image_response.data) - return output_cost_per_image * num_images - else: - raise ValueError(f"image_response must be of type ImageResponse got type={type(image_response)}") + return output_cost_per_image * num_images diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 5a1c988c21a..d700e75f6aa 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -17400,7 +17400,7 @@ "fal_ai/openai/gpt-image-2": { "litellm_provider": "fal_ai", "metadata": { - "notes": "OpenAI gpt-image-2 served through fal.ai. fal bills by token, so the flat output_cost_per_image here is fal's published per-image rate for a default request (quality=high, image_size=landscape_4_3 at 1024x768). Other canonical sizes at high quality: 1024x1024 $0.211, 1024x1536 $0.165, 1920x1080 $0.158, 2560x1440 $0.222, 3840x2160 $0.401" + "notes": "OpenAI gpt-image-2 served through fal.ai. fal bills by token but publishes deterministic per-image prices per size and quality, mirrored here as keyed entries fal_ai/{quality}/{width}-x-{height}/openai/gpt-image-2 that litellm's fal_ai cost calculator picks from the request params. This flat entry is the fallback when no keyed entry matches and carries the default request rate (quality=high, image_size=landscape_4_3 at 1024x768). quality=auto is priced as high" }, "mode": "image_generation", "output_cost_per_image": 0.145, @@ -17410,10 +17410,190 @@ ], "supports_vision": true }, + "fal_ai/low/1024-x-768/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1024/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.006, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1536/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1920-x-1080/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/2560-x-1440/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.007, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/3840-x-2160/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.012, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-768/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.037, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1024/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.053, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1536/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.042, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1920-x-1080/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.04, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/2560-x-1440/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.056, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/3840-x-2160/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.101, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-768/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.145, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1024/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.211, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1536/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.165, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1920-x-1080/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.158, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/2560-x-1440/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.222, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/3840-x-2160/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.401, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, "fal_ai/gpt-image-2": { "litellm_provider": "fal_ai", "metadata": { - "notes": "Alias of fal_ai/openai/gpt-image-2, which litellm also accepts without the openai/ prefix. Same rate, see that entry for the size and quality caveat" + "notes": "Alias of fal_ai/openai/gpt-image-2, which litellm also accepts without the openai/ prefix. Same rates, including the keyed fal_ai/{quality}/{width}-x-{height}/gpt-image-2 entries; see that entry for details" }, "mode": "image_generation", "output_cost_per_image": 0.145, @@ -17423,13 +17603,373 @@ ], "supports_vision": true }, + "fal_ai/low/1024-x-768/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1024/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.006, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1536/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1920-x-1080/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/2560-x-1440/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.007, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/3840-x-2160/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.012, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-768/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.037, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1024/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.053, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1536/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.042, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1920-x-1080/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.04, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/2560-x-1440/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.056, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/3840-x-2160/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.101, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-768/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.145, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1024/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.211, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1536/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.165, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1920-x-1080/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.158, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/2560-x-1440/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.222, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/3840-x-2160/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.401, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, "fal_ai/openai/gpt-image-2/edit": { "litellm_provider": "fal_ai", "metadata": { - "notes": "Editing endpoint of gpt-image-2 on fal.ai, reached through the image generation path with fal's image_urls param since /v1/images/edits is not wired for fal_ai. Same rate as fal_ai/openai/gpt-image-2, see that entry for the size and quality caveat" + "notes": "Editing endpoint of gpt-image-2 on fal.ai, reached through the image generation path with fal's image_urls param since /v1/images/edits is not wired for fal_ai. Prices include one input image and live in keyed entries fal_ai/{quality}/{width}-x-{height}/openai/gpt-image-2/edit. This flat entry is the fallback for the default edit request (quality=high, image_size=auto, inferred from the input image, priced as 1024x768 high)" }, "mode": "image_generation", - "output_cost_per_image": 0.145, + "output_cost_per_image": 0.151, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-768/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.011, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1024/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.015, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1536/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.018, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1920-x-1080/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.017, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/2560-x-1440/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.019, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/3840-x-2160/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.024, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-768/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.043, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1024/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.061, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1536/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.054, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1920-x-1080/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.053, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/2560-x-1440/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.068, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/3840-x-2160/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.113, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-768/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.151, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1024/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.219, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1536/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.178, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1920-x-1080/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.158, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/2560-x-1440/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.234, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/3840-x-2160/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.413, "source": "https://fal.ai/models/openai/gpt-image-2/edit", "supported_endpoints": [ "/v1/images/generations" diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 5a1c988c21a..d700e75f6aa 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -17400,7 +17400,7 @@ "fal_ai/openai/gpt-image-2": { "litellm_provider": "fal_ai", "metadata": { - "notes": "OpenAI gpt-image-2 served through fal.ai. fal bills by token, so the flat output_cost_per_image here is fal's published per-image rate for a default request (quality=high, image_size=landscape_4_3 at 1024x768). Other canonical sizes at high quality: 1024x1024 $0.211, 1024x1536 $0.165, 1920x1080 $0.158, 2560x1440 $0.222, 3840x2160 $0.401" + "notes": "OpenAI gpt-image-2 served through fal.ai. fal bills by token but publishes deterministic per-image prices per size and quality, mirrored here as keyed entries fal_ai/{quality}/{width}-x-{height}/openai/gpt-image-2 that litellm's fal_ai cost calculator picks from the request params. This flat entry is the fallback when no keyed entry matches and carries the default request rate (quality=high, image_size=landscape_4_3 at 1024x768). quality=auto is priced as high" }, "mode": "image_generation", "output_cost_per_image": 0.145, @@ -17410,10 +17410,190 @@ ], "supports_vision": true }, + "fal_ai/low/1024-x-768/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1024/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.006, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1536/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1920-x-1080/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/2560-x-1440/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.007, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/3840-x-2160/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.012, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-768/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.037, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1024/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.053, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1536/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.042, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1920-x-1080/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.04, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/2560-x-1440/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.056, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/3840-x-2160/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.101, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-768/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.145, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1024/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.211, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1536/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.165, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1920-x-1080/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.158, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/2560-x-1440/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.222, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/3840-x-2160/openai/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.401, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, "fal_ai/gpt-image-2": { "litellm_provider": "fal_ai", "metadata": { - "notes": "Alias of fal_ai/openai/gpt-image-2, which litellm also accepts without the openai/ prefix. Same rate, see that entry for the size and quality caveat" + "notes": "Alias of fal_ai/openai/gpt-image-2, which litellm also accepts without the openai/ prefix. Same rates, including the keyed fal_ai/{quality}/{width}-x-{height}/gpt-image-2 entries; see that entry for details" }, "mode": "image_generation", "output_cost_per_image": 0.145, @@ -17423,13 +17603,373 @@ ], "supports_vision": true }, + "fal_ai/low/1024-x-768/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1024/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.006, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1536/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1920-x-1080/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.005, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/2560-x-1440/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.007, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/3840-x-2160/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.012, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-768/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.037, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1024/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.053, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1536/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.042, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1920-x-1080/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.04, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/2560-x-1440/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.056, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/3840-x-2160/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.101, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-768/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.145, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1024/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.211, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1536/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.165, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1920-x-1080/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.158, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/2560-x-1440/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.222, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/3840-x-2160/gpt-image-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.401, + "source": "https://fal.ai/models/openai/gpt-image-2", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, "fal_ai/openai/gpt-image-2/edit": { "litellm_provider": "fal_ai", "metadata": { - "notes": "Editing endpoint of gpt-image-2 on fal.ai, reached through the image generation path with fal's image_urls param since /v1/images/edits is not wired for fal_ai. Same rate as fal_ai/openai/gpt-image-2, see that entry for the size and quality caveat" + "notes": "Editing endpoint of gpt-image-2 on fal.ai, reached through the image generation path with fal's image_urls param since /v1/images/edits is not wired for fal_ai. Prices include one input image and live in keyed entries fal_ai/{quality}/{width}-x-{height}/openai/gpt-image-2/edit. This flat entry is the fallback for the default edit request (quality=high, image_size=auto, inferred from the input image, priced as 1024x768 high)" }, "mode": "image_generation", - "output_cost_per_image": 0.145, + "output_cost_per_image": 0.151, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-768/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.011, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1024/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.015, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1024-x-1536/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.018, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/1920-x-1080/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.017, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/2560-x-1440/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.019, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/low/3840-x-2160/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.024, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-768/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.043, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1024/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.061, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1024-x-1536/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.054, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/1920-x-1080/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.053, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/2560-x-1440/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.068, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/medium/3840-x-2160/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.113, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-768/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.151, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1024/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.219, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1024-x-1536/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.178, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/1920-x-1080/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.158, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/2560-x-1440/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.234, + "source": "https://fal.ai/models/openai/gpt-image-2/edit", + "supported_endpoints": [ + "/v1/images/generations" + ], + "supports_vision": true + }, + "fal_ai/high/3840-x-2160/openai/gpt-image-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.413, "source": "https://fal.ai/models/openai/gpt-image-2/edit", "supported_endpoints": [ "/v1/images/generations" diff --git a/tests/test_litellm/llms/fal_ai/image_generation/test_fal_ai_gpt_image_2_transformation.py b/tests/test_litellm/llms/fal_ai/image_generation/test_fal_ai_gpt_image_2_transformation.py index 3c8cf9f9e0a..1a527230f1b 100644 --- a/tests/test_litellm/llms/fal_ai/image_generation/test_fal_ai_gpt_image_2_transformation.py +++ b/tests/test_litellm/llms/fal_ai/image_generation/test_fal_ai_gpt_image_2_transformation.py @@ -128,19 +128,23 @@ def test_transform_image_generation_request(): @pytest.mark.parametrize( - "model", + ("model", "expected_cost_for_two_images"), [ - "openai/gpt-image-2", - "gpt-image-2", - "openai/gpt-image-2/edit", + ("openai/gpt-image-2", 0.29), + ("gpt-image-2", 0.29), + ("openai/gpt-image-2/edit", 0.302), ], ) -def test_cost_calculator_uses_registry_price(model, monkeypatch: pytest.MonkeyPatch): +def test_cost_calculator_uses_registry_price( + model, expected_cost_for_two_images, monkeypatch: pytest.MonkeyPatch +): + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url="")) + litellm.get_model_info.cache_clear() response = ImageResponse( data=[ ImageObject(url="https://v3b.fal.media/files/b/one.png"), ImageObject(url="https://v3b.fal.media/files/b/two.png"), ] ) - assert cost_calculator(model=model, image_response=response) == pytest.approx(0.29) + assert cost_calculator(model=model, image_response=response) == pytest.approx(expected_cost_for_two_images) diff --git a/tests/test_litellm/llms/fal_ai/test_cost_calculator.py b/tests/test_litellm/llms/fal_ai/test_cost_calculator.py new file mode 100644 index 00000000000..f167aceaa95 --- /dev/null +++ b/tests/test_litellm/llms/fal_ai/test_cost_calculator.py @@ -0,0 +1,156 @@ +import pytest + +import litellm +from litellm.litellm_core_utils.llm_cost_calc.utils import CostCalculatorUtils +from litellm.llms.fal_ai.cost_calculator import cost_calculator +from litellm.types.utils import ImageObject, ImageResponse + + +@pytest.fixture(autouse=True) +def _use_local_model_cost_map(monkeypatch): + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url="")) + litellm.get_model_info.cache_clear() + yield + litellm.get_model_info.cache_clear() + + +def _image_response(num_images: int = 1) -> ImageResponse: + return ImageResponse(data=[ImageObject(url="https://example.com/img.png") for _ in range(num_images)]) + + +def test_high_quality_1024x1024_uses_keyed_price(): + cost = cost_calculator( + model="openai/gpt-image-2", + image_response=_image_response(), + optional_params={"quality": "high", "image_size": {"width": 1024, "height": 1024}}, + ) + assert cost == pytest.approx(0.211) + + +def test_alias_model_uses_keyed_price(): + cost = cost_calculator( + model="gpt-image-2", + image_response=_image_response(), + optional_params={"quality": "high", "image_size": {"width": 1024, "height": 1024}}, + ) + assert cost == pytest.approx(0.211) + + +def test_provider_prefixed_model_uses_keyed_price(): + cost = cost_calculator( + model="fal_ai/openai/gpt-image-2", + image_response=_image_response(), + optional_params={"quality": "high", "image_size": {"width": 1024, "height": 1024}}, + ) + assert cost == pytest.approx(0.211) + + +def test_provider_prefixed_edit_model_uses_keyed_edit_price(): + cost = cost_calculator( + model="fal_ai/openai/gpt-image-2/edit", + image_response=_image_response(), + optional_params={"quality": "high", "image_size": {"width": 1024, "height": 1024}}, + ) + assert cost == pytest.approx(0.219) + + +def test_default_request_priced_at_default_size_and_quality(): + cost = cost_calculator( + model="openai/gpt-image-2", + image_response=_image_response(), + optional_params={}, + ) + assert cost == pytest.approx(0.145) + + +def test_auto_quality_priced_as_high(): + cost = cost_calculator( + model="openai/gpt-image-2", + image_response=_image_response(), + optional_params={"quality": "auto", "image_size": {"width": 1024, "height": 1024}}, + ) + assert cost == pytest.approx(0.211) + + +def test_low_quality_4k_uses_keyed_price(): + cost = cost_calculator( + model="openai/gpt-image-2", + image_response=_image_response(), + optional_params={"quality": "low", "image_size": {"width": 3840, "height": 2160}}, + ) + assert cost == pytest.approx(0.012) + + +def test_named_fal_size_uses_keyed_price(): + cost = cost_calculator( + model="openai/gpt-image-2", + image_response=_image_response(), + optional_params={"quality": "high", "image_size": "square_hd"}, + ) + assert cost == pytest.approx(0.211) + + +def test_edit_model_uses_keyed_edit_price(): + cost = cost_calculator( + model="openai/gpt-image-2/edit", + image_response=_image_response(), + optional_params={"quality": "high", "image_size": {"width": 1024, "height": 1024}}, + ) + assert cost == pytest.approx(0.219) + + +def test_edit_model_without_size_falls_back_to_flat_price(): + cost = cost_calculator( + model="openai/gpt-image-2/edit", + image_response=_image_response(), + optional_params={"quality": "high"}, + ) + assert cost == pytest.approx(0.151) + + +def test_missing_optional_params_falls_back_to_flat_price(): + cost = cost_calculator( + model="openai/gpt-image-2", + image_response=_image_response(), + optional_params=None, + ) + assert cost == pytest.approx(0.145) + + +def test_unlisted_size_falls_back_to_flat_price(): + cost = cost_calculator( + model="openai/gpt-image-2", + image_response=_image_response(), + optional_params={"quality": "high", "image_size": {"width": 999, "height": 999}}, + ) + assert cost == pytest.approx(0.145) + + +def test_keyed_price_multiplies_per_image(): + cost = cost_calculator( + model="openai/gpt-image-2", + image_response=_image_response(num_images=2), + optional_params={"quality": "high", "image_size": {"width": 1024, "height": 1024}}, + ) + assert cost == pytest.approx(0.422) + + +def test_route_image_generation_passes_optional_params_to_fal(): + cost = CostCalculatorUtils.route_image_generation_cost_calculator( + model="openai/gpt-image-2", + completion_response=_image_response(), + custom_llm_provider="fal_ai", + optional_params={"quality": "high", "image_size": {"width": 1024, "height": 1024}}, + ) + assert cost == pytest.approx(0.211) + + +def test_route_image_generation_with_provider_prefixed_model_uses_keyed_price(): + cost = CostCalculatorUtils.route_image_generation_cost_calculator( + model="fal_ai/openai/gpt-image-2", + completion_response=_image_response(), + custom_llm_provider="fal_ai", + optional_params={"quality": "high", "image_size": {"width": 1024, "height": 1024}}, + ) + assert cost == pytest.approx(0.211)