Skip to content

Commit f8252a7

Browse files
committed
Merge branch 'main' into fix/update_func
2 parents 40537e3 + 21fb381 commit f8252a7

File tree

6 files changed

+304
-20
lines changed

6 files changed

+304
-20
lines changed

veadk/integrations/ve_tos/ve_tos.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,80 @@ def download(self, bucket_name: str, object_key: str, save_path: str) -> bool:
705705
logger.error(f"Image download failed: {str(e)}")
706706
return False
707707

708+
def download_directory(
709+
self, bucket_name: str, prefix: str, local_dir: str = "/tmp"
710+
) -> bool:
711+
"""Download entire directory from TOS bucket to local directory
712+
713+
Args:
714+
bucket_name: TOS bucket name
715+
prefix: Directory prefix in TOS (e.g., "skills/pdf/")
716+
local_dir: Local directory path to save files
717+
718+
Returns:
719+
bool: True if download succeeds, False otherwise
720+
"""
721+
bucket_name = self._check_bucket_name(bucket_name)
722+
723+
if not self._client:
724+
logger.error("TOS client is not initialized")
725+
return False
726+
727+
try:
728+
# Ensure prefix ends with /
729+
if prefix and not prefix.endswith("/"):
730+
prefix += "/"
731+
732+
# Create local directory if not exists
733+
os.makedirs(local_dir, exist_ok=True)
734+
735+
# List all objects with the prefix
736+
is_truncated = True
737+
next_continuation_token = ""
738+
downloaded_count = 0
739+
740+
while is_truncated:
741+
out = self._client.list_objects_type2(
742+
bucket_name,
743+
prefix=prefix,
744+
continuation_token=next_continuation_token,
745+
)
746+
is_truncated = out.is_truncated
747+
next_continuation_token = out.next_continuation_token
748+
749+
# Download each object
750+
for content in out.contents:
751+
object_key = content.key
752+
753+
# Skip directory markers (objects ending with /)
754+
if object_key.endswith("/"):
755+
continue
756+
757+
# Calculate relative path and local file path
758+
relative_path = object_key[len(prefix) :]
759+
local_file_path = os.path.join(local_dir, relative_path)
760+
761+
# Create subdirectories if needed
762+
local_file_dir = os.path.dirname(local_file_path)
763+
if local_file_dir:
764+
os.makedirs(local_file_dir, exist_ok=True)
765+
766+
# Download the file
767+
if self.download(bucket_name, object_key, local_file_path):
768+
downloaded_count += 1
769+
logger.debug(f"Downloaded: {object_key} -> {local_file_path}")
770+
else:
771+
logger.warning(f"Failed to download: {object_key}")
772+
773+
logger.info(
774+
f"Downloaded {downloaded_count} files from {bucket_name}/{prefix} to {local_dir}"
775+
)
776+
return downloaded_count > 0
777+
778+
except Exception as e:
779+
logger.error(f"Failed to download directory: {str(e)}")
780+
return False
781+
708782
def close(self):
709783
if self._client:
710784
self._client.close()
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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 json
16+
import os
17+
from typing import Optional, List
18+
19+
from google.adk.tools import ToolContext
20+
21+
from veadk.config import getenv
22+
from veadk.utils.logger import get_logger
23+
from veadk.utils.volcengine_sign import ve_request
24+
from veadk.auth.veauth.utils import get_credential_from_vefaas_iam
25+
26+
logger = get_logger(__name__)
27+
28+
29+
def execute_skills(
30+
workflow_prompt: str,
31+
skills: Optional[List[str]] = None,
32+
tool_context: ToolContext = None,
33+
timeout: int = 300,
34+
) -> str:
35+
"""execute skills in a code sandbox and return the output.
36+
For C++ code, don't execute it directly, compile and execute via Python; write sources and object files to /tmp.
37+
38+
Args:
39+
workflow_prompt (str): instruction of workflow
40+
skills (Optional[List[str]]): The skills will be invoked
41+
timeout (int, optional): The timeout in seconds for the code execution, less than or equal to 300. Defaults to 300.
42+
43+
Returns:
44+
str: The output of the code execution.
45+
"""
46+
47+
tool_id = getenv("AGENTKIT_TOOL_ID")
48+
49+
service = getenv(
50+
"AGENTKIT_TOOL_SERVICE_CODE", "agentkit"
51+
) # temporary service for code run tool
52+
region = getenv("AGENTKIT_TOOL_REGION", "cn-beijing")
53+
host = getenv(
54+
"AGENTKIT_TOOL_HOST", service + "." + region + ".volces.com"
55+
) # temporary host for code run tool
56+
logger.debug(f"tools endpoint: {host}")
57+
58+
session_id = tool_context._invocation_context.session.id
59+
agent_name = tool_context._invocation_context.agent.name
60+
user_id = tool_context._invocation_context.user_id
61+
tool_user_session_id = agent_name + "_" + user_id + "_" + session_id
62+
logger.debug(f"tool_user_session_id: {tool_user_session_id}")
63+
64+
logger.debug(
65+
f"Execute skills in session_id={session_id}, tool_id={tool_id}, host={host}, service={service}, region={region}, timeout={timeout}"
66+
)
67+
68+
ak = getenv("VOLCENGINE_ACCESS_KEY")
69+
sk = getenv("VOLCENGINE_SECRET_KEY")
70+
header = {}
71+
72+
if not (ak and sk):
73+
logger.debug("Get AK/SK from tool context failed.")
74+
ak = os.getenv("VOLCENGINE_ACCESS_KEY")
75+
sk = os.getenv("VOLCENGINE_SECRET_KEY")
76+
if not (ak and sk):
77+
logger.debug(
78+
"Get AK/SK from environment variables failed. Try to use credential from Iam."
79+
)
80+
credential = get_credential_from_vefaas_iam()
81+
ak = credential.access_key_id
82+
sk = credential.secret_access_key
83+
header = {"X-Security-Token": credential.session_token}
84+
else:
85+
logger.debug("Successfully get AK/SK from environment variables.")
86+
else:
87+
logger.debug("Successfully get AK/SK from tool context.")
88+
89+
cmd = ["python", "agent.py", workflow_prompt]
90+
if skills:
91+
cmd.extend(["--skills"] + skills)
92+
93+
# TODO: remove after agentkit supports custom environment variables setting
94+
env_vars = {
95+
"MODEL_AGENT_API_KEY": os.getenv("MODEL_AGENT_API_KEY"),
96+
"TOS_SKILLS_DIR": os.getenv("TOS_SKILLS_DIR"),
97+
}
98+
99+
code = f"""
100+
import subprocess
101+
import os
102+
103+
env = os.environ.copy()
104+
for key, value in {env_vars!r}.items():
105+
if key not in env:
106+
env[key] = value
107+
108+
result = subprocess.run(
109+
{cmd!r},
110+
cwd='/home/gem/veadk_skills',
111+
capture_output=True,
112+
text=True,
113+
env=env,
114+
timeout={timeout - 10},
115+
)
116+
print(result.stdout)
117+
if result.stderr:
118+
print(result.stderr)
119+
"""
120+
121+
res = ve_request(
122+
request_body={
123+
"ToolId": tool_id,
124+
"UserSessionId": tool_user_session_id,
125+
"OperationType": "RunCode",
126+
"OperationPayload": json.dumps(
127+
{
128+
"code": code,
129+
"timeout": timeout,
130+
"kernel_name": "python3",
131+
}
132+
),
133+
},
134+
action="InvokeTool",
135+
ak=ak,
136+
sk=sk,
137+
service=service,
138+
version="2025-10-30",
139+
region=region,
140+
host=host,
141+
header=header,
142+
)
143+
logger.debug(f"Invoke run code response: {res}")
144+
145+
try:
146+
return res["Result"]["Result"]
147+
except KeyError as e:
148+
logger.error(f"Error occurred while running code: {e}, response is {res}")
149+
return res

veadk/tools/builtin_tools/generate_image.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from veadk.tools.builtin_tools.image_generate import image_generate # noqa: F401
15+
from veadk.tools.builtin_tools.image_generate import (
16+
image_generate, # noqa: F401
17+
)
1618
from veadk.utils.logger import get_logger
1719

1820
logger = get_logger(__name__)

veadk/tools/builtin_tools/image_edit.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,30 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import base64
16+
import json
17+
import traceback
1518
from typing import Dict
19+
1620
from google.adk.tools import ToolContext
21+
from opentelemetry import trace
22+
from opentelemetry.trace import Span
1723
from volcenginesdkarkruntime import Ark
24+
1825
from veadk.config import getenv, settings
1926
from veadk.consts import (
2027
DEFAULT_IMAGE_EDIT_MODEL_API_BASE,
2128
DEFAULT_IMAGE_EDIT_MODEL_NAME,
2229
)
23-
import base64
24-
from opentelemetry import trace
25-
import traceback
26-
import json
27-
from veadk.version import VERSION
28-
from opentelemetry.trace import Span
2930
from veadk.utils.logger import get_logger
31+
from veadk.version import VERSION
3032

3133
logger = get_logger(__name__)
3234

3335
client = Ark(
3436
api_key=getenv(
35-
"MODEL_EDIT_API_KEY", getenv("MODEL_AGENT_API_KEY", settings.model.api_key)
37+
"MODEL_EDIT_API_KEY",
38+
getenv("MODEL_AGENT_API_KEY", settings.model.api_key),
3639
),
3740
base_url=getenv("MODEL_EDIT_API_BASE", DEFAULT_IMAGE_EDIT_MODEL_API_BASE),
3841
)
@@ -135,6 +138,14 @@ async def image_edit(
135138
response = client.images.generate(
136139
model=getenv("MODEL_EDIT_NAME", DEFAULT_IMAGE_EDIT_MODEL_NAME),
137140
**inputs,
141+
extra_headers={
142+
"veadk-source": "veadk",
143+
"veadk-version": VERSION,
144+
"User-Agent": f"VeADK/{VERSION}",
145+
"X-Client-Request-Id": getenv(
146+
"MODEL_AGENT_CLIENT_REQ_ID", f"veadk/{VERSION}"
147+
),
148+
},
138149
)
139150
output_part = None
140151
if response.data and len(response.data) > 0:
@@ -154,7 +165,8 @@ async def image_edit(
154165
image_bytes = base64.b64decode(image)
155166

156167
tos_url = _upload_image_to_tos(
157-
image_bytes=image_bytes, object_key=f"{image_name}.png"
168+
image_bytes=image_bytes,
169+
object_key=f"{image_name}.png",
158170
)
159171
if tos_url:
160172
tool_context.state[f"{image_name}_url"] = tos_url
@@ -277,10 +289,11 @@ def add_span_attributes(
277289

278290
def _upload_image_to_tos(image_bytes: bytes, object_key: str) -> None:
279291
try:
280-
from veadk.integrations.ve_tos.ve_tos import VeTOS
281292
import os
282293
from datetime import datetime
283294

295+
from veadk.integrations.ve_tos.ve_tos import VeTOS
296+
284297
timestamp: str = datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3]
285298
object_key = f"{timestamp}-{object_key}"
286299
bucket_name = os.getenv("DATABASE_TOS_BUCKET")

veadk/tools/builtin_tools/image_generate.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
from opentelemetry import trace
2727
from opentelemetry.trace import Span
2828
from volcenginesdkarkruntime import Ark
29-
from volcenginesdkarkruntime.types.images.images import SequentialImageGenerationOptions
29+
from volcenginesdkarkruntime.types.images.images import (
30+
SequentialImageGenerationOptions,
31+
)
3032

3133
from veadk.config import getenv, settings
3234
from veadk.consts import (
@@ -41,7 +43,8 @@
4143

4244
client = Ark(
4345
api_key=getenv(
44-
"MODEL_IMAGE_API_KEY", getenv("MODEL_AGENT_API_KEY", settings.model.api_key)
46+
"MODEL_IMAGE_API_KEY",
47+
getenv("MODEL_AGENT_API_KEY", settings.model.api_key),
4548
),
4649
base_url=getenv("MODEL_IMAGE_API_BASE", DEFAULT_IMAGE_GENERATE_MODEL_API_BASE),
4750
)
@@ -119,11 +122,24 @@ def handle_single_task_sync(
119122
and sequential_image_generation == "auto"
120123
and max_images
121124
):
122-
response = client.images.generate(
123-
model=getenv("MODEL_IMAGE_NAME", DEFAULT_IMAGE_GENERATE_MODEL_NAME),
124-
**inputs,
125-
sequential_image_generation_options=SequentialImageGenerationOptions(
126-
max_images=max_images
125+
response = (
126+
client.images.generate(
127+
model=getenv(
128+
"MODEL_IMAGE_NAME",
129+
DEFAULT_IMAGE_GENERATE_MODEL_NAME,
130+
),
131+
**inputs,
132+
sequential_image_generation_options=SequentialImageGenerationOptions(
133+
max_images=max_images
134+
),
135+
extra_headers={
136+
"veadk-source": "veadk",
137+
"veadk-version": VERSION,
138+
"User-Agent": f"VeADK/{VERSION}",
139+
"X-Client-Request-Id": getenv(
140+
"MODEL_AGENT_CLIENT_REQ_ID", f"veadk/{VERSION}"
141+
),
142+
},
127143
),
128144
)
129145
else:
@@ -157,7 +173,8 @@ def handle_single_task_sync(
157173
continue
158174
image_bytes = base64.b64decode(b64)
159175
image_url = _upload_image_to_tos(
160-
image_bytes=image_bytes, object_key=f"{image_name}.png"
176+
image_bytes=image_bytes,
177+
object_key=f"{image_name}.png",
161178
)
162179
if not image_url:
163180
logger.error(f"Upload image to TOS failed: {image_name}")
@@ -367,7 +384,11 @@ def make_task(idx, item):
367384
logger.debug(
368385
f"image_generate success_list: {success_list}\nerror_list: {error_list}"
369386
)
370-
return {"status": "success", "success_list": success_list, "error_list": error_list}
387+
return {
388+
"status": "success",
389+
"success_list": success_list,
390+
"error_list": error_list,
391+
}
371392

372393

373394
def add_span_attributes(

0 commit comments

Comments
 (0)