Feat(dashscope): add image generation support for qwen-image-2.0 and qwen-image-2.0-pro (#25672)
* feat: add dashscope/qwen-image-2.0 and qwen-image-2.0-pro to model cost map * feat: implement DashScope image generation transformation class * feat: register DashScope in ProviderConfigManager for image generation * feat: add DashScope to image generation provider routing * feat: auto-route qwen-image /chat/completions requests to /images/generations * test: add unit tests for DashScope image generation (22 cases) * refactor: remove proxy-layer qwen-image auto-routing * feat: auto-redirect image_generation models in acompletion() * test: add acompletion auto-redirect test for image_generation models * fix: remove unused Union import in DashScope transformation * fix: scope acompletion redirect to dashscope and narrow exception handler * fix: move get_str_from_messages to module-level import and forward n param to aimage_generation * refactor: remove acompletion image_generation auto-redirect for dashscope * test: remove acompletion auto-redirect test for dashscope image models --------- Co-authored-by: zark.lin <zark.lin@thinkchina.com>
This commit is contained in:
parent
d26bcda52a
commit
fcf917df6d
@ -410,6 +410,7 @@ def image_generation( # noqa: PLR0915
|
||||
litellm.LlmProviders.RUNWAYML,
|
||||
litellm.LlmProviders.VERTEX_AI,
|
||||
litellm.LlmProviders.OPENROUTER,
|
||||
litellm.LlmProviders.DASHSCOPE,
|
||||
):
|
||||
if image_generation_config is None:
|
||||
raise ValueError(
|
||||
|
||||
9
litellm/llms/dashscope/image_generation/__init__.py
Normal file
9
litellm/llms/dashscope/image_generation/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
from litellm.llms.base_llm.image_generation.transformation import BaseImageGenerationConfig
|
||||
|
||||
from .transformation import DashScopeImageGenerationConfig
|
||||
|
||||
__all__ = ["DashScopeImageGenerationConfig"]
|
||||
|
||||
|
||||
def get_dashscope_image_generation_config(model: str) -> BaseImageGenerationConfig:
|
||||
return DashScopeImageGenerationConfig()
|
||||
187
litellm/llms/dashscope/image_generation/transformation.py
Normal file
187
litellm/llms/dashscope/image_generation/transformation.py
Normal file
@ -0,0 +1,187 @@
|
||||
"""
|
||||
DashScope Image Generation Configuration
|
||||
|
||||
Handles transformation between OpenAI-compatible format and DashScope multimodal-generation API.
|
||||
|
||||
API endpoint: POST https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation
|
||||
|
||||
Request format:
|
||||
{
|
||||
"model": "qwen-image-2.0-pro",
|
||||
"input": {
|
||||
"messages": [{"role": "user", "content": [{"text": "<prompt>"}]}]
|
||||
},
|
||||
"parameters": {"size": "1024*1024", ...}
|
||||
}
|
||||
|
||||
Response format:
|
||||
{
|
||||
"output": {
|
||||
"choices": [{"message": {"content": [{"image": "<url>"}]}}]
|
||||
},
|
||||
"usage": {"input_tokens": 0, "output_tokens": 0, "width": 1024, "height": 1024, "image_count": 1}
|
||||
}
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, List, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from litellm.llms.base_llm.image_generation.transformation import BaseImageGenerationConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.openai import AllMessageValues, OpenAIImageGenerationOptionalParams
|
||||
from litellm.types.utils import ImageObject, ImageResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj
|
||||
|
||||
LiteLLMLoggingObj = _LiteLLMLoggingObj
|
||||
else:
|
||||
LiteLLMLoggingObj = Any
|
||||
|
||||
DEFAULT_API_BASE = "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
|
||||
|
||||
# Maps OpenAI size strings (WxH) to DashScope size strings (W*H)
|
||||
OPENAI_TO_DASHSCOPE_SIZE: dict = {
|
||||
"256x256": "256*256",
|
||||
"512x512": "512*512",
|
||||
"1024x1024": "1024*1024",
|
||||
"1792x1024": "1792*1024",
|
||||
"1024x1792": "1024*1792",
|
||||
"2048x2048": "2048*2048",
|
||||
}
|
||||
|
||||
|
||||
class DashScopeImageGenerationConfig(BaseImageGenerationConfig):
|
||||
"""
|
||||
Configuration for DashScope image generation (qwen-image-2.0, qwen-image-2.0-pro).
|
||||
"""
|
||||
|
||||
def get_supported_openai_params(
|
||||
self, model: str
|
||||
) -> List[OpenAIImageGenerationOptionalParams]:
|
||||
return ["n", "size"]
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
optional_params: dict,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
) -> dict:
|
||||
supported_params = self.get_supported_openai_params(model)
|
||||
mapped: dict = {}
|
||||
for k, v in non_default_params.items():
|
||||
if k in optional_params:
|
||||
continue
|
||||
if k not in supported_params:
|
||||
continue
|
||||
if k == "size":
|
||||
# Convert "WxH" → "W*H"
|
||||
mapped["size"] = OPENAI_TO_DASHSCOPE_SIZE.get(v, v.replace("x", "*"))
|
||||
elif k == "n":
|
||||
mapped["image_count"] = v
|
||||
return mapped
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: Optional[str],
|
||||
api_key: Optional[str],
|
||||
model: str,
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
stream: Optional[bool] = None,
|
||||
) -> str:
|
||||
return (
|
||||
api_base
|
||||
or get_secret_str("DASHSCOPE_API_BASE_IMAGE")
|
||||
or DEFAULT_API_BASE
|
||||
)
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
messages: List[AllMessageValues],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
api_key: Optional[str] = None,
|
||||
api_base: Optional[str] = None,
|
||||
) -> dict:
|
||||
final_api_key = api_key or get_secret_str("DASHSCOPE_API_KEY")
|
||||
if not final_api_key:
|
||||
raise ValueError("DASHSCOPE_API_KEY is not set")
|
||||
headers["Authorization"] = f"Bearer {final_api_key}"
|
||||
headers["Content-Type"] = "application/json"
|
||||
return headers
|
||||
|
||||
def transform_image_generation_request(
|
||||
self,
|
||||
model: str,
|
||||
prompt: str,
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
headers: dict,
|
||||
) -> dict:
|
||||
"""
|
||||
Transform OpenAI-style image generation request to DashScope multimodal-generation format.
|
||||
"""
|
||||
parameters: dict = {}
|
||||
for k, v in optional_params.items():
|
||||
parameters[k] = v
|
||||
|
||||
return {
|
||||
"model": model,
|
||||
"input": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [{"text": prompt}],
|
||||
}
|
||||
]
|
||||
},
|
||||
"parameters": parameters,
|
||||
}
|
||||
|
||||
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: Optional[str] = None,
|
||||
json_mode: Optional[bool] = None,
|
||||
) -> ImageResponse:
|
||||
"""
|
||||
Transform DashScope response to litellm ImageResponse.
|
||||
|
||||
DashScope response: output.choices[0].message.content[0].image
|
||||
OpenAI response: data[0].url
|
||||
"""
|
||||
try:
|
||||
response_data = raw_response.json()
|
||||
except Exception as e:
|
||||
raise self.get_error_class(
|
||||
error_message=f"Failed to parse DashScope image generation response: {e}",
|
||||
status_code=raw_response.status_code,
|
||||
headers=raw_response.headers,
|
||||
)
|
||||
|
||||
if not model_response.data:
|
||||
model_response.data = []
|
||||
|
||||
choices = response_data.get("output", {}).get("choices", [])
|
||||
for choice in choices:
|
||||
content_list = (
|
||||
choice.get("message", {}).get("content", [])
|
||||
)
|
||||
for content_item in content_list:
|
||||
image_url = content_item.get("image")
|
||||
if image_url:
|
||||
model_response.data.append(ImageObject(url=image_url))
|
||||
|
||||
return model_response
|
||||
@ -10383,6 +10383,22 @@
|
||||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"dashscope/qwen-image-2.0": {
|
||||
"litellm_provider": "dashscope",
|
||||
"mode": "image_generation",
|
||||
"source": "https://www.alibabacloud.com/help/en/model-studio/models",
|
||||
"supported_endpoints": [
|
||||
"/v1/images/generations"
|
||||
]
|
||||
},
|
||||
"dashscope/qwen-image-2.0-pro": {
|
||||
"litellm_provider": "dashscope",
|
||||
"mode": "image_generation",
|
||||
"source": "https://www.alibabacloud.com/help/en/model-studio/models",
|
||||
"supported_endpoints": [
|
||||
"/v1/images/generations"
|
||||
]
|
||||
},
|
||||
"databricks/databricks-bge-large-en": {
|
||||
"input_cost_per_token": 1.0003e-07,
|
||||
"input_dbu_cost_per_token": 1.429e-06,
|
||||
|
||||
@ -7246,6 +7246,7 @@ async def chat_completion( # noqa: PLR0915
|
||||
and user_api_key_dict.agent_id is not None
|
||||
):
|
||||
data["metadata"]["agent_id"] = user_api_key_dict.agent_id
|
||||
|
||||
base_llm_response_processor = ProxyBaseLLMRequestProcessing(data=data)
|
||||
try:
|
||||
result = await base_llm_response_processor.base_process_llm_request(
|
||||
|
||||
@ -8952,6 +8952,12 @@ class ProviderConfigManager:
|
||||
)
|
||||
|
||||
return get_openrouter_image_generation_config(model)
|
||||
elif LlmProviders.DASHSCOPE == provider:
|
||||
from litellm.llms.dashscope.image_generation import (
|
||||
get_dashscope_image_generation_config,
|
||||
)
|
||||
|
||||
return get_dashscope_image_generation_config(model)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -10397,6 +10397,22 @@
|
||||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"dashscope/qwen-image-2.0": {
|
||||
"litellm_provider": "dashscope",
|
||||
"mode": "image_generation",
|
||||
"source": "https://www.alibabacloud.com/help/en/model-studio/models",
|
||||
"supported_endpoints": [
|
||||
"/v1/images/generations"
|
||||
]
|
||||
},
|
||||
"dashscope/qwen-image-2.0-pro": {
|
||||
"litellm_provider": "dashscope",
|
||||
"mode": "image_generation",
|
||||
"source": "https://www.alibabacloud.com/help/en/model-studio/models",
|
||||
"supported_endpoints": [
|
||||
"/v1/images/generations"
|
||||
]
|
||||
},
|
||||
"databricks/databricks-bge-large-en": {
|
||||
"input_cost_per_token": 1.0003e-07,
|
||||
"input_dbu_cost_per_token": 1.429e-06,
|
||||
|
||||
328
tests/test_litellm/test_dashscope_image_generation.py
Normal file
328
tests/test_litellm/test_dashscope_image_generation.py
Normal file
@ -0,0 +1,328 @@
|
||||
"""
|
||||
Unit tests for DashScope image generation support (qwen-image-2.0, qwen-image-2.0-pro).
|
||||
|
||||
Run in docker: pytest tests/test_litellm/test_dashscope_image_generation.py -v
|
||||
"""
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.llms.dashscope.image_generation.transformation import (
|
||||
DashScopeImageGenerationConfig,
|
||||
DEFAULT_API_BASE,
|
||||
)
|
||||
from litellm.types.utils import ImageObject, ImageResponse
|
||||
from litellm.utils import get_llm_provider
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Provider detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_string",
|
||||
[
|
||||
"dashscope/qwen-image-2.0",
|
||||
"dashscope/qwen-image-2.0-pro",
|
||||
],
|
||||
)
|
||||
def test_get_llm_provider_returns_dashscope(model_string: str):
|
||||
model, provider, _, _ = get_llm_provider(model_string)
|
||||
assert provider == "dashscope", f"Expected 'dashscope', got '{provider}'"
|
||||
assert "qwen-image" in model
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Model info: mode == "image_generation"
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model_string, custom_provider",
|
||||
[
|
||||
("dashscope/qwen-image-2.0", "dashscope"),
|
||||
("dashscope/qwen-image-2.0-pro", "dashscope"),
|
||||
],
|
||||
)
|
||||
def test_get_model_info_mode_is_image_generation(model_string: str, custom_provider: str):
|
||||
import os
|
||||
|
||||
prev_env = os.environ.get("LITELLM_LOCAL_MODEL_COST_MAP")
|
||||
prev_model_cost = litellm.model_cost
|
||||
try:
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
|
||||
litellm.model_cost = litellm.get_model_cost_map(url="")
|
||||
|
||||
info = litellm.get_model_info(model=model_string, custom_llm_provider=custom_provider)
|
||||
assert info["mode"] == "image_generation", (
|
||||
f"Expected mode='image_generation', got '{info['mode']}'"
|
||||
)
|
||||
finally:
|
||||
if prev_env is None:
|
||||
os.environ.pop("LITELLM_LOCAL_MODEL_COST_MAP", None)
|
||||
else:
|
||||
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = prev_env
|
||||
litellm.model_cost = prev_model_cost
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Request transformation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestDashScopeImageGenerationConfig:
|
||||
def setup_method(self):
|
||||
self.cfg = DashScopeImageGenerationConfig()
|
||||
|
||||
def test_get_complete_url_default(self):
|
||||
url = self.cfg.get_complete_url(None, None, "qwen-image-2.0", {}, {})
|
||||
assert url == DEFAULT_API_BASE
|
||||
|
||||
def test_get_complete_url_custom(self):
|
||||
custom = "https://custom.endpoint/generate"
|
||||
url = self.cfg.get_complete_url(custom, None, "qwen-image-2.0", {}, {})
|
||||
assert url == custom
|
||||
|
||||
def test_validate_environment_sets_auth_header(self):
|
||||
headers = self.cfg.validate_environment(
|
||||
headers={},
|
||||
model="qwen-image-2.0",
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key="sk-test-key",
|
||||
)
|
||||
assert headers["Authorization"] == "Bearer sk-test-key"
|
||||
assert headers["Content-Type"] == "application/json"
|
||||
|
||||
def test_validate_environment_raises_without_key(self):
|
||||
with patch("litellm.llms.dashscope.image_generation.transformation.get_secret_str", return_value=None):
|
||||
with pytest.raises(ValueError, match="DASHSCOPE_API_KEY"):
|
||||
self.cfg.validate_environment(
|
||||
headers={},
|
||||
model="qwen-image-2.0",
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
)
|
||||
|
||||
def test_transform_request_structure(self):
|
||||
req = self.cfg.transform_image_generation_request(
|
||||
model="qwen-image-2.0",
|
||||
prompt="a puppy on green grass",
|
||||
optional_params={"size": "1024*1024"},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
assert req["model"] == "qwen-image-2.0"
|
||||
messages = req["input"]["messages"]
|
||||
assert len(messages) == 1
|
||||
assert messages[0]["role"] == "user"
|
||||
assert messages[0]["content"][0]["text"] == "a puppy on green grass"
|
||||
assert req["parameters"]["size"] == "1024*1024"
|
||||
|
||||
def test_transform_request_empty_params(self):
|
||||
req = self.cfg.transform_image_generation_request(
|
||||
model="qwen-image-2.0-pro",
|
||||
prompt="sunset over the ocean",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
assert req["parameters"] == {}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Response transformation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _make_mock_response(self, image_url: str) -> httpx.Response:
|
||||
body = {
|
||||
"status_code": 200,
|
||||
"request_id": "test-request-id",
|
||||
"output": {
|
||||
"choices": [
|
||||
{
|
||||
"finish_reason": "stop",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": [{"image": image_url}],
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
"usage": {
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"image_count": 1,
|
||||
},
|
||||
}
|
||||
mock_resp = MagicMock(spec=httpx.Response)
|
||||
mock_resp.status_code = 200
|
||||
mock_resp.headers = {}
|
||||
mock_resp.json.return_value = body
|
||||
return mock_resp
|
||||
|
||||
def test_transform_response_extracts_url(self):
|
||||
image_url = "https://example.oss.aliyuncs.com/generated/test.png"
|
||||
mock_resp = self._make_mock_response(image_url)
|
||||
model_response = ImageResponse()
|
||||
result = self.cfg.transform_image_generation_response(
|
||||
model="qwen-image-2.0",
|
||||
raw_response=mock_resp,
|
||||
model_response=model_response,
|
||||
logging_obj=MagicMock(),
|
||||
request_data={},
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
encoding=None,
|
||||
)
|
||||
assert result.data is not None
|
||||
assert len(result.data) == 1
|
||||
assert result.data[0].url == image_url
|
||||
|
||||
def test_transform_response_multiple_images(self):
|
||||
body = {
|
||||
"output": {
|
||||
"choices": [
|
||||
{"finish_reason": "stop", "message": {"role": "assistant", "content": [{"image": "https://example.com/img1.png"}]}},
|
||||
{"finish_reason": "stop", "message": {"role": "assistant", "content": [{"image": "https://example.com/img2.png"}]}},
|
||||
]
|
||||
},
|
||||
"usage": {},
|
||||
}
|
||||
mock_resp = MagicMock(spec=httpx.Response)
|
||||
mock_resp.status_code = 200
|
||||
mock_resp.headers = {}
|
||||
mock_resp.json.return_value = body
|
||||
|
||||
model_response = ImageResponse()
|
||||
result = self.cfg.transform_image_generation_response(
|
||||
model="qwen-image-2.0",
|
||||
raw_response=mock_resp,
|
||||
model_response=model_response,
|
||||
logging_obj=MagicMock(),
|
||||
request_data={},
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
encoding=None,
|
||||
)
|
||||
assert len(result.data) == 2
|
||||
assert result.data[0].url == "https://example.com/img1.png"
|
||||
assert result.data[1].url == "https://example.com/img2.png"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. OpenAI → DashScope parameter mapping
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_map_openai_params_size_conversion(self):
|
||||
mapped = self.cfg.map_openai_params(
|
||||
non_default_params={"size": "1024x1024"},
|
||||
optional_params={},
|
||||
model="qwen-image-2.0",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped["size"] == "1024*1024"
|
||||
|
||||
def test_map_openai_params_n_to_image_count(self):
|
||||
mapped = self.cfg.map_openai_params(
|
||||
non_default_params={"n": 2},
|
||||
optional_params={},
|
||||
model="qwen-image-2.0",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped["image_count"] == 2
|
||||
|
||||
def test_map_openai_params_unknown_size_uses_asterisk(self):
|
||||
mapped = self.cfg.map_openai_params(
|
||||
non_default_params={"size": "768x768"},
|
||||
optional_params={},
|
||||
model="qwen-image-2.0",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped["size"] == "768*768"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"openai_size, expected",
|
||||
[
|
||||
("256x256", "256*256"),
|
||||
("512x512", "512*512"),
|
||||
("1024x1024", "1024*1024"),
|
||||
("1792x1024", "1792*1024"),
|
||||
("1024x1792", "1024*1792"),
|
||||
("2048x2048", "2048*2048"),
|
||||
],
|
||||
)
|
||||
def test_map_openai_params_size_table(self, openai_size: str, expected: str):
|
||||
mapped = self.cfg.map_openai_params(
|
||||
non_default_params={"size": openai_size},
|
||||
optional_params={},
|
||||
model="qwen-image-2.0",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped["size"] == expected
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. End-to-end flow via litellm.image_generation (HTTP mocked)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_litellm_image_generation_dashscope_end_to_end():
|
||||
mock_response_body = {
|
||||
"output": {
|
||||
"choices": [
|
||||
{
|
||||
"finish_reason": "stop",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{"image": "https://dashscope-result.oss.aliyuncs.com/test.png"}
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
"usage": {"input_tokens": 0, "output_tokens": 0, "width": 1024, "height": 1024, "image_count": 1},
|
||||
}
|
||||
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.llm_http_handler.HTTPHandler.post"
|
||||
) as mock_post:
|
||||
mock_http_response = MagicMock()
|
||||
mock_http_response.json.return_value = mock_response_body
|
||||
mock_http_response.status_code = 200
|
||||
mock_http_response.headers = {}
|
||||
mock_post.return_value = mock_http_response
|
||||
|
||||
response = litellm.image_generation(
|
||||
model="dashscope/qwen-image-2.0",
|
||||
prompt="a puppy playing on green grass",
|
||||
api_key="sk-test-key",
|
||||
size="1024x1024",
|
||||
)
|
||||
|
||||
assert response is not None
|
||||
assert response.data is not None
|
||||
assert len(response.data) == 1
|
||||
assert response.data[0].url == "https://dashscope-result.oss.aliyuncs.com/test.png"
|
||||
|
||||
# Verify the HTTP call was made to the DashScope endpoint
|
||||
call_args = mock_post.call_args
|
||||
called_url = call_args[0][0] if call_args[0] else call_args.kwargs.get("url", "")
|
||||
assert "dashscope" in called_url or "aliyuncs" in called_url
|
||||
|
||||
# Verify request body contains DashScope format
|
||||
call_kwargs = call_args[1] if call_args[1] else {}
|
||||
if "json" in call_kwargs:
|
||||
body = call_kwargs["json"]
|
||||
assert "input" in body
|
||||
assert "messages" in body["input"]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user