Skip to content

Commit e38f0a9

Browse files
authored
Merge pull request #432 from volcengine/feat/local-skills
feat(skills): support loading skills in agent init
2 parents 968f09e + 088d950 commit e38f0a9

File tree

6 files changed

+132
-5
lines changed

6 files changed

+132
-5
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ dependencies = [
3939
"opensearch-py==2.8.0",
4040
"filetype==1.2.0",
4141
"vikingdb-python-sdk==0.1.3",
42-
"agentkit-sdk-python>=0.2.0"
42+
"agentkit-sdk-python>=0.2.0",
43+
"python-frontmatter==1.1.0",
4344
]
4445

4546
[project.scripts]

veadk/agent.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ class Agent(LlmAgent):
145145

146146
auto_save_session: bool = False
147147

148+
skills: list[str] = Field(default_factory=list)
149+
148150
def model_post_init(self, __context: Any) -> None:
149151
super().model_post_init(None) # for sub_agents init
150152

@@ -277,11 +279,14 @@ def model_post_init(self, __context: Any) -> None:
277279
else:
278280
self.after_agent_callback = save_session_to_long_term_memory
279281

282+
if self.skills:
283+
self.load_skills()
284+
280285
logger.info(f"VeADK version: {VERSION}")
281286

282287
logger.info(f"{self.__class__.__name__} `{self.name}` init done.")
283288
logger.debug(
284-
f"Agent: {self.model_dump(include={'id', 'name', 'model_name', 'model_api_base', 'tools'})}"
289+
f"Agent: {self.model_dump(include={'id', 'name', 'model_name', 'model_api_base', 'tools', 'skills'})}"
285290
)
286291

287292
def update_model(self, model_name: str):
@@ -290,6 +295,28 @@ def update_model(self, model_name: str):
290295
update={"model": f"{self.model_provider}/{model_name}"}
291296
)
292297

298+
def load_skills(self):
299+
from pathlib import Path
300+
301+
from veadk.skills.utils import load_skills_from_directory
302+
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))
308+
else:
309+
logger.error(
310+
f"Skill {skill} is not a directory, skip. Loading skills from cloud is WIP."
311+
)
312+
if skills:
313+
self.instruction += "\nYou have the following skills:\n"
314+
315+
for skill in skills:
316+
self.instruction += (
317+
f"- name: {skill.name}\n- description: {skill.description}\n\n"
318+
)
319+
293320
async def _run(
294321
self,
295322
runner,

veadk/skills/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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.

veadk/skills/skill.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 pydantic import BaseModel
16+
17+
18+
class Skill(BaseModel):
19+
name: str
20+
description: str
21+
path: str

veadk/skills/utils.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 pathlib import Path
16+
17+
import frontmatter
18+
19+
from veadk.skills.skill import Skill
20+
from veadk.utils.logger import get_logger
21+
22+
logger = get_logger(__name__)
23+
24+
25+
def load_skill_from_directory(skill_directory: Path) -> Skill:
26+
logger.info(f"Load skill from {skill_directory}")
27+
skill_readme = skill_directory / "SKILL.md"
28+
skill = frontmatter.load(str(skill_readme))
29+
30+
skill_name = skill.get("name", "")
31+
skill_description = skill.get("description", "")
32+
33+
if not skill_name or not skill_description:
34+
logger.error(
35+
f"Skill {skill_readme} is missing name or description. Please check the SKILL.md file."
36+
)
37+
raise ValueError(
38+
f"Skill {skill_readme} is missing name or description. Please check the SKILL.md file."
39+
)
40+
41+
logger.info(
42+
f"Successfully loaded skill from {skill_readme}, name={skill['name']}, description={skill['description']}"
43+
)
44+
return Skill(
45+
name=skill_name, # type: ignore
46+
description=skill_description, # type: ignore
47+
path=str(skill_directory),
48+
)
49+
50+
51+
def load_skills_from_directory(skills_directory: Path) -> list[Skill]:
52+
skills = []
53+
logger.info(f"Load skills from {skills_directory}")
54+
for skill_directory in skills_directory.iterdir():
55+
if skill_directory.is_dir():
56+
skill = load_skill_from_directory(skill_directory)
57+
skills.append(skill)
58+
return skills
59+
60+
61+
def load_skills_from_cloud(space_name: str) -> list[Skill]: ...

veadk/utils/misc.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818
import sys
1919
import time
2020
import types
21-
from typing import Any, Dict, List, MutableMapping, Tuple, Optional
21+
from typing import Any, Dict, List, MutableMapping, Optional, Tuple
2222

2323
import requests
2424
from yaml import safe_load
25+
2526
import __main__
2627

2728

@@ -190,9 +191,10 @@ async def upload_to_files_api(
190191
poll_interval: float = 3.0,
191192
max_wait_seconds: float = 10 * 60,
192193
) -> str:
194+
from volcenginesdkarkruntime import AsyncArk
195+
193196
from veadk.config import getenv, settings
194197
from veadk.consts import DEFAULT_MODEL_AGENT_API_BASE
195-
from volcenginesdkarkruntime import AsyncArk
196198

197199
client = AsyncArk(
198200
api_key=getenv("MODEL_AGENT_API_KEY", settings.model.api_key),
@@ -210,6 +212,8 @@ async def upload_to_files_api(
210212
else None,
211213
)
212214
await client.files.wait_for_processing(
213-
id=file.id, poll_interval=poll_interval, max_wait_seconds=max_wait_seconds
215+
id=file.id,
216+
poll_interval=poll_interval,
217+
max_wait_seconds=max_wait_seconds,
214218
)
215219
return file.id

0 commit comments

Comments
 (0)