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 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)
@@ -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
- DEFAULT_TEXT_TO_IMAGE_MODEL_NAME,
21
- DEFAULT_TEXT_TO_IMAGE_MODEL_API_BASE,
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", DEFAULT_TEXT_TO_IMAGE_MODEL_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
- async def image_generate(
43
- params: list,
44
- tool_context: ToolContext,
45
- ) -> Dict:
46
- """
47
- Generate images in batch according to prompts and optional settings.
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
- Each item in `params` describes a single image-generation request.
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
- params (list[dict]):
53
- A list of image generation requests. Each item supports:
54
-
55
- Required:
56
- - prompt (str):
57
- The textual description of the desired image.
58
- Supports English and Chinese.
59
-
60
- Optional:
61
- - image_name (str):
62
- Name/identifier for the generated image.
63
-
64
- - response_format (str):
65
- Format of the returned image.
66
- * "url": JPEG link (default)
67
- * "b64_json": Base64 string in JSON
68
-
69
- - size (str):
70
- Resolution of the generated image.
71
- Default: "1024x1024".
72
- Must be within [512x512, 2048x2048].
73
- Common options: 1024x1024, 864x1152, 1280x720, etc.
74
-
75
- - guidance_scale (float):
76
- How strongly the prompt affects the result.
77
- Range: [1.0, 10.0], default 2.5.
78
-
79
- - watermark (bool):
80
- Whether to add watermark.
81
- Default: True.
82
-
83
- - seed (int):
84
- Random seed for reproducibility.
85
- Range: [-1, 2^31-1], default -1 (random).
86
-
87
- Returns:
88
- Dict: API response containing generated image metadata.
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": [{"image_name": ""}],
93
- "error_list": [{}]
278
+ "success_list": [
279
+ {"image_name": "url"}
280
+ ],
281
+ "error_list": ["image_name"]
94
282
  }
95
-
96
283
  Notes:
97
- - Best suited for creating original images from text.
98
- - Use a fixed `seed` for reproducibility.
99
- - Choose appropriate `size` for desired aspect ratio.
284
+ - 组图任务必须 sequential_image_generation="auto"。
285
+ - 如果想要指定生成组图的数量,请在prompt里添加数量说明,例如:"生成3张图片"。
286
+ - size 推荐使用 2048x2048 或表格里的标准比例,确保生成质量。
100
287
  """
101
- logger.debug(
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
- try:
118
- tracer = trace.get_tracer("gcp.vertex.agent")
119
- with tracer.start_as_current_span("call_llm") as span:
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": "error",
208
- "success_list": success_list,
209
- "error_list": 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
- else:
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": "success",
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
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- VERSION = "0.2.13"
15
+ VERSION = "0.2.14"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: veadk-python
3
- Version: 0.2.13
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=LTl4NQYJf7C8EvconGa96NRschzZZmmOqxlEikJe2Nk,2831
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=verJVWp2IHUGhnn0kH5_-upx08hfZiViE_fI27Xhh4A,654
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=-IGJKG9bGZpZXdLirOHv0LlpN9Vo42qzeEyNnZKsKv8,1430
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=-L_3k3KRJ_arljLfKz946fbd9ppxTDNvJmkNGhgj6qQ,11357
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.13.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
185
- veadk_python-0.2.13.dist-info/METADATA,sha256=xonF3Utpt7sSjF-2uewm1laDYbSrgbDeiX9dEqwb_pM,18428
186
- veadk_python-0.2.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
187
- veadk_python-0.2.13.dist-info/entry_points.txt,sha256=-g28D6dNV-2UvAiRP9VF0oOVSDSJ5zlLUIZ34ArAqF8,46
188
- veadk_python-0.2.13.dist-info/top_level.txt,sha256=Qqi3ycJ4anKiZWBXtUBIy8zK9ZuXJsFa05oFq8O8qqY,6
189
- veadk_python-0.2.13.dist-info/RECORD,,
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