mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
fix(together_ai): add image generation and image edit transformations for image-to-image models
This commit is contained in:
parent
215ce9f7c1
commit
08d36eb6ed
14 changed files with 962 additions and 52 deletions
|
|
@ -393,6 +393,7 @@ def image_generation(
|
|||
litellm.LlmProviders.VERTEX_AI,
|
||||
litellm.LlmProviders.OPENROUTER,
|
||||
litellm.LlmProviders.DASHSCOPE,
|
||||
litellm.LlmProviders.TOGETHER_AI,
|
||||
):
|
||||
if image_generation_config is None:
|
||||
raise ValueError(f"image generation config is not supported for {custom_llm_provider}")
|
||||
|
|
|
|||
62
litellm/llms/together_ai/common_utils.py
Normal file
62
litellm/llms/together_ai/common_utils.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import litellm
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.utils import ImageObject
|
||||
|
||||
TOGETHER_AI_DEFAULT_API_BASE = "https://api.together.xyz/v1"
|
||||
|
||||
|
||||
class TogetherAIException(BaseLLMException):
|
||||
pass
|
||||
|
||||
|
||||
def resolve_together_ai_api_key(api_key: str | None) -> str | None:
|
||||
return (
|
||||
api_key
|
||||
or litellm.api_key
|
||||
or get_secret_str("TOGETHER_API_KEY")
|
||||
or get_secret_str("TOGETHER_AI_API_KEY")
|
||||
or get_secret_str("TOGETHERAI_API_KEY")
|
||||
or get_secret_str("TOGETHER_AI_TOKEN")
|
||||
)
|
||||
|
||||
|
||||
def get_together_ai_images_generations_url(api_base: str | None) -> str:
|
||||
base = (api_base or get_secret_str("TOGETHER_AI_API_BASE") or TOGETHER_AI_DEFAULT_API_BASE).rstrip("/")
|
||||
if base.endswith("/images/generations"):
|
||||
return base
|
||||
return f"{base}/images/generations"
|
||||
|
||||
|
||||
def parse_openai_size_to_width_height(size: str) -> tuple[int, int] | None:
|
||||
parts = size.lower().split("x")
|
||||
if len(parts) != 2 or not all(part.isdigit() for part in parts):
|
||||
return None
|
||||
return int(parts[0]), int(parts[1])
|
||||
|
||||
|
||||
def map_openai_image_param_to_together_ai(key: str, value: object) -> tuple[tuple[str, object], ...]:
|
||||
if key == "size" and isinstance(value, str):
|
||||
dimensions = parse_openai_size_to_width_height(value)
|
||||
if dimensions is None:
|
||||
return ()
|
||||
width, height = dimensions
|
||||
return (("width", width), ("height", height))
|
||||
if key == "response_format" and value == "b64_json":
|
||||
return (("response_format", "base64"),)
|
||||
return ((key, value),)
|
||||
|
||||
|
||||
def together_ai_image_data_to_image_objects(response_json: dict) -> list[ImageObject]:
|
||||
data = response_json.get("data")
|
||||
if not isinstance(data, list):
|
||||
return []
|
||||
return [
|
||||
ImageObject(
|
||||
url=item.get("url"),
|
||||
b64_json=item.get("b64_json"),
|
||||
revised_prompt=None,
|
||||
)
|
||||
for item in data
|
||||
if isinstance(item, dict)
|
||||
]
|
||||
11
litellm/llms/together_ai/image_edit/__init__.py
Normal file
11
litellm/llms/together_ai/image_edit/__init__.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig
|
||||
|
||||
from .transformation import TogetherAIImageEditConfig
|
||||
|
||||
__all__ = [
|
||||
"TogetherAIImageEditConfig",
|
||||
]
|
||||
|
||||
|
||||
def get_together_ai_image_edit_config(model: str) -> BaseImageEditConfig:
|
||||
return TogetherAIImageEditConfig()
|
||||
164
litellm/llms/together_ai/image_edit/transformation.py
Normal file
164
litellm/llms/together_ai/image_edit/transformation.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import base64
|
||||
from io import BufferedReader, BytesIO
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
|
||||
import httpx
|
||||
from httpx._types import RequestFiles
|
||||
|
||||
from litellm.images.utils import ImageEditRequestUtils
|
||||
from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig
|
||||
from litellm.llms.together_ai.common_utils import (
|
||||
TogetherAIException,
|
||||
get_together_ai_images_generations_url,
|
||||
map_openai_image_param_to_together_ai,
|
||||
resolve_together_ai_api_key,
|
||||
together_ai_image_data_to_image_objects,
|
||||
)
|
||||
from litellm.types.images.main import ImageEditOptionalRequestParams
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.utils import FileTypes, ImageResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj
|
||||
|
||||
from ...base_llm.chat.transformation import BaseLLMException as _BaseLLMException
|
||||
|
||||
LiteLLMLoggingObj = _LiteLLMLoggingObj
|
||||
BaseLLMException = _BaseLLMException
|
||||
else:
|
||||
LiteLLMLoggingObj = Any
|
||||
BaseLLMException = Any
|
||||
|
||||
|
||||
class TogetherAIImageEditConfig(BaseImageEditConfig):
|
||||
def get_supported_openai_params(self, model: str) -> list:
|
||||
return ["n", "size", "response_format"]
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
image_edit_optional_params: ImageEditOptionalRequestParams,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
) -> dict:
|
||||
return dict(
|
||||
mapped_item
|
||||
for key, value in image_edit_optional_params.items()
|
||||
for mapped_item in map_openai_image_param_to_together_ai(key, value)
|
||||
)
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
api_key: str | None = None,
|
||||
litellm_params: dict | None = None,
|
||||
api_base: str | None = None,
|
||||
) -> dict:
|
||||
resolved_api_key = resolve_together_ai_api_key(api_key)
|
||||
if resolved_api_key is None:
|
||||
raise TogetherAIException(
|
||||
message="Together AI API key is not set. Set TOGETHERAI_API_KEY or pass api_key.",
|
||||
status_code=401,
|
||||
headers={},
|
||||
)
|
||||
return {**headers, "Authorization": f"Bearer {resolved_api_key}"}
|
||||
|
||||
def use_multipart_form_data(self) -> bool:
|
||||
return False
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
model: str,
|
||||
api_base: str | None,
|
||||
litellm_params: dict,
|
||||
) -> str:
|
||||
return get_together_ai_images_generations_url(api_base)
|
||||
|
||||
def transform_image_edit_request(
|
||||
self,
|
||||
model: str,
|
||||
prompt: str | None,
|
||||
image: FileTypes | None,
|
||||
image_edit_optional_request_params: dict,
|
||||
litellm_params: GenericLiteLLMParams,
|
||||
headers: dict,
|
||||
) -> tuple[dict, RequestFiles]:
|
||||
images = image if isinstance(image, list) else ([image] if image is not None else [])
|
||||
data_urls = tuple(self._to_data_url(img) for img in images if img is not None)
|
||||
if len(data_urls) == 0:
|
||||
raise TogetherAIException(
|
||||
message="image is required for Together AI image edit requests.",
|
||||
status_code=400,
|
||||
headers={},
|
||||
)
|
||||
if len(data_urls) > 1:
|
||||
raise TogetherAIException(
|
||||
message="Together AI image edit supports a single input image per request.",
|
||||
status_code=400,
|
||||
headers={},
|
||||
)
|
||||
prompt_params = {"prompt": prompt} if prompt is not None else {}
|
||||
optional_body_params = {
|
||||
k: v for k, v in image_edit_optional_request_params.items() if k not in ("extra_headers", "extra_body")
|
||||
}
|
||||
extra_body = image_edit_optional_request_params.get("extra_body") or {}
|
||||
request_body: dict[str, Any] = {
|
||||
"model": model,
|
||||
"image_url": data_urls[0],
|
||||
**prompt_params,
|
||||
**optional_body_params,
|
||||
**extra_body,
|
||||
}
|
||||
empty_files: RequestFiles = []
|
||||
return request_body, empty_files
|
||||
|
||||
def transform_image_edit_response(
|
||||
self,
|
||||
model: str,
|
||||
raw_response: httpx.Response,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
) -> ImageResponse:
|
||||
try:
|
||||
response_json = raw_response.json()
|
||||
except ValueError as e:
|
||||
raise TogetherAIException(
|
||||
message=f"Error parsing Together AI image edit response: {e}",
|
||||
status_code=raw_response.status_code,
|
||||
headers=raw_response.headers,
|
||||
)
|
||||
model_response = ImageResponse()
|
||||
model_response.data = together_ai_image_data_to_image_objects(response_json)
|
||||
return model_response
|
||||
|
||||
def get_error_class(
|
||||
self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers]
|
||||
) -> BaseLLMException:
|
||||
return TogetherAIException(
|
||||
message=error_message,
|
||||
status_code=status_code,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
def _to_data_url(self, image: FileTypes) -> str:
|
||||
mime_type = ImageEditRequestUtils.get_image_content_type(image)
|
||||
encoded = base64.b64encode(self._read_image_bytes(image)).decode("utf-8")
|
||||
return f"data:{mime_type};base64,{encoded}"
|
||||
|
||||
def _read_image_bytes(self, image: FileTypes) -> bytes:
|
||||
if isinstance(image, bytes):
|
||||
return image
|
||||
if isinstance(image, bytearray):
|
||||
return bytes(image)
|
||||
if isinstance(image, (BytesIO, BufferedReader)):
|
||||
current_pos = image.tell()
|
||||
image.seek(0)
|
||||
data = image.read()
|
||||
image.seek(current_pos)
|
||||
return data
|
||||
if isinstance(image, tuple) and len(image) >= 2 and image[1] is not None:
|
||||
return self._read_image_bytes(image[1])
|
||||
raise TogetherAIException(
|
||||
message=f"Unsupported image type for Together AI image edit: {type(image)}",
|
||||
status_code=400,
|
||||
headers={},
|
||||
)
|
||||
13
litellm/llms/together_ai/image_generation/__init__.py
Normal file
13
litellm/llms/together_ai/image_generation/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from litellm.llms.base_llm.image_generation.transformation import (
|
||||
BaseImageGenerationConfig,
|
||||
)
|
||||
|
||||
from .transformation import TogetherAIImageGenerationConfig
|
||||
|
||||
__all__ = [
|
||||
"TogetherAIImageGenerationConfig",
|
||||
]
|
||||
|
||||
|
||||
def get_together_ai_image_generation_config(model: str) -> BaseImageGenerationConfig:
|
||||
return TogetherAIImageGenerationConfig()
|
||||
123
litellm/llms/together_ai/image_generation/transformation.py
Normal file
123
litellm/llms/together_ai/image_generation/transformation.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
from typing import TYPE_CHECKING, Any, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from litellm.llms.base_llm.chat.transformation import BaseLLMException
|
||||
from litellm.llms.base_llm.image_generation.transformation import (
|
||||
BaseImageGenerationConfig,
|
||||
)
|
||||
from litellm.llms.together_ai.common_utils import (
|
||||
TogetherAIException,
|
||||
get_together_ai_images_generations_url,
|
||||
map_openai_image_param_to_together_ai,
|
||||
resolve_together_ai_api_key,
|
||||
together_ai_image_data_to_image_objects,
|
||||
)
|
||||
from litellm.types.llms.openai import (
|
||||
AllMessageValues,
|
||||
OpenAIImageGenerationOptionalParams,
|
||||
)
|
||||
from litellm.types.utils import ImageResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj
|
||||
|
||||
LiteLLMLoggingObj = _LiteLLMLoggingObj
|
||||
else:
|
||||
LiteLLMLoggingObj = Any
|
||||
|
||||
|
||||
class TogetherAIImageGenerationConfig(BaseImageGenerationConfig):
|
||||
def get_supported_openai_params(self, model: str) -> list[OpenAIImageGenerationOptionalParams]:
|
||||
return ["n", "size", "response_format"]
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
optional_params: dict,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
) -> dict:
|
||||
return {
|
||||
**optional_params,
|
||||
**dict(
|
||||
mapped_item
|
||||
for key, value in non_default_params.items()
|
||||
for mapped_item in map_openai_image_param_to_together_ai(key, value)
|
||||
),
|
||||
}
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: str | None,
|
||||
api_key: str | None,
|
||||
model: str,
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
stream: bool | None = None,
|
||||
) -> str:
|
||||
return get_together_ai_images_generations_url(api_base)
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
messages: list[AllMessageValues],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
api_key: str | None = None,
|
||||
api_base: str | None = None,
|
||||
) -> dict:
|
||||
resolved_api_key = resolve_together_ai_api_key(api_key)
|
||||
if resolved_api_key is None:
|
||||
raise TogetherAIException(
|
||||
message="Together AI API key is not set. Set TOGETHERAI_API_KEY or pass api_key.",
|
||||
status_code=401,
|
||||
headers={},
|
||||
)
|
||||
return {**headers, "Authorization": f"Bearer {resolved_api_key}"}
|
||||
|
||||
def transform_image_generation_request(
|
||||
self,
|
||||
model: str,
|
||||
prompt: str,
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
headers: dict,
|
||||
) -> dict:
|
||||
extra_body = optional_params.get("extra_body") or {}
|
||||
body_params = {k: v for k, v in optional_params.items() if k not in ("extra_body", "extra_headers")}
|
||||
return {"model": model, "prompt": prompt, **body_params, **extra_body}
|
||||
|
||||
def transform_image_generation_response(
|
||||
self,
|
||||
model: str,
|
||||
raw_response: httpx.Response,
|
||||
model_response: ImageResponse,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
request_data: dict,
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
encoding: Any,
|
||||
api_key: str | None = None,
|
||||
json_mode: bool | None = None,
|
||||
) -> ImageResponse:
|
||||
try:
|
||||
response_json = raw_response.json()
|
||||
except ValueError as e:
|
||||
raise TogetherAIException(
|
||||
message=f"Error parsing Together AI image generation response: {e}",
|
||||
status_code=raw_response.status_code,
|
||||
headers=raw_response.headers,
|
||||
)
|
||||
model_response.data = together_ai_image_data_to_image_objects(response_json)
|
||||
return model_response
|
||||
|
||||
def get_error_class(
|
||||
self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers]
|
||||
) -> BaseLLMException:
|
||||
return TogetherAIException(
|
||||
message=error_message,
|
||||
status_code=status_code,
|
||||
headers=headers,
|
||||
)
|
||||
|
|
@ -17381,6 +17381,46 @@
|
|||
"tpm": 8000000,
|
||||
"supports_image_size": false
|
||||
},
|
||||
"gemini-3-pro-image": {
|
||||
"input_cost_per_image": 0.0011,
|
||||
"input_cost_per_token": 2e-06,
|
||||
"input_cost_per_token_batches": 1e-06,
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"max_input_tokens": 65536,
|
||||
"max_output_tokens": 32768,
|
||||
"max_tokens": 32768,
|
||||
"mode": "image_generation",
|
||||
"output_cost_per_image": 0.134,
|
||||
"output_cost_per_image_token": 0.00012,
|
||||
"output_cost_per_token": 1.2e-05,
|
||||
"output_cost_per_token_batches": 6e-06,
|
||||
"source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/completions",
|
||||
"/v1/batch"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supports_function_calling": false,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.014,
|
||||
"search_context_size_medium": 0.014,
|
||||
"search_context_size_high": 0.014
|
||||
},
|
||||
"web_search_billing_unit": "per_query"
|
||||
},
|
||||
"gemini-3-pro-image-preview": {
|
||||
"input_cost_per_image": 0.0011,
|
||||
"input_cost_per_token": 2e-06,
|
||||
|
|
@ -17421,6 +17461,44 @@
|
|||
},
|
||||
"web_search_billing_unit": "per_query"
|
||||
},
|
||||
"gemini-3.1-flash-image": {
|
||||
"input_cost_per_image": 0.00056,
|
||||
"input_cost_per_token": 5e-07,
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"max_input_tokens": 65536,
|
||||
"max_output_tokens": 32768,
|
||||
"max_tokens": 32768,
|
||||
"mode": "image_generation",
|
||||
"output_cost_per_image": 0.0672,
|
||||
"output_cost_per_image_token": 6e-05,
|
||||
"output_cost_per_token": 3e-06,
|
||||
"source": "https://cloud.google.com/vertex-ai/generative-ai/pricing#gemini-models",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/completions",
|
||||
"/v1/batch"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supports_function_calling": false,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.014,
|
||||
"search_context_size_medium": 0.014,
|
||||
"search_context_size_high": 0.014
|
||||
},
|
||||
"web_search_billing_unit": "per_query"
|
||||
},
|
||||
"gemini-3.1-flash-image-preview": {
|
||||
"input_cost_per_image": 0.00056,
|
||||
"input_cost_per_token": 5e-07,
|
||||
|
|
@ -18866,7 +18944,6 @@
|
|||
],
|
||||
"supports_function_calling": false,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_vision": true,
|
||||
|
|
@ -18876,7 +18953,8 @@
|
|||
"search_context_size_medium": 0.014,
|
||||
"search_context_size_high": 0.014
|
||||
},
|
||||
"web_search_billing_unit": "per_query"
|
||||
"web_search_billing_unit": "per_query",
|
||||
"supports_reasoning": false
|
||||
},
|
||||
"gemini/gemini-3-pro-image-preview": {
|
||||
"input_cost_per_image": 0.0011,
|
||||
|
|
@ -20312,8 +20390,7 @@
|
|||
"max_tokens": 16000,
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/messages"
|
||||
"/v1/chat/completions"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
|
|
@ -20326,8 +20403,7 @@
|
|||
"max_tokens": 16000,
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/messages"
|
||||
"/v1/chat/completions"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
|
|
@ -20379,8 +20455,7 @@
|
|||
"max_tokens": 16000,
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/messages"
|
||||
"/v1/chat/completions"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
|
|
@ -23672,7 +23747,7 @@
|
|||
"input_cost_per_token_batches": 3.75e-07,
|
||||
"input_cost_per_token_priority": 1.5e-06,
|
||||
"litellm_provider": "openai",
|
||||
"max_input_tokens": 272000,
|
||||
"max_input_tokens": 1050000,
|
||||
"max_output_tokens": 128000,
|
||||
"max_tokens": 128000,
|
||||
"mode": "chat",
|
||||
|
|
@ -23718,7 +23793,7 @@
|
|||
"input_cost_per_token_batches": 3.75e-07,
|
||||
"input_cost_per_token_priority": 1.5e-06,
|
||||
"litellm_provider": "openai",
|
||||
"max_input_tokens": 272000,
|
||||
"max_input_tokens": 1050000,
|
||||
"max_output_tokens": 128000,
|
||||
"max_tokens": 128000,
|
||||
"mode": "chat",
|
||||
|
|
@ -23762,7 +23837,7 @@
|
|||
"input_cost_per_token_flex": 1e-07,
|
||||
"input_cost_per_token_batches": 1e-07,
|
||||
"litellm_provider": "openai",
|
||||
"max_input_tokens": 272000,
|
||||
"max_input_tokens": 1050000,
|
||||
"max_output_tokens": 128000,
|
||||
"max_tokens": 128000,
|
||||
"mode": "chat",
|
||||
|
|
@ -23805,7 +23880,7 @@
|
|||
"input_cost_per_token_flex": 1e-07,
|
||||
"input_cost_per_token_batches": 1e-07,
|
||||
"litellm_provider": "openai",
|
||||
"max_input_tokens": 272000,
|
||||
"max_input_tokens": 1050000,
|
||||
"max_output_tokens": 128000,
|
||||
"max_tokens": 128000,
|
||||
"mode": "chat",
|
||||
|
|
@ -23845,9 +23920,9 @@
|
|||
"input_cost_per_token": 1.5e-05,
|
||||
"input_cost_per_token_batches": 7.5e-06,
|
||||
"litellm_provider": "openai",
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 272000,
|
||||
"max_tokens": 272000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"max_tokens": 128000,
|
||||
"mode": "responses",
|
||||
"output_cost_per_token": 0.00012,
|
||||
"output_cost_per_token_batches": 6e-05,
|
||||
|
|
@ -23881,9 +23956,9 @@
|
|||
"input_cost_per_token": 1.5e-05,
|
||||
"input_cost_per_token_batches": 7.5e-06,
|
||||
"litellm_provider": "openai",
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 272000,
|
||||
"max_tokens": 272000,
|
||||
"max_input_tokens": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"max_tokens": 128000,
|
||||
"mode": "responses",
|
||||
"output_cost_per_token": 0.00012,
|
||||
"output_cost_per_token_batches": 6e-05,
|
||||
|
|
@ -31246,6 +31321,22 @@
|
|||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"openrouter/z-ai/glm-5.1": {
|
||||
"input_cost_per_token": 1.05e-06,
|
||||
"output_cost_per_token": 3.5e-06,
|
||||
"cache_read_input_token_cost": 5.25e-07,
|
||||
"cache_creation_input_token_cost": 0.0,
|
||||
"litellm_provider": "openrouter",
|
||||
"max_input_tokens": 202752,
|
||||
"max_output_tokens": 65535,
|
||||
"max_tokens": 65535,
|
||||
"mode": "chat",
|
||||
"source": "https://openrouter.ai/z-ai/glm-5.1",
|
||||
"supports_function_calling": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"openrouter/minimax/minimax-m2.1": {
|
||||
"input_cost_per_token": 2.7e-07,
|
||||
"output_cost_per_token": 1.2e-06,
|
||||
|
|
@ -33759,6 +33850,16 @@
|
|||
"mode": "chat",
|
||||
"output_cost_per_token": 1e-07
|
||||
},
|
||||
"together_ai/ByteDance-Seed/Seedream-4.0": {
|
||||
"litellm_provider": "together_ai",
|
||||
"mode": "image_generation",
|
||||
"output_cost_per_image": 0.03,
|
||||
"source": "https://www.together.ai/pricing",
|
||||
"supported_endpoints": [
|
||||
"/v1/images/generations",
|
||||
"/v1/images/edits"
|
||||
]
|
||||
},
|
||||
"together_ai/Qwen/Qwen2.5-72B-Instruct-Turbo": {
|
||||
"litellm_provider": "together_ai",
|
||||
"mode": "chat",
|
||||
|
|
@ -39395,6 +39496,21 @@
|
|||
"supports_tool_choice": true,
|
||||
"source": "https://docs.z.ai/guides/overview/pricing"
|
||||
},
|
||||
"zai/glm-5.1": {
|
||||
"cache_creation_input_token_cost": 0,
|
||||
"cache_read_input_token_cost": 2.6e-07,
|
||||
"input_cost_per_token": 1.4e-06,
|
||||
"output_cost_per_token": 4.4e-06,
|
||||
"litellm_provider": "zai",
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://docs.z.ai/guides/overview/pricing"
|
||||
},
|
||||
"zai/glm-5-code": {
|
||||
"cache_creation_input_token_cost": 0,
|
||||
"cache_read_input_token_cost": 3e-07,
|
||||
|
|
@ -39425,6 +39541,21 @@
|
|||
"supports_tool_choice": true,
|
||||
"source": "https://docs.z.ai/guides/overview/pricing"
|
||||
},
|
||||
"zai/glm-4.7-flash": {
|
||||
"cache_creation_input_token_cost": 0,
|
||||
"cache_read_input_token_cost": 0,
|
||||
"input_cost_per_token": 0,
|
||||
"output_cost_per_token": 0,
|
||||
"litellm_provider": "zai",
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 128000,
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://docs.z.ai/guides/overview/pricing"
|
||||
},
|
||||
"zai/glm-4.6": {
|
||||
"cache_creation_input_token_cost": 0,
|
||||
"cache_read_input_token_cost": 1.1e-07,
|
||||
|
|
@ -44835,6 +44966,7 @@
|
|||
"supports_response_schema": true
|
||||
},
|
||||
"snowflake/claude-sonnet-4-6": {
|
||||
"supports_adaptive_thinking": true,
|
||||
"max_tokens": 16384,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 16384,
|
||||
|
|
@ -45253,40 +45385,6 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": false
|
||||
},
|
||||
"darkbloom/gemma-4-26b": {
|
||||
"input_cost_per_token": 3e-08,
|
||||
"litellm_provider": "darkbloom",
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 32768,
|
||||
"max_tokens": 32768,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 1.65e-07,
|
||||
"source": "https://www.darkbloom.dev/",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"darkbloom/gpt-oss-20b": {
|
||||
"input_cost_per_token": 1.45e-08,
|
||||
"litellm_provider": "darkbloom",
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 32768,
|
||||
"max_tokens": 32768,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 7e-08,
|
||||
"source": "https://www.darkbloom.dev/",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"deepseek/deepseek-v4-pro": {
|
||||
"cache_creation_input_token_cost": 0.0,
|
||||
"cache_read_input_token_cost": 3.625e-09,
|
||||
|
|
@ -45442,6 +45540,40 @@
|
|||
"supports_reasoning": false,
|
||||
"source": "https://pinstripes.io/pricing"
|
||||
},
|
||||
"darkbloom/gemma-4-26b": {
|
||||
"input_cost_per_token": 3e-08,
|
||||
"litellm_provider": "darkbloom",
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 32768,
|
||||
"max_tokens": 32768,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 1.65e-07,
|
||||
"source": "https://www.darkbloom.dev/",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"darkbloom/gpt-oss-20b": {
|
||||
"input_cost_per_token": 1.45e-08,
|
||||
"litellm_provider": "darkbloom",
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 32768,
|
||||
"max_tokens": 32768,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 7e-08,
|
||||
"source": "https://www.darkbloom.dev/",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_native_streaming": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"fallback_generalizations": {
|
||||
"rules": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8653,6 +8653,12 @@ class ProviderConfigManager:
|
|||
)
|
||||
|
||||
return get_modelscope_image_generation_config(model)
|
||||
elif LlmProviders.TOGETHER_AI == provider:
|
||||
from litellm.llms.together_ai.image_generation import (
|
||||
get_together_ai_image_generation_config,
|
||||
)
|
||||
|
||||
return get_together_ai_image_generation_config(model)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -8800,6 +8806,12 @@ class ProviderConfigManager:
|
|||
)
|
||||
|
||||
return get_openrouter_image_edit_config(model)
|
||||
elif LlmProviders.TOGETHER_AI == provider:
|
||||
from litellm.llms.together_ai.image_edit import (
|
||||
get_together_ai_image_edit_config,
|
||||
)
|
||||
|
||||
return get_together_ai_image_edit_config(model)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -33850,6 +33850,16 @@
|
|||
"mode": "chat",
|
||||
"output_cost_per_token": 1e-07
|
||||
},
|
||||
"together_ai/ByteDance-Seed/Seedream-4.0": {
|
||||
"litellm_provider": "together_ai",
|
||||
"mode": "image_generation",
|
||||
"output_cost_per_image": 0.03,
|
||||
"source": "https://www.together.ai/pricing",
|
||||
"supported_endpoints": [
|
||||
"/v1/images/generations",
|
||||
"/v1/images/edits"
|
||||
]
|
||||
},
|
||||
"together_ai/Qwen/Qwen2.5-72B-Instruct-Turbo": {
|
||||
"litellm_provider": "together_ai",
|
||||
"mode": "chat",
|
||||
|
|
|
|||
0
tests/test_litellm/llms/together_ai/__init__.py
Normal file
0
tests/test_litellm/llms/together_ai/__init__.py
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import base64
|
||||
from io import BytesIO
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
from litellm.llms.together_ai.common_utils import TogetherAIException
|
||||
from litellm.llms.together_ai.image_edit.transformation import TogetherAIImageEditConfig
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.utils import LlmProviders
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
SAMPLE_PNG_BYTES = b"\x89PNG\r\n\x1a\n" + b"\x00" * 32
|
||||
|
||||
TOGETHER_KEY_ENV_VARS = (
|
||||
"TOGETHER_API_KEY",
|
||||
"TOGETHER_AI_API_KEY",
|
||||
"TOGETHERAI_API_KEY",
|
||||
"TOGETHER_AI_TOKEN",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def together_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
for var in TOGETHER_KEY_ENV_VARS:
|
||||
monkeypatch.delenv(var, raising=False)
|
||||
monkeypatch.delenv("TOGETHER_AI_API_BASE", raising=False)
|
||||
monkeypatch.setenv("TOGETHERAI_API_KEY", "test-together-key")
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
|
||||
|
||||
class RecordingHTTPHandler(HTTPHandler):
|
||||
def __init__(self, response_json: dict):
|
||||
super().__init__()
|
||||
self.response_json = response_json
|
||||
self.captured_url: Optional[str] = None
|
||||
self.captured_json: Optional[dict] = None
|
||||
self.captured_headers: Optional[dict] = None
|
||||
|
||||
def post(self, url, data=None, json=None, params=None, headers=None, stream=False, timeout=None, files=None, content=None, logging_obj=None):
|
||||
self.captured_url = url
|
||||
self.captured_json = json
|
||||
self.captured_headers = headers
|
||||
return httpx.Response(
|
||||
status_code=200,
|
||||
json=self.response_json,
|
||||
request=httpx.Request("POST", url),
|
||||
)
|
||||
|
||||
|
||||
def test_provider_config_manager_returns_together_ai_image_edit_config():
|
||||
config = ProviderConfigManager.get_provider_image_edit_config(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
provider=LlmProviders.TOGETHER_AI,
|
||||
)
|
||||
assert isinstance(config, TogetherAIImageEditConfig)
|
||||
|
||||
|
||||
def test_use_multipart_form_data_is_false():
|
||||
assert TogetherAIImageEditConfig().use_multipart_form_data() is False
|
||||
|
||||
|
||||
def test_map_openai_params_maps_size_to_width_height():
|
||||
mapped = TogetherAIImageEditConfig().map_openai_params(
|
||||
image_edit_optional_params={"size": "1024x1024", "n": 1},
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped == {"width": 1024, "height": 1024, "n": 1}
|
||||
|
||||
|
||||
def test_get_complete_url_defaults_to_together_images_generations():
|
||||
url = TogetherAIImageEditConfig().get_complete_url(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
api_base=None,
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == "https://api.together.xyz/v1/images/generations"
|
||||
|
||||
|
||||
def test_validate_environment_sets_bearer_auth_from_env(together_env):
|
||||
headers = TogetherAIImageEditConfig().validate_environment(
|
||||
headers={},
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
)
|
||||
assert headers["Authorization"] == "Bearer test-together-key"
|
||||
|
||||
|
||||
def test_transform_image_edit_request_builds_json_body_with_data_url():
|
||||
body, files = TogetherAIImageEditConfig().transform_image_edit_request(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
prompt="Make this look like a watercolor painting",
|
||||
image=[BytesIO(SAMPLE_PNG_BYTES)],
|
||||
image_edit_optional_request_params={"width": 1024, "height": 1024},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
expected_data_url = f"data:image/png;base64,{base64.b64encode(SAMPLE_PNG_BYTES).decode('utf-8')}"
|
||||
assert body == {
|
||||
"model": "ByteDance-Seed/Seedream-4.0",
|
||||
"prompt": "Make this look like a watercolor painting",
|
||||
"image_url": expected_data_url,
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
}
|
||||
assert files == []
|
||||
|
||||
|
||||
def test_transform_image_edit_request_rejects_multiple_images():
|
||||
with pytest.raises(TogetherAIException):
|
||||
TogetherAIImageEditConfig().transform_image_edit_request(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
prompt="edit",
|
||||
image=[BytesIO(SAMPLE_PNG_BYTES), BytesIO(SAMPLE_PNG_BYTES)],
|
||||
image_edit_optional_request_params={},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
|
||||
def test_transform_image_edit_request_rejects_missing_image():
|
||||
with pytest.raises(TogetherAIException):
|
||||
TogetherAIImageEditConfig().transform_image_edit_request(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
prompt="edit",
|
||||
image=None,
|
||||
image_edit_optional_request_params={},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data_item,expected_url,expected_b64",
|
||||
[
|
||||
({"index": 0, "url": "https://api.together.ai/imgproxy/abc.png", "type": "url"}, "https://api.together.ai/imgproxy/abc.png", None),
|
||||
({"index": 0, "b64_json": "aGVsbG8=", "type": "b64_json"}, None, "aGVsbG8="),
|
||||
],
|
||||
)
|
||||
def test_transform_image_edit_response(data_item, expected_url, expected_b64):
|
||||
raw_response = httpx.Response(
|
||||
status_code=200,
|
||||
json={"id": "abc", "model": "ByteDance-Seed/Seedream-4.0", "object": "list", "data": [data_item]},
|
||||
request=httpx.Request("POST", "https://api.together.xyz/v1/images/generations"),
|
||||
)
|
||||
model_response = TogetherAIImageEditConfig().transform_image_edit_response(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
raw_response=raw_response,
|
||||
logging_obj=None,
|
||||
)
|
||||
assert model_response.data is not None and len(model_response.data) == 1
|
||||
assert model_response.data[0].url == expected_url
|
||||
assert model_response.data[0].b64_json == expected_b64
|
||||
|
||||
|
||||
def test_image_edit_no_longer_raises_not_supported(together_env):
|
||||
client = RecordingHTTPHandler(
|
||||
response_json={"id": "abc", "data": [{"index": 0, "url": "https://api.together.ai/imgproxy/out.png"}]}
|
||||
)
|
||||
response = litellm.image_edit(
|
||||
model="together_ai/ByteDance-Seed/Seedream-4.0",
|
||||
image=BytesIO(SAMPLE_PNG_BYTES),
|
||||
prompt="Make this look like a watercolor painting",
|
||||
size="1024x1024",
|
||||
client=client,
|
||||
)
|
||||
assert client.captured_url == "https://api.together.xyz/v1/images/generations"
|
||||
assert client.captured_json is not None
|
||||
expected_data_url = f"data:image/png;base64,{base64.b64encode(SAMPLE_PNG_BYTES).decode('utf-8')}"
|
||||
assert client.captured_json["image_url"] == expected_data_url
|
||||
assert client.captured_json["model"] == "ByteDance-Seed/Seedream-4.0"
|
||||
assert client.captured_json["prompt"] == "Make this look like a watercolor painting"
|
||||
assert client.captured_json["width"] == 1024
|
||||
assert client.captured_json["height"] == 1024
|
||||
assert "size" not in client.captured_json
|
||||
assert client.captured_headers is not None
|
||||
assert client.captured_headers["Authorization"] == "Bearer test-together-key"
|
||||
assert response.data is not None
|
||||
assert response.data[0].url == "https://api.together.ai/imgproxy/out.png"
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
import json
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
||||
from litellm.llms.together_ai.common_utils import TogetherAIException
|
||||
from litellm.llms.together_ai.image_generation.transformation import (
|
||||
TogetherAIImageGenerationConfig,
|
||||
)
|
||||
from litellm.types.utils import LlmProviders
|
||||
from litellm.utils import ProviderConfigManager, get_optional_params_image_gen
|
||||
|
||||
TOGETHER_KEY_ENV_VARS = (
|
||||
"TOGETHER_API_KEY",
|
||||
"TOGETHER_AI_API_KEY",
|
||||
"TOGETHERAI_API_KEY",
|
||||
"TOGETHER_AI_TOKEN",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def together_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
for var in TOGETHER_KEY_ENV_VARS:
|
||||
monkeypatch.delenv(var, raising=False)
|
||||
monkeypatch.delenv("TOGETHER_AI_API_BASE", raising=False)
|
||||
monkeypatch.setenv("TOGETHERAI_API_KEY", "test-together-key")
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
|
||||
|
||||
class RecordingHTTPHandler(HTTPHandler):
|
||||
def __init__(self, response_json: dict):
|
||||
super().__init__()
|
||||
self.response_json = response_json
|
||||
self.captured_url: Optional[str] = None
|
||||
self.captured_json: Optional[dict] = None
|
||||
self.captured_headers: Optional[dict] = None
|
||||
|
||||
def post(self, url, data=None, json=None, params=None, headers=None, stream=False, timeout=None, files=None, content=None, logging_obj=None):
|
||||
self.captured_url = url
|
||||
self.captured_json = json
|
||||
self.captured_headers = headers
|
||||
return httpx.Response(
|
||||
status_code=200,
|
||||
json=self.response_json,
|
||||
request=httpx.Request("POST", url),
|
||||
)
|
||||
|
||||
|
||||
def test_provider_config_manager_returns_together_ai_image_generation_config():
|
||||
config = ProviderConfigManager.get_provider_image_generation_config(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
provider=LlmProviders.TOGETHER_AI,
|
||||
)
|
||||
assert isinstance(config, TogetherAIImageGenerationConfig)
|
||||
|
||||
|
||||
def test_map_openai_params_maps_size_to_width_height():
|
||||
config = TogetherAIImageGenerationConfig()
|
||||
mapped = config.map_openai_params(
|
||||
non_default_params={"size": "1792x1024", "n": 2},
|
||||
optional_params={},
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped == {"width": 1792, "height": 1024, "n": 2}
|
||||
|
||||
|
||||
def test_map_openai_params_maps_b64_json_response_format_to_base64():
|
||||
config = TogetherAIImageGenerationConfig()
|
||||
mapped = config.map_openai_params(
|
||||
non_default_params={"response_format": "b64_json"},
|
||||
optional_params={},
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped == {"response_format": "base64"}
|
||||
|
||||
|
||||
def test_get_optional_params_image_gen_keeps_image_input_params():
|
||||
config = TogetherAIImageGenerationConfig()
|
||||
optional_params = get_optional_params_image_gen(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
n=1,
|
||||
size="1024x1024",
|
||||
custom_llm_provider="together_ai",
|
||||
provider_config=config,
|
||||
image_url="https://example.com/yosemite.png",
|
||||
steps=28,
|
||||
)
|
||||
body = config.transform_image_generation_request(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
prompt="Make this look like a watercolor painting",
|
||||
optional_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
assert body["image_url"] == "https://example.com/yosemite.png"
|
||||
assert body["steps"] == 28
|
||||
assert body["width"] == 1024
|
||||
assert body["height"] == 1024
|
||||
assert "size" not in body
|
||||
assert "extra_body" not in body
|
||||
|
||||
|
||||
def test_get_complete_url_appends_images_generations():
|
||||
config = TogetherAIImageGenerationConfig()
|
||||
url = config.get_complete_url(
|
||||
api_base="https://api.together.xyz/v1",
|
||||
api_key=None,
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == "https://api.together.xyz/v1/images/generations"
|
||||
|
||||
|
||||
def test_validate_environment_sets_bearer_auth_from_env(together_env):
|
||||
config = TogetherAIImageGenerationConfig()
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert headers["Authorization"] == "Bearer test-together-key"
|
||||
|
||||
|
||||
def test_validate_environment_raises_without_key(monkeypatch: pytest.MonkeyPatch):
|
||||
for var in TOGETHER_KEY_ENV_VARS:
|
||||
monkeypatch.delenv(var, raising=False)
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
config = TogetherAIImageGenerationConfig()
|
||||
with pytest.raises(TogetherAIException):
|
||||
config.validate_environment(
|
||||
headers={},
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data_item,expected_url,expected_b64",
|
||||
[
|
||||
({"index": 0, "url": "https://api.together.ai/imgproxy/abc.png", "type": "url"}, "https://api.together.ai/imgproxy/abc.png", None),
|
||||
({"index": 0, "b64_json": "aGVsbG8=", "type": "b64_json"}, None, "aGVsbG8="),
|
||||
],
|
||||
)
|
||||
def test_transform_image_generation_response(data_item, expected_url, expected_b64):
|
||||
config = TogetherAIImageGenerationConfig()
|
||||
raw_response = httpx.Response(
|
||||
status_code=200,
|
||||
json={"id": "abc", "model": "ByteDance-Seed/Seedream-4.0", "object": "list", "data": [data_item]},
|
||||
request=httpx.Request("POST", "https://api.together.xyz/v1/images/generations"),
|
||||
)
|
||||
model_response = config.transform_image_generation_response(
|
||||
model="ByteDance-Seed/Seedream-4.0",
|
||||
raw_response=raw_response,
|
||||
model_response=litellm.ImageResponse(),
|
||||
logging_obj=None,
|
||||
request_data={},
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
encoding=None,
|
||||
)
|
||||
assert model_response.data is not None and len(model_response.data) == 1
|
||||
assert model_response.data[0].url == expected_url
|
||||
assert model_response.data[0].b64_json == expected_b64
|
||||
|
||||
|
||||
def test_image_generation_sends_together_shaped_json_body(together_env):
|
||||
client = RecordingHTTPHandler(
|
||||
response_json={"id": "abc", "data": [{"index": 0, "url": "https://api.together.ai/imgproxy/out.png"}]}
|
||||
)
|
||||
response = litellm.image_generation(
|
||||
model="together_ai/ByteDance-Seed/Seedream-4.0",
|
||||
prompt="Make this look like a watercolor painting",
|
||||
n=1,
|
||||
size="1024x1024",
|
||||
image_url="https://example.com/yosemite.png",
|
||||
client=client,
|
||||
)
|
||||
assert client.captured_url == "https://api.together.xyz/v1/images/generations"
|
||||
assert client.captured_json == {
|
||||
"model": "ByteDance-Seed/Seedream-4.0",
|
||||
"prompt": "Make this look like a watercolor painting",
|
||||
"n": 1,
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"image_url": "https://example.com/yosemite.png",
|
||||
}
|
||||
assert client.captured_headers is not None
|
||||
assert client.captured_headers["Authorization"] == "Bearer test-together-key"
|
||||
assert response.data is not None
|
||||
assert response.data[0].url == "https://api.together.ai/imgproxy/out.png"
|
||||
Loading…
Add table
Reference in a new issue