diff --git a/pyproject.toml b/pyproject.toml index a106a1b7..4a9cbe4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,8 @@ dependencies = [ "opensearch-py==2.8.0", "filetype==1.2.0", "vikingdb-python-sdk==0.1.3", - "agentkit-sdk-python>=0.2.0" + "agentkit-sdk-python>=0.2.0", + "python-frontmatter==1.1.0", ] [project.scripts] diff --git a/veadk/agent.py b/veadk/agent.py index 0d5592b7..28066635 100644 --- a/veadk/agent.py +++ b/veadk/agent.py @@ -145,6 +145,8 @@ class Agent(LlmAgent): auto_save_session: bool = False + skills: list[str] = Field(default_factory=list) + def model_post_init(self, __context: Any) -> None: super().model_post_init(None) # for sub_agents init @@ -277,11 +279,14 @@ def model_post_init(self, __context: Any) -> None: else: self.after_agent_callback = save_session_to_long_term_memory + if self.skills: + self.load_skills() + logger.info(f"VeADK version: {VERSION}") logger.info(f"{self.__class__.__name__} `{self.name}` init done.") logger.debug( - f"Agent: {self.model_dump(include={'id', 'name', 'model_name', 'model_api_base', 'tools'})}" + f"Agent: {self.model_dump(include={'id', 'name', 'model_name', 'model_api_base', 'tools', 'skills'})}" ) def update_model(self, model_name: str): @@ -290,6 +295,28 @@ def update_model(self, model_name: str): update={"model": f"{self.model_provider}/{model_name}"} ) + def load_skills(self): + from pathlib import Path + + from veadk.skills.utils import load_skills_from_directory + + skills = [] + for skill in self.skills: + path = Path(skill) + if path.is_dir(): + skills.extend(load_skills_from_directory(path)) + else: + logger.error( + f"Skill {skill} is not a directory, skip. Loading skills from cloud is WIP." + ) + if skills: + self.instruction += "\nYou have the following skills:\n" + + for skill in skills: + self.instruction += ( + f"- name: {skill.name}\n- description: {skill.description}\n\n" + ) + async def _run( self, runner, diff --git a/veadk/skills/__init__.py b/veadk/skills/__init__.py new file mode 100644 index 00000000..7f463206 --- /dev/null +++ b/veadk/skills/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/veadk/skills/skill.py b/veadk/skills/skill.py new file mode 100644 index 00000000..8953c612 --- /dev/null +++ b/veadk/skills/skill.py @@ -0,0 +1,21 @@ +# 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 pydantic import BaseModel + + +class Skill(BaseModel): + name: str + description: str + path: str diff --git a/veadk/skills/utils.py b/veadk/skills/utils.py new file mode 100644 index 00000000..b64d7d56 --- /dev/null +++ b/veadk/skills/utils.py @@ -0,0 +1,61 @@ +# 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 pathlib import Path + +import frontmatter + +from veadk.skills.skill import Skill +from veadk.utils.logger import get_logger + +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" + skill = frontmatter.load(str(skill_readme)) + + skill_name = skill.get("name", "") + skill_description = skill.get("description", "") + + if not skill_name or not skill_description: + logger.error( + f"Skill {skill_readme} is missing name or description. Please check the SKILL.md file." + ) + raise ValueError( + f"Skill {skill_readme} is missing name or description. Please check the SKILL.md file." + ) + + logger.info( + f"Successfully loaded skill from {skill_readme}, name={skill['name']}, description={skill['description']}" + ) + return Skill( + name=skill_name, # type: ignore + description=skill_description, # type: ignore + path=str(skill_directory), + ) + + +def load_skills_from_directory(skills_directory: Path) -> list[Skill]: + skills = [] + logger.info(f"Load skills from {skills_directory}") + for skill_directory in skills_directory.iterdir(): + if skill_directory.is_dir(): + skill = load_skill_from_directory(skill_directory) + skills.append(skill) + return skills + + +def load_skills_from_cloud(space_name: str) -> list[Skill]: ... diff --git a/veadk/utils/misc.py b/veadk/utils/misc.py index ffc825e7..50963548 100644 --- a/veadk/utils/misc.py +++ b/veadk/utils/misc.py @@ -18,10 +18,11 @@ import sys import time import types -from typing import Any, Dict, List, MutableMapping, Tuple, Optional +from typing import Any, Dict, List, MutableMapping, Optional, Tuple import requests from yaml import safe_load + import __main__ @@ -190,9 +191,10 @@ async def upload_to_files_api( poll_interval: float = 3.0, max_wait_seconds: float = 10 * 60, ) -> str: + from volcenginesdkarkruntime import AsyncArk + from veadk.config import getenv, settings from veadk.consts import DEFAULT_MODEL_AGENT_API_BASE - from volcenginesdkarkruntime import AsyncArk client = AsyncArk( api_key=getenv("MODEL_AGENT_API_KEY", settings.model.api_key), @@ -210,6 +212,8 @@ async def upload_to_files_api( else None, ) await client.files.wait_for_processing( - id=file.id, poll_interval=poll_interval, max_wait_seconds=max_wait_seconds + id=file.id, + poll_interval=poll_interval, + max_wait_seconds=max_wait_seconds, ) return file.id