Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 30 additions & 11 deletions veadk/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from __future__ import annotations

import os
from typing import Optional, Union
from typing import Dict, Optional, Union

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

def load_skills(self):
from pathlib import Path
from veadk.skills.skill import Skill
from veadk.skills.utils import (
load_skills_from_directory,
load_skills_from_cloud,
)
from veadk.tools.builtin_tools.playwright import playwright_tools
from veadk.tools.skills_tools import (
SkillsTool,
read_file_tool,
write_file_tool,
edit_file_tool,
bash_tool,
)

from veadk.skills.utils import load_skills_from_directory
skills: Dict[str, Skill] = {}

skills = []
for skill in self.skills:
path = Path(skill)
if path.is_dir():
skills.extend(load_skills_from_directory(path))
for item in self.skills:
path = Path(item)
if path.exists() and path.is_dir():
for skill in load_skills_from_directory(path):
skills[skill.name] = skill
else:
logger.error(
f"Skill {skill} is not a directory, skip. Loading skills from cloud is WIP."
)
for skill in load_skills_from_cloud(item):
skills[skill.name] = skill
if skills:
self.instruction += "\nYou have the following skills:\n"

for skill in skills:
for skill in skills.values():
self.instruction += (
f"- name: {skill.name}\n- description: {skill.description}\n\n"
)

self.tools.append(SkillsTool(skills))
self.tools.append(read_file_tool)
self.tools.append(write_file_tool)
self.tools.append(edit_file_tool)
self.tools.append(bash_tool)
self.tools.append(playwright_tools)

async def _run(
self,
runner,
Expand Down
5 changes: 5 additions & 0 deletions veadk/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,11 @@ async def run(
)
logger.info(f"Run config: {run_config}")

if self.agent.skills:
from veadk.tools.skills_tools.session_path import initialize_session_path

initialize_session_path(session_id)

user_id = user_id or self.user_id

converted_messages: list = _convert_messages(
Expand Down
5 changes: 4 additions & 1 deletion veadk/skills/skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
# limitations under the License.

from pydantic import BaseModel
from typing import Optional


class Skill(BaseModel):
name: str
description: str
path: str
path: str # local path or tos path
skill_space_id: Optional[str] = None
bucket_name: Optional[str] = None
84 changes: 81 additions & 3 deletions veadk/skills/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
from pathlib import Path

import os
import frontmatter

from veadk.skills.skill import Skill
from veadk.utils.logger import get_logger
from veadk.utils.volcengine_sign import ve_request

logger = get_logger(__name__)


def load_skill_from_directory(skill_directory: Path) -> Skill:
logger.info(f"Load skill from {skill_directory}")
skill_readme = skill_directory / "SKILL.md"
if not skill_readme.exists():
logger.error(f"Skill '{skill_directory}' has no SKILL.md file.")
raise ValueError(f"Skill '{skill_directory}' has no SKILL.md file")

skill = frontmatter.load(str(skill_readme))

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

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


def load_skills_from_cloud(space_name: str) -> list[Skill]: ...
def load_skills_from_cloud(skill_space_ids: str) -> list[Skill]:
skill_space_ids_list = [x.strip() for x in skill_space_ids.split(",")]
logger.info(f"Load skills from skill spaces: {skill_space_ids_list}")

from veadk.auth.veauth.utils import get_credential_from_vefaas_iam

skills = []

for skill_space_id in skill_space_ids_list:
try:
service = os.getenv("AGENTKIT_TOOL_SERVICE_CODE", "agentkit")
region = os.getenv("AGENTKIT_TOOL_REGION", "cn-beijing")
host = os.getenv("AGENTKIT_SKILL_HOST", "open.volcengineapi.com")

access_key = os.getenv("VOLCENGINE_ACCESS_KEY")
secret_key = os.getenv("VOLCENGINE_SECRET_KEY")
session_token = ""

if not (access_key and secret_key):
# Try to get from vefaas iam
cred = get_credential_from_vefaas_iam()
access_key = cred.access_key_id
secret_key = cred.secret_access_key
session_token = cred.session_token

response = ve_request(
request_body={
"SkillSpaceId": skill_space_id,
"InnerTags": {"source": "sandbox"},
},
action="ListSkillsBySpaceId",
ak=access_key,
sk=secret_key,
service=service,
version="2025-10-30",
region=region,
host=host,
header={"X-Security-Token": session_token},
)

if isinstance(response, str):
response = json.loads(response)

list_skills_result = response.get("Result")
items = list_skills_result.get("Items")

for item in items:
if not isinstance(item, dict):
continue
skill_name = item.get("Name")
skill_description = item.get("Description")
tos_bucket = item.get("BucketName")
tos_path = item.get("TosPath")
if not skill_name:
continue

skill = Skill(
name=skill_name, # type: ignore
description=skill_description, # type: ignore
path=tos_path,
skill_space_id=skill_space_id,
bucket_name=tos_bucket,
)

skills.append(skill)

logger.info(
f"Successfully loaded skill {skill_name} from skill space={skill_space_id}, name={skill_name}, description={skill_description}"
)
except Exception as e:
logger.error(f"Failed to load skill from skill space: {e}")

return skills
25 changes: 5 additions & 20 deletions veadk/tools/builtin_tools/execute_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,18 @@ def execute_skills(
workflow_prompt: str,
skills: Optional[List[str]] = None,
tool_context: ToolContext = None,
timeout: int = 900,
) -> str:
"""execute skills in a code sandbox and return the output.
For C++ code, don't execute it directly, compile and execute via Python; write sources and object files to /tmp.

Args:
workflow_prompt (str): instruction of workflow
skills (Optional[List[str]]): The skills will be invoked
timeout (int, optional): The timeout in seconds for the code execution, less than or equal to 900. Defaults to 900.

Returns:
str: The output of the code execution.
"""
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.

tool_id = getenv("AGENTKIT_TOOL_ID")

Expand Down Expand Up @@ -131,26 +130,12 @@ def execute_skills(
if skills:
cmd.extend(["--skills"] + skills)

# TODO: remove after agentkit supports custom environment variables setting
res = ve_request(
request_body={},
action="GetCallerIdentity",
ak=ak,
sk=sk,
service="sts",
version="2018-01-01",
region=region,
host="sts.volcengineapi.com",
header=header,
)
try:
account_id = res["Result"]["AccountId"]
except KeyError as e:
logger.error(f"Error occurred while getting account id: {e}, response is {res}")
return res
skill_space_id = os.getenv("SKILL_SPACE_ID", "")
if not skill_space_id:
logger.warning("SKILL_SPACE_ID environment variable is not set")

env_vars = {
"TOS_SKILLS_DIR": f"tos://agentkit-platform-{account_id}/skills/",
"SKILL_SPACE_ID": skill_space_id,
"TOOL_USER_SESSION_ID": tool_user_session_id,
}

Expand Down
30 changes: 30 additions & 0 deletions veadk/tools/skills_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from .bash_tool import bash_tool
from .file_tool import edit_file_tool, read_file_tool, write_file_tool
from .skills_tool import SkillsTool
from .session_path import initialize_session_path, get_session_path, clear_session_cache


__all__ = [
"bash_tool",
"edit_file_tool",
"read_file_tool",
"write_file_tool",
"SkillsTool",
"initialize_session_path",
"get_session_path",
"clear_session_cache",
]
Loading