From e5d278c4547eed080419894bdadf924ee084e829 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 21 May 2025 14:03:09 -0700 Subject: [PATCH] [Feat] Add Image Edits Support to LiteLLM (#11020) * refactor: use 1 file for image methods * refactor: use 1 file for image methods * feat: add stubs for image edits * fix: types for image edits * feat: add async image edits * feat: add base config for image edits * feat: add basic structure for image edits * feat: add ImageEditRequestUtils * feat: complete instrumentation of image edits * tes: test_openai_image_edit_litellm_sdk * tets: test_openai_image_edit_litellm_sdk * feat: get_provider_image_edit_config * feat: add OpenAIImageEditConfig * feat: working image edits * fixes: working image edits * fix: code qa * fix: using image edits * fix: linting errors --- litellm/__init__.py | 106 ++- litellm/constants.py | 2 + litellm/images/main.py | 736 ++++++++++++++++++ litellm/images/utils.py | 71 ++ litellm/llms/base_llm/__init__.py | 2 + .../base_llm/image_edit/transformation.py | 120 +++ litellm/llms/custom_httpx/aiohttp_handler.py | 2 +- litellm/llms/custom_httpx/http_handler.py | 13 +- litellm/llms/custom_httpx/llm_http_handler.py | 200 ++++- .../llms/openai/image_edit/transformation.py | 147 ++++ .../llms/openai/image_variations/handler.py | 4 +- litellm/main.py | 513 ------------ litellm/types/images/main.py | 31 + litellm/utils.py | 15 + tests/image_gen_tests/ishaan_github.png | Bin 0 -> 618676 bytes tests/image_gen_tests/litellm_site.png | Bin 0 -> 775174 bytes tests/image_gen_tests/test_image_edit.png | Bin 0 -> 2732125 bytes tests/image_gen_tests/test_image_edits.py | 57 ++ 18 files changed, 1442 insertions(+), 577 deletions(-) create mode 100644 litellm/images/main.py create mode 100644 litellm/images/utils.py create mode 100644 litellm/llms/base_llm/image_edit/transformation.py create mode 100644 litellm/llms/openai/image_edit/transformation.py create mode 100644 litellm/types/images/main.py create mode 100644 tests/image_gen_tests/ishaan_github.png create mode 100644 tests/image_gen_tests/litellm_site.png create mode 100644 tests/image_gen_tests/test_image_edit.png create mode 100644 tests/image_gen_tests/test_image_edits.py diff --git a/litellm/__init__.py b/litellm/__init__.py index 96c1552c36..dd09810d46 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -132,22 +132,22 @@ prometheus_initialize_budget_metrics: Optional[bool] = False require_auth_for_metrics_endpoint: Optional[bool] = False argilla_batch_size: Optional[int] = None datadog_use_v1: Optional[bool] = False # if you want to use v1 datadog logged payload -gcs_pub_sub_use_v1: Optional[bool] = ( - False # if you want to use v1 gcs pubsub logged payload -) -generic_api_use_v1: Optional[bool] = ( - False # if you want to use v1 generic api logged payload -) +gcs_pub_sub_use_v1: Optional[ + bool +] = False # if you want to use v1 gcs pubsub logged payload +generic_api_use_v1: Optional[ + bool +] = False # if you want to use v1 generic api logged payload argilla_transformation_object: Optional[Dict[str, Any]] = None -_async_input_callback: List[Union[str, Callable, CustomLogger]] = ( - [] -) # internal variable - async custom callbacks are routed here. -_async_success_callback: List[Union[str, Callable, CustomLogger]] = ( - [] -) # internal variable - async custom callbacks are routed here. -_async_failure_callback: List[Union[str, Callable, CustomLogger]] = ( - [] -) # internal variable - async custom callbacks are routed here. +_async_input_callback: List[ + Union[str, Callable, CustomLogger] +] = [] # internal variable - async custom callbacks are routed here. +_async_success_callback: List[ + Union[str, Callable, CustomLogger] +] = [] # internal variable - async custom callbacks are routed here. +_async_failure_callback: List[ + Union[str, Callable, CustomLogger] +] = [] # internal variable - async custom callbacks are routed here. pre_call_rules: List[Callable] = [] post_call_rules: List[Callable] = [] turn_off_message_logging: Optional[bool] = False @@ -155,18 +155,18 @@ log_raw_request_response: bool = False redact_messages_in_exceptions: Optional[bool] = False redact_user_api_key_info: Optional[bool] = False filter_invalid_headers: Optional[bool] = False -add_user_information_to_llm_headers: Optional[bool] = ( - None # adds user_id, team_id, token hash (params from StandardLoggingMetadata) to request headers -) +add_user_information_to_llm_headers: Optional[ + bool +] = None # adds user_id, team_id, token hash (params from StandardLoggingMetadata) to request headers store_audit_logs = False # Enterprise feature, allow users to see audit logs ### end of callbacks ############# -email: Optional[str] = ( - None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -) -token: Optional[str] = ( - None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -) +email: Optional[ + str +] = None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +token: Optional[ + str +] = None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 telemetry = True max_tokens: int = DEFAULT_MAX_TOKENS # OpenAI Defaults drop_params = bool(os.getenv("LITELLM_DROP_PARAMS", False)) @@ -247,24 +247,20 @@ enable_loadbalancing_on_batch_endpoints: Optional[bool] = None enable_caching_on_provider_specific_optional_params: bool = ( False # feature-flag for caching on optional params - e.g. 'top_k' ) -caching: bool = ( - False # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -) -caching_with_models: bool = ( - False # # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 -) -cache: Optional[Cache] = ( - None # cache object <- use this - https://docs.litellm.ai/docs/caching -) +caching: bool = False # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +caching_with_models: bool = False # # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648 +cache: Optional[ + Cache +] = None # cache object <- use this - https://docs.litellm.ai/docs/caching default_in_memory_ttl: Optional[float] = None default_redis_ttl: Optional[float] = None default_redis_batch_cache_expiry: Optional[float] = None model_alias_map: Dict[str, str] = {} model_group_alias_map: Dict[str, str] = {} max_budget: float = 0.0 # set the max budget across all providers -budget_duration: Optional[str] = ( - None # proxy only - resets budget after fixed duration. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d"). -) +budget_duration: Optional[ + str +] = None # proxy only - resets budget after fixed duration. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d"). default_soft_budget: float = ( DEFAULT_SOFT_BUDGET # by default all litellm proxy keys have a soft budget of 50.0 ) @@ -273,15 +269,11 @@ forward_traceparent_to_llm_provider: bool = False _current_cost = 0.0 # private variable, used if max budget is set error_logs: Dict = {} -add_function_to_prompt: bool = ( - False # if function calling not supported by api, append function call details to system prompt -) +add_function_to_prompt: bool = False # if function calling not supported by api, append function call details to system prompt client_session: Optional[httpx.Client] = None aclient_session: Optional[httpx.AsyncClient] = None model_fallbacks: Optional[List] = None # Deprecated for 'litellm.fallbacks' -model_cost_map_url: str = ( - "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json" -) +model_cost_map_url: str = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json" suppress_debug_info = False dynamodb_table_name: Optional[str] = None s3_callback_params: Optional[Dict] = None @@ -304,9 +296,7 @@ disable_end_user_cost_tracking_prometheus_only: Optional[bool] = None custom_prometheus_metadata_labels: List[str] = [] #### REQUEST PRIORITIZATION #### priority_reservation: Optional[Dict[str, float]] = None -force_ipv4: bool = ( - False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6. -) +force_ipv4: bool = False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6. module_level_aclient = AsyncHTTPHandler( timeout=request_timeout, client_alias="module level aclient" ) @@ -320,13 +310,13 @@ fallbacks: Optional[List] = None context_window_fallbacks: Optional[List] = None content_policy_fallbacks: Optional[List] = None allowed_fails: int = 3 -num_retries_per_request: Optional[int] = ( - None # for the request overall (incl. fallbacks + model retries) -) +num_retries_per_request: Optional[ + int +] = None # for the request overall (incl. fallbacks + model retries) ####### SECRET MANAGERS ##################### -secret_manager_client: Optional[Any] = ( - None # list of instantiated key management clients - e.g. azure kv, infisical, etc. -) +secret_manager_client: Optional[ + Any +] = None # list of instantiated key management clients - e.g. azure kv, infisical, etc. _google_kms_resource_name: Optional[str] = None _key_management_system: Optional[KeyManagementSystem] = None _key_management_settings: KeyManagementSettings = KeyManagementSettings() @@ -448,6 +438,7 @@ snowflake_models: List = [] llama_models: List = [] nscale_models: List = [] + def is_bedrock_pricing_only_model(key: str) -> bool: """ Excludes keys with the pattern 'bedrock//'. These are in the model_prices_and_context_window.json file for pricing purposes only. @@ -1091,6 +1082,7 @@ from .proxy.proxy_cli import run_server from .router import Router from .assistants.main import * from .batches.main import * +from .images.main import * from .batch_completion.main import * # type: ignore from .rerank_api.main import * from .llms.anthropic.experimental_pass_through.messages.handler import * @@ -1117,10 +1109,10 @@ from .types.llms.custom_llm import CustomLLMItem from .types.utils import GenericStreamingChunk custom_provider_map: List[CustomLLMItem] = [] -_custom_providers: List[str] = ( - [] -) # internal helper util, used to track names of custom providers -disable_hf_tokenizer_download: Optional[bool] = ( - None # disable huggingface tokenizer download. Defaults to openai clk100 -) +_custom_providers: List[ + str +] = [] # internal helper util, used to track names of custom providers +disable_hf_tokenizer_download: Optional[ + bool +] = None # disable huggingface tokenizer download. Defaults to openai clk100 global_disable_no_log_param: bool = False diff --git a/litellm/constants.py b/litellm/constants.py index e224c0dd69..37d44f64c5 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -153,12 +153,14 @@ FIREWORKS_AI_80_B = int(os.getenv("FIREWORKS_AI_80_B", 80)) #### Logging callback constants #### REDACTED_BY_LITELM_STRING = "REDACTED_BY_LITELM" +############### LLM Provider Constants ############### ### ANTHROPIC CONSTANTS ### ANTHROPIC_WEB_SEARCH_TOOL_MAX_USES = { "low": 1, "medium": 5, "high": 10, } +DEFAULT_IMAGE_ENDPOINT_MODEL = "dall-e-2" LITELLM_CHAT_PROVIDERS = [ "openai", diff --git a/litellm/images/main.py b/litellm/images/main.py new file mode 100644 index 0000000000..cf62b3a365 --- /dev/null +++ b/litellm/images/main.py @@ -0,0 +1,736 @@ +import asyncio +import contextvars +from functools import partial +from typing import Any, Coroutine, Dict, Literal, Optional, Union, cast + +import httpx + +import litellm +from litellm import Logging, client, exception_type, get_litellm_params +from litellm.constants import DEFAULT_IMAGE_ENDPOINT_MODEL +from litellm.constants import request_timeout as DEFAULT_REQUEST_TIMEOUT +from litellm.exceptions import LiteLLMUnknownProvider +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.litellm_core_utils.mock_functions import mock_image_generation +from litellm.llms.base_llm import BaseImageEditConfig, BaseImageGenerationConfig +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler +from litellm.llms.custom_llm import CustomLLM + +#################### Initialize provider clients #################### +from litellm.main import ( + azure_chat_completions, + base_llm_aiohttp_handler, + base_llm_http_handler, + bedrock_image_generation, + openai_chat_completions, + openai_image_variations, + vertex_image_generation, +) +from litellm.secret_managers.main import get_secret_str +from litellm.types.images.main import ImageEditOptionalRequestParams +from litellm.types.llms.openai import ImageGenerationRequestQuality +from litellm.types.router import GenericLiteLLMParams +from litellm.types.utils import ( + LITELLM_IMAGE_VARIATION_PROVIDERS, + FileTypes, + LlmProviders, + all_litellm_params, +) +from litellm.utils import ( + ImageResponse, + ProviderConfigManager, + get_llm_provider, + get_optional_params_image_gen, +) + +from .utils import ImageEditRequestUtils + + +##### Image Generation ####################### +@client +async def aimage_generation(*args, **kwargs) -> ImageResponse: + """ + Asynchronously calls the `image_generation` function with the given arguments and keyword arguments. + + Parameters: + - `args` (tuple): Positional arguments to be passed to the `image_generation` function. + - `kwargs` (dict): Keyword arguments to be passed to the `image_generation` function. + + Returns: + - `response` (Any): The response returned by the `image_generation` function. + """ + loop = asyncio.get_event_loop() + model = args[0] if len(args) > 0 else kwargs["model"] + ### PASS ARGS TO Image Generation ### + kwargs["aimg_generation"] = True + custom_llm_provider = None + try: + # Use a partial function to pass your keyword arguments + func = partial(image_generation, *args, **kwargs) + + # Add the context to the function + ctx = contextvars.copy_context() + func_with_context = partial(ctx.run, func) + + _, custom_llm_provider, _, _ = get_llm_provider( + model=model, api_base=kwargs.get("api_base", None) + ) + + # Await normally + init_response = await loop.run_in_executor(None, func_with_context) + if isinstance(init_response, dict) or isinstance( + init_response, ImageResponse + ): ## CACHING SCENARIO + if isinstance(init_response, dict): + init_response = ImageResponse(**init_response) + response = init_response + elif asyncio.iscoroutine(init_response): + response = await init_response # type: ignore + else: + # Call the synchronous function using run_in_executor + response = await loop.run_in_executor(None, func_with_context) + return response + except Exception as e: + custom_llm_provider = custom_llm_provider or "openai" + raise exception_type( + model=model, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=args, + extra_kwargs=kwargs, + ) + + +@client +def image_generation( # noqa: PLR0915 + prompt: str, + model: Optional[str] = None, + n: Optional[int] = None, + quality: Optional[Union[str, ImageGenerationRequestQuality]] = None, + response_format: Optional[str] = None, + size: Optional[str] = None, + style: Optional[str] = None, + user: Optional[str] = None, + timeout=600, # default to 10 minutes + api_key: Optional[str] = None, + api_base: Optional[str] = None, + api_version: Optional[str] = None, + custom_llm_provider=None, + **kwargs, +) -> ImageResponse: + """ + Maps the https://api.openai.com/v1/images/generations endpoint. + + Currently supports just Azure + OpenAI. + """ + try: + args = locals() + aimg_generation = kwargs.get("aimg_generation", False) + litellm_call_id = kwargs.get("litellm_call_id", None) + logger_fn = kwargs.get("logger_fn", None) + mock_response: Optional[str] = kwargs.get("mock_response", None) # type: ignore + proxy_server_request = kwargs.get("proxy_server_request", None) + azure_ad_token_provider = kwargs.get("azure_ad_token_provider", None) + model_info = kwargs.get("model_info", None) + metadata = kwargs.get("metadata", {}) + litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore + client = kwargs.get("client", None) + extra_headers = kwargs.get("extra_headers", None) + headers: dict = kwargs.get("headers", None) or {} + base_model = kwargs.get("base_model", None) + if extra_headers is not None: + headers.update(extra_headers) + model_response: ImageResponse = litellm.utils.ImageResponse() + dynamic_api_key: Optional[str] = None + if model is not None or custom_llm_provider is not None: + model, custom_llm_provider, dynamic_api_key, api_base = get_llm_provider( + model=model, # type: ignore + custom_llm_provider=custom_llm_provider, + api_base=api_base, + ) + else: + model = "dall-e-2" + custom_llm_provider = "openai" # default to dall-e-2 on openai + model_response._hidden_params["model"] = model + openai_params = [ + "user", + "request_timeout", + "api_base", + "api_version", + "api_key", + "deployment_id", + "organization", + "base_url", + "default_headers", + "timeout", + "max_retries", + "n", + "quality", + "size", + "style", + ] + litellm_params = all_litellm_params + default_params = openai_params + litellm_params + non_default_params = { + k: v for k, v in kwargs.items() if k not in default_params + } # model-specific params - pass them straight to the model/provider + + image_generation_config: Optional[BaseImageGenerationConfig] = None + if ( + custom_llm_provider is not None + and custom_llm_provider in LlmProviders._member_map_.values() + ): + image_generation_config = ( + ProviderConfigManager.get_provider_image_generation_config( + model=base_model or model, + provider=LlmProviders(custom_llm_provider), + ) + ) + + optional_params = get_optional_params_image_gen( + model=base_model or model, + n=n, + quality=quality, + response_format=response_format, + size=size, + style=style, + user=user, + custom_llm_provider=custom_llm_provider, + provider_config=image_generation_config, + **non_default_params, + ) + + litellm_params_dict = get_litellm_params(**kwargs) + + logging: Logging = litellm_logging_obj + logging.update_environment_variables( + model=model, + user=user, + optional_params=optional_params, + litellm_params={ + "timeout": timeout, + "azure": False, + "litellm_call_id": litellm_call_id, + "logger_fn": logger_fn, + "proxy_server_request": proxy_server_request, + "model_info": model_info, + "metadata": metadata, + "preset_cache_key": None, + "stream_response": {}, + }, + custom_llm_provider=custom_llm_provider, + ) + if "custom_llm_provider" not in logging.model_call_details: + logging.model_call_details["custom_llm_provider"] = custom_llm_provider + if mock_response is not None: + return mock_image_generation(model=model, mock_response=mock_response) + + if custom_llm_provider == "azure": + # azure configs + api_type = get_secret_str("AZURE_API_TYPE") or "azure" + + api_base = api_base or litellm.api_base or get_secret_str("AZURE_API_BASE") + + api_version = ( + api_version + or litellm.api_version + or get_secret_str("AZURE_API_VERSION") + ) + + api_key = ( + api_key + or litellm.api_key + or litellm.azure_key + or get_secret_str("AZURE_OPENAI_API_KEY") + or get_secret_str("AZURE_API_KEY") + ) + + azure_ad_token = optional_params.pop( + "azure_ad_token", None + ) or get_secret_str("AZURE_AD_TOKEN") + + default_headers = { + "Content-Type": "application/json;", + "api-key": api_key, + } + for k, v in default_headers.items(): + if k not in headers: + headers[k] = v + + model_response = azure_chat_completions.image_generation( + model=model, + prompt=prompt, + timeout=timeout, + api_key=api_key, + api_base=api_base, + azure_ad_token=azure_ad_token, + azure_ad_token_provider=azure_ad_token_provider, + logging_obj=litellm_logging_obj, + optional_params=optional_params, + model_response=model_response, + api_version=api_version, + aimg_generation=aimg_generation, + client=client, + headers=headers, + litellm_params=litellm_params_dict, + ) + elif ( + custom_llm_provider == "openai" + or custom_llm_provider in litellm.openai_compatible_providers + ): + model_response = openai_chat_completions.image_generation( + model=model, + prompt=prompt, + timeout=timeout, + api_key=api_key or dynamic_api_key, + api_base=api_base, + logging_obj=litellm_logging_obj, + optional_params=optional_params, + model_response=model_response, + aimg_generation=aimg_generation, + client=client, + ) + elif custom_llm_provider == "bedrock": + if model is None: + raise Exception("Model needs to be set for bedrock") + model_response = bedrock_image_generation.image_generation( # type: ignore + model=model, + prompt=prompt, + timeout=timeout, + logging_obj=litellm_logging_obj, + optional_params=optional_params, + model_response=model_response, + aimg_generation=aimg_generation, + client=client, + ) + elif custom_llm_provider == "vertex_ai": + vertex_ai_project = ( + optional_params.pop("vertex_project", None) + or optional_params.pop("vertex_ai_project", None) + or litellm.vertex_project + or get_secret_str("VERTEXAI_PROJECT") + ) + vertex_ai_location = ( + optional_params.pop("vertex_location", None) + or optional_params.pop("vertex_ai_location", None) + or litellm.vertex_location + or get_secret_str("VERTEXAI_LOCATION") + ) + vertex_credentials = ( + optional_params.pop("vertex_credentials", None) + or optional_params.pop("vertex_ai_credentials", None) + or get_secret_str("VERTEXAI_CREDENTIALS") + ) + + api_base = ( + api_base + or litellm.api_base + or get_secret_str("VERTEXAI_API_BASE") + or get_secret_str("VERTEX_API_BASE") + ) + + model_response = vertex_image_generation.image_generation( + model=model, + prompt=prompt, + timeout=timeout, + logging_obj=litellm_logging_obj, + optional_params=optional_params, + model_response=model_response, + vertex_project=vertex_ai_project, + vertex_location=vertex_ai_location, + vertex_credentials=vertex_credentials, + aimg_generation=aimg_generation, + api_base=api_base, + client=client, + ) + elif ( + custom_llm_provider in litellm._custom_providers + ): # Assume custom LLM provider + # Get the Custom Handler + custom_handler: Optional[CustomLLM] = None + for item in litellm.custom_provider_map: + if item["provider"] == custom_llm_provider: + custom_handler = item["custom_handler"] + + if custom_handler is None: + raise LiteLLMUnknownProvider( + model=model, custom_llm_provider=custom_llm_provider + ) + + ## ROUTE LLM CALL ## + if aimg_generation is True: + async_custom_client: Optional[AsyncHTTPHandler] = None + if client is not None and isinstance(client, AsyncHTTPHandler): + async_custom_client = client + + ## CALL FUNCTION + model_response = custom_handler.aimage_generation( # type: ignore + model=model, + prompt=prompt, + api_key=api_key, + api_base=api_base, + model_response=model_response, + optional_params=optional_params, + logging_obj=litellm_logging_obj, + timeout=timeout, + client=async_custom_client, + ) + else: + custom_client: Optional[HTTPHandler] = None + if client is not None and isinstance(client, HTTPHandler): + custom_client = client + + ## CALL FUNCTION + model_response = custom_handler.image_generation( + model=model, + prompt=prompt, + api_key=api_key, + api_base=api_base, + model_response=model_response, + optional_params=optional_params, + logging_obj=litellm_logging_obj, + timeout=timeout, + client=custom_client, + ) + + return model_response + except Exception as e: + ## Map to OpenAI Exception + raise exception_type( + model=model, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=locals(), + extra_kwargs=kwargs, + ) + + +@client +async def aimage_variation(*args, **kwargs) -> ImageResponse: + """ + Asynchronously calls the `image_variation` function with the given arguments and keyword arguments. + + Parameters: + - `args` (tuple): Positional arguments to be passed to the `image_variation` function. + - `kwargs` (dict): Keyword arguments to be passed to the `image_variation` function. + + Returns: + - `response` (Any): The response returned by the `image_variation` function. + """ + loop = asyncio.get_event_loop() + model = kwargs.get("model", None) + custom_llm_provider = kwargs.get("custom_llm_provider", None) + ### PASS ARGS TO Image Generation ### + kwargs["async_call"] = True + try: + # Use a partial function to pass your keyword arguments + func = partial(image_variation, *args, **kwargs) + + # Add the context to the function + ctx = contextvars.copy_context() + func_with_context = partial(ctx.run, func) + + if custom_llm_provider is None and model is not None: + _, custom_llm_provider, _, _ = get_llm_provider( + model=model, api_base=kwargs.get("api_base", None) + ) + + # Await normally + init_response = await loop.run_in_executor(None, func_with_context) + if isinstance(init_response, dict) or isinstance( + init_response, ImageResponse + ): ## CACHING SCENARIO + if isinstance(init_response, dict): + init_response = ImageResponse(**init_response) + response = init_response + elif asyncio.iscoroutine(init_response): + response = await init_response # type: ignore + else: + # Call the synchronous function using run_in_executor + response = await loop.run_in_executor(None, func_with_context) + return response + except Exception as e: + custom_llm_provider = custom_llm_provider or "openai" + raise exception_type( + model=model, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=args, + extra_kwargs=kwargs, + ) + + +@client +def image_variation( + image: FileTypes, + model: str = "dall-e-2", # set to dall-e-2 by default - like OpenAI. + n: int = 1, + response_format: Literal["url", "b64_json"] = "url", + size: Optional[str] = None, + user: Optional[str] = None, + **kwargs, +) -> ImageResponse: + # get non-default params + client = kwargs.get("client", None) + # get logging object + litellm_logging_obj = cast(LiteLLMLoggingObj, kwargs.get("litellm_logging_obj")) + + # get the litellm params + litellm_params = get_litellm_params(**kwargs) + # get the custom llm provider + model, custom_llm_provider, dynamic_api_key, api_base = get_llm_provider( + model=model, + custom_llm_provider=litellm_params.get("custom_llm_provider", None), + api_base=litellm_params.get("api_base", None), + api_key=litellm_params.get("api_key", None), + ) + + # route to the correct provider w/ the params + try: + llm_provider = LlmProviders(custom_llm_provider) + image_variation_provider = LITELLM_IMAGE_VARIATION_PROVIDERS(llm_provider) + except ValueError: + raise ValueError( + f"Invalid image variation provider: {custom_llm_provider}. Supported providers are: {LITELLM_IMAGE_VARIATION_PROVIDERS}" + ) + model_response = ImageResponse() + + response: Optional[ImageResponse] = None + + provider_config = ProviderConfigManager.get_provider_model_info( + model=model or "", # openai defaults to dall-e-2 + provider=llm_provider, + ) + + if provider_config is None: + raise ValueError( + f"image variation provider has no known model info config - required for getting api keys, etc.: {custom_llm_provider}. Supported providers are: {LITELLM_IMAGE_VARIATION_PROVIDERS}" + ) + + api_key = provider_config.get_api_key(litellm_params.get("api_key", None)) + api_base = provider_config.get_api_base(litellm_params.get("api_base", None)) + + if image_variation_provider == LITELLM_IMAGE_VARIATION_PROVIDERS.OPENAI: + if api_key is None: + raise ValueError("API key is required for OpenAI image variations") + if api_base is None: + raise ValueError("API base is required for OpenAI image variations") + + response = openai_image_variations.image_variations( + model_response=model_response, + api_key=api_key, + api_base=api_base, + model=model, + image=image, + timeout=litellm_params.get("timeout", None), + custom_llm_provider=custom_llm_provider, + logging_obj=litellm_logging_obj, + optional_params={}, + litellm_params=litellm_params, + ) + elif image_variation_provider == LITELLM_IMAGE_VARIATION_PROVIDERS.TOPAZ: + if api_key is None: + raise ValueError("API key is required for Topaz image variations") + if api_base is None: + raise ValueError("API base is required for Topaz image variations") + + response = base_llm_aiohttp_handler.image_variations( + model_response=model_response, + api_key=api_key, + api_base=api_base, + model=model, + image=image, + timeout=litellm_params.get("timeout", None) or DEFAULT_REQUEST_TIMEOUT, + custom_llm_provider=custom_llm_provider, + logging_obj=litellm_logging_obj, + optional_params={}, + litellm_params=litellm_params, + client=client, + ) + + # return the response + if response is None: + raise ValueError( + f"Invalid image variation provider: {custom_llm_provider}. Supported providers are: {LITELLM_IMAGE_VARIATION_PROVIDERS}" + ) + return response + + +@client +def image_edit( + image: FileTypes, + prompt: str, + model: Optional[str] = None, + mask: Optional[str] = None, + n: Optional[int] = None, + quality: Optional[Union[str, ImageGenerationRequestQuality]] = None, + response_format: Optional[str] = None, + size: Optional[str] = None, + user: Optional[str] = None, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Optional[Dict[str, Any]] = None, + extra_query: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + # LiteLLM specific params, + custom_llm_provider: Optional[str] = None, + **kwargs, +) -> Union[ImageResponse, Coroutine[Any, Any, ImageResponse]]: + """ + Maps the image edit functionality, similar to OpenAI's images/edits endpoint. + """ + local_vars = locals() + try: + litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore + litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None) + _is_async = kwargs.pop("adelete_responses", False) is True + + # get llm provider logic + litellm_params = GenericLiteLLMParams(**kwargs) + model, custom_llm_provider, _, _ = get_llm_provider( + model=model or DEFAULT_IMAGE_ENDPOINT_MODEL, + custom_llm_provider=custom_llm_provider, + ) + + # get provider config + image_edit_provider_config: Optional[ + BaseImageEditConfig + ] = ProviderConfigManager.get_provider_image_edit_config( + model=model, + provider=litellm.LlmProviders(custom_llm_provider), + ) + + if image_edit_provider_config is None: + raise ValueError(f"image edit is not supported for {custom_llm_provider}") + + local_vars.update(kwargs) + # Get ImageEditOptionalRequestParams with only valid parameters + image_edit_optional_params: ImageEditOptionalRequestParams = ( + ImageEditRequestUtils.get_requested_image_edit_optional_param(local_vars) + ) + + # Get optional parameters for the responses API + image_edit_request_params: Dict = ( + ImageEditRequestUtils.get_optional_params_image_edit( + model=model, + image_edit_provider_config=image_edit_provider_config, + image_edit_optional_params=image_edit_optional_params, + ) + ) + + # Pre Call logging + litellm_logging_obj.update_environment_variables( + model=model, + user=user, + optional_params=dict(image_edit_request_params), + litellm_params={ + "litellm_call_id": litellm_call_id, + **image_edit_request_params, + }, + custom_llm_provider=custom_llm_provider, + ) + + # Call the handler with _is_async flag instead of directly calling the async handler + return base_llm_http_handler.image_edit_handler( + model=model, + image=image, + prompt=prompt, + image_edit_provider_config=image_edit_provider_config, + image_edit_optional_request_params=image_edit_request_params, + custom_llm_provider=custom_llm_provider, + litellm_params=litellm_params, + logging_obj=litellm_logging_obj, + extra_headers=extra_headers, + extra_body=extra_body, + timeout=timeout or DEFAULT_REQUEST_TIMEOUT, + _is_async=_is_async, + client=kwargs.get("client"), + ) + + except Exception as e: + raise litellm.exception_type( + model=None, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=local_vars, + extra_kwargs=kwargs, + ) + + +@client +async def aimage_edit( + image: FileTypes, + model: str, + prompt: str, + mask: Optional[str] = None, + n: Optional[int] = None, + quality: Optional[Union[str, ImageGenerationRequestQuality]] = None, + response_format: Optional[str] = None, + size: Optional[str] = None, + user: Optional[str] = None, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Optional[Dict[str, Any]] = None, + extra_query: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + timeout: Optional[Union[float, httpx.Timeout]] = None, + # LiteLLM specific params, + custom_llm_provider: Optional[str] = None, + **kwargs, +) -> ImageResponse: + """ + Asynchronously calls the `image_edit` function with the given arguments and keyword arguments. + + Parameters: + - `args` (tuple): Positional arguments to be passed to the `image_edit` function. + - `kwargs` (dict): Keyword arguments to be passed to the `image_edit` function. + + Returns: + - `response` (Any): The response returned by the `image_edit` function. + """ + local_vars = locals() + try: + loop = asyncio.get_event_loop() + kwargs["async_call"] = True + + # get custom llm provider so we can use this for mapping exceptions + if custom_llm_provider is None: + _, custom_llm_provider, _, _ = litellm.get_llm_provider( + model=model, api_base=local_vars.get("base_url", None) + ) + + func = partial( + image_edit, + image=image, + prompt=prompt, + mask=mask, + model=model, + n=n, + quality=quality, + response_format=response_format, + size=size, + user=user, + timeout=timeout, + custom_llm_provider=custom_llm_provider, + **kwargs, + ) + + ctx = contextvars.copy_context() + func_with_context = partial(ctx.run, func) + init_response = await loop.run_in_executor(None, func_with_context) + + if asyncio.iscoroutine(init_response): + response = await init_response + else: + response = init_response + + return response + except Exception as e: + raise litellm.exception_type( + model=None, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=local_vars, + extra_kwargs=kwargs, + ) diff --git a/litellm/images/utils.py b/litellm/images/utils.py new file mode 100644 index 0000000000..4bf338605a --- /dev/null +++ b/litellm/images/utils.py @@ -0,0 +1,71 @@ +from typing import Any, Dict, cast, get_type_hints + +import litellm +from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig +from litellm.types.images.main import ImageEditOptionalRequestParams + + +class ImageEditRequestUtils: + @staticmethod + def get_optional_params_image_edit( + model: str, + image_edit_provider_config: BaseImageEditConfig, + image_edit_optional_params: ImageEditOptionalRequestParams, + ) -> Dict: + """ + Get optional parameters for the image edit API. + + Args: + params: Dictionary of all parameters + model: The model name + image_edit_provider_config: The provider configuration for image edit API + + Returns: + A dictionary of supported parameters for the image edit API + """ + # Remove None values and internal parameters + + # Get supported parameters for the model + supported_params = image_edit_provider_config.get_supported_openai_params(model) + + # Check for unsupported parameters + unsupported_params = [ + param + for param in image_edit_optional_params + if param not in supported_params + ] + + if unsupported_params: + raise litellm.UnsupportedParamsError( + model=model, + message=f"The following parameters are not supported for model {model}: {', '.join(unsupported_params)}", + ) + + # Map parameters to provider-specific format + mapped_params = image_edit_provider_config.map_openai_params( + image_edit_optional_params=image_edit_optional_params, + model=model, + drop_params=litellm.drop_params, + ) + + return mapped_params + + @staticmethod + def get_requested_image_edit_optional_param( + params: Dict[str, Any], + ) -> ImageEditOptionalRequestParams: + """ + Filter parameters to only include those defined in ImageEditOptionalRequestParams. + + Args: + params: Dictionary of parameters to filter + + Returns: + ImageEditOptionalRequestParams instance with only the valid parameters + """ + valid_keys = get_type_hints(ImageEditOptionalRequestParams).keys() + filtered_params = { + k: v for k, v in params.items() if k in valid_keys and v is not None + } + + return cast(ImageEditOptionalRequestParams, filtered_params) diff --git a/litellm/llms/base_llm/__init__.py b/litellm/llms/base_llm/__init__.py index cd682a0dbe..187c985fd6 100644 --- a/litellm/llms/base_llm/__init__.py +++ b/litellm/llms/base_llm/__init__.py @@ -2,6 +2,7 @@ from .anthropic_messages.transformation import BaseAnthropicMessagesConfig from .audio_transcription.transformation import BaseAudioTranscriptionConfig from .chat.transformation import BaseConfig from .embedding.transformation import BaseEmbeddingConfig +from .image_edit.transformation import BaseImageEditConfig from .image_generation.transformation import BaseImageGenerationConfig __all__ = [ @@ -10,4 +11,5 @@ __all__ = [ "BaseAudioTranscriptionConfig", "BaseAnthropicMessagesConfig", "BaseEmbeddingConfig", + "BaseImageEditConfig", ] diff --git a/litellm/llms/base_llm/image_edit/transformation.py b/litellm/llms/base_llm/image_edit/transformation.py new file mode 100644 index 0000000000..d471f496af --- /dev/null +++ b/litellm/llms/base_llm/image_edit/transformation.py @@ -0,0 +1,120 @@ +import types +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple + +import httpx +from httpx._types import RequestFiles + +from litellm.types.images.main import ImageEditOptionalRequestParams +from litellm.types.responses.main import * +from litellm.types.router import GenericLiteLLMParams +from litellm.types.utils import FileTypes + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj + from litellm.utils import ImageResponse as _ImageResponse + + from ..chat.transformation import BaseLLMException as _BaseLLMException + + LiteLLMLoggingObj = _LiteLLMLoggingObj + BaseLLMException = _BaseLLMException + ImageResponse = _ImageResponse +else: + LiteLLMLoggingObj = Any + BaseLLMException = Any + ImageResponse = Any + + +class BaseImageEditConfig(ABC): + def __init__(self): + pass + + @classmethod + def get_config(cls): + return { + k: v + for k, v in cls.__dict__.items() + if not k.startswith("__") + and not k.startswith("_abc") + and not isinstance( + v, + ( + types.FunctionType, + types.BuiltinFunctionType, + classmethod, + staticmethod, + ), + ) + and v is not None + } + + @abstractmethod + def get_supported_openai_params(self, model: str) -> list: + pass + + @abstractmethod + def map_openai_params( + self, + image_edit_optional_params: ImageEditOptionalRequestParams, + model: str, + drop_params: bool, + ) -> Dict: + pass + + @abstractmethod + def validate_environment( + self, + headers: dict, + model: str, + api_key: Optional[str] = None, + ) -> dict: + return {} + + @abstractmethod + def get_complete_url( + self, + api_base: Optional[str], + litellm_params: dict, + ) -> str: + """ + OPTIONAL + + Get the complete url for the request + + Some providers need `model` in `api_base` + """ + if api_base is None: + raise ValueError("api_base is required") + return api_base + + @abstractmethod + def transform_image_edit_request( + self, + model: str, + prompt: str, + image: FileTypes, + image_edit_optional_request_params: Dict, + litellm_params: GenericLiteLLMParams, + headers: dict, + ) -> Tuple[Dict, RequestFiles]: + pass + + @abstractmethod + def transform_image_edit_response( + self, + model: str, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + ) -> ImageResponse: + pass + + def get_error_class( + self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers] + ) -> BaseLLMException: + from ..chat.transformation import BaseLLMException + + raise BaseLLMException( + status_code=status_code, + message=error_message, + headers=headers, + ) diff --git a/litellm/llms/custom_httpx/aiohttp_handler.py b/litellm/llms/custom_httpx/aiohttp_handler.py index 13141fc19a..5a1d420865 100644 --- a/litellm/llms/custom_httpx/aiohttp_handler.py +++ b/litellm/llms/custom_httpx/aiohttp_handler.py @@ -102,7 +102,7 @@ class BaseLLMAIOHTTPHandler: api_base: str, headers: dict, data: dict, - timeout: Union[float, httpx.Timeout], + timeout: Optional[Union[float, httpx.Timeout]], litellm_params: dict, stream: bool = False, files: Optional[dict] = None, diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index f99e04ab9d..d026810c44 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Union import httpx from httpx import USE_CLIENT_DEFAULT, AsyncHTTPTransport, HTTPTransport +from httpx._types import RequestFiles import litellm from litellm.constants import _DEFAULT_TTL_FOR_HTTPX_CLIENTS @@ -199,6 +200,7 @@ class AsyncHTTPHandler: timeout: Optional[Union[float, httpx.Timeout]] = None, stream: bool = False, logging_obj: Optional[LiteLLMLoggingObject] = None, + files: Optional[RequestFiles] = None, ): start_time = time.time() try: @@ -206,7 +208,14 @@ class AsyncHTTPHandler: timeout = self.timeout req = self.client.build_request( - "POST", url, data=data, json=json, params=params, headers=headers, timeout=timeout # type: ignore + "POST", + url, + data=data, # type: ignore + json=json, + params=params, + headers=headers, + timeout=timeout, + files=files, ) response = await self.client.send(req, stream=stream) response.raise_for_status() @@ -533,7 +542,7 @@ class HTTPHandler: headers: Optional[dict] = None, stream: bool = False, timeout: Optional[Union[float, httpx.Timeout]] = None, - files: Optional[dict] = None, + files: Optional[Union[dict, RequestFiles]] = None, content: Any = None, logging_obj: Optional[LiteLLMLoggingObject] = None, ): diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 4cf89accfc..cde532e7b0 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -30,6 +30,7 @@ from litellm.llms.base_llm.base_model_iterator import MockResponseIterator from litellm.llms.base_llm.chat.transformation import BaseConfig from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig from litellm.llms.base_llm.files.transformation import BaseFilesConfig +from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig from litellm.llms.base_llm.realtime.transformation import BaseRealtimeConfig from litellm.llms.base_llm.rerank.transformation import BaseRerankConfig from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig @@ -58,7 +59,12 @@ from litellm.types.rerank import OptionalRerankParams, RerankResponse from litellm.types.responses.main import DeleteResponseResult from litellm.types.router import GenericLiteLLMParams from litellm.types.utils import EmbeddingResponse, FileTypes, TranscriptionResponse -from litellm.utils import CustomStreamWrapper, ModelResponse, ProviderConfigManager +from litellm.utils import ( + CustomStreamWrapper, + ImageResponse, + ModelResponse, + ProviderConfigManager, +) if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj @@ -2018,7 +2024,9 @@ class BaseLLMHTTPHandler: def _handle_error( self, e: Exception, - provider_config: Union[BaseConfig, BaseRerankConfig, BaseResponsesAPIConfig], + provider_config: Union[ + BaseConfig, BaseRerankConfig, BaseResponsesAPIConfig, BaseImageEditConfig + ], ): status_code = getattr(e, "status_code", 500) error_headers = getattr(e, "headers", None) @@ -2098,3 +2106,191 @@ class BaseLLMHTTPHandler: raise Exception( f"Unexpected error while closing WebSocket: {close_error}" ) + + def image_edit_handler( + self, + model: str, + image: Any, + prompt: str, + image_edit_provider_config: BaseImageEditConfig, + image_edit_optional_request_params: Dict, + custom_llm_provider: str, + litellm_params: GenericLiteLLMParams, + logging_obj: LiteLLMLoggingObj, + timeout: Union[float, httpx.Timeout], + extra_headers: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, + _is_async: bool = False, + fake_stream: bool = False, + litellm_metadata: Optional[Dict[str, Any]] = None, + ) -> Union[ImageResponse, Coroutine[Any, Any, ImageResponse],]: + """ + + Handles image edit requests. + When _is_async=True, returns a coroutine instead of making the call directly. + """ + if _is_async: + # Return the async coroutine if called with _is_async=True + return self.async_image_edit_handler( + model=model, + image=image, + prompt=prompt, + image_edit_provider_config=image_edit_provider_config, + image_edit_optional_request_params=image_edit_optional_request_params, + custom_llm_provider=custom_llm_provider, + litellm_params=litellm_params, + logging_obj=logging_obj, + extra_headers=extra_headers, + extra_body=extra_body, + timeout=timeout, + client=client if isinstance(client, AsyncHTTPHandler) else None, + fake_stream=fake_stream, + litellm_metadata=litellm_metadata, + ) + + if client is None or not isinstance(client, HTTPHandler): + sync_httpx_client = _get_httpx_client( + params={"ssl_verify": litellm_params.get("ssl_verify", None)} + ) + else: + sync_httpx_client = client + + headers = image_edit_provider_config.validate_environment( + api_key=litellm_params.api_key, + headers=image_edit_optional_request_params.get("extra_headers", {}) or {}, + model=model, + ) + + if extra_headers: + headers.update(extra_headers) + + api_base = image_edit_provider_config.get_complete_url( + api_base=litellm_params.api_base, + litellm_params=dict(litellm_params), + ) + + data, files = image_edit_provider_config.transform_image_edit_request( + model=model, + image=image, + prompt=prompt, + image_edit_optional_request_params=image_edit_optional_request_params, + litellm_params=litellm_params, + headers=headers, + ) + + ## LOGGING + logging_obj.pre_call( + input=prompt, + api_key="", + additional_args={ + "complete_input_dict": data, + "api_base": api_base, + "headers": headers, + }, + ) + + try: + response = sync_httpx_client.post( + url=api_base, + headers=headers, + data=data, + files=files, + timeout=timeout, + ) + + except Exception as e: + raise self._handle_error( + e=e, + provider_config=image_edit_provider_config, + ) + + return image_edit_provider_config.transform_image_edit_response( + model=model, + raw_response=response, + logging_obj=logging_obj, + ) + + async def async_image_edit_handler( + self, + model: str, + image: FileTypes, + prompt: str, + image_edit_provider_config: BaseImageEditConfig, + image_edit_optional_request_params: Dict, + custom_llm_provider: str, + litellm_params: GenericLiteLLMParams, + logging_obj: LiteLLMLoggingObj, + timeout: Union[float, httpx.Timeout], + extra_headers: Optional[Dict[str, Any]] = None, + extra_body: Optional[Dict[str, Any]] = None, + client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None, + fake_stream: bool = False, + litellm_metadata: Optional[Dict[str, Any]] = None, + ) -> ImageResponse: + """ + Async version of the image edit handler. + Uses async HTTP client to make requests. + """ + if client is None or not isinstance(client, AsyncHTTPHandler): + async_httpx_client = get_async_httpx_client( + llm_provider=litellm.LlmProviders(custom_llm_provider), + params={"ssl_verify": litellm_params.get("ssl_verify", None)}, + ) + else: + async_httpx_client = client + + headers = image_edit_provider_config.validate_environment( + api_key=litellm_params.api_key, + headers=image_edit_optional_request_params.get("extra_headers", {}) or {}, + model=model, + ) + + if extra_headers: + headers.update(extra_headers) + + api_base = image_edit_provider_config.get_complete_url( + api_base=litellm_params.api_base, + litellm_params=dict(litellm_params), + ) + + data, files = image_edit_provider_config.transform_image_edit_request( + model=model, + image=image, + prompt=prompt, + image_edit_optional_request_params=image_edit_optional_request_params, + litellm_params=litellm_params, + headers=headers, + ) + + ## LOGGING + logging_obj.pre_call( + input=prompt, + api_key="", + additional_args={ + "complete_input_dict": data, + "api_base": api_base, + "headers": headers, + }, + ) + + try: + response = await async_httpx_client.post( + url=api_base, + headers=headers, + data=data, + files=files, + timeout=timeout, + ) + + except Exception as e: + raise self._handle_error( + e=e, + provider_config=image_edit_provider_config, + ) + + return image_edit_provider_config.transform_image_edit_response( + model=model, + raw_response=response, + logging_obj=logging_obj, + ) diff --git a/litellm/llms/openai/image_edit/transformation.py b/litellm/llms/openai/image_edit/transformation.py new file mode 100644 index 0000000000..bcec4aa029 --- /dev/null +++ b/litellm/llms/openai/image_edit/transformation.py @@ -0,0 +1,147 @@ +from io import BufferedReader +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast + +import httpx +from httpx._types import RequestFiles + +import litellm +from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig +from litellm.secret_managers.main import get_secret_str +from litellm.types.images.main import ( + ImageEditOptionalRequestParams, + ImageEditRequestParams, +) +from litellm.types.llms.openai import FileTypes +from litellm.types.router import GenericLiteLLMParams +from litellm.utils import ImageResponse + +from ..common_utils import OpenAIError + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj + + LiteLLMLoggingObj = _LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + + +class OpenAIImageEditConfig(BaseImageEditConfig): + def get_supported_openai_params(self, model: str) -> list: + """ + All OpenAI Image Edits params are supported + """ + return [ + "image", + "prompt", + "background", + "mask", + "model", + "n", + "quality", + "response_format", + "size", + "user", + "extra_headers", + "extra_query", + "extra_body", + "timeout", + ] + + def map_openai_params( + self, + image_edit_optional_params: ImageEditOptionalRequestParams, + model: str, + drop_params: bool, + ) -> Dict: + """No mapping applied since inputs are in OpenAI spec already""" + return dict(image_edit_optional_params) + + def transform_image_edit_request( + self, + model: str, + prompt: str, + image: FileTypes, + image_edit_optional_request_params: Dict, + litellm_params: GenericLiteLLMParams, + headers: dict, + ) -> Tuple[Dict, RequestFiles]: + """ + No transform applied since inputs are in OpenAI spec already + + This handles buffered readers as images to be sent as multipart/form-data for OpenAI + """ + request = ImageEditRequestParams( + model=model, + image=image, + prompt=prompt, + **image_edit_optional_request_params, + ) + request_dict = cast(Dict, request) + + ######################################################### + # Separate images as `files` and send other parameters as `data` + ######################################################### + _images = request_dict.get("image") or [] + data_without_images = {k: v for k, v in request_dict.items() if k != "image"} + files_list: List[Tuple[str, Any]] = [] + for _image in _images: + if isinstance(_image, BufferedReader): + files_list.append(("image[]", (_image.name, _image, "image/png"))) + else: + files_list.append(("image[]", (_image, "image/png"))) + return data_without_images, files_list + + def transform_image_edit_response( + self, + model: str, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + ) -> ImageResponse: + """No transform applied since outputs are in OpenAI spec already""" + try: + raw_response_json = raw_response.json() + except Exception: + raise OpenAIError( + message=raw_response.text, status_code=raw_response.status_code + ) + return ImageResponse(**raw_response_json) + + def validate_environment( + self, + headers: dict, + model: str, + api_key: Optional[str] = None, + ) -> dict: + api_key = ( + api_key + or litellm.api_key + or litellm.openai_key + or get_secret_str("OPENAI_API_KEY") + ) + headers.update( + { + "Authorization": f"Bearer {api_key}", + } + ) + return headers + + def get_complete_url( + self, + api_base: Optional[str], + litellm_params: dict, + ) -> str: + """ + Get the endpoint for OpenAI responses API + """ + api_base = ( + api_base + or litellm.api_base + or get_secret_str("OPENAI_BASE_URL") + or get_secret_str("OPENAI_API_BASE") + or "https://api.openai.com/v1" + ) + + # Remove trailing slashes + api_base = api_base.rstrip("/") + + return f"{api_base}/images/edits" diff --git a/litellm/llms/openai/image_variations/handler.py b/litellm/llms/openai/image_variations/handler.py index f738115a29..8b96fb6ef7 100644 --- a/litellm/llms/openai/image_variations/handler.py +++ b/litellm/llms/openai/image_variations/handler.py @@ -50,7 +50,7 @@ class OpenAIImageVariationsHandler: data: dict, headers: dict, model: Optional[str], - timeout: float, + timeout: Optional[float], max_retries: int, logging_obj: LiteLLMLoggingObj, model_response: ImageResponse, @@ -123,7 +123,7 @@ class OpenAIImageVariationsHandler: api_base: str, model: Optional[str], image: FileTypes, - timeout: float, + timeout: Optional[float], custom_llm_provider: str, logging_obj: LiteLLMLoggingObj, optional_params: dict, diff --git a/litellm/main.py b/litellm/main.py index 68589d7127..7cae5acd97 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -183,12 +183,10 @@ from .types.llms.openai import ( ChatCompletionPredictionContentParam, ChatCompletionUserMessage, HttpxBinaryResponseContent, - ImageGenerationRequestQuality, OpenAIModerationResponse, OpenAIWebSearchOptions, ) from .types.utils import ( - LITELLM_IMAGE_VARIATION_PROVIDERS, AdapterCompletionStreamWrapper, ChatCompletionMessageToolCall, CompletionTokensDetails, @@ -204,7 +202,6 @@ encoding = tiktoken.get_encoding("cl100k_base") from litellm.utils import ( Choices, EmbeddingResponse, - ImageResponse, Message, ModelResponse, TextChoices, @@ -4578,516 +4575,6 @@ async def amoderation( ) -##### Image Generation ####################### -@client -async def aimage_generation(*args, **kwargs) -> ImageResponse: - """ - Asynchronously calls the `image_generation` function with the given arguments and keyword arguments. - - Parameters: - - `args` (tuple): Positional arguments to be passed to the `image_generation` function. - - `kwargs` (dict): Keyword arguments to be passed to the `image_generation` function. - - Returns: - - `response` (Any): The response returned by the `image_generation` function. - """ - loop = asyncio.get_event_loop() - model = args[0] if len(args) > 0 else kwargs["model"] - ### PASS ARGS TO Image Generation ### - kwargs["aimg_generation"] = True - custom_llm_provider = None - try: - # Use a partial function to pass your keyword arguments - func = partial(image_generation, *args, **kwargs) - - # Add the context to the function - ctx = contextvars.copy_context() - func_with_context = partial(ctx.run, func) - - _, custom_llm_provider, _, _ = get_llm_provider( - model=model, api_base=kwargs.get("api_base", None) - ) - - # Await normally - init_response = await loop.run_in_executor(None, func_with_context) - if isinstance(init_response, dict) or isinstance( - init_response, ImageResponse - ): ## CACHING SCENARIO - if isinstance(init_response, dict): - init_response = ImageResponse(**init_response) - response = init_response - elif asyncio.iscoroutine(init_response): - response = await init_response # type: ignore - else: - # Call the synchronous function using run_in_executor - response = await loop.run_in_executor(None, func_with_context) - return response - except Exception as e: - custom_llm_provider = custom_llm_provider or "openai" - raise exception_type( - model=model, - custom_llm_provider=custom_llm_provider, - original_exception=e, - completion_kwargs=args, - extra_kwargs=kwargs, - ) - - -@client -def image_generation( # noqa: PLR0915 - prompt: str, - model: Optional[str] = None, - n: Optional[int] = None, - quality: Optional[Union[str, ImageGenerationRequestQuality]] = None, - response_format: Optional[str] = None, - size: Optional[str] = None, - style: Optional[str] = None, - user: Optional[str] = None, - timeout=600, # default to 10 minutes - api_key: Optional[str] = None, - api_base: Optional[str] = None, - api_version: Optional[str] = None, - custom_llm_provider=None, - **kwargs, -) -> ImageResponse: - """ - Maps the https://api.openai.com/v1/images/generations endpoint. - - Currently supports just Azure + OpenAI. - """ - try: - args = locals() - aimg_generation = kwargs.get("aimg_generation", False) - litellm_call_id = kwargs.get("litellm_call_id", None) - logger_fn = kwargs.get("logger_fn", None) - mock_response: Optional[str] = kwargs.get("mock_response", None) # type: ignore - proxy_server_request = kwargs.get("proxy_server_request", None) - azure_ad_token_provider = kwargs.get("azure_ad_token_provider", None) - model_info = kwargs.get("model_info", None) - metadata = kwargs.get("metadata", {}) - litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore - client = kwargs.get("client", None) - extra_headers = kwargs.get("extra_headers", None) - headers: dict = kwargs.get("headers", None) or {} - base_model = kwargs.get("base_model", None) - if extra_headers is not None: - headers.update(extra_headers) - model_response: ImageResponse = litellm.utils.ImageResponse() - dynamic_api_key: Optional[str] = None - if model is not None or custom_llm_provider is not None: - model, custom_llm_provider, dynamic_api_key, api_base = get_llm_provider( - model=model, # type: ignore - custom_llm_provider=custom_llm_provider, - api_base=api_base, - ) - else: - model = "dall-e-2" - custom_llm_provider = "openai" # default to dall-e-2 on openai - model_response._hidden_params["model"] = model - openai_params = [ - "user", - "request_timeout", - "api_base", - "api_version", - "api_key", - "deployment_id", - "organization", - "base_url", - "default_headers", - "timeout", - "max_retries", - "n", - "quality", - "size", - "style", - ] - litellm_params = all_litellm_params - default_params = openai_params + litellm_params - non_default_params = { - k: v for k, v in kwargs.items() if k not in default_params - } # model-specific params - pass them straight to the model/provider - - image_generation_config: Optional[BaseImageGenerationConfig] = None - if ( - custom_llm_provider is not None - and custom_llm_provider in LlmProviders._member_map_.values() - ): - image_generation_config = ( - ProviderConfigManager.get_provider_image_generation_config( - model=base_model or model, - provider=LlmProviders(custom_llm_provider), - ) - ) - - optional_params = get_optional_params_image_gen( - model=base_model or model, - n=n, - quality=quality, - response_format=response_format, - size=size, - style=style, - user=user, - custom_llm_provider=custom_llm_provider, - provider_config=image_generation_config, - **non_default_params, - ) - - litellm_params_dict = get_litellm_params(**kwargs) - - logging: Logging = litellm_logging_obj - logging.update_environment_variables( - model=model, - user=user, - optional_params=optional_params, - litellm_params={ - "timeout": timeout, - "azure": False, - "litellm_call_id": litellm_call_id, - "logger_fn": logger_fn, - "proxy_server_request": proxy_server_request, - "model_info": model_info, - "metadata": metadata, - "preset_cache_key": None, - "stream_response": {}, - }, - custom_llm_provider=custom_llm_provider, - ) - if "custom_llm_provider" not in logging.model_call_details: - logging.model_call_details["custom_llm_provider"] = custom_llm_provider - if mock_response is not None: - return mock_image_generation(model=model, mock_response=mock_response) - - if custom_llm_provider == "azure": - # azure configs - api_type = get_secret_str("AZURE_API_TYPE") or "azure" - - api_base = api_base or litellm.api_base or get_secret_str("AZURE_API_BASE") - - api_version = ( - api_version - or litellm.api_version - or get_secret_str("AZURE_API_VERSION") - ) - - api_key = ( - api_key - or litellm.api_key - or litellm.azure_key - or get_secret_str("AZURE_OPENAI_API_KEY") - or get_secret_str("AZURE_API_KEY") - ) - - azure_ad_token = optional_params.pop( - "azure_ad_token", None - ) or get_secret_str("AZURE_AD_TOKEN") - - default_headers = { - "Content-Type": "application/json;", - "api-key": api_key, - } - for k, v in default_headers.items(): - if k not in headers: - headers[k] = v - - model_response = azure_chat_completions.image_generation( - model=model, - prompt=prompt, - timeout=timeout, - api_key=api_key, - api_base=api_base, - azure_ad_token=azure_ad_token, - azure_ad_token_provider=azure_ad_token_provider, - logging_obj=litellm_logging_obj, - optional_params=optional_params, - model_response=model_response, - api_version=api_version, - aimg_generation=aimg_generation, - client=client, - headers=headers, - litellm_params=litellm_params_dict, - ) - elif ( - custom_llm_provider == "openai" - or custom_llm_provider in litellm.openai_compatible_providers - ): - model_response = openai_chat_completions.image_generation( - model=model, - prompt=prompt, - timeout=timeout, - api_key=api_key or dynamic_api_key, - api_base=api_base, - logging_obj=litellm_logging_obj, - optional_params=optional_params, - model_response=model_response, - aimg_generation=aimg_generation, - client=client, - ) - elif custom_llm_provider == "bedrock": - if model is None: - raise Exception("Model needs to be set for bedrock") - model_response = bedrock_image_generation.image_generation( # type: ignore - model=model, - prompt=prompt, - timeout=timeout, - logging_obj=litellm_logging_obj, - optional_params=optional_params, - model_response=model_response, - aimg_generation=aimg_generation, - client=client, - ) - elif custom_llm_provider == "vertex_ai": - vertex_ai_project = ( - optional_params.pop("vertex_project", None) - or optional_params.pop("vertex_ai_project", None) - or litellm.vertex_project - or get_secret_str("VERTEXAI_PROJECT") - ) - vertex_ai_location = ( - optional_params.pop("vertex_location", None) - or optional_params.pop("vertex_ai_location", None) - or litellm.vertex_location - or get_secret_str("VERTEXAI_LOCATION") - ) - vertex_credentials = ( - optional_params.pop("vertex_credentials", None) - or optional_params.pop("vertex_ai_credentials", None) - or get_secret_str("VERTEXAI_CREDENTIALS") - ) - - api_base = ( - api_base - or litellm.api_base - or get_secret_str("VERTEXAI_API_BASE") - or get_secret_str("VERTEX_API_BASE") - ) - - model_response = vertex_image_generation.image_generation( - model=model, - prompt=prompt, - timeout=timeout, - logging_obj=litellm_logging_obj, - optional_params=optional_params, - model_response=model_response, - vertex_project=vertex_ai_project, - vertex_location=vertex_ai_location, - vertex_credentials=vertex_credentials, - aimg_generation=aimg_generation, - api_base=api_base, - client=client, - ) - elif ( - custom_llm_provider in litellm._custom_providers - ): # Assume custom LLM provider - # Get the Custom Handler - custom_handler: Optional[CustomLLM] = None - for item in litellm.custom_provider_map: - if item["provider"] == custom_llm_provider: - custom_handler = item["custom_handler"] - - if custom_handler is None: - raise LiteLLMUnknownProvider( - model=model, custom_llm_provider=custom_llm_provider - ) - - ## ROUTE LLM CALL ## - if aimg_generation is True: - async_custom_client: Optional[AsyncHTTPHandler] = None - if client is not None and isinstance(client, AsyncHTTPHandler): - async_custom_client = client - - ## CALL FUNCTION - model_response = custom_handler.aimage_generation( # type: ignore - model=model, - prompt=prompt, - api_key=api_key, - api_base=api_base, - model_response=model_response, - optional_params=optional_params, - logging_obj=litellm_logging_obj, - timeout=timeout, - client=async_custom_client, - ) - else: - custom_client: Optional[HTTPHandler] = None - if client is not None and isinstance(client, HTTPHandler): - custom_client = client - - ## CALL FUNCTION - model_response = custom_handler.image_generation( - model=model, - prompt=prompt, - api_key=api_key, - api_base=api_base, - model_response=model_response, - optional_params=optional_params, - logging_obj=litellm_logging_obj, - timeout=timeout, - client=custom_client, - ) - - return model_response - except Exception as e: - ## Map to OpenAI Exception - raise exception_type( - model=model, - custom_llm_provider=custom_llm_provider, - original_exception=e, - completion_kwargs=locals(), - extra_kwargs=kwargs, - ) - - -@client -async def aimage_variation(*args, **kwargs) -> ImageResponse: - """ - Asynchronously calls the `image_variation` function with the given arguments and keyword arguments. - - Parameters: - - `args` (tuple): Positional arguments to be passed to the `image_variation` function. - - `kwargs` (dict): Keyword arguments to be passed to the `image_variation` function. - - Returns: - - `response` (Any): The response returned by the `image_variation` function. - """ - loop = asyncio.get_event_loop() - model = kwargs.get("model", None) - custom_llm_provider = kwargs.get("custom_llm_provider", None) - ### PASS ARGS TO Image Generation ### - kwargs["async_call"] = True - try: - # Use a partial function to pass your keyword arguments - func = partial(image_variation, *args, **kwargs) - - # Add the context to the function - ctx = contextvars.copy_context() - func_with_context = partial(ctx.run, func) - - if custom_llm_provider is None and model is not None: - _, custom_llm_provider, _, _ = get_llm_provider( - model=model, api_base=kwargs.get("api_base", None) - ) - - # Await normally - init_response = await loop.run_in_executor(None, func_with_context) - if isinstance(init_response, dict) or isinstance( - init_response, ImageResponse - ): ## CACHING SCENARIO - if isinstance(init_response, dict): - init_response = ImageResponse(**init_response) - response = init_response - elif asyncio.iscoroutine(init_response): - response = await init_response # type: ignore - else: - # Call the synchronous function using run_in_executor - response = await loop.run_in_executor(None, func_with_context) - return response - except Exception as e: - custom_llm_provider = custom_llm_provider or "openai" - raise exception_type( - model=model, - custom_llm_provider=custom_llm_provider, - original_exception=e, - completion_kwargs=args, - extra_kwargs=kwargs, - ) - - -@client -def image_variation( - image: FileTypes, - model: str = "dall-e-2", # set to dall-e-2 by default - like OpenAI. - n: int = 1, - response_format: Literal["url", "b64_json"] = "url", - size: Optional[str] = None, - user: Optional[str] = None, - **kwargs, -) -> ImageResponse: - # get non-default params - client = kwargs.get("client", None) - # get logging object - litellm_logging_obj = cast(LiteLLMLoggingObj, kwargs.get("litellm_logging_obj")) - - # get the litellm params - litellm_params = get_litellm_params(**kwargs) - # get the custom llm provider - model, custom_llm_provider, dynamic_api_key, api_base = get_llm_provider( - model=model, - custom_llm_provider=litellm_params.get("custom_llm_provider", None), - api_base=litellm_params.get("api_base", None), - api_key=litellm_params.get("api_key", None), - ) - - # route to the correct provider w/ the params - try: - llm_provider = LlmProviders(custom_llm_provider) - image_variation_provider = LITELLM_IMAGE_VARIATION_PROVIDERS(llm_provider) - except ValueError: - raise ValueError( - f"Invalid image variation provider: {custom_llm_provider}. Supported providers are: {LITELLM_IMAGE_VARIATION_PROVIDERS}" - ) - model_response = ImageResponse() - - response: Optional[ImageResponse] = None - - provider_config = ProviderConfigManager.get_provider_model_info( - model=model or "", # openai defaults to dall-e-2 - provider=llm_provider, - ) - - if provider_config is None: - raise ValueError( - f"image variation provider has no known model info config - required for getting api keys, etc.: {custom_llm_provider}. Supported providers are: {LITELLM_IMAGE_VARIATION_PROVIDERS}" - ) - - api_key = provider_config.get_api_key(litellm_params.get("api_key", None)) - api_base = provider_config.get_api_base(litellm_params.get("api_base", None)) - - if image_variation_provider == LITELLM_IMAGE_VARIATION_PROVIDERS.OPENAI: - if api_key is None: - raise ValueError("API key is required for OpenAI image variations") - if api_base is None: - raise ValueError("API base is required for OpenAI image variations") - - response = openai_image_variations.image_variations( - model_response=model_response, - api_key=api_key, - api_base=api_base, - model=model, - image=image, - timeout=litellm_params.get("timeout", None), - custom_llm_provider=custom_llm_provider, - logging_obj=litellm_logging_obj, - optional_params={}, - litellm_params=litellm_params, - ) - elif image_variation_provider == LITELLM_IMAGE_VARIATION_PROVIDERS.TOPAZ: - if api_key is None: - raise ValueError("API key is required for Topaz image variations") - if api_base is None: - raise ValueError("API base is required for Topaz image variations") - - response = base_llm_aiohttp_handler.image_variations( - model_response=model_response, - api_key=api_key, - api_base=api_base, - model=model, - image=image, - timeout=litellm_params.get("timeout", None), - custom_llm_provider=custom_llm_provider, - logging_obj=litellm_logging_obj, - optional_params={}, - litellm_params=litellm_params, - client=client, - ) - - # return the response - if response is None: - raise ValueError( - f"Invalid image variation provider: {custom_llm_provider}. Supported providers are: {LITELLM_IMAGE_VARIATION_PROVIDERS}" - ) - return response - - ##### Transcription ####################### diff --git a/litellm/types/images/main.py b/litellm/types/images/main.py new file mode 100644 index 0000000000..1e7466f4e8 --- /dev/null +++ b/litellm/types/images/main.py @@ -0,0 +1,31 @@ +from typing import Any, Dict, List, Literal, Optional, TypedDict, Union + +from litellm.types.utils import FileTypes + + +class ImageEditOptionalRequestParams(TypedDict, total=False): + """ + TypedDict for Optional parameters supported by OpenAI's image edit API. + + Params here: https://platform.openai.com/docs/api-reference/images/createEdit + """ + + background: Optional[Literal["transparent", "opaque", "auto"]] + mask: Optional[str] + n: Optional[int] + quality: Optional[Literal["high", "medium", "low", "standard", "auto"]] + response_format: Optional[Literal["url", "b64_json"]] + size: Optional[str] + user: Optional[str] + + +class ImageEditRequestParams(ImageEditOptionalRequestParams, total=False): + """ + TypedDict for request parameters supported by OpenAI's image edit API. + + Params here: https://platform.openai.com/docs/api-reference/images/createEdit + """ + + image: FileTypes + prompt: str + model: Optional[str] diff --git a/litellm/utils.py b/litellm/utils.py index 0dd4c8cd1e..773196077d 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -229,6 +229,7 @@ from litellm.llms.base_llm.chat.transformation import BaseConfig from litellm.llms.base_llm.completion.transformation import BaseTextCompletionConfig from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig from litellm.llms.base_llm.files.transformation import BaseFilesConfig +from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig from litellm.llms.base_llm.image_generation.transformation import ( BaseImageGenerationConfig, ) @@ -1967,6 +1968,7 @@ def supports_prompt_caching( key="supports_prompt_caching", ) + def supports_computer_use( model: str, custom_llm_provider: Optional[str] = None ) -> bool: @@ -6681,6 +6683,19 @@ class ProviderConfigManager: return GeminiRealtimeConfig() return None + @staticmethod + def get_provider_image_edit_config( + model: str, + provider: LlmProviders, + ) -> Optional[BaseImageEditConfig]: + if LlmProviders.OPENAI == provider: + from litellm.llms.openai.image_edit.transformation import ( + OpenAIImageEditConfig, + ) + + return OpenAIImageEditConfig() + return None + def get_end_user_id_for_cost_tracking( litellm_params: dict, diff --git a/tests/image_gen_tests/ishaan_github.png b/tests/image_gen_tests/ishaan_github.png new file mode 100644 index 0000000000000000000000000000000000000000..2e6c0dadc91747e7b5a19cb2bba1fba51fec1e7e GIT binary patch literal 618676 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