From 926e2e641c1fb317ff9ab223a026bfc375a00cfe Mon Sep 17 00:00:00 2001 From: Albert Sebastian Date: Tue, 14 Apr 2026 04:31:12 +0530 Subject: [PATCH] feat: add resolution-based and per-megapixel pricing for FAL models Enhance the FAL AI cost calculator to support three pricing modes: - PER_MEGAPIXEL: cost scales with image dimensions (flux-2/klein models) - PER_IMAGE_RESOLUTION: different rates for 2K vs 4K (nano-banana, gemini, seedream) - PER_CALL: flat per-image rate (default, backward compatible) Add pricing_basis, output_cost_per_megapixel, and output_cost_per_image_by_resolution fields to ModelInfo. Update pricing JSON with correct USD values (1 credit = $0.01). Set response size from request params in FAL transformation for cost calculation. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../litellm_core_utils/llm_cost_calc/utils.py | 1 + litellm/llms/fal_ai/cost_calculator.py | 82 +++++- .../fal_ai/image_generation/transformation.py | 4 + ...odel_prices_and_context_window_backup.json | 250 ++++++++++++++++++ litellm/types/utils.py | 3 + litellm/utils.py | 7 + model_prices_and_context_window.json | 96 +++++-- 7 files changed, 416 insertions(+), 27 deletions(-) diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 191231f3e66..557bff3c6fc 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -968,6 +968,7 @@ class CostCalculatorUtils: return fal_ai_image_cost_calculator( model=model, image_response=completion_response, + size=size, ) 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 9cdd0cd485b..bd4212d2ea2 100644 --- a/litellm/llms/fal_ai/cost_calculator.py +++ b/litellm/llms/fal_ai/cost_calculator.py @@ -1,27 +1,85 @@ -from typing import Any +from typing import Any, Optional import litellm from litellm.types.utils import ImageResponse +def _parse_megapixels(size: Optional[str]) -> Optional[float]: + """Parse size string ('1024x1024' or '1024-x-1024') into megapixels.""" + if not size: + return None + normalized = size.replace("-x-", "x").replace(" ", "") + parts = normalized.split("x") + if len(parts) != 2: + return None + try: + w, h = int(parts[0]), int(parts[1]) + return (w * h) / 1_000_000 + except (ValueError, TypeError): + return None + + +def _resolve_resolution_tier(size: Optional[str]) -> Optional[str]: + """Map a size param to a resolution tier ('2K' or '4K'). + + Handles three cases: + 1. Already a tier string: '2K', '4K' -> return as-is + 2. Pixel dimensions: '1024x1024' -> compute MP -> classify + 3. Unknown -> None (caller falls back to flat pricing) + + Threshold: < 2.5 MP = '2K', >= 2.5 MP = '4K' + """ + if not size: + return None + if size.upper() in ("2K", "4K"): + return size.upper() + mp = _parse_megapixels(size) + if mp is None: + return None + return "4K" if mp >= 2.5 else "2K" + + def cost_calculator( model: str, image_response: Any, + size: Optional[str] = None, ) -> float: + """fal.ai image generation cost calculator. + + Dispatches on pricing_basis from model info: + - PER_MEGAPIXEL: output_cost_per_megapixel * megapixels * num_images + - PER_IMAGE_RESOLUTION: output_cost_per_image_by_resolution[tier] * num_images + - PER_CALL / default: output_cost_per_image * num_images """ - 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)}" + ) + _model_info = litellm.get_model_info( model=model, custom_llm_provider=litellm.LlmProviders.FAL_AI.value, ) + + num_images = len(image_response.data) if image_response.data else 0 + effective_size = size or getattr(image_response, "size", None) + pricing_basis = _model_info.get("pricing_basis", "PER_CALL") + + if pricing_basis == "PER_MEGAPIXEL": + cost_per_mp: float = _model_info.get("output_cost_per_megapixel") or 0.0 + megapixels = _parse_megapixels(effective_size) + if cost_per_mp > 0 and megapixels and megapixels > 0: + return cost_per_mp * megapixels * num_images + # Fall through to flat if MP can't be computed + + elif pricing_basis == "PER_IMAGE_RESOLUTION": + cost_by_res = _model_info.get("output_cost_per_image_by_resolution") + if cost_by_res: + tier = _resolve_resolution_tier(effective_size) + if tier and tier in cost_by_res: + return cost_by_res[tier] * num_images + # Fall through to flat if tier can't be resolved + + # PER_CALL / fallback output_cost_per_image: 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/llms/fal_ai/image_generation/transformation.py b/litellm/llms/fal_ai/image_generation/transformation.py index 7a001e0d695..276452df6cc 100644 --- a/litellm/llms/fal_ai/image_generation/transformation.py +++ b/litellm/llms/fal_ai/image_generation/transformation.py @@ -100,6 +100,10 @@ class FalAIBaseConfig(BaseImageGenerationConfig): if not model_response.data: model_response.data = [] + # Preserve requested size on the response for cost calculation + if optional_params.get("size"): + model_response.size = optional_params["size"] + # Handle fal.ai response format images = response_data.get("images", []) if isinstance(images, list): diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index c38693c7eba..b4c8202d779 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -38206,5 +38206,255 @@ "tool_use_system_prompt_tokens": 346, "supports_native_structured_output": true, "supports_pdf_input": true + }, + "fal_ai/bytedance/seedream/v4.5/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.03, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/bytedance/seedream/v4.5/text-to-image": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.03, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/bytedance/seedream/v4/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.03, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/bytedance/seedream/v4/text-to-image": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.03, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/bytedance/seedream/v5/lite/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/bytedance/seedream/v5/lite/text-to-image": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/flux-2/klein/9b": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_MEGAPIXEL", + "output_cost_per_megapixel": 0.01, + "output_cost_per_image": 0.01, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/flux-2/klein/9b/base/edit/lora": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_MEGAPIXEL", + "output_cost_per_megapixel": 0.01, + "output_cost_per_image": 0.01, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/flux-2/klein/9b/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_MEGAPIXEL", + "output_cost_per_megapixel": 0.01, + "output_cost_per_image": 0.01, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/flux-pro": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.04, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/flux-pro/kontext": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.05, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/gemini-25-flash-image": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/gemini-25-flash-image/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/gemini-3-pro-image-preview": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/gemini-3-pro-image-preview/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/gemini-3.1-flash-image-preview": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/gemini-3.1-flash-image-preview/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/iclight-v2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.02, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/kling-image/omni": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "output_cost_per_image": 0.04, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/nano-banana-2": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/nano-banana-2/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/nano-banana-pro": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] + }, + "fal_ai/nano-banana-pro/edit": { + "litellm_provider": "fal_ai", + "mode": "image_generation", + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, + "supported_endpoints": [ + "/v1/images/generations" + ] } } \ No newline at end of file diff --git a/litellm/types/utils.py b/litellm/types/utils.py index cd5806b3ab7..a3f63c8866e 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -227,6 +227,9 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False): ] # only for vertex ai models output_cost_per_image: Optional[float] output_cost_per_image_token: Optional[float] + output_cost_per_megapixel: Optional[float] # for per-megapixel image pricing + output_cost_per_image_by_resolution: Optional[Dict[str, float]] # e.g. {"2K": 0.15, "4K": 0.30} + pricing_basis: Optional[str] # PER_CALL, PER_MEGAPIXEL, PER_IMAGE_RESOLUTION output_vector_size: Optional[int] output_cost_per_reasoning_token: Optional[float] output_cost_per_video_per_second: Optional[float] # only for vertex ai models diff --git a/litellm/utils.py b/litellm/utils.py index 6169e7bc733..13015045fd4 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5834,6 +5834,13 @@ def _get_model_info_helper( # noqa: PLR0915 output_cost_per_image_token=_model_info.get( "output_cost_per_image_token", None ), + output_cost_per_megapixel=_model_info.get( + "output_cost_per_megapixel", None + ), + output_cost_per_image_by_resolution=_model_info.get( + "output_cost_per_image_by_resolution", None + ), + pricing_basis=_model_info.get("pricing_basis", None), output_vector_size=_model_info.get("output_vector_size", None), citation_cost_per_token=_model_info.get( "citation_cost_per_token", None diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index d8ea5daca5a..b6e4b4908ee 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -12736,7 +12736,12 @@ "fal_ai/nano-banana-2": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.01, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12744,7 +12749,12 @@ "fal_ai/nano-banana-2/edit": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.01, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12752,7 +12762,12 @@ "fal_ai/nano-banana-pro": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.02, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12760,7 +12775,12 @@ "fal_ai/nano-banana-pro/edit": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.02, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12768,7 +12788,9 @@ "fal_ai/flux-2/klein/9b": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.025, + "pricing_basis": "PER_MEGAPIXEL", + "output_cost_per_megapixel": 0.01, + "output_cost_per_image": 0.01, "supported_endpoints": [ "/v1/images/generations" ] @@ -12776,7 +12798,9 @@ "fal_ai/flux-2/klein/9b/edit": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.025, + "pricing_basis": "PER_MEGAPIXEL", + "output_cost_per_megapixel": 0.01, + "output_cost_per_image": 0.01, "supported_endpoints": [ "/v1/images/generations" ] @@ -12784,7 +12808,9 @@ "fal_ai/flux-2/klein/9b/base/edit/lora": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.025, + "pricing_basis": "PER_MEGAPIXEL", + "output_cost_per_megapixel": 0.01, + "output_cost_per_image": 0.01, "supported_endpoints": [ "/v1/images/generations" ] @@ -12808,7 +12834,12 @@ "fal_ai/gemini-3-pro-image-preview": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.04, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12816,7 +12847,12 @@ "fal_ai/gemini-3-pro-image-preview/edit": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.04, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12824,7 +12860,12 @@ "fal_ai/gemini-3.1-flash-image-preview": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.02, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12832,7 +12873,12 @@ "fal_ai/gemini-3.1-flash-image-preview/edit": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.02, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12840,7 +12886,12 @@ "fal_ai/gemini-25-flash-image": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.02, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12848,7 +12899,12 @@ "fal_ai/gemini-25-flash-image/edit": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.02, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12888,7 +12944,12 @@ "fal_ai/bytedance/seedream/v5/lite/text-to-image": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.03, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ] @@ -12896,7 +12957,12 @@ "fal_ai/bytedance/seedream/v5/lite/edit": { "litellm_provider": "fal_ai", "mode": "image_generation", - "output_cost_per_image": 0.03, + "pricing_basis": "PER_IMAGE_RESOLUTION", + "output_cost_per_image": 0.15, + "output_cost_per_image_by_resolution": { + "2K": 0.15, + "4K": 0.3 + }, "supported_endpoints": [ "/v1/images/generations" ]