Skip to content

Commit 8da4a0b

Browse files
authored
Merge pull request #407 from BhAem/feat/skills
feat: integrate skills in veadk
2 parents 336d882 + 4c32c93 commit 8da4a0b

File tree

10 files changed

+890
-35
lines changed

10 files changed

+890
-35
lines changed

veadk/agent.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from __future__ import annotations
1616

1717
import os
18-
from typing import Optional, Union
18+
from typing import Dict, Optional, Union
1919

2020
# If user didn't set LITELLM_LOCAL_MODEL_COST_MAP, set it to True
2121
# to enable local model cost map.
@@ -297,26 +297,45 @@ def update_model(self, model_name: str):
297297

298298
def load_skills(self):
299299
from pathlib import Path
300+
from veadk.skills.skill import Skill
301+
from veadk.skills.utils import (
302+
load_skills_from_directory,
303+
load_skills_from_cloud,
304+
)
305+
from veadk.tools.builtin_tools.playwright import playwright_tools
306+
from veadk.tools.skills_tools import (
307+
SkillsTool,
308+
read_file_tool,
309+
write_file_tool,
310+
edit_file_tool,
311+
bash_tool,
312+
)
300313

301-
from veadk.skills.utils import load_skills_from_directory
314+
skills: Dict[str, Skill] = {}
302315

303-
skills = []
304-
for skill in self.skills:
305-
path = Path(skill)
306-
if path.is_dir():
307-
skills.extend(load_skills_from_directory(path))
316+
for item in self.skills:
317+
path = Path(item)
318+
if path.exists() and path.is_dir():
319+
for skill in load_skills_from_directory(path):
320+
skills[skill.name] = skill
308321
else:
309-
logger.error(
310-
f"Skill {skill} is not a directory, skip. Loading skills from cloud is WIP."
311-
)
322+
for skill in load_skills_from_cloud(item):
323+
skills[skill.name] = skill
312324
if skills:
313325
self.instruction += "\nYou have the following skills:\n"
314326

315-
for skill in skills:
327+
for skill in skills.values():
316328
self.instruction += (
317329
f"- name: {skill.name}\n- description: {skill.description}\n\n"
318330
)
319331

332+
self.tools.append(SkillsTool(skills))
333+
self.tools.append(read_file_tool)
334+
self.tools.append(write_file_tool)
335+
self.tools.append(edit_file_tool)
336+
self.tools.append(bash_tool)
337+
self.tools.append(playwright_tools)
338+
320339
async def _run(
321340
self,
322341
runner,

veadk/runner.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,11 @@ async def run(
472472
)
473473
logger.info(f"Run config: {run_config}")
474474

475+
if self.agent.skills:
476+
from veadk.tools.skills_tools.session_path import initialize_session_path
477+
478+
initialize_session_path(session_id)
479+
475480
user_id = user_id or self.user_id
476481

477482
converted_messages: list = _convert_messages(

veadk/skills/skill.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
# limitations under the License.
1414

1515
from pydantic import BaseModel
16+
from typing import Optional
1617

1718

1819
class Skill(BaseModel):
1920
name: str
2021
description: str
21-
path: str
22+
path: str # local path or tos path
23+
skill_space_id: Optional[str] = None
24+
bucket_name: Optional[str] = None

veadk/skills/utils.py

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

15+
import json
1516
from pathlib import Path
16-
17+
import os
1718
import frontmatter
1819

1920
from veadk.skills.skill import Skill
2021
from veadk.utils.logger import get_logger
22+
from veadk.utils.volcengine_sign import ve_request
2123

2224
logger = get_logger(__name__)
2325

2426

2527
def load_skill_from_directory(skill_directory: Path) -> Skill:
2628
logger.info(f"Load skill from {skill_directory}")
2729
skill_readme = skill_directory / "SKILL.md"
30+
if not skill_readme.exists():
31+
logger.error(f"Skill '{skill_directory}' has no SKILL.md file.")
32+
raise ValueError(f"Skill '{skill_directory}' has no SKILL.md file")
33+
2834
skill = frontmatter.load(str(skill_readme))
2935

3036
skill_name = skill.get("name", "")
@@ -39,7 +45,7 @@ def load_skill_from_directory(skill_directory: Path) -> Skill:
3945
)
4046

4147
logger.info(
42-
f"Successfully loaded skill from {skill_readme}, name={skill['name']}, description={skill['description']}"
48+
f"Successfully loaded skill {skill_name} locally from {skill_readme}, name={skill_name}, description={skill_description}"
4349
)
4450
return Skill(
4551
name=skill_name, # type: ignore
@@ -58,4 +64,76 @@ def load_skills_from_directory(skills_directory: Path) -> list[Skill]:
5864
return skills
5965

6066

61-
def load_skills_from_cloud(space_name: str) -> list[Skill]: ...
67+
def load_skills_from_cloud(skill_space_ids: str) -> list[Skill]:
68+
skill_space_ids_list = [x.strip() for x in skill_space_ids.split(",")]
69+
logger.info(f"Load skills from skill spaces: {skill_space_ids_list}")
70+
71+
from veadk.auth.veauth.utils import get_credential_from_vefaas_iam
72+
73+
skills = []
74+
75+
for skill_space_id in skill_space_ids_list:
76+
try:
77+
service = os.getenv("AGENTKIT_TOOL_SERVICE_CODE", "agentkit")
78+
region = os.getenv("AGENTKIT_TOOL_REGION", "cn-beijing")
79+
host = os.getenv("AGENTKIT_SKILL_HOST", "open.volcengineapi.com")
80+
81+
access_key = os.getenv("VOLCENGINE_ACCESS_KEY")
82+
secret_key = os.getenv("VOLCENGINE_SECRET_KEY")
83+
session_token = ""
84+
85+
if not (access_key and secret_key):
86+
# Try to get from vefaas iam
87+
cred = get_credential_from_vefaas_iam()
88+
access_key = cred.access_key_id
89+
secret_key = cred.secret_access_key
90+
session_token = cred.session_token
91+
92+
response = ve_request(
93+
request_body={
94+
"SkillSpaceId": skill_space_id,
95+
"InnerTags": {"source": "sandbox"},
96+
},
97+
action="ListSkillsBySpaceId",
98+
ak=access_key,
99+
sk=secret_key,
100+
service=service,
101+
version="2025-10-30",
102+
region=region,
103+
host=host,
104+
header={"X-Security-Token": session_token},
105+
)
106+
107+
if isinstance(response, str):
108+
response = json.loads(response)
109+
110+
list_skills_result = response.get("Result")
111+
items = list_skills_result.get("Items")
112+
113+
for item in items:
114+
if not isinstance(item, dict):
115+
continue
116+
skill_name = item.get("Name")
117+
skill_description = item.get("Description")
118+
tos_bucket = item.get("BucketName")
119+
tos_path = item.get("TosPath")
120+
if not skill_name:
121+
continue
122+
123+
skill = Skill(
124+
name=skill_name, # type: ignore
125+
description=skill_description, # type: ignore
126+
path=tos_path,
127+
skill_space_id=skill_space_id,
128+
bucket_name=tos_bucket,
129+
)
130+
131+
skills.append(skill)
132+
133+
logger.info(
134+
f"Successfully loaded skill {skill_name} from skill space={skill_space_id}, name={skill_name}, description={skill_description}"
135+
)
136+
except Exception as e:
137+
logger.error(f"Failed to load skill from skill space: {e}")
138+
139+
return skills

veadk/tools/builtin_tools/execute_skills.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,18 @@ def execute_skills(
7171
workflow_prompt: str,
7272
skills: Optional[List[str]] = None,
7373
tool_context: ToolContext = None,
74-
timeout: int = 900,
7574
) -> str:
7675
"""execute skills in a code sandbox and return the output.
7776
For C++ code, don't execute it directly, compile and execute via Python; write sources and object files to /tmp.
7877
7978
Args:
8079
workflow_prompt (str): instruction of workflow
8180
skills (Optional[List[str]]): The skills will be invoked
82-
timeout (int, optional): The timeout in seconds for the code execution, less than or equal to 900. Defaults to 900.
8381
8482
Returns:
8583
str: The output of the code execution.
8684
"""
85+
timeout = 900 # The timeout in seconds for the code execution, less than or equal to 900. Defaults to 900. Hard-coded to prevent the Agent from adjusting this parameter.
8786

8887
tool_id = getenv("AGENTKIT_TOOL_ID")
8988

@@ -131,26 +130,12 @@ def execute_skills(
131130
if skills:
132131
cmd.extend(["--skills"] + skills)
133132

134-
# TODO: remove after agentkit supports custom environment variables setting
135-
res = ve_request(
136-
request_body={},
137-
action="GetCallerIdentity",
138-
ak=ak,
139-
sk=sk,
140-
service="sts",
141-
version="2018-01-01",
142-
region=region,
143-
host="sts.volcengineapi.com",
144-
header=header,
145-
)
146-
try:
147-
account_id = res["Result"]["AccountId"]
148-
except KeyError as e:
149-
logger.error(f"Error occurred while getting account id: {e}, response is {res}")
150-
return res
133+
skill_space_id = os.getenv("SKILL_SPACE_ID", "")
134+
if not skill_space_id:
135+
logger.warning("SKILL_SPACE_ID environment variable is not set")
151136

152137
env_vars = {
153-
"TOS_SKILLS_DIR": f"tos://agentkit-platform-{account_id}/skills/",
138+
"SKILL_SPACE_ID": skill_space_id,
154139
"TOOL_USER_SESSION_ID": tool_user_session_id,
155140
}
156141

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
from .bash_tool import bash_tool
16+
from .file_tool import edit_file_tool, read_file_tool, write_file_tool
17+
from .skills_tool import SkillsTool
18+
from .session_path import initialize_session_path, get_session_path, clear_session_cache
19+
20+
21+
__all__ = [
22+
"bash_tool",
23+
"edit_file_tool",
24+
"read_file_tool",
25+
"write_file_tool",
26+
"SkillsTool",
27+
"initialize_session_path",
28+
"get_session_path",
29+
"clear_session_cache",
30+
]

0 commit comments

Comments
 (0)