veadk-python 0.2.13__py3-none-any.whl → 0.2.14__py3-none-any.whl
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.
Potentially problematic release.
This version of veadk-python might be problematic. Click here for more details.
- veadk/cli/cli.py +2 -0
- veadk/cli/cli_create.py +122 -0
- veadk/consts.py +0 -3
- veadk/tools/builtin_tools/image_generate.py +319 -169
- veadk/version.py +1 -1
- {veadk_python-0.2.13.dist-info → veadk_python-0.2.14.dist-info}/METADATA +1 -1
- {veadk_python-0.2.13.dist-info → veadk_python-0.2.14.dist-info}/RECORD +11 -11
- veadk/tools/builtin_tools/generate_image.py +0 -430
- {veadk_python-0.2.13.dist-info → veadk_python-0.2.14.dist-info}/WHEEL +0 -0
- {veadk_python-0.2.13.dist-info → veadk_python-0.2.14.dist-info}/entry_points.txt +0 -0
- {veadk_python-0.2.13.dist-info → veadk_python-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {veadk_python-0.2.13.dist-info → veadk_python-0.2.14.dist-info}/top_level.txt +0 -0
veadk/cli/cli.py
CHANGED
|
@@ -18,6 +18,7 @@ import click
|
|
|
18
18
|
from veadk.cli.cli_deploy import deploy
|
|
19
19
|
from veadk.cli.cli_eval import eval
|
|
20
20
|
from veadk.cli.cli_init import init
|
|
21
|
+
from veadk.cli.cli_create import create
|
|
21
22
|
from veadk.cli.cli_kb import kb
|
|
22
23
|
from veadk.cli.cli_pipeline import pipeline
|
|
23
24
|
from veadk.cli.cli_prompt import prompt
|
|
@@ -37,6 +38,7 @@ def veadk():
|
|
|
37
38
|
|
|
38
39
|
veadk.add_command(deploy)
|
|
39
40
|
veadk.add_command(init)
|
|
41
|
+
veadk.add_command(create)
|
|
40
42
|
veadk.add_command(prompt)
|
|
41
43
|
veadk.add_command(web)
|
|
42
44
|
veadk.add_command(pipeline)
|
veadk/cli/cli_create.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
import shutil
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
_CONFIG_YAML_TEMPLATE = """\
|
|
20
|
+
model:
|
|
21
|
+
agent:
|
|
22
|
+
name: doubao-seed-1-6-251015
|
|
23
|
+
api_key: {ark_api_key}
|
|
24
|
+
video:
|
|
25
|
+
name: doubao-seedance-1-0-pro-250528
|
|
26
|
+
# if you want to use different api_key, just uncomment following line and complete api_key
|
|
27
|
+
# api_key:
|
|
28
|
+
image:
|
|
29
|
+
name: doubao-seedream-4-0-250828
|
|
30
|
+
# if you want to use different api_key, just uncomment following line and complete api_key
|
|
31
|
+
# api_key:
|
|
32
|
+
|
|
33
|
+
logging:
|
|
34
|
+
# ERROR
|
|
35
|
+
# WARNING
|
|
36
|
+
# INFO
|
|
37
|
+
# DEBUG
|
|
38
|
+
level: DEBUG
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_INIT_PY_TEMPLATE = """\
|
|
42
|
+
from . import agent
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
_AGENT_PY_TEMPLATE = """\
|
|
46
|
+
from veadk import Agent
|
|
47
|
+
|
|
48
|
+
root_agent = Agent(
|
|
49
|
+
name="root_agent",
|
|
50
|
+
description="A helpful assistant for user questions.",
|
|
51
|
+
instruction="Answer user questions to the best of your knowledge",
|
|
52
|
+
)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
_SUCCESS_MSG = """\
|
|
56
|
+
Agent '{agent_name}' created successfully at '{agent_folder}':
|
|
57
|
+
- config.yaml
|
|
58
|
+
- {agent_name}/__init__.py
|
|
59
|
+
- {agent_name}/agent.py
|
|
60
|
+
|
|
61
|
+
You can run the agent by executing: cd {agent_name} && veadk web
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _prompt_for_ark_api_key() -> str:
|
|
66
|
+
click.secho(
|
|
67
|
+
"An API key is required to run the agent. See https://www.volcengine.com/docs/82379/1541594 for details.",
|
|
68
|
+
fg="green",
|
|
69
|
+
)
|
|
70
|
+
click.echo("You have two options:")
|
|
71
|
+
click.echo(" 1. Enter the API key now.")
|
|
72
|
+
click.echo(" 2. Configure it later in the generated config.yaml file.")
|
|
73
|
+
choice = click.prompt("Please select an option", type=click.Choice(["1", "2"]))
|
|
74
|
+
if choice == "1":
|
|
75
|
+
return click.prompt("Please enter your ARK API key")
|
|
76
|
+
else:
|
|
77
|
+
click.secho(
|
|
78
|
+
"You can set the `api_key` in the config.yaml file later.", fg="yellow"
|
|
79
|
+
)
|
|
80
|
+
return ""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _generate_files(agent_name: str, ark_api_key: str, target_dir_path: Path) -> None:
|
|
84
|
+
agent_dir_path = target_dir_path / agent_name
|
|
85
|
+
agent_dir_path.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
config_yaml_path = target_dir_path / "config.yaml"
|
|
87
|
+
init_file_path = agent_dir_path / "__init__.py"
|
|
88
|
+
agent_file_path = agent_dir_path / "agent.py"
|
|
89
|
+
|
|
90
|
+
config_yaml_content = _CONFIG_YAML_TEMPLATE.format(ark_api_key=ark_api_key)
|
|
91
|
+
config_yaml_path.write_text(config_yaml_content)
|
|
92
|
+
init_file_path.write_text(_INIT_PY_TEMPLATE)
|
|
93
|
+
agent_file_path.write_text(_AGENT_PY_TEMPLATE)
|
|
94
|
+
|
|
95
|
+
click.secho(
|
|
96
|
+
_SUCCESS_MSG.format(agent_name=agent_name, agent_folder=target_dir_path),
|
|
97
|
+
fg="green",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@click.command()
|
|
102
|
+
@click.option("--agent-name", help="The name of the agent.")
|
|
103
|
+
@click.option("--ark-api-key", help="The ARK API key.")
|
|
104
|
+
def create(agent_name: str, ark_api_key: str) -> None:
|
|
105
|
+
"""Creates a new agent in the current folder with prepopulated agent template."""
|
|
106
|
+
if not agent_name:
|
|
107
|
+
agent_name = click.prompt("Enter the agent name")
|
|
108
|
+
if not ark_api_key:
|
|
109
|
+
ark_api_key = _prompt_for_ark_api_key()
|
|
110
|
+
|
|
111
|
+
cwd = Path.cwd()
|
|
112
|
+
target_dir_path = cwd / agent_name
|
|
113
|
+
|
|
114
|
+
if target_dir_path.exists() and any(target_dir_path.iterdir()):
|
|
115
|
+
if not click.confirm(
|
|
116
|
+
f"Directory '{target_dir_path}' already exists and is not empty. Do you want to overwrite it?"
|
|
117
|
+
):
|
|
118
|
+
click.secho("Operation cancelled.", fg="red")
|
|
119
|
+
return
|
|
120
|
+
shutil.rmtree(target_dir_path)
|
|
121
|
+
|
|
122
|
+
_generate_files(agent_name, ark_api_key, target_dir_path)
|
veadk/consts.py
CHANGED
|
@@ -62,9 +62,6 @@ DEFAULT_TOS_BUCKET_NAME = "ark-tutorial"
|
|
|
62
62
|
|
|
63
63
|
DEFAULT_COZELOOP_SPACE_NAME = "VeADK Space"
|
|
64
64
|
|
|
65
|
-
DEFAULT_TEXT_TO_IMAGE_MODEL_NAME = "doubao-seedream-3-0-t2i-250415"
|
|
66
|
-
DEFAULT_TEXT_TO_IMAGE_MODEL_API_BASE = "https://ark.cn-beijing.volces.com/api/v3/"
|
|
67
|
-
|
|
68
65
|
DEFAULT_IMAGE_EDIT_MODEL_NAME = "doubao-seededit-3-0-i2i-250628"
|
|
69
66
|
DEFAULT_IMAGE_EDIT_MODEL_API_BASE = "https://ark.cn-beijing.volces.com/api/v3/"
|
|
70
67
|
|
|
@@ -12,22 +12,30 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import asyncio
|
|
16
|
+
import base64
|
|
17
|
+
import concurrent.futures
|
|
18
|
+
import contextvars
|
|
19
|
+
import json
|
|
20
|
+
import mimetypes
|
|
21
|
+
import traceback
|
|
15
22
|
from typing import Dict
|
|
16
23
|
|
|
17
24
|
from google.adk.tools import ToolContext
|
|
25
|
+
from google.genai.types import Blob, Part
|
|
26
|
+
from opentelemetry import trace
|
|
27
|
+
from opentelemetry.trace import Span
|
|
28
|
+
from volcenginesdkarkruntime import Ark
|
|
29
|
+
from volcenginesdkarkruntime.types.images.images import SequentialImageGenerationOptions
|
|
30
|
+
|
|
18
31
|
from veadk.config import getenv, settings
|
|
19
32
|
from veadk.consts import (
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
DEFAULT_IMAGE_GENERATE_MODEL_API_BASE,
|
|
34
|
+
DEFAULT_IMAGE_GENERATE_MODEL_NAME,
|
|
22
35
|
)
|
|
23
|
-
import base64
|
|
24
|
-
from volcenginesdkarkruntime import Ark
|
|
25
|
-
from opentelemetry import trace
|
|
26
|
-
import traceback
|
|
27
|
-
import json
|
|
28
|
-
from veadk.version import VERSION
|
|
29
|
-
from opentelemetry.trace import Span
|
|
30
36
|
from veadk.utils.logger import get_logger
|
|
37
|
+
from veadk.utils.misc import formatted_timestamp, read_file_to_bytes
|
|
38
|
+
from veadk.version import VERSION
|
|
31
39
|
|
|
32
40
|
logger = get_logger(__name__)
|
|
33
41
|
|
|
@@ -35,188 +43,329 @@ client = Ark(
|
|
|
35
43
|
api_key=getenv(
|
|
36
44
|
"MODEL_IMAGE_API_KEY", getenv("MODEL_AGENT_API_KEY", settings.model.api_key)
|
|
37
45
|
),
|
|
38
|
-
base_url=getenv("MODEL_IMAGE_API_BASE",
|
|
46
|
+
base_url=getenv("MODEL_IMAGE_API_BASE", DEFAULT_IMAGE_GENERATE_MODEL_API_BASE),
|
|
39
47
|
)
|
|
40
48
|
|
|
49
|
+
executor = concurrent.futures.ThreadPoolExecutor(max_workers=8)
|
|
50
|
+
tracer = trace.get_tracer("veadk")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _build_input_parts(item: dict, task_type: str, image_field):
|
|
54
|
+
input_part = {"role": "user"}
|
|
55
|
+
input_part["parts.0.type"] = "text"
|
|
56
|
+
input_part["parts.0.text"] = json.dumps(item, ensure_ascii=False)
|
|
57
|
+
|
|
58
|
+
if image_field:
|
|
59
|
+
if task_type.startswith("single"):
|
|
60
|
+
assert isinstance(image_field, str), (
|
|
61
|
+
f"single_* task_type image must be str, got {type(image_field)}"
|
|
62
|
+
)
|
|
63
|
+
input_part["parts.1.type"] = "image_url"
|
|
64
|
+
input_part["parts.1.image_url.name"] = "origin_image"
|
|
65
|
+
input_part["parts.1.image_url.url"] = image_field
|
|
66
|
+
elif task_type.startswith("multi"):
|
|
67
|
+
assert isinstance(image_field, list), (
|
|
68
|
+
f"multi_* task_type image must be list, got {type(image_field)}"
|
|
69
|
+
)
|
|
70
|
+
assert len(image_field) <= 10, (
|
|
71
|
+
f"multi_* task_type image list length must be <= 10, got {len(image_field)}"
|
|
72
|
+
)
|
|
73
|
+
for i, image_url in enumerate(image_field):
|
|
74
|
+
idx = i + 1
|
|
75
|
+
input_part[f"parts.{idx}.type"] = "image_url"
|
|
76
|
+
input_part[f"parts.{idx}.image_url.name"] = f"origin_image_{i}"
|
|
77
|
+
input_part[f"parts.{idx}.image_url.url"] = image_url
|
|
78
|
+
|
|
79
|
+
return input_part
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def handle_single_task_sync(
|
|
83
|
+
idx: int, item: dict, tool_context
|
|
84
|
+
) -> tuple[list[dict], list[str]]:
|
|
85
|
+
logger.debug(f"handle_single_task_sync item {idx}: {item}")
|
|
86
|
+
success_list: list[dict] = []
|
|
87
|
+
error_list: list[str] = []
|
|
88
|
+
total_tokens = 0
|
|
89
|
+
output_tokens = 0
|
|
90
|
+
output_part = {"message.role": "model"}
|
|
91
|
+
|
|
92
|
+
task_type = item.get("task_type", "text_to_single")
|
|
93
|
+
prompt = item.get("prompt", "")
|
|
94
|
+
response_format = item.get("response_format", None)
|
|
95
|
+
size = item.get("size", None)
|
|
96
|
+
watermark = item.get("watermark", None)
|
|
97
|
+
image_field = item.get("image", None)
|
|
98
|
+
sequential_image_generation = item.get("sequential_image_generation", None)
|
|
99
|
+
max_images = item.get("max_images", None)
|
|
100
|
+
|
|
101
|
+
input_part = _build_input_parts(item, task_type, image_field)
|
|
102
|
+
|
|
103
|
+
inputs = {"prompt": prompt}
|
|
104
|
+
if size:
|
|
105
|
+
inputs["size"] = size
|
|
106
|
+
if response_format:
|
|
107
|
+
inputs["response_format"] = response_format
|
|
108
|
+
if watermark is not None:
|
|
109
|
+
inputs["watermark"] = watermark
|
|
110
|
+
if sequential_image_generation:
|
|
111
|
+
inputs["sequential_image_generation"] = sequential_image_generation
|
|
112
|
+
|
|
113
|
+
with tracer.start_as_current_span(f"call_llm_task_{idx}") as span:
|
|
114
|
+
try:
|
|
115
|
+
if (
|
|
116
|
+
sequential_image_generation
|
|
117
|
+
and sequential_image_generation == "auto"
|
|
118
|
+
and max_images
|
|
119
|
+
):
|
|
120
|
+
response = client.images.generate(
|
|
121
|
+
model=getenv("MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME),
|
|
122
|
+
**inputs,
|
|
123
|
+
sequential_image_generation_options=SequentialImageGenerationOptions(
|
|
124
|
+
max_images=max_images
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
response = client.images.generate(
|
|
129
|
+
model=getenv("MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME),
|
|
130
|
+
**inputs,
|
|
131
|
+
)
|
|
41
132
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
133
|
+
if not response.error:
|
|
134
|
+
logger.debug(f"task {idx} Image generate response: {response}")
|
|
135
|
+
|
|
136
|
+
total_tokens += getattr(response.usage, "total_tokens", 0) or 0
|
|
137
|
+
output_tokens += getattr(response.usage, "output_tokens", 0) or 0
|
|
138
|
+
|
|
139
|
+
for i, image_data in enumerate(response.data):
|
|
140
|
+
image_name = f"task_{idx}_image_{i}"
|
|
141
|
+
if "error" in image_data:
|
|
142
|
+
logger.error(f"Image {image_name} error: {image_data.error}")
|
|
143
|
+
error_list.append(image_name)
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
if getattr(image_data, "url", None):
|
|
147
|
+
image_url = image_data.url
|
|
148
|
+
else:
|
|
149
|
+
b64 = getattr(image_data, "b64_json", None)
|
|
150
|
+
if not b64:
|
|
151
|
+
logger.error(
|
|
152
|
+
f"Image {image_name} missing data (no url/b64)"
|
|
153
|
+
)
|
|
154
|
+
error_list.append(image_name)
|
|
155
|
+
continue
|
|
156
|
+
image_bytes = base64.b64decode(b64)
|
|
157
|
+
image_url = _upload_image_to_tos(
|
|
158
|
+
image_bytes=image_bytes, object_key=f"{image_name}.png"
|
|
159
|
+
)
|
|
160
|
+
if not image_url:
|
|
161
|
+
logger.error(f"Upload image to TOS failed: {image_name}")
|
|
162
|
+
error_list.append(image_name)
|
|
163
|
+
continue
|
|
164
|
+
logger.debug(f"Image saved as ADK artifact: {image_name}")
|
|
165
|
+
|
|
166
|
+
tool_context.state[f"{image_name}_url"] = image_url
|
|
167
|
+
output_part[f"message.parts.{i}.type"] = "image_url"
|
|
168
|
+
output_part[f"message.parts.{i}.image_url.name"] = image_name
|
|
169
|
+
output_part[f"message.parts.{i}.image_url.url"] = image_url
|
|
170
|
+
logger.debug(
|
|
171
|
+
f"Image {image_name} generated successfully: {image_url}"
|
|
172
|
+
)
|
|
173
|
+
success_list.append({image_name: image_url})
|
|
174
|
+
else:
|
|
175
|
+
logger.error(
|
|
176
|
+
f"Task {idx} No images returned by model: {response.error}"
|
|
177
|
+
)
|
|
178
|
+
error_list.append(f"task_{idx}")
|
|
48
179
|
|
|
49
|
-
|
|
180
|
+
except Exception as e:
|
|
181
|
+
logger.error(f"Error in task {idx}: {e}")
|
|
182
|
+
traceback.print_exc()
|
|
183
|
+
error_list.append(f"task_{idx}")
|
|
184
|
+
|
|
185
|
+
finally:
|
|
186
|
+
add_span_attributes(
|
|
187
|
+
span,
|
|
188
|
+
tool_context,
|
|
189
|
+
input_part=input_part,
|
|
190
|
+
output_part=output_part,
|
|
191
|
+
output_tokens=output_tokens,
|
|
192
|
+
total_tokens=total_tokens,
|
|
193
|
+
request_model=getenv(
|
|
194
|
+
"MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME
|
|
195
|
+
),
|
|
196
|
+
response_model=getenv(
|
|
197
|
+
"MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
logger.debug(
|
|
201
|
+
f"task {idx} Image generate success_list: {success_list}\nerror_list: {error_list}"
|
|
202
|
+
)
|
|
203
|
+
return success_list, error_list
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
async def image_generate(tasks: list[dict], tool_context) -> Dict:
|
|
207
|
+
"""Generate images with Seedream 4.0.
|
|
208
|
+
|
|
209
|
+
Commit batch image generation requests via tasks.
|
|
50
210
|
|
|
51
211
|
Args:
|
|
52
|
-
|
|
53
|
-
A list of image
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
212
|
+
tasks (list[dict]):
|
|
213
|
+
A list of image-generation tasks. Each task is a dict.
|
|
214
|
+
Per-task schema
|
|
215
|
+
---------------
|
|
216
|
+
Required:
|
|
217
|
+
- task_type (str):
|
|
218
|
+
One of:
|
|
219
|
+
* "multi_image_to_group" # 多图生组图
|
|
220
|
+
* "single_image_to_group" # 单图生组图
|
|
221
|
+
* "text_to_group" # 文生组图
|
|
222
|
+
* "multi_image_to_single" # 多图生单图
|
|
223
|
+
* "single_image_to_single" # 单图生单图
|
|
224
|
+
* "text_to_single" # 文生单图
|
|
225
|
+
- prompt (str)
|
|
226
|
+
Text description of the desired image(s). 中文/English 均可。
|
|
227
|
+
若要指定生成图片的数量,请在prompt中添加"生成N张图片",其中N为具体的数字。
|
|
228
|
+
Optional:
|
|
229
|
+
- size (str)
|
|
230
|
+
指定生成图像的大小,有两种用法(二选一,不可混用):
|
|
231
|
+
方式 1:分辨率级别
|
|
232
|
+
可选值: "1K", "2K", "4K"
|
|
233
|
+
模型会结合 prompt 中的语义推断合适的宽高比、长宽。
|
|
234
|
+
方式 2:具体宽高值
|
|
235
|
+
格式: "<宽度>x<高度>",如 "2048x2048", "2384x1728"
|
|
236
|
+
约束:
|
|
237
|
+
* 总像素数范围: [1024x1024, 4096x4096]
|
|
238
|
+
* 宽高比范围: [1/16, 16]
|
|
239
|
+
推荐值:
|
|
240
|
+
- 1:1 → 2048x2048
|
|
241
|
+
- 4:3 → 2384x1728
|
|
242
|
+
- 3:4 → 1728x2304
|
|
243
|
+
- 16:9 → 2560x1440
|
|
244
|
+
- 9:16 → 1440x2560
|
|
245
|
+
- 3:2 → 2496x1664
|
|
246
|
+
- 2:3 → 1664x2496
|
|
247
|
+
- 21:9 → 3024x1296
|
|
248
|
+
默认值: "2048x2048"
|
|
249
|
+
- response_format (str)
|
|
250
|
+
Return format: "url" (default, URL 24h 过期) | "b64_json".
|
|
251
|
+
- watermark (bool)
|
|
252
|
+
Add watermark. Default: true.
|
|
253
|
+
- image (str | list[str]) # 仅“非文生图”需要。文生图请不要提供 image
|
|
254
|
+
Reference image(s) as URL or Base64.
|
|
255
|
+
* 生成“单图”的任务:传入 string(exactly 1 image)。
|
|
256
|
+
* 生成“组图”的任务:传入 array(2–10 images)。
|
|
257
|
+
- sequential_image_generation (str)
|
|
258
|
+
控制是否生成“组图”。Default: "disabled".
|
|
259
|
+
* 若要生成组图:必须设为 "auto"。
|
|
260
|
+
- max_images (int)
|
|
261
|
+
仅当生成组图时生效。控制模型能生成的最多张数,范围 [1, 15], 不设置默认为15。
|
|
262
|
+
注意这个参数不等于生成的图片数量,而是模型最多能生成的图片数量。
|
|
263
|
+
在单图组图场景最多 14;多图组图场景需满足 (len(images)+max_images ≤ 15)。
|
|
264
|
+
Model 行为说明(如何由参数推断模式)
|
|
265
|
+
---------------------------------
|
|
266
|
+
1) 文生单图: 不提供 image 且 (S 未设置或 S="disabled") → 1 张图。
|
|
267
|
+
2) 文生组图: 不提供 image 且 S="auto" → 组图,数量由 max_images 控制。
|
|
268
|
+
3) 单图生单图: image=string 且 (S 未设置或 S="disabled") → 1 张图。
|
|
269
|
+
4) 单图生组图: image=string 且 S="auto" → 组图,数量 ≤14。
|
|
270
|
+
5) 多图生单图: image=array (2–10) 且 (S 未设置或 S="disabled") → 1 张图。
|
|
271
|
+
6) 多图生组图: image=array (2–10) 且 S="auto" → 组图,需满足总数 ≤15。
|
|
272
|
+
返回结果
|
|
273
|
+
--------
|
|
274
|
+
Dict with generation summary.
|
|
89
275
|
Example:
|
|
90
276
|
{
|
|
91
277
|
"status": "success",
|
|
92
|
-
"success_list": [
|
|
93
|
-
|
|
278
|
+
"success_list": [
|
|
279
|
+
{"image_name": "url"}
|
|
280
|
+
],
|
|
281
|
+
"error_list": ["image_name"]
|
|
94
282
|
}
|
|
95
|
-
|
|
96
283
|
Notes:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
284
|
+
- 组图任务必须 sequential_image_generation="auto"。
|
|
285
|
+
- 如果想要指定生成组图的数量,请在prompt里添加数量说明,例如:"生成3张图片"。
|
|
286
|
+
- size 推荐使用 2048x2048 或表格里的标准比例,确保生成质量。
|
|
100
287
|
"""
|
|
101
|
-
|
|
102
|
-
f"Using model: {getenv('MODEL_IMAGE_NAME', DEFAULT_TEXT_TO_IMAGE_MODEL_NAME)}"
|
|
103
|
-
)
|
|
104
|
-
success_list = []
|
|
105
|
-
error_list = []
|
|
106
|
-
logger.debug(f"image_generate params: {params}")
|
|
107
|
-
for idx, item in enumerate(params):
|
|
108
|
-
logger.debug(f"image_generate item {idx}: {item}")
|
|
109
|
-
prompt = item.get("prompt", "")
|
|
110
|
-
image_name = item.get("image_name", f"generated_image_{idx}")
|
|
111
|
-
response_format = item.get("response_format", "url")
|
|
112
|
-
size = item.get("size", "1024x1024")
|
|
113
|
-
guidance_scale = item.get("guidance_scale", 2.5)
|
|
114
|
-
watermark = item.get("watermark", True)
|
|
115
|
-
seed = item.get("seed", -1)
|
|
288
|
+
model = getenv("MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME)
|
|
116
289
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
inputs = {
|
|
121
|
-
"prompt": prompt,
|
|
122
|
-
"response_format": response_format,
|
|
123
|
-
"size": size,
|
|
124
|
-
"guidance_scale": guidance_scale,
|
|
125
|
-
"watermark": watermark,
|
|
126
|
-
"seed": seed,
|
|
127
|
-
}
|
|
128
|
-
input_part = {
|
|
129
|
-
"role": "user",
|
|
130
|
-
"content": json.dumps(inputs, ensure_ascii=False),
|
|
131
|
-
}
|
|
132
|
-
response = client.images.generate(
|
|
133
|
-
model=getenv("MODEL_IMAGE_NAME", DEFAULT_TEXT_TO_IMAGE_MODEL_NAME),
|
|
134
|
-
**inputs,
|
|
135
|
-
)
|
|
136
|
-
output_part = None
|
|
137
|
-
if response.data and len(response.data) > 0:
|
|
138
|
-
logger.debug(f"task {idx} Image generate response: {response}")
|
|
139
|
-
for item in response.data:
|
|
140
|
-
if response_format == "url":
|
|
141
|
-
image = item.url
|
|
142
|
-
tool_context.state[f"{image_name}_url"] = image
|
|
143
|
-
output_part = {
|
|
144
|
-
"message.role": "model",
|
|
145
|
-
"message.parts.0.type": "image_url",
|
|
146
|
-
"message.parts.0.image_url.name": image_name,
|
|
147
|
-
"message.parts.0.image_url.url": image,
|
|
148
|
-
}
|
|
149
|
-
elif response_format == "b64_json":
|
|
150
|
-
image = item.b64_json
|
|
151
|
-
image_bytes = base64.b64decode(image)
|
|
152
|
-
|
|
153
|
-
tos_url = _upload_image_to_tos(
|
|
154
|
-
image_bytes=image_bytes, object_key=f"{image_name}.png"
|
|
155
|
-
)
|
|
156
|
-
if tos_url:
|
|
157
|
-
tool_context.state[f"{image_name}_url"] = tos_url
|
|
158
|
-
image = tos_url
|
|
159
|
-
output_part = {
|
|
160
|
-
"message.role": "model",
|
|
161
|
-
"message.parts.0.type": "image_url",
|
|
162
|
-
"message.parts.0.image_url.name": image_name,
|
|
163
|
-
"message.parts.0.image_url.url": image,
|
|
164
|
-
}
|
|
165
|
-
else:
|
|
166
|
-
logger.error(
|
|
167
|
-
f"Upload image to TOS failed: {image_name}"
|
|
168
|
-
)
|
|
169
|
-
error_list.append(image_name)
|
|
170
|
-
continue
|
|
171
|
-
|
|
172
|
-
logger.debug(f"Image saved as ADK artifact: {image_name}")
|
|
173
|
-
logger.debug(
|
|
174
|
-
f"Image {image_name} generated successfully: {image}"
|
|
175
|
-
)
|
|
176
|
-
success_list.append({image_name: image})
|
|
177
|
-
else:
|
|
178
|
-
error_details = f"No images returned by Doubao model: {response}"
|
|
179
|
-
logger.error(error_details)
|
|
180
|
-
error_list.append(image_name)
|
|
181
|
-
|
|
182
|
-
add_span_attributes(
|
|
183
|
-
span,
|
|
184
|
-
tool_context,
|
|
185
|
-
input_part=input_part,
|
|
186
|
-
output_part=output_part,
|
|
187
|
-
output_tokens=response.usage.output_tokens,
|
|
188
|
-
total_tokens=response.usage.total_tokens,
|
|
189
|
-
request_model=getenv(
|
|
190
|
-
"MODEL_IMAGE_NAME", DEFAULT_TEXT_TO_IMAGE_MODEL_NAME
|
|
191
|
-
),
|
|
192
|
-
response_model=getenv(
|
|
193
|
-
"MODEL_IMAGE_NAME", DEFAULT_TEXT_TO_IMAGE_MODEL_NAME
|
|
194
|
-
),
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
except Exception as e:
|
|
198
|
-
error_details = f"No images returned by Doubao model: {e}"
|
|
199
|
-
logger.error(error_details)
|
|
200
|
-
error_list.append(image_name)
|
|
201
|
-
|
|
202
|
-
if len(success_list) == 0:
|
|
203
|
-
logger.debug(
|
|
204
|
-
f"image_generate success_list: {success_list}\nerror_list: {error_list}"
|
|
290
|
+
if model.startswith("doubao-seedream-3-0"):
|
|
291
|
+
logger.error(
|
|
292
|
+
f"Image generation by Doubao Seedream 3.0 ({model}) is depracated. Please use Doubao Seedream 4.0 (e.g., doubao-seedream-4-0-250828) instead."
|
|
205
293
|
)
|
|
206
294
|
return {
|
|
207
|
-
"status": "
|
|
208
|
-
"success_list":
|
|
209
|
-
"error_list":
|
|
295
|
+
"status": "failed",
|
|
296
|
+
"success_list": [],
|
|
297
|
+
"error_list": [
|
|
298
|
+
"Image generation by Doubao Seedream 3.0 ({model}) is depracated. Please use Doubao Seedream 4.0 (e.g., doubao-seedream-4-0-250828) instead."
|
|
299
|
+
],
|
|
210
300
|
}
|
|
211
|
-
|
|
301
|
+
|
|
302
|
+
logger.debug(f"Using model to generate image: {model}")
|
|
303
|
+
|
|
304
|
+
success_list: list[dict] = []
|
|
305
|
+
error_list: list[str] = []
|
|
306
|
+
|
|
307
|
+
logger.debug(f"image_generate tasks: {tasks}")
|
|
308
|
+
|
|
309
|
+
with tracer.start_as_current_span("image_generate"):
|
|
310
|
+
base_ctx = contextvars.copy_context()
|
|
311
|
+
|
|
312
|
+
def make_task(idx, item):
|
|
313
|
+
ctx = base_ctx.copy()
|
|
314
|
+
return lambda: ctx.run(handle_single_task_sync, idx, item, tool_context)
|
|
315
|
+
|
|
316
|
+
loop = asyncio.get_event_loop()
|
|
317
|
+
futures = [
|
|
318
|
+
loop.run_in_executor(executor, make_task(idx, item))
|
|
319
|
+
for idx, item in enumerate(tasks)
|
|
320
|
+
]
|
|
321
|
+
|
|
322
|
+
results = await asyncio.gather(*futures, return_exceptions=True)
|
|
323
|
+
|
|
324
|
+
for res in results:
|
|
325
|
+
if isinstance(res, Exception):
|
|
326
|
+
logger.error(f"Task raised exception: {res}")
|
|
327
|
+
error_list.append("unknown_task_exception")
|
|
328
|
+
continue
|
|
329
|
+
s, e = res
|
|
330
|
+
success_list.extend(s)
|
|
331
|
+
error_list.extend(e)
|
|
332
|
+
|
|
333
|
+
if not success_list:
|
|
212
334
|
logger.debug(
|
|
213
335
|
f"image_generate success_list: {success_list}\nerror_list: {error_list}"
|
|
214
336
|
)
|
|
215
337
|
return {
|
|
216
|
-
"status": "
|
|
338
|
+
"status": "error",
|
|
217
339
|
"success_list": success_list,
|
|
218
340
|
"error_list": error_list,
|
|
219
341
|
}
|
|
342
|
+
app_name = tool_context._invocation_context.app_name
|
|
343
|
+
user_id = tool_context._invocation_context.user_id
|
|
344
|
+
session_id = tool_context._invocation_context.session.id
|
|
345
|
+
artifact_service = tool_context._invocation_context.artifact_service
|
|
346
|
+
|
|
347
|
+
if artifact_service:
|
|
348
|
+
for image in success_list:
|
|
349
|
+
for _, image_tos_url in image.items():
|
|
350
|
+
filename = f"artifact_{formatted_timestamp()}"
|
|
351
|
+
await artifact_service.save_artifact(
|
|
352
|
+
app_name=app_name,
|
|
353
|
+
user_id=user_id,
|
|
354
|
+
session_id=session_id,
|
|
355
|
+
filename=filename,
|
|
356
|
+
artifact=Part(
|
|
357
|
+
inline_data=Blob(
|
|
358
|
+
display_name=filename,
|
|
359
|
+
data=read_file_to_bytes(image_tos_url),
|
|
360
|
+
mime_type=mimetypes.guess_type(image_tos_url)[0],
|
|
361
|
+
)
|
|
362
|
+
),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
logger.debug(
|
|
366
|
+
f"image_generate success_list: {success_list}\nerror_list: {error_list}"
|
|
367
|
+
)
|
|
368
|
+
return {"status": "success", "success_list": success_list, "error_list": error_list}
|
|
220
369
|
|
|
221
370
|
|
|
222
371
|
def add_span_attributes(
|
|
@@ -273,10 +422,11 @@ def add_span_attributes(
|
|
|
273
422
|
|
|
274
423
|
def _upload_image_to_tos(image_bytes: bytes, object_key: str) -> None:
|
|
275
424
|
try:
|
|
276
|
-
from veadk.integrations.ve_tos.ve_tos import VeTOS
|
|
277
425
|
import os
|
|
278
426
|
from datetime import datetime
|
|
279
427
|
|
|
428
|
+
from veadk.integrations.ve_tos.ve_tos import VeTOS
|
|
429
|
+
|
|
280
430
|
timestamp: str = datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3]
|
|
281
431
|
object_key = f"{timestamp}-{object_key}"
|
|
282
432
|
bucket_name = os.getenv("DATABASE_TOS_BUCKET")
|
veadk/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: veadk-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: Volcengine agent development kit, integrations with Volcengine cloud services.
|
|
5
5
|
Author-email: Yaozheng Fang <fangyozheng@gmail.com>, Guodong Li <cu.eric.lee@gmail.com>, Zhi Han <sliverydayday@gmail.com>, Meng Wang <mengwangwm@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -2,10 +2,10 @@ veadk/__init__.py,sha256=9l1lyb9ifhHQeetmIBWZnIdwUCVyMyz1EnKsKz8BBG8,1135
|
|
|
2
2
|
veadk/agent.py,sha256=4ijmzPXvY9iAx4s2NqBzwJ0VEYSWiUezeP2OvQFO9ek,11108
|
|
3
3
|
veadk/agent_builder.py,sha256=HI7mRrUZ72_7i-jVzNDx1anTZHy7UxX7jr2Drxxx6j8,3031
|
|
4
4
|
veadk/config.py,sha256=Ezl9Lna9iriC_Uf7m1ZXTWzylLyd7YspUFAQqh94Ong,3203
|
|
5
|
-
veadk/consts.py,sha256=
|
|
5
|
+
veadk/consts.py,sha256=8FcwnvKxQM50r8HDQxnhI-Ml_mjyfYdkGDxbyPBrW5Q,2679
|
|
6
6
|
veadk/runner.py,sha256=_DGNwX-t3sHJFJvHs-rRHXbjCZza8I_zU8AN3Fw5nRY,14217
|
|
7
7
|
veadk/types.py,sha256=zOOzG-QJy-MkzHeicWJzy2_L5U4ERrWziPubIUEbd8c,1656
|
|
8
|
-
veadk/version.py,sha256=
|
|
8
|
+
veadk/version.py,sha256=AKkIbQyneK3kC3HOfdbOb8RMQ58A-Ly6yj_PKz8jElQ,654
|
|
9
9
|
veadk/a2a/__init__.py,sha256=pkSabKw7_ai4NOo56pXKL40EcaxIDh6HYxPXOY7qWbo,634
|
|
10
10
|
veadk/a2a/agent_card.py,sha256=lhtgW1acMpxYUdULHEZwVFXOi6Xh4lNkf4S7QIhbFFI,1525
|
|
11
11
|
veadk/a2a/remote_ve_agent.py,sha256=L2nzT8PlDI-lLtcaTJqk-D2Uvw9beKl8OEUqp-8qCbA,3510
|
|
@@ -28,7 +28,8 @@ veadk/auth/veauth/prompt_pilot_veauth.py,sha256=cls1LK2Un4cOMfHdaAqRhDHIXuk7cTuA
|
|
|
28
28
|
veadk/auth/veauth/utils.py,sha256=cVEKWQZeX5fzx3JLB1odv59D8lhOAF1Pb3rsgO6evmM,2152
|
|
29
29
|
veadk/auth/veauth/vesearch_veauth.py,sha256=rgup3VBbRSLligrsDFOEwpneq1BEtFwf9xpgNFWHKqc,2008
|
|
30
30
|
veadk/cli/__init__.py,sha256=pkSabKw7_ai4NOo56pXKL40EcaxIDh6HYxPXOY7qWbo,634
|
|
31
|
-
veadk/cli/cli.py,sha256
|
|
31
|
+
veadk/cli/cli.py,sha256=Iti595PE0g_R04njivyl0_fgNlPEYhCP-9j9ZkN80vQ,1496
|
|
32
|
+
veadk/cli/cli_create.py,sha256=1qsTCww60rotvMPi_ASjKEe9nAoqxtEQ6mo1d7jDJnE,3949
|
|
32
33
|
veadk/cli/cli_deploy.py,sha256=-P4PmXLGByypXGksshBT7BQ0U42hIvlHibXd_k4YfhQ,5328
|
|
33
34
|
veadk/cli/cli_eval.py,sha256=TVSraCTyTxo_pLu5fhtk3TiZUOZkN3G2BLam1ybFXBc,5446
|
|
34
35
|
veadk/cli/cli_init.py,sha256=f2A3RwUj9pApmUTl6FHmMwTTwyKl83pkvZRorTgl-XM,3982
|
|
@@ -143,9 +144,8 @@ veadk/tools/__init__.py,sha256=pkSabKw7_ai4NOo56pXKL40EcaxIDh6HYxPXOY7qWbo,634
|
|
|
143
144
|
veadk/tools/demo_tools.py,sha256=Gu3sxygcYVS2cv3WqUOl-Gq4JhMlDAktoCHOFT0gbFQ,2216
|
|
144
145
|
veadk/tools/load_knowledgebase_tool.py,sha256=UUTv0Za9GkEXAkl1SXmyq0HGCKGvSlH_f8Ok6O6e52M,4704
|
|
145
146
|
veadk/tools/builtin_tools/__init__.py,sha256=pkSabKw7_ai4NOo56pXKL40EcaxIDh6HYxPXOY7qWbo,634
|
|
146
|
-
veadk/tools/builtin_tools/generate_image.py,sha256=aDMlR-IrxUMepIl9hPkywlH-4e7uIRiyFzLTtmezOnw,17495
|
|
147
147
|
veadk/tools/builtin_tools/image_edit.py,sha256=KslsuabBchAYR3ZrWSO5viEe5ORUAe0GI1qQ6mxoIU0,11588
|
|
148
|
-
veadk/tools/builtin_tools/image_generate.py,sha256
|
|
148
|
+
veadk/tools/builtin_tools/image_generate.py,sha256=frBUYEmizq6B4cRIUTfWKpLF2RywposJf-hZ8kkZvV8,18075
|
|
149
149
|
veadk/tools/builtin_tools/lark.py,sha256=b2IWsN8fZFh9aweSGznaOqA30TCOLpVjNCDNa1LHZl4,2046
|
|
150
150
|
veadk/tools/builtin_tools/las.py,sha256=rgKfnK5GsHVbmkp-rc7rtCvWg-yYNxMjeV0ayCyRpjM,913
|
|
151
151
|
veadk/tools/builtin_tools/load_knowledgebase.py,sha256=Xqtq25DL720goRegCVmmkpH2Ye2VWLcrF5ncC37gK_Y,3427
|
|
@@ -181,9 +181,9 @@ veadk/utils/mcp_utils.py,sha256=aET7pX3LXmRe2-Jh7_xRvxrVyl1dN7uPAUk16luwMlQ,1525
|
|
|
181
181
|
veadk/utils/misc.py,sha256=ghEqrqoDfKrW9ZD3IB0bwcfyyB0gRWN2yEP9eRxQ4nE,4953
|
|
182
182
|
veadk/utils/patches.py,sha256=dcHdlJ8IciyMjDuMy6-_6McUqJYyLz0yHmJ0xH8lWOw,2752
|
|
183
183
|
veadk/utils/volcengine_sign.py,sha256=3xn6ca2OAg_AFyP2dqFTSioqkeDel_BoKURUtCcO-EQ,6736
|
|
184
|
-
veadk_python-0.2.
|
|
185
|
-
veadk_python-0.2.
|
|
186
|
-
veadk_python-0.2.
|
|
187
|
-
veadk_python-0.2.
|
|
188
|
-
veadk_python-0.2.
|
|
189
|
-
veadk_python-0.2.
|
|
184
|
+
veadk_python-0.2.14.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
185
|
+
veadk_python-0.2.14.dist-info/METADATA,sha256=Gk-dXheONF6hhaRA3yMsOON7aqet5oSPXV9bZe2ehY4,18428
|
|
186
|
+
veadk_python-0.2.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
187
|
+
veadk_python-0.2.14.dist-info/entry_points.txt,sha256=-g28D6dNV-2UvAiRP9VF0oOVSDSJ5zlLUIZ34ArAqF8,46
|
|
188
|
+
veadk_python-0.2.14.dist-info/top_level.txt,sha256=Qqi3ycJ4anKiZWBXtUBIy8zK9ZuXJsFa05oFq8O8qqY,6
|
|
189
|
+
veadk_python-0.2.14.dist-info/RECORD,,
|
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
import base64
|
|
16
|
-
import json
|
|
17
|
-
import mimetypes
|
|
18
|
-
import traceback
|
|
19
|
-
from typing import Dict
|
|
20
|
-
|
|
21
|
-
from google.adk.tools import ToolContext
|
|
22
|
-
from google.genai.types import Blob, Part
|
|
23
|
-
from opentelemetry import trace
|
|
24
|
-
from opentelemetry.trace import Span
|
|
25
|
-
from volcenginesdkarkruntime import Ark
|
|
26
|
-
from volcenginesdkarkruntime.types.images.images import SequentialImageGenerationOptions
|
|
27
|
-
|
|
28
|
-
from veadk.config import getenv, settings
|
|
29
|
-
from veadk.consts import (
|
|
30
|
-
DEFAULT_IMAGE_GENERATE_MODEL_NAME,
|
|
31
|
-
DEFAULT_IMAGE_GENERATE_MODEL_API_BASE,
|
|
32
|
-
)
|
|
33
|
-
from veadk.utils.logger import get_logger
|
|
34
|
-
from veadk.utils.misc import formatted_timestamp, read_file_to_bytes
|
|
35
|
-
from veadk.version import VERSION
|
|
36
|
-
import asyncio
|
|
37
|
-
import concurrent.futures
|
|
38
|
-
import contextvars
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
logger = get_logger(__name__)
|
|
42
|
-
|
|
43
|
-
client = Ark(
|
|
44
|
-
api_key=getenv(
|
|
45
|
-
"MODEL_IMAGE_API_KEY", getenv("MODEL_AGENT_API_KEY", settings.model.api_key)
|
|
46
|
-
),
|
|
47
|
-
base_url=getenv("MODEL_IMAGE_API_BASE", DEFAULT_IMAGE_GENERATE_MODEL_API_BASE),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
executor = concurrent.futures.ThreadPoolExecutor(max_workers=8)
|
|
51
|
-
tracer = trace.get_tracer("gcp.vertex.agent")
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _build_input_parts(item: dict, task_type: str, image_field):
|
|
55
|
-
input_part = {"role": "user"}
|
|
56
|
-
input_part["parts.0.type"] = "text"
|
|
57
|
-
input_part["parts.0.text"] = json.dumps(item, ensure_ascii=False)
|
|
58
|
-
|
|
59
|
-
if image_field:
|
|
60
|
-
if task_type.startswith("single"):
|
|
61
|
-
assert isinstance(image_field, str), (
|
|
62
|
-
f"single_* task_type image must be str, got {type(image_field)}"
|
|
63
|
-
)
|
|
64
|
-
input_part["parts.1.type"] = "image_url"
|
|
65
|
-
input_part["parts.1.image_url.name"] = "origin_image"
|
|
66
|
-
input_part["parts.1.image_url.url"] = image_field
|
|
67
|
-
elif task_type.startswith("multi"):
|
|
68
|
-
assert isinstance(image_field, list), (
|
|
69
|
-
f"multi_* task_type image must be list, got {type(image_field)}"
|
|
70
|
-
)
|
|
71
|
-
assert len(image_field) <= 10, (
|
|
72
|
-
f"multi_* task_type image list length must be <= 10, got {len(image_field)}"
|
|
73
|
-
)
|
|
74
|
-
for i, image_url in enumerate(image_field):
|
|
75
|
-
idx = i + 1
|
|
76
|
-
input_part[f"parts.{idx}.type"] = "image_url"
|
|
77
|
-
input_part[f"parts.{idx}.image_url.name"] = f"origin_image_{i}"
|
|
78
|
-
input_part[f"parts.{idx}.image_url.url"] = image_url
|
|
79
|
-
|
|
80
|
-
return input_part
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def handle_single_task_sync(
|
|
84
|
-
idx: int, item: dict, tool_context
|
|
85
|
-
) -> tuple[list[dict], list[str]]:
|
|
86
|
-
logger.debug(f"handle_single_task_sync item {idx}: {item}")
|
|
87
|
-
success_list: list[dict] = []
|
|
88
|
-
error_list: list[str] = []
|
|
89
|
-
total_tokens = 0
|
|
90
|
-
output_tokens = 0
|
|
91
|
-
output_part = {"message.role": "model"}
|
|
92
|
-
|
|
93
|
-
task_type = item.get("task_type", "text_to_single")
|
|
94
|
-
prompt = item.get("prompt", "")
|
|
95
|
-
response_format = item.get("response_format", None)
|
|
96
|
-
size = item.get("size", None)
|
|
97
|
-
watermark = item.get("watermark", None)
|
|
98
|
-
image_field = item.get("image", None)
|
|
99
|
-
sequential_image_generation = item.get("sequential_image_generation", None)
|
|
100
|
-
max_images = item.get("max_images", None)
|
|
101
|
-
|
|
102
|
-
input_part = _build_input_parts(item, task_type, image_field)
|
|
103
|
-
|
|
104
|
-
inputs = {"prompt": prompt}
|
|
105
|
-
if size:
|
|
106
|
-
inputs["size"] = size
|
|
107
|
-
if response_format:
|
|
108
|
-
inputs["response_format"] = response_format
|
|
109
|
-
if watermark is not None:
|
|
110
|
-
inputs["watermark"] = watermark
|
|
111
|
-
if sequential_image_generation:
|
|
112
|
-
inputs["sequential_image_generation"] = sequential_image_generation
|
|
113
|
-
|
|
114
|
-
with tracer.start_as_current_span(f"call_llm_task_{idx}") as span:
|
|
115
|
-
try:
|
|
116
|
-
if (
|
|
117
|
-
sequential_image_generation
|
|
118
|
-
and sequential_image_generation == "auto"
|
|
119
|
-
and max_images
|
|
120
|
-
):
|
|
121
|
-
response = client.images.generate(
|
|
122
|
-
model=getenv("MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME),
|
|
123
|
-
**inputs,
|
|
124
|
-
sequential_image_generation_options=SequentialImageGenerationOptions(
|
|
125
|
-
max_images=max_images
|
|
126
|
-
),
|
|
127
|
-
)
|
|
128
|
-
else:
|
|
129
|
-
response = client.images.generate(
|
|
130
|
-
model=getenv("MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME),
|
|
131
|
-
**inputs,
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
if not response.error:
|
|
135
|
-
logger.debug(f"task {idx} Image generate response: {response}")
|
|
136
|
-
|
|
137
|
-
total_tokens += getattr(response.usage, "total_tokens", 0) or 0
|
|
138
|
-
output_tokens += getattr(response.usage, "output_tokens", 0) or 0
|
|
139
|
-
|
|
140
|
-
for i, image_data in enumerate(response.data):
|
|
141
|
-
image_name = f"task_{idx}_image_{i}"
|
|
142
|
-
if "error" in image_data:
|
|
143
|
-
logger.error(f"Image {image_name} error: {image_data.error}")
|
|
144
|
-
error_list.append(image_name)
|
|
145
|
-
continue
|
|
146
|
-
|
|
147
|
-
if getattr(image_data, "url", None):
|
|
148
|
-
image_url = image_data.url
|
|
149
|
-
else:
|
|
150
|
-
b64 = getattr(image_data, "b64_json", None)
|
|
151
|
-
if not b64:
|
|
152
|
-
logger.error(
|
|
153
|
-
f"Image {image_name} missing data (no url/b64)"
|
|
154
|
-
)
|
|
155
|
-
error_list.append(image_name)
|
|
156
|
-
continue
|
|
157
|
-
image_bytes = base64.b64decode(b64)
|
|
158
|
-
image_url = _upload_image_to_tos(
|
|
159
|
-
image_bytes=image_bytes, object_key=f"{image_name}.png"
|
|
160
|
-
)
|
|
161
|
-
if not image_url:
|
|
162
|
-
logger.error(f"Upload image to TOS failed: {image_name}")
|
|
163
|
-
error_list.append(image_name)
|
|
164
|
-
continue
|
|
165
|
-
logger.debug(f"Image saved as ADK artifact: {image_name}")
|
|
166
|
-
|
|
167
|
-
tool_context.state[f"{image_name}_url"] = image_url
|
|
168
|
-
output_part[f"message.parts.{i}.type"] = "image_url"
|
|
169
|
-
output_part[f"message.parts.{i}.image_url.name"] = image_name
|
|
170
|
-
output_part[f"message.parts.{i}.image_url.url"] = image_url
|
|
171
|
-
logger.debug(
|
|
172
|
-
f"Image {image_name} generated successfully: {image_url}"
|
|
173
|
-
)
|
|
174
|
-
success_list.append({image_name: image_url})
|
|
175
|
-
else:
|
|
176
|
-
logger.error(
|
|
177
|
-
f"Task {idx} No images returned by model: {response.error}"
|
|
178
|
-
)
|
|
179
|
-
error_list.append(f"task_{idx}")
|
|
180
|
-
|
|
181
|
-
except Exception as e:
|
|
182
|
-
logger.error(f"Error in task {idx}: {e}")
|
|
183
|
-
traceback.print_exc()
|
|
184
|
-
error_list.append(f"task_{idx}")
|
|
185
|
-
|
|
186
|
-
finally:
|
|
187
|
-
add_span_attributes(
|
|
188
|
-
span,
|
|
189
|
-
tool_context,
|
|
190
|
-
input_part=input_part,
|
|
191
|
-
output_part=output_part,
|
|
192
|
-
output_tokens=output_tokens,
|
|
193
|
-
total_tokens=total_tokens,
|
|
194
|
-
request_model=getenv(
|
|
195
|
-
"MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME
|
|
196
|
-
),
|
|
197
|
-
response_model=getenv(
|
|
198
|
-
"MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME
|
|
199
|
-
),
|
|
200
|
-
)
|
|
201
|
-
logger.debug(
|
|
202
|
-
f"task {idx} Image generate success_list: {success_list}\nerror_list: {error_list}"
|
|
203
|
-
)
|
|
204
|
-
return success_list, error_list
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
async def image_generate(tasks: list[dict], tool_context) -> Dict:
|
|
208
|
-
"""
|
|
209
|
-
Seedream 4.0: batch image generation via tasks.
|
|
210
|
-
Args:
|
|
211
|
-
tasks (list[dict]):
|
|
212
|
-
A list of image-generation tasks. Each task is a dict.
|
|
213
|
-
Per-task schema
|
|
214
|
-
---------------
|
|
215
|
-
Required:
|
|
216
|
-
- task_type (str):
|
|
217
|
-
One of:
|
|
218
|
-
* "multi_image_to_group" # 多图生组图
|
|
219
|
-
* "single_image_to_group" # 单图生组图
|
|
220
|
-
* "text_to_group" # 文生组图
|
|
221
|
-
* "multi_image_to_single" # 多图生单图
|
|
222
|
-
* "single_image_to_single" # 单图生单图
|
|
223
|
-
* "text_to_single" # 文生单图
|
|
224
|
-
- prompt (str)
|
|
225
|
-
Text description of the desired image(s). 中文/English 均可。
|
|
226
|
-
若要指定生成图片的数量,请在prompt中添加"生成N张图片",其中N为具体的数字。
|
|
227
|
-
Optional:
|
|
228
|
-
- size (str)
|
|
229
|
-
指定生成图像的大小,有两种用法(二选一,不可混用):
|
|
230
|
-
方式 1:分辨率级别
|
|
231
|
-
可选值: "1K", "2K", "4K"
|
|
232
|
-
模型会结合 prompt 中的语义推断合适的宽高比、长宽。
|
|
233
|
-
方式 2:具体宽高值
|
|
234
|
-
格式: "<宽度>x<高度>",如 "2048x2048", "2384x1728"
|
|
235
|
-
约束:
|
|
236
|
-
* 总像素数范围: [1024x1024, 4096x4096]
|
|
237
|
-
* 宽高比范围: [1/16, 16]
|
|
238
|
-
推荐值:
|
|
239
|
-
- 1:1 → 2048x2048
|
|
240
|
-
- 4:3 → 2384x1728
|
|
241
|
-
- 3:4 → 1728x2304
|
|
242
|
-
- 16:9 → 2560x1440
|
|
243
|
-
- 9:16 → 1440x2560
|
|
244
|
-
- 3:2 → 2496x1664
|
|
245
|
-
- 2:3 → 1664x2496
|
|
246
|
-
- 21:9 → 3024x1296
|
|
247
|
-
默认值: "2048x2048"
|
|
248
|
-
- response_format (str)
|
|
249
|
-
Return format: "url" (default, URL 24h 过期) | "b64_json".
|
|
250
|
-
- watermark (bool)
|
|
251
|
-
Add watermark. Default: true.
|
|
252
|
-
- image (str | list[str]) # 仅“非文生图”需要。文生图请不要提供 image
|
|
253
|
-
Reference image(s) as URL or Base64.
|
|
254
|
-
* 生成“单图”的任务:传入 string(exactly 1 image)。
|
|
255
|
-
* 生成“组图”的任务:传入 array(2–10 images)。
|
|
256
|
-
- sequential_image_generation (str)
|
|
257
|
-
控制是否生成“组图”。Default: "disabled".
|
|
258
|
-
* 若要生成组图:必须设为 "auto"。
|
|
259
|
-
- max_images (int)
|
|
260
|
-
仅当生成组图时生效。控制模型能生成的最多张数,范围 [1, 15], 不设置默认为15。
|
|
261
|
-
注意这个参数不等于生成的图片数量,而是模型最多能生成的图片数量。
|
|
262
|
-
在单图组图场景最多 14;多图组图场景需满足 (len(images)+max_images ≤ 15)。
|
|
263
|
-
Model 行为说明(如何由参数推断模式)
|
|
264
|
-
---------------------------------
|
|
265
|
-
1) 文生单图: 不提供 image 且 (S 未设置或 S="disabled") → 1 张图。
|
|
266
|
-
2) 文生组图: 不提供 image 且 S="auto" → 组图,数量由 max_images 控制。
|
|
267
|
-
3) 单图生单图: image=string 且 (S 未设置或 S="disabled") → 1 张图。
|
|
268
|
-
4) 单图生组图: image=string 且 S="auto" → 组图,数量 ≤14。
|
|
269
|
-
5) 多图生单图: image=array (2–10) 且 (S 未设置或 S="disabled") → 1 张图。
|
|
270
|
-
6) 多图生组图: image=array (2–10) 且 S="auto" → 组图,需满足总数 ≤15。
|
|
271
|
-
返回结果
|
|
272
|
-
--------
|
|
273
|
-
Dict with generation summary.
|
|
274
|
-
Example:
|
|
275
|
-
{
|
|
276
|
-
"status": "success",
|
|
277
|
-
"success_list": [
|
|
278
|
-
{"image_name": "url"}
|
|
279
|
-
],
|
|
280
|
-
"error_list": ["image_name"]
|
|
281
|
-
}
|
|
282
|
-
Notes:
|
|
283
|
-
- 组图任务必须 sequential_image_generation="auto"。
|
|
284
|
-
- 如果想要指定生成组图的数量,请在prompt里添加数量说明,例如:"生成3张图片"。
|
|
285
|
-
- size 推荐使用 2048x2048 或表格里的标准比例,确保生成质量。
|
|
286
|
-
"""
|
|
287
|
-
logger.debug(
|
|
288
|
-
f"Using model: {getenv('MODEL_IMAGE_NAME', DEFAULT_IMAGE_GENERATE_MODEL_NAME)}"
|
|
289
|
-
)
|
|
290
|
-
success_list: list[dict] = []
|
|
291
|
-
error_list: list[str] = []
|
|
292
|
-
logger.debug(f"image_generate tasks: {tasks}")
|
|
293
|
-
with tracer.start_as_current_span("image_generate"):
|
|
294
|
-
base_ctx = contextvars.copy_context()
|
|
295
|
-
|
|
296
|
-
def make_task(idx, item):
|
|
297
|
-
ctx = base_ctx.copy()
|
|
298
|
-
return lambda: ctx.run(handle_single_task_sync, idx, item, tool_context)
|
|
299
|
-
|
|
300
|
-
loop = asyncio.get_event_loop()
|
|
301
|
-
futures = [
|
|
302
|
-
loop.run_in_executor(executor, make_task(idx, item))
|
|
303
|
-
for idx, item in enumerate(tasks)
|
|
304
|
-
]
|
|
305
|
-
|
|
306
|
-
results = await asyncio.gather(*futures, return_exceptions=True)
|
|
307
|
-
|
|
308
|
-
for res in results:
|
|
309
|
-
if isinstance(res, Exception):
|
|
310
|
-
logger.error(f"Task raised exception: {res}")
|
|
311
|
-
error_list.append("unknown_task_exception")
|
|
312
|
-
continue
|
|
313
|
-
s, e = res
|
|
314
|
-
success_list.extend(s)
|
|
315
|
-
error_list.extend(e)
|
|
316
|
-
|
|
317
|
-
if not success_list:
|
|
318
|
-
logger.debug(
|
|
319
|
-
f"image_generate success_list: {success_list}\nerror_list: {error_list}"
|
|
320
|
-
)
|
|
321
|
-
return {
|
|
322
|
-
"status": "error",
|
|
323
|
-
"success_list": success_list,
|
|
324
|
-
"error_list": error_list,
|
|
325
|
-
}
|
|
326
|
-
app_name = tool_context._invocation_context.app_name
|
|
327
|
-
user_id = tool_context._invocation_context.user_id
|
|
328
|
-
session_id = tool_context._invocation_context.session.id
|
|
329
|
-
artifact_service = tool_context._invocation_context.artifact_service
|
|
330
|
-
|
|
331
|
-
if artifact_service:
|
|
332
|
-
for image in success_list:
|
|
333
|
-
for _, image_tos_url in image.items():
|
|
334
|
-
filename = f"artifact_{formatted_timestamp()}"
|
|
335
|
-
await artifact_service.save_artifact(
|
|
336
|
-
app_name=app_name,
|
|
337
|
-
user_id=user_id,
|
|
338
|
-
session_id=session_id,
|
|
339
|
-
filename=filename,
|
|
340
|
-
artifact=Part(
|
|
341
|
-
inline_data=Blob(
|
|
342
|
-
display_name=filename,
|
|
343
|
-
data=read_file_to_bytes(image_tos_url),
|
|
344
|
-
mime_type=mimetypes.guess_type(image_tos_url)[0],
|
|
345
|
-
)
|
|
346
|
-
),
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
logger.debug(
|
|
350
|
-
f"image_generate success_list: {success_list}\nerror_list: {error_list}"
|
|
351
|
-
)
|
|
352
|
-
return {"status": "success", "success_list": success_list, "error_list": error_list}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def add_span_attributes(
|
|
356
|
-
span: Span,
|
|
357
|
-
tool_context: ToolContext,
|
|
358
|
-
input_part: dict = None,
|
|
359
|
-
output_part: dict = None,
|
|
360
|
-
input_tokens: int = None,
|
|
361
|
-
output_tokens: int = None,
|
|
362
|
-
total_tokens: int = None,
|
|
363
|
-
request_model: str = None,
|
|
364
|
-
response_model: str = None,
|
|
365
|
-
):
|
|
366
|
-
try:
|
|
367
|
-
# common attributes
|
|
368
|
-
app_name = tool_context._invocation_context.app_name
|
|
369
|
-
user_id = tool_context._invocation_context.user_id
|
|
370
|
-
agent_name = tool_context.agent_name
|
|
371
|
-
session_id = tool_context._invocation_context.session.id
|
|
372
|
-
span.set_attribute("gen_ai.agent.name", agent_name)
|
|
373
|
-
span.set_attribute("openinference.instrumentation.veadk", VERSION)
|
|
374
|
-
span.set_attribute("gen_ai.app.name", app_name)
|
|
375
|
-
span.set_attribute("gen_ai.user.id", user_id)
|
|
376
|
-
span.set_attribute("gen_ai.session.id", session_id)
|
|
377
|
-
span.set_attribute("agent_name", agent_name)
|
|
378
|
-
span.set_attribute("agent.name", agent_name)
|
|
379
|
-
span.set_attribute("app_name", app_name)
|
|
380
|
-
span.set_attribute("app.name", app_name)
|
|
381
|
-
span.set_attribute("user.id", user_id)
|
|
382
|
-
span.set_attribute("session.id", session_id)
|
|
383
|
-
span.set_attribute("cozeloop.report.source", "veadk")
|
|
384
|
-
|
|
385
|
-
# llm attributes
|
|
386
|
-
span.set_attribute("gen_ai.system", "openai")
|
|
387
|
-
span.set_attribute("gen_ai.operation.name", "chat")
|
|
388
|
-
if request_model:
|
|
389
|
-
span.set_attribute("gen_ai.request.model", request_model)
|
|
390
|
-
if response_model:
|
|
391
|
-
span.set_attribute("gen_ai.response.model", response_model)
|
|
392
|
-
if total_tokens:
|
|
393
|
-
span.set_attribute("gen_ai.usage.total_tokens", total_tokens)
|
|
394
|
-
if output_tokens:
|
|
395
|
-
span.set_attribute("gen_ai.usage.output_tokens", output_tokens)
|
|
396
|
-
if input_tokens:
|
|
397
|
-
span.set_attribute("gen_ai.usage.input_tokens", input_tokens)
|
|
398
|
-
if input_part:
|
|
399
|
-
span.add_event("gen_ai.user.message", input_part)
|
|
400
|
-
if output_part:
|
|
401
|
-
span.add_event("gen_ai.choice", output_part)
|
|
402
|
-
|
|
403
|
-
except Exception:
|
|
404
|
-
traceback.print_exc()
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def _upload_image_to_tos(image_bytes: bytes, object_key: str) -> None:
|
|
408
|
-
try:
|
|
409
|
-
import os
|
|
410
|
-
from datetime import datetime
|
|
411
|
-
|
|
412
|
-
from veadk.integrations.ve_tos.ve_tos import VeTOS
|
|
413
|
-
|
|
414
|
-
timestamp: str = datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3]
|
|
415
|
-
object_key = f"{timestamp}-{object_key}"
|
|
416
|
-
bucket_name = os.getenv("DATABASE_TOS_BUCKET")
|
|
417
|
-
ve_tos = VeTOS()
|
|
418
|
-
|
|
419
|
-
tos_url = ve_tos.build_tos_signed_url(
|
|
420
|
-
object_key=object_key, bucket_name=bucket_name
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
ve_tos.upload_bytes(
|
|
424
|
-
data=image_bytes, object_key=object_key, bucket_name=bucket_name
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
return tos_url
|
|
428
|
-
except Exception as e:
|
|
429
|
-
logger.error(f"Upload to TOS failed: {e}")
|
|
430
|
-
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|