runapi-seedream 0.3.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/PKG-INFO +3 -3
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/pyproject.toml +3 -3
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/src/runapi/seedream/resources/decompose_layers.py +0 -2
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/src/runapi/seedream/resources/edit_image.py +2 -39
- runapi_seedream-0.5.0/src/runapi/seedream/resources/text_to_image.py +56 -0
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/src/runapi/seedream/types.py +1 -10
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/tests/test_client.py +34 -86
- runapi_seedream-0.3.0/src/runapi/seedream/contract_gen.py +0 -222
- runapi_seedream-0.3.0/src/runapi/seedream/resources/text_to_image.py +0 -105
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/README.md +0 -0
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/src/runapi/seedream/__init__.py +0 -0
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/src/runapi/seedream/client.py +0 -0
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/src/runapi/seedream/py.typed +0 -0
- {runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/src/runapi/seedream/resources/__init__.py +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: runapi-seedream
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: RunAPI Seedream SDK for text-to-image and edit-image workflows in JavaScript, Python, Ruby, Go, Java, and PHP
|
|
5
5
|
Project-URL: Homepage, https://runapi.ai/models/seedream
|
|
6
6
|
Project-URL: Documentation, https://runapi.ai/docs/resources/sdks
|
|
7
7
|
Project-URL: Source, https://github.com/runapi-ai/seedream-sdk
|
|
8
8
|
Project-URL: Issues, https://github.com/runapi-ai/seedream-sdk/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/runapi-ai/seedream-sdk/blob/main/CHANGELOG.md
|
|
10
|
-
Project-URL: Release Notes, https://github.com/runapi-ai/seedream-sdk/releases/tag/python%2Fv0.
|
|
10
|
+
Project-URL: Release Notes, https://github.com/runapi-ai/seedream-sdk/releases/tag/python%2Fv0.5.0
|
|
11
11
|
Author-email: RunAPI <contact@runapi.ai>
|
|
12
12
|
License-Expression: Apache-2.0
|
|
13
13
|
Keywords: api,golang,gradle,image-api,image-generation,java,maven,python,ruby,runapi,runapi-ai,sdk,seedream,seedream-api,text-to-image,typescript
|
|
14
14
|
Requires-Python: >=3.9
|
|
15
|
-
Requires-Dist: runapi-core
|
|
15
|
+
Requires-Dist: runapi-core~=0.10.1
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
18
|
# Seedream Python SDK for RunAPI
|
|
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "runapi-seedream"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "RunAPI Seedream SDK for text-to-image and edit-image workflows in JavaScript, Python, Ruby, Go, Java, and PHP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
authors = [{ name = "RunAPI", email = "contact@runapi.ai" }]
|
|
13
13
|
keywords = ["runapi", "runapi-ai", "seedream", "api", "sdk", "typescript", "python", "ruby", "golang", "java", "maven", "gradle", "image-generation", "text-to-image", "image-api", "seedream-api"]
|
|
14
|
-
dependencies = ["runapi-core
|
|
14
|
+
dependencies = ["runapi-core~=0.10.1"]
|
|
15
15
|
|
|
16
16
|
[project.urls]
|
|
17
17
|
Homepage = "https://runapi.ai/models/seedream"
|
|
@@ -19,7 +19,7 @@ Documentation = "https://runapi.ai/docs/resources/sdks"
|
|
|
19
19
|
Source = "https://github.com/runapi-ai/seedream-sdk"
|
|
20
20
|
Issues = "https://github.com/runapi-ai/seedream-sdk/issues"
|
|
21
21
|
Changelog = "https://github.com/runapi-ai/seedream-sdk/blob/main/CHANGELOG.md"
|
|
22
|
-
"Release Notes" = "https://github.com/runapi-ai/seedream-sdk/releases/tag/python%2Fv0.
|
|
22
|
+
"Release Notes" = "https://github.com/runapi-ai/seedream-sdk/releases/tag/python%2Fv0.5.0"
|
|
23
23
|
|
|
24
24
|
[tool.hatch.build.targets.wheel]
|
|
25
25
|
packages = ["src/runapi"]
|
{runapi_seedream-0.3.0 → runapi_seedream-0.5.0}/src/runapi/seedream/resources/decompose_layers.py
RENAMED
|
@@ -6,7 +6,6 @@ from typing import Any, Optional
|
|
|
6
6
|
|
|
7
7
|
from runapi.core import RequestOptions, Resource
|
|
8
8
|
|
|
9
|
-
from ..contract_gen import CONTRACT
|
|
10
9
|
from ..types import CompletedDecomposeLayersResponse, DecomposeLayersResponse
|
|
11
10
|
|
|
12
11
|
|
|
@@ -23,7 +22,6 @@ class DecomposeLayers(Resource):
|
|
|
23
22
|
|
|
24
23
|
def create(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
|
|
25
24
|
compacted = self._compact_params(params)
|
|
26
|
-
self._validate_contract(CONTRACT["decompose-layers"], compacted)
|
|
27
25
|
return self._request("post", self.ENDPOINT, body=compacted, options=options)
|
|
28
26
|
|
|
29
27
|
def get(self, id: str, options: Optional[RequestOptions] = None) -> Any:
|
|
@@ -2,15 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Optional
|
|
6
6
|
|
|
7
|
-
from runapi.core import Resource,
|
|
7
|
+
from runapi.core import Resource, RequestOptions
|
|
8
8
|
|
|
9
|
-
from ..contract_gen import CONTRACT
|
|
10
9
|
from ..types import (
|
|
11
|
-
LONG_PROMPT_MODELS,
|
|
12
|
-
MINIMUM_THREE_PROMPT_MODELS,
|
|
13
|
-
V4_MODELS,
|
|
14
10
|
CompletedEditImageResponse,
|
|
15
11
|
EditImageResponse,
|
|
16
12
|
)
|
|
@@ -24,10 +20,6 @@ class EditImage(Resource):
|
|
|
24
20
|
RESPONSE_CLASS = EditImageResponse
|
|
25
21
|
COMPLETED_RESPONSE_CLASS = CompletedEditImageResponse
|
|
26
22
|
|
|
27
|
-
PROMPT_MAX_LENGTH = 3000
|
|
28
|
-
V4_PROMPT_MAX_LENGTH = 5000
|
|
29
|
-
PROMPT_MIN_LENGTH_LITE = 3
|
|
30
|
-
|
|
31
23
|
def run(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
|
|
32
24
|
"""Edit an image and poll until it completes.
|
|
33
25
|
|
|
@@ -50,7 +42,6 @@ class EditImage(Resource):
|
|
|
50
42
|
The task creation result with an id.
|
|
51
43
|
"""
|
|
52
44
|
compacted = self._compact_params(params)
|
|
53
|
-
self._validate_params(compacted)
|
|
54
45
|
return self._request("post", self.ENDPOINT, body=compacted, options=options)
|
|
55
46
|
|
|
56
47
|
def get(self, id: str, options: Optional[RequestOptions] = None) -> Any:
|
|
@@ -63,31 +54,3 @@ class EditImage(Resource):
|
|
|
63
54
|
The current task status.
|
|
64
55
|
"""
|
|
65
56
|
return self._request("get", f"{self.ENDPOINT}/{id}", options=options)
|
|
66
|
-
|
|
67
|
-
def _validate_params(self, params: Dict[str, Any]) -> None:
|
|
68
|
-
self._validate_contract(CONTRACT["edit-image"], params)
|
|
69
|
-
|
|
70
|
-
model = params.get("model")
|
|
71
|
-
|
|
72
|
-
prompt = params.get("prompt")
|
|
73
|
-
if not (isinstance(prompt, str) and prompt):
|
|
74
|
-
raise ValidationError("prompt is required")
|
|
75
|
-
max_length = self.V4_PROMPT_MAX_LENGTH if model in LONG_PROMPT_MODELS else self.PROMPT_MAX_LENGTH
|
|
76
|
-
if len(prompt) > max_length:
|
|
77
|
-
raise ValidationError(f"prompt must be at most {max_length} characters")
|
|
78
|
-
if model in MINIMUM_THREE_PROMPT_MODELS and len(prompt) < self.PROMPT_MIN_LENGTH_LITE:
|
|
79
|
-
raise ValidationError(
|
|
80
|
-
f"prompt must be between {self.PROMPT_MIN_LENGTH_LITE} and {max_length} characters"
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
if model in V4_MODELS:
|
|
84
|
-
self._validate_integer(params, "seed")
|
|
85
|
-
|
|
86
|
-
@staticmethod
|
|
87
|
-
def _validate_integer(params: Dict[str, Any], key: str) -> None:
|
|
88
|
-
value = params.get(key)
|
|
89
|
-
if value is None:
|
|
90
|
-
return
|
|
91
|
-
if isinstance(value, int) and not isinstance(value, bool):
|
|
92
|
-
return
|
|
93
|
-
raise ValidationError(f"{key} must be an integer")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Seedream text-to-image resource."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from runapi.core import Resource, RequestOptions
|
|
8
|
+
|
|
9
|
+
from ..types import (
|
|
10
|
+
CompletedTextToImageResponse,
|
|
11
|
+
TextToImageResponse,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TextToImage(Resource):
|
|
16
|
+
"""Generate images from text prompts with Seedream models."""
|
|
17
|
+
|
|
18
|
+
ENDPOINT = "/api/v1/seedream/text_to_image"
|
|
19
|
+
|
|
20
|
+
RESPONSE_CLASS = TextToImageResponse
|
|
21
|
+
COMPLETED_RESPONSE_CLASS = CompletedTextToImageResponse
|
|
22
|
+
|
|
23
|
+
def run(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
|
|
24
|
+
"""Create a text-to-image task and poll until it completes.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
**params: text-to-image parameters (model, ...).
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
The completed (narrowed) text-to-image response.
|
|
31
|
+
"""
|
|
32
|
+
task = self.create(options=options, **params)
|
|
33
|
+
return self._poll_until_complete(lambda: self.get(task.id, options=options))
|
|
34
|
+
|
|
35
|
+
def create(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
|
|
36
|
+
"""Create a text-to-image task and return immediately with an id.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
**params: text-to-image parameters (model, ...).
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
The task creation result with an id.
|
|
43
|
+
"""
|
|
44
|
+
compacted = self._compact_params(params)
|
|
45
|
+
return self._request("post", self.ENDPOINT, body=compacted, options=options)
|
|
46
|
+
|
|
47
|
+
def get(self, id: str, options: Optional[RequestOptions] = None) -> Any:
|
|
48
|
+
"""Fetch the current status of a text-to-image task.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
id: The task id returned by ``create``.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
The current task status.
|
|
55
|
+
"""
|
|
56
|
+
return self._request("get", f"{self.ENDPOINT}/{id}", options=options)
|
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
"""Seedream
|
|
1
|
+
"""Seedream response models."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from runapi.core import BaseModel, TaskResponse, optional, required
|
|
6
6
|
|
|
7
|
-
# Model groupings used by bespoke prompt-length selection (per-model rule the
|
|
8
|
-
# contract cannot express). Model membership and field enums are validated by
|
|
9
|
-
# the generated CONTRACT.
|
|
10
|
-
LITE_MODELS = ["seedream-5-lite-text-to-image", "seedream-5-lite-edit"]
|
|
11
|
-
PRO_MODELS = ["seedream-5-pro-text-to-image", "seedream-5-pro-edit"]
|
|
12
|
-
V4_MODELS = ["seedream-v4-text-to-image", "seedream-v4-edit"]
|
|
13
|
-
LONG_PROMPT_MODELS = [*PRO_MODELS, *V4_MODELS]
|
|
14
|
-
MINIMUM_THREE_PROMPT_MODELS = [*LITE_MODELS, *PRO_MODELS]
|
|
15
|
-
|
|
16
7
|
|
|
17
8
|
class Image(BaseModel):
|
|
18
9
|
url = optional(str)
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
1
4
|
import pytest
|
|
2
5
|
|
|
3
6
|
from runapi.core import config
|
|
4
7
|
from runapi.core.errors import AuthenticationError, ValidationError
|
|
8
|
+
from runapi.core.http_client import HttpClient
|
|
9
|
+
from runapi.core.options import ClientOptions
|
|
5
10
|
from runapi.seedream import SeedreamClient
|
|
6
11
|
from runapi.seedream.resources.edit_image import EditImage
|
|
7
12
|
from runapi.seedream.resources.decompose_layers import DecomposeLayers
|
|
@@ -73,10 +78,9 @@ def test_decompose_layers_create_get_and_completed_response():
|
|
|
73
78
|
{"id": "layers-1", "status": "processing"},
|
|
74
79
|
{
|
|
75
80
|
"id": "layers-1",
|
|
76
|
-
"status": "completed",
|
|
81
|
+
"status": "completed", "usage": {"cost": 0.05, "generated_images": 2},
|
|
77
82
|
"base_image": {"url": "https://file.runapi.ai/base.jpeg"},
|
|
78
|
-
"layers": [{"url": "https://file.runapi.ai/layer.png", "z_index": 1}],
|
|
79
|
-
},
|
|
83
|
+
"layers": [{"url": "https://file.runapi.ai/layer.png", "z_index": 1}]},
|
|
80
84
|
)
|
|
81
85
|
client = SeedreamClient(api_key="k", http_client=fake)
|
|
82
86
|
created = client.decompose_layers.create(
|
|
@@ -92,11 +96,11 @@ def test_decompose_layers_create_get_and_completed_response():
|
|
|
92
96
|
{
|
|
93
97
|
"model": "seedream-5-pro-layer-decomposition",
|
|
94
98
|
"image_url": "https://cdn.runapi.ai/public/samples/image.jpg",
|
|
95
|
-
"output_format": "jpeg",
|
|
96
|
-
},
|
|
99
|
+
"output_format": "jpeg"},
|
|
97
100
|
)
|
|
98
101
|
assert isinstance(result, DecomposeLayersResponse)
|
|
99
102
|
assert result.base_image.url == "https://file.runapi.ai/base.jpeg"
|
|
103
|
+
assert result.usage.generated_images == 2
|
|
100
104
|
|
|
101
105
|
|
|
102
106
|
# --- request shapes -------------------------------------------------------
|
|
@@ -109,8 +113,7 @@ def test_create_posts_compacted_body():
|
|
|
109
113
|
model="seedream-v4-text-to-image", prompt="hello world", aspect_ratio="1:1", seed=None
|
|
110
114
|
)
|
|
111
115
|
assert fake.calls == [
|
|
112
|
-
("post", "/api/v1/seedream/text_to_image", {"model": "seedream-v4-text-to-image", "prompt": "hello world", "aspect_ratio": "1:1"})
|
|
113
|
-
]
|
|
116
|
+
("post", "/api/v1/seedream/text_to_image", {"model": "seedream-v4-text-to-image", "prompt": "hello world", "aspect_ratio": "1:1"})]
|
|
114
117
|
assert isinstance(result, TextToImageResponse)
|
|
115
118
|
|
|
116
119
|
|
|
@@ -135,10 +138,8 @@ def test_lite_output_format_is_forwarded():
|
|
|
135
138
|
"source_image_urls": ["https://cdn.runapi.ai/public/samples/image.jpg"],
|
|
136
139
|
"aspect_ratio": "1:1",
|
|
137
140
|
"output_quality": "high",
|
|
138
|
-
"output_format": "jpeg",
|
|
139
|
-
|
|
140
|
-
),
|
|
141
|
-
]
|
|
141
|
+
"output_format": "jpeg"},
|
|
142
|
+
)]
|
|
142
143
|
|
|
143
144
|
|
|
144
145
|
def test_pro_edit_is_forwarded():
|
|
@@ -151,9 +152,11 @@ def test_pro_edit_is_forwarded():
|
|
|
151
152
|
aspect_ratio="3:2",
|
|
152
153
|
output_quality="basic",
|
|
153
154
|
output_format="png",
|
|
155
|
+
background="transparent",
|
|
154
156
|
)
|
|
155
157
|
assert fake.calls[0][2]["model"] == "seedream-5-pro-edit"
|
|
156
158
|
assert fake.calls[0][2]["output_format"] == "png"
|
|
159
|
+
assert fake.calls[0][2]["background"] == "transparent"
|
|
157
160
|
|
|
158
161
|
|
|
159
162
|
def test_get_fetches_by_id():
|
|
@@ -166,7 +169,7 @@ def test_get_fetches_by_id():
|
|
|
166
169
|
def test_run_narrows_completed_type():
|
|
167
170
|
fake = FakeHttp(
|
|
168
171
|
{"id": "t1", "status": "pending"},
|
|
169
|
-
{"id": "t1", "status": "completed", "images": [{"url": "https://x/y.png"}]},
|
|
172
|
+
{"id": "t1", "status": "completed", "usage": {"cost": 0.05}, "images": [{"url": "https://x/y.png"}]},
|
|
170
173
|
)
|
|
171
174
|
client = SeedreamClient(api_key="k", http_client=fake)
|
|
172
175
|
result = client.text_to_image.run(model="seedream-v4-text-to-image", prompt="a serene lake")
|
|
@@ -174,82 +177,27 @@ def test_run_narrows_completed_type():
|
|
|
174
177
|
assert result.images[0].url == "https://x/y.png"
|
|
175
178
|
|
|
176
179
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def test_rejects_unknown_model():
|
|
181
|
-
client = SeedreamClient(api_key="k", http_client=FakeHttp())
|
|
182
|
-
with pytest.raises(ValidationError, match="model must be one of:"):
|
|
183
|
-
client.text_to_image.create(model="nope", prompt="hi there")
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def test_requires_prompt():
|
|
187
|
-
client = SeedreamClient(api_key="k", http_client=FakeHttp())
|
|
188
|
-
with pytest.raises(ValidationError, match="prompt is required"):
|
|
189
|
-
client.text_to_image.create(model="seedream-v4-text-to-image")
|
|
190
|
-
|
|
180
|
+
def test_server_decides_unknown_models_and_params():
|
|
181
|
+
sent = []
|
|
191
182
|
|
|
192
|
-
def
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def test_non_v4_requires_aspect_ratio_and_quality():
|
|
199
|
-
client = SeedreamClient(api_key="k", http_client=FakeHttp())
|
|
200
|
-
with pytest.raises(ValidationError, match="aspect_ratio is required"):
|
|
201
|
-
client.text_to_image.create(model="seedream-4.5-text-to-image", prompt="hi there")
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def test_text_to_image_rejects_source_image_urls():
|
|
205
|
-
client = SeedreamClient(api_key="k", http_client=FakeHttp())
|
|
206
|
-
with pytest.raises(ValidationError, match="source_image_urls is not supported"):
|
|
207
|
-
client.text_to_image.create(
|
|
208
|
-
model="seedream-v4-text-to-image", prompt="hi there", source_image_urls=["https://x/a.png"]
|
|
209
|
-
)
|
|
183
|
+
def handler(request):
|
|
184
|
+
body = json.loads(request.content)
|
|
185
|
+
sent.append(body)
|
|
186
|
+
if body["output_quality"] == "bad":
|
|
187
|
+
return httpx.Response(400, json={"error": "output_quality is not supported"})
|
|
188
|
+
return httpx.Response(200, json={"id": "task_1", "status": "processing"})
|
|
210
189
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
with pytest.raises(ValidationError, match="source_image_urls is required"):
|
|
215
|
-
client.edit_image.create(model="seedream-v4-edit", prompt="make it pop")
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def test_lite_prompt_min_length():
|
|
219
|
-
client = SeedreamClient(api_key="k", http_client=FakeHttp())
|
|
220
|
-
with pytest.raises(ValidationError, match="between 3 and 3000"):
|
|
221
|
-
client.text_to_image.create(model="seedream-5-lite-text-to-image", prompt="hi", aspect_ratio="1:1", output_quality="high")
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def test_pro_prompt_allows_5000_characters():
|
|
225
|
-
client = SeedreamClient(api_key="k", http_client=FakeHttp())
|
|
226
|
-
client.text_to_image.create(
|
|
227
|
-
model="seedream-5-pro-text-to-image",
|
|
228
|
-
prompt="x" * 5000,
|
|
229
|
-
aspect_ratio="1:1",
|
|
230
|
-
output_quality="high",
|
|
190
|
+
http = HttpClient(
|
|
191
|
+
ClientOptions(api_key="k", base_url="https://runapi.ai", max_retries=0),
|
|
192
|
+
transport=httpx.MockTransport(handler),
|
|
231
193
|
)
|
|
194
|
+
client = SeedreamClient(api_key="k", http_client=http)
|
|
195
|
+
params = {"model": "seedream-future-text-to-image", "prompt": "a lake", "future_setting": "on"}
|
|
232
196
|
|
|
197
|
+
result = client.text_to_image.create(**params, output_quality="ultra")
|
|
198
|
+
with pytest.raises(ValidationError) as error:
|
|
199
|
+
client.text_to_image.create(**params, output_quality="bad")
|
|
233
200
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
client.edit_image.create(
|
|
238
|
-
model="seedream-5-pro-edit",
|
|
239
|
-
prompt="restyle this image",
|
|
240
|
-
source_image_urls=["https://cdn.runapi.ai/public/samples/image.jpg"] * 11,
|
|
241
|
-
aspect_ratio="1:1",
|
|
242
|
-
output_quality="high",
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def test_lite_output_format_enum():
|
|
247
|
-
client = SeedreamClient(api_key="k", http_client=FakeHttp())
|
|
248
|
-
with pytest.raises(ValidationError, match="output_format must be one of: png, jpeg"):
|
|
249
|
-
client.text_to_image.create(
|
|
250
|
-
model="seedream-5-lite-text-to-image",
|
|
251
|
-
prompt="hi there",
|
|
252
|
-
aspect_ratio="1:1",
|
|
253
|
-
output_quality="high",
|
|
254
|
-
output_format="webp",
|
|
255
|
-
)
|
|
201
|
+
assert sent[0] == {**params, "output_quality": "ultra"}
|
|
202
|
+
assert result.id == "task_1"
|
|
203
|
+
assert error.value.message == "output_quality is not supported"
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
CONTRACT = {
|
|
2
|
-
"decompose-layers": {
|
|
3
|
-
"models": ["seedream-5-pro-layer-decomposition"],
|
|
4
|
-
"fields_by_model": {
|
|
5
|
-
"seedream-5-pro-layer-decomposition": {
|
|
6
|
-
"image_url": {
|
|
7
|
-
"required": True
|
|
8
|
-
},
|
|
9
|
-
"output_format": {
|
|
10
|
-
"enum": ["png", "jpeg"]
|
|
11
|
-
},
|
|
12
|
-
"prompt": {
|
|
13
|
-
"max": 5000,
|
|
14
|
-
"length": True
|
|
15
|
-
},
|
|
16
|
-
"size": {
|
|
17
|
-
"enum": ["auto", "1K", "1.5K", "2K"]
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"edit-image": {
|
|
23
|
-
"models": ["seedream-4.5-edit", "seedream-5-lite-edit", "seedream-5-pro-edit", "seedream-v4-edit"],
|
|
24
|
-
"fields_by_model": {
|
|
25
|
-
"seedream-4.5-edit": {
|
|
26
|
-
"aspect_ratio": {
|
|
27
|
-
"enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "2:3", "3:2", "21:9"],
|
|
28
|
-
"required": True
|
|
29
|
-
},
|
|
30
|
-
"output_count": {
|
|
31
|
-
"type": "integer"
|
|
32
|
-
},
|
|
33
|
-
"output_quality": {
|
|
34
|
-
"enum": ["basic", "high"],
|
|
35
|
-
"required": True
|
|
36
|
-
},
|
|
37
|
-
"seed": {
|
|
38
|
-
"type": "integer"
|
|
39
|
-
},
|
|
40
|
-
"source_image_urls": {
|
|
41
|
-
"required": True,
|
|
42
|
-
"min_items": 1,
|
|
43
|
-
"max_items": 14
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
"seedream-5-lite-edit": {
|
|
47
|
-
"aspect_ratio": {
|
|
48
|
-
"enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "2:3", "3:2", "21:9"],
|
|
49
|
-
"required": True
|
|
50
|
-
},
|
|
51
|
-
"output_count": {
|
|
52
|
-
"type": "integer"
|
|
53
|
-
},
|
|
54
|
-
"output_format": {
|
|
55
|
-
"enum": ["png", "jpeg"]
|
|
56
|
-
},
|
|
57
|
-
"output_quality": {
|
|
58
|
-
"enum": ["basic", "high", "ultra"],
|
|
59
|
-
"required": True
|
|
60
|
-
},
|
|
61
|
-
"seed": {
|
|
62
|
-
"type": "integer"
|
|
63
|
-
},
|
|
64
|
-
"source_image_urls": {
|
|
65
|
-
"required": True,
|
|
66
|
-
"min_items": 1,
|
|
67
|
-
"max_items": 14
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
"seedream-5-pro-edit": {
|
|
71
|
-
"aspect_ratio": {
|
|
72
|
-
"enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "2:3", "3:2", "21:9"],
|
|
73
|
-
"required": True
|
|
74
|
-
},
|
|
75
|
-
"output_format": {
|
|
76
|
-
"enum": ["png", "jpeg"]
|
|
77
|
-
},
|
|
78
|
-
"output_quality": {
|
|
79
|
-
"enum": ["basic", "high"],
|
|
80
|
-
"required": True
|
|
81
|
-
},
|
|
82
|
-
"prompt": {
|
|
83
|
-
"required": True,
|
|
84
|
-
"min": 3,
|
|
85
|
-
"max": 5000,
|
|
86
|
-
"length": True
|
|
87
|
-
},
|
|
88
|
-
"source_image_urls": {
|
|
89
|
-
"required": True,
|
|
90
|
-
"min_items": 1,
|
|
91
|
-
"max_items": 10
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
"seedream-v4-edit": {
|
|
95
|
-
"aspect_ratio": {
|
|
96
|
-
"enum": ["1:1", "4:3", "3:4", "3:2", "2:3", "16:9", "9:16", "21:9"]
|
|
97
|
-
},
|
|
98
|
-
"output_count": {
|
|
99
|
-
"enum": [1, 2, 3, 4, 5, 6],
|
|
100
|
-
"type": "integer"
|
|
101
|
-
},
|
|
102
|
-
"output_resolution": {
|
|
103
|
-
"enum": ["1k", "2k", "4k"]
|
|
104
|
-
},
|
|
105
|
-
"seed": {
|
|
106
|
-
"type": "integer"
|
|
107
|
-
},
|
|
108
|
-
"source_image_urls": {
|
|
109
|
-
"required": True,
|
|
110
|
-
"min_items": 1,
|
|
111
|
-
"max_items": 10
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
"rules": [{
|
|
116
|
-
"when": {
|
|
117
|
-
"model": "seedream-4.5-edit"
|
|
118
|
-
},
|
|
119
|
-
"forbidden": ["output_format"]
|
|
120
|
-
}, {
|
|
121
|
-
"when": {
|
|
122
|
-
"model": "seedream-5-pro-edit"
|
|
123
|
-
},
|
|
124
|
-
"forbidden": ["output_resolution", "output_count", "seed"]
|
|
125
|
-
}, {
|
|
126
|
-
"when": {
|
|
127
|
-
"model": "seedream-v4-edit"
|
|
128
|
-
},
|
|
129
|
-
"forbidden": ["output_format"]
|
|
130
|
-
}]
|
|
131
|
-
},
|
|
132
|
-
"text-to-image": {
|
|
133
|
-
"models": ["seedream-4.5-text-to-image", "seedream-5-lite-text-to-image", "seedream-5-pro-text-to-image", "seedream-v4-text-to-image"],
|
|
134
|
-
"fields_by_model": {
|
|
135
|
-
"seedream-4.5-text-to-image": {
|
|
136
|
-
"aspect_ratio": {
|
|
137
|
-
"enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "2:3", "3:2", "21:9"],
|
|
138
|
-
"required": True
|
|
139
|
-
},
|
|
140
|
-
"output_count": {
|
|
141
|
-
"type": "integer"
|
|
142
|
-
},
|
|
143
|
-
"output_quality": {
|
|
144
|
-
"enum": ["basic", "high"],
|
|
145
|
-
"required": True
|
|
146
|
-
},
|
|
147
|
-
"seed": {
|
|
148
|
-
"type": "integer"
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
"seedream-5-lite-text-to-image": {
|
|
152
|
-
"aspect_ratio": {
|
|
153
|
-
"enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "2:3", "3:2", "21:9"],
|
|
154
|
-
"required": True
|
|
155
|
-
},
|
|
156
|
-
"output_count": {
|
|
157
|
-
"type": "integer"
|
|
158
|
-
},
|
|
159
|
-
"output_format": {
|
|
160
|
-
"enum": ["png", "jpeg"]
|
|
161
|
-
},
|
|
162
|
-
"output_quality": {
|
|
163
|
-
"enum": ["basic", "high", "ultra"],
|
|
164
|
-
"required": True
|
|
165
|
-
},
|
|
166
|
-
"seed": {
|
|
167
|
-
"type": "integer"
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
"seedream-5-pro-text-to-image": {
|
|
171
|
-
"aspect_ratio": {
|
|
172
|
-
"enum": ["1:1", "4:3", "3:4", "16:9", "9:16", "2:3", "3:2", "21:9"],
|
|
173
|
-
"required": True
|
|
174
|
-
},
|
|
175
|
-
"output_format": {
|
|
176
|
-
"enum": ["png", "jpeg"]
|
|
177
|
-
},
|
|
178
|
-
"output_quality": {
|
|
179
|
-
"enum": ["basic", "high"],
|
|
180
|
-
"required": True
|
|
181
|
-
},
|
|
182
|
-
"prompt": {
|
|
183
|
-
"required": True,
|
|
184
|
-
"min": 3,
|
|
185
|
-
"max": 5000,
|
|
186
|
-
"length": True
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
"seedream-v4-text-to-image": {
|
|
190
|
-
"aspect_ratio": {
|
|
191
|
-
"enum": ["1:1", "4:3", "3:4", "3:2", "2:3", "16:9", "9:16", "21:9"]
|
|
192
|
-
},
|
|
193
|
-
"output_count": {
|
|
194
|
-
"enum": [1, 2, 3, 4, 5, 6],
|
|
195
|
-
"type": "integer"
|
|
196
|
-
},
|
|
197
|
-
"output_resolution": {
|
|
198
|
-
"enum": ["1k", "2k", "4k"]
|
|
199
|
-
},
|
|
200
|
-
"seed": {
|
|
201
|
-
"type": "integer"
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
"rules": [{
|
|
206
|
-
"when": {
|
|
207
|
-
"model": "seedream-4.5-text-to-image"
|
|
208
|
-
},
|
|
209
|
-
"forbidden": ["output_format"]
|
|
210
|
-
}, {
|
|
211
|
-
"when": {
|
|
212
|
-
"model": "seedream-5-pro-text-to-image"
|
|
213
|
-
},
|
|
214
|
-
"forbidden": ["output_resolution", "output_count", "seed"]
|
|
215
|
-
}, {
|
|
216
|
-
"when": {
|
|
217
|
-
"model": "seedream-v4-text-to-image"
|
|
218
|
-
},
|
|
219
|
-
"forbidden": ["output_format"]
|
|
220
|
-
}]
|
|
221
|
-
}
|
|
222
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
"""Seedream text-to-image resource."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any, Dict, Optional
|
|
6
|
-
|
|
7
|
-
from runapi.core import Resource, ValidationError, RequestOptions
|
|
8
|
-
|
|
9
|
-
from ..contract_gen import CONTRACT
|
|
10
|
-
from ..types import (
|
|
11
|
-
LONG_PROMPT_MODELS,
|
|
12
|
-
MINIMUM_THREE_PROMPT_MODELS,
|
|
13
|
-
V4_MODELS,
|
|
14
|
-
CompletedTextToImageResponse,
|
|
15
|
-
TextToImageResponse,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TextToImage(Resource):
|
|
20
|
-
"""Generate images from text prompts with Seedream models."""
|
|
21
|
-
|
|
22
|
-
ENDPOINT = "/api/v1/seedream/text_to_image"
|
|
23
|
-
|
|
24
|
-
RESPONSE_CLASS = TextToImageResponse
|
|
25
|
-
COMPLETED_RESPONSE_CLASS = CompletedTextToImageResponse
|
|
26
|
-
|
|
27
|
-
PROMPT_MAX_LENGTH = 3000
|
|
28
|
-
V4_PROMPT_MAX_LENGTH = 5000
|
|
29
|
-
PROMPT_MIN_LENGTH_LITE = 3
|
|
30
|
-
|
|
31
|
-
def run(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
|
|
32
|
-
"""Create a text-to-image task and poll until it completes.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
**params: text-to-image parameters (model, ...).
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
The completed (narrowed) text-to-image response.
|
|
39
|
-
"""
|
|
40
|
-
task = self.create(options=options, **params)
|
|
41
|
-
return self._poll_until_complete(lambda: self.get(task.id, options=options))
|
|
42
|
-
|
|
43
|
-
def create(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
|
|
44
|
-
"""Create a text-to-image task and return immediately with an id.
|
|
45
|
-
|
|
46
|
-
Args:
|
|
47
|
-
**params: text-to-image parameters (model, ...).
|
|
48
|
-
|
|
49
|
-
Returns:
|
|
50
|
-
The task creation result with an id.
|
|
51
|
-
"""
|
|
52
|
-
compacted = self._compact_params(params)
|
|
53
|
-
self._validate_params(compacted)
|
|
54
|
-
return self._request("post", self.ENDPOINT, body=compacted, options=options)
|
|
55
|
-
|
|
56
|
-
def get(self, id: str, options: Optional[RequestOptions] = None) -> Any:
|
|
57
|
-
"""Fetch the current status of a text-to-image task.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
id: The task id returned by ``create``.
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
The current task status.
|
|
64
|
-
"""
|
|
65
|
-
return self._request("get", f"{self.ENDPOINT}/{id}", options=options)
|
|
66
|
-
|
|
67
|
-
def _validate_params(self, params: Dict[str, Any]) -> None:
|
|
68
|
-
self._validate_contract(CONTRACT["text-to-image"], params)
|
|
69
|
-
|
|
70
|
-
model = params.get("model")
|
|
71
|
-
|
|
72
|
-
prompt = params.get("prompt")
|
|
73
|
-
if not (isinstance(prompt, str) and prompt):
|
|
74
|
-
raise ValidationError("prompt is required")
|
|
75
|
-
max_length = self.V4_PROMPT_MAX_LENGTH if model in LONG_PROMPT_MODELS else self.PROMPT_MAX_LENGTH
|
|
76
|
-
if len(prompt) > max_length:
|
|
77
|
-
raise ValidationError(f"prompt must be at most {max_length} characters")
|
|
78
|
-
if model in MINIMUM_THREE_PROMPT_MODELS and len(prompt) < self.PROMPT_MIN_LENGTH_LITE:
|
|
79
|
-
raise ValidationError(
|
|
80
|
-
f"prompt must be between {self.PROMPT_MIN_LENGTH_LITE} and {max_length} characters"
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
if model in V4_MODELS:
|
|
84
|
-
self._validate_integer(params, "seed")
|
|
85
|
-
|
|
86
|
-
if self._field_present(params, "source_image_urls"):
|
|
87
|
-
raise ValidationError(f"source_image_urls is not supported for {model}")
|
|
88
|
-
|
|
89
|
-
@staticmethod
|
|
90
|
-
def _field_present(params: Dict[str, Any], key: str) -> bool:
|
|
91
|
-
value = params.get(key)
|
|
92
|
-
if value is None:
|
|
93
|
-
return False
|
|
94
|
-
if hasattr(value, "__len__"):
|
|
95
|
-
return len(value) > 0
|
|
96
|
-
return True
|
|
97
|
-
|
|
98
|
-
@staticmethod
|
|
99
|
-
def _validate_integer(params: Dict[str, Any], key: str) -> None:
|
|
100
|
-
value = params.get(key)
|
|
101
|
-
if value is None:
|
|
102
|
-
return
|
|
103
|
-
if isinstance(value, int) and not isinstance(value, bool):
|
|
104
|
-
return
|
|
105
|
-
raise ValidationError(f"{key} must be an integer")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|