Skill 基本概念
Skills 的概念由 Anthropic 提出,本质上是一种更高层次的模块化能力封装,用于扩展智能体的功能边界。每一个 Skill 都封装了指令、元数据以及可选的资源(如脚本、模板等),智能体在执行任务时,会根据上下文相关性自动选择并调用合适的 Skill。
Skill 🆚 Tools:
- Skill:加载专业知识
- Tools:执行动作,调用API
Skills的核心功能:
- 专业工作流:特定领域的多步骤操作流程
- 工具集成:使用特定文件格式或 API 的指导说明
- 领域专长:企业特有知识、数据架构、业务规则
- 资源包:处理复杂和重复任务所需的脚本、参考文档和相关资源
Skills的组成部分:
SKILL.md:- 头部元数据 (YAML 格式):包含
name(名称)和description(描述)字段。 - 主体内容(Markdown 格式):关于如何使用该技能的说明和指引。只有在技能被触发后才会加载。
- 头部元数据 (YAML 格式):包含
- 脚本 (
scripts/):可执行代码(Python/Bash 等),适用于需要确保可靠性或经常重复编写的任务。 - 参考资料 (
references/):文档和参考材料,按需加载到上下文中,用于指导 Claude 的工作流程和思考方式。 - 资源文件 (
assets/):在最终输出内容中可能需要使用到的文件
Skills的核心特征:
| 特征 | 说明 |
|---|---|
| Prompt 驱动 | Skill 的本质是一段专业领域的 prompt,而非代码逻辑 |
| 按需加载 | Agent 根据用户问题判断需要哪个 Skill,再动态加载 |
| 自动发现 | SkillMiddleware 自动把所有 Skill 的简短描述注入 system prompt |
| 团队解耦 | 不同团队可以独立开发和维护各自的 Skill(只需新建 .md 文件) |
| 数据注入 | 需要案件数据的 Skill 在加载时自动注入相关数据 |
通过 LangChain 中间件实现 Skill 渐进式加载框架
整体框架
- 扫描Skills目录下所有的Skill的name和description,封装到系统提示词;
- Agent读取所有Skill的name和description,选定skill执行load_skill工具,获取skill的详细内容。
实现方法
- 在Skills目录下创建一个新的Skill,目录名称为skill的名称,必须包含
Skill.md,可以包含scripts、references等目录。
text
---
name: food-calorie
description: 精准计算各类食物卡路里,支持按食材、重量、烹饪方式拆分核算总热量
metadata:
author: nobody
version: 2.0.0
---
- 根据Skill文件格式确定数据类型,部分skill可能包含脚本文件:
python
class Skill(TypedDict):
"""文件夹型技能:支持 scripts / references / assets"""
name: str # 技能名 = 文件夹名
description: str # 简短描述(来自 SKILL.md 第一行)
content: str # SKILL.md 完整内容
path: Path # 技能根目录
scripts: list[Path] # scripts/ 下所有文件
references: list[Path] # references/ 下所有文件
assets: list[Path] # assets/ 下所有文件
- 从skills目录中读取所有skill的基础信息:
python
def load_skills_from_dir(skills_root_dir: Path) -> list[Skill]:
skills = []
for skill_dir in sorted(skills_root_dir.iterdir()):
if not skill_dir.is_dir():
continue
skill_md = skill_dir / "SKILL.md"
if not skill_md.exists():
continue
text = skill_md.read_text(encoding="utf-8")
# ======================
# 修复点:从 YAML 头读取 description
# ======================
lines = text.splitlines()
description = "No description"
# 解析 --- 包裹的 YAML 头部
if len(lines) > 2 and lines[0].strip() == "---":
try:
# 找到下一个 ---
end_idx = lines[1:].index("---") + 1
header_lines = lines[1:end_idx]
# 读取 description 字段
for line in header_lines:
if line.strip().startswith("description:"):
description = line.split(":", 1)[1].strip()
break
except ValueError:
pass
skills.append(
Skill(
name=skill_dir.name,
description=description,
content=text,
path=skill_dir,
scripts=(
list((skill_dir / "scripts").glob("*"))
if (skill_dir / "scripts").exists()
else []
),
references=(
list((skill_dir / "references").rglob("*"))
if (skill_dir / "references").exists()
else []
),
assets=(
list((skill_dir / "assets").glob("*"))
if (skill_dir / "assets").exists()
else []
),
)
)
return skills
SKILLS = load_skills_from_dir(Path(__file__).parent)
- 定义工具,供Agent加载指定的skill
python
@tool
def load_skill(skill_name: str) -> str:
"""Load the full content of a skill into the agent's context."""
for skill in SKILLS:
if skill["name"] == skill_name:
return f"Loaded skill: {skill_name}\n\n{skill['content']}"
available = ", ".join(s["name"] for s in SKILLS)
return f"Skill '{skill_name}' not found. Available skills: {available}"
- 通过 AgentMiddleware 中间件实现 Skill 渐进式信息披露:
python
class SkillMiddleware(AgentMiddleware):
"""自动把所有【文件夹技能】注入系统提示"""
tools = [load_skill, list_skill_files, read_skill_file]
def __init__(self):
skills_list = []
for skill in SKILLS:
skills_list.append(f"- **{skill['name']}**: {skill['description']}")
self.skills_prompt = "\n".join(skills_list)
def wrap_model_call(
self,
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
skills_addendum = (
f"\n\n## Available Skills\n\n{self.skills_prompt}\n\n"
"Use load_skill to get full instructions.\n"
"Use list_skill_files to list files in a skill.\n"
"Use read_skill_file to read any file in a skill."
)
new_content = list(request.system_message.content_blocks) + [
{"type": "text", "text": skills_addendum}
]
new_system_message = SystemMessage(content=new_content)
modified_request = request.override(system_message=new_system_message)
return handler(modified_request)
- 测试:
python
def test_skill_middleware():
llm = ChatOpenAI(model="qwen-flash", temperature=0.3)
agent = create_agent(
model=llm,
middleware=[SkillMiddleware()],
checkpointer=InMemorySaver(),
system_prompt="你是一位运动与营养学的专家",
)
config = {"configurable": {"thread_id": "1"}}
result = agent.invoke(
input={
"messages": [
{
"role": "user",
"content": "我今天吃了10碗米饭,而且还用青椒肉丝的汤汁浇在米饭上,摄入的卡路里是多少?",
},
]
},
config=config,
)
# Print the conversation
for message in result["messages"]:
if hasattr(message, 'pretty_print'):
message.pretty_print()
else:
print(f"{message.type}: {message.content}")
可以看到 Agent 加载了 food-calori skill:
yaml
================================== Ai Message ==================================
Tool Calls:
load_skill (call_c790c732842245a98dc85c)
Call ID: call_c790c732842245a98dc85c
Args:
skill_name: food-calorie
================================= Tool Message =================================
Name: load_skill
Loaded skill: food-calorie
---
name: food-calorie
description: 精准计算各类食物卡路里,支持按食材、重量、烹饪方式拆分核算总热量
metadata:
author: DevTeam
version: 2.0.0
---
# 食物卡路里计算专家指南
## 角色定位
你是专业营养学计算助手,擅长拆解混合食材、区分原生食材/加工烹饪方式、按国标营养热量标准,精准核算单种或多种食物总卡路里。
......
......
================================= Ai Message ==================================
Tool Calls:
read_skill_file (call_76edf6d96c894868b889aa)
Call ID: call_76edf6d96c894868b889aa
Args:
skill_name: food-calorie
file_path: references/standard_calories.md
================================= Tool Message =================================
Name: read_skill_file
参考资料: