AI Agent Skill 技术详解
什么是 Skill?
在 AI Agent 体系中,Skill(技能) 是对 Agent 能力的一种封装和组织方式。它比单个工具(Tool)更高层——一个 Skill 往往包含一组相关的功能,加上对应的提示词模板、输入输出规范,是可以被复用、可以被组合的能力单元。
一句话理解:Tool 是原子操作,Skill 是能力模块。
Agent ├── Skill: 信息处理 │ ├── Tool: 网页搜索 │ ├── Tool: 内容摘要 │ └── Tool: 实体提取 │ ├── Skill: 代码能力 │ ├── Tool: 代码生成 │ ├── Tool: 代码执行 │ └── Tool: 错误调试 │ └── Skill: 写作能力 ├── Tool: 草稿生成 ├── Tool: 风格改写 └── Tool: 语法检查
|
Semantic Kernel 中的 Skill
微软的 Semantic Kernel 是最早系统化提出 Skill 概念的框架,它把技能分为两类:
1. Semantic Skill(语义技能)
纯粹由提示词驱动的技能,通过 LLM 完成任务:
my_skills/ └── WritingSkill/ ├── Summarize/ │ ├── skprompt.txt ← 提示词模板 │ └── config.json ← 参数配置 └── Translate/ ├── skprompt.txt └── config.json
|
skprompt.txt 示例:
请将以下文本翻译成{{$language}},保持原文的语气和风格:
{{$input}}
翻译结果:
|
config.json 示例:
{ "schema": 1, "description": "将文本翻译为指定语言", "type": "completion", "completion": { "max_tokens": 1000, "temperature": 0.3 }, "input": { "parameters": [ {"name": "input", "description": "要翻译的文本"}, {"name": "language", "description": "目标语言", "defaultValue": "中文"} ] } }
|
使用方式(Python):
import semantic_kernel as sk
kernel = sk.Kernel() kernel.add_chat_service("gpt4", AzureChatCompletion(...))
writing_skill = kernel.import_semantic_skill_from_directory("my_skills", "WritingSkill")
result = await kernel.run_async( writing_skill["Translate"], input_str="Hello, world!", input_vars={"language": "日语"} ) print(result)
|
2. Native Skill(原生技能)
用代码实现的技能,适合需要精确计算或调用外部 API 的场景:
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter
class MathSkill: @sk_function( description="计算两个数字的和", name="Add" ) @sk_function_context_parameter(name="number1", description="第一个数字") @sk_function_context_parameter(name="number2", description="第二个数字") def add(self, context: SKContext) -> str: a = float(context["number1"]) b = float(context["number2"]) return str(a + b)
@sk_function( description="计算复利", name="CompoundInterest" ) def compound_interest(self, context: SKContext) -> str: principal = float(context["principal"]) rate = float(context["rate"]) years = int(context["years"]) result = principal * (1 + rate) ** years return f"{result:.2f}"
kernel.import_skill(MathSkill(), "Math")
|
AutoGen 中的 Skill
微软的 AutoGen 框架里,Skill 以可执行的 Python 函数形式存在,Agent 可以动态学习和使用新技能:
from autogen import AssistantAgent, UserProxyAgent
def plot_stock_price(stock_symbol: str, start_date: str, end_date: str): """ 绘制股票价格走势图
Args: stock_symbol: 股票代码,如 'AAPL' start_date: 开始日期,格式 'YYYY-MM-DD' end_date: 结束日期,格式 'YYYY-MM-DD' """ import yfinance as yf import matplotlib.pyplot as plt
data = yf.download(stock_symbol, start=start_date, end=end_date) plt.figure(figsize=(12, 6)) plt.plot(data['Close']) plt.title(f"{stock_symbol} 股价走势") plt.savefig(f"{stock_symbol}_price.png") return f"图表已保存为 {stock_symbol}_price.png"
assistant = AssistantAgent("analyst") assistant.register_function( function_map={"plot_stock_price": plot_stock_price} )
|
AutoGen 还支持 Agent 自己生成代码来创造新技能,并存入技能库供后续复用,这让系统具备了一定的自我进化能力。
在 CrewAI 中,Skill 的概念通过 Tool 来实现,每个 Agent 被赋予一组工具,形成专属能力集:
from crewai import Agent, Task, Crew from crewai_tools import SerperDevTool, WebsiteSearchTool
researcher = Agent( role="市场调研专家", goal="收集和分析市场信息", backstory="你是一位经验丰富的市场分析师", tools=[ SerperDevTool(), WebsiteSearchTool() ], verbose=True )
writer = Agent( role="内容撰写专家", goal="基于研究成果撰写高质量报告", backstory="你是一位专业的商业报告撰写人", tools=[], verbose=True )
|
自定义工具(技能):
from crewai.tools import BaseTool from pydantic import BaseModel, Field
class CompanyDataInput(BaseModel): company_name: str = Field(description="公司名称")
class CompanyInfoTool(BaseTool): name: str = "公司信息查询" description: str = "查询指定公司的基本信息、营收和市值数据" args_schema: type[BaseModel] = CompanyDataInput
def _run(self, company_name: str) -> str: return f"{company_name}:成立于2010年,2025年营收500亿,市值3000亿"
researcher.tools.append(CompanyInfoTool())
|
Claude Code 的 Skill 系统
Claude Code 内置了一套成熟的 Skill 机制,直接在日常 AI 编程助手里落地实现。深入研究它的技术细节,对理解 Agent Skill 设计很有参考价值。
Skill 的本质:上下文注入
Skill 的核心原理是提示词注入(Prompt Injection)——用户输入 /skill-name 时,系统把预先写好的指令内容作为一条消息注入到对话上下文,Claude 就会按这段指令行事。
用户输入:/commit ↓ 系统读取 commit/SKILL.md 文件 ↓ 渲染变量、执行 Shell 预处理命令 ↓ 将完整指令注入对话上下文(作为一条消息) ↓ Claude 按指令执行:分析 git diff → 生成提交信息
|
文件结构
每个 Skill 是一个独立目录,核心是 SKILL.md 文件:
.claude/skills/ └── my-skill/ ├── SKILL.md ← 必须,主指令文件 ├── reference.md ← 可选,详细参考文档 ├── examples/ │ └── sample.md ← 可选,示例输出 └── scripts/ └── helper.sh ← 可选,辅助脚本
|
SKILL.md 使用 YAML frontmatter + Markdown 正文格式:
--- name: code-review description: 对代码进行专业审查,包含安全、性能、可维护性三个维度。当用户说"帮我看看这段代码"或"review 一下"时使用。 allowed-tools: Read Grep argument-hint: "[文件路径]" ---
请对 `$ARGUMENTS` 进行代码审查,重点关注:
1. **安全漏洞**:SQL 注入、XSS、未验证的用户输入等 2. **性能问题**:不必要的循环、重复查询、内存泄漏 3. **可维护性**:命名规范、函数职责单一、重复代码
当前改动概要:!`git diff --stat`
请给出具体的改进建议,按严重程度排序。
|
存储位置与优先级
Skill 有多个存储层级,优先级从高到低:
| 层级 |
路径 |
适用范围 |
| 企业级 |
由管理员统一下发 |
团队所有成员 |
| 个人全局 |
~/.claude/skills/<name>/ |
你的所有项目 |
| 项目级 |
.claude/skills/<name>/ |
当前项目 |
| 插件 |
<plugin>/skills/<name>/ |
以 /plugin:skill 命名空间调用 |
同名 Skill 高优先级覆盖低优先级。修改 ~/.claude/skills/ 或 .claude/skills/ 中的文件无需重启即可在当前会话中生效。
变量替换与 Shell 命令预执行
Skill 支持两类动态内容,在内容发送给 Claude 之前处理完毕:
变量替换:
| 变量 |
含义 |
$ARGUMENTS |
用户输入的完整参数字符串 |
$0、$1、$2… |
按空格分割的第 N 个参数 |
${CLAUDE_SESSION_ID} |
当前会话 ID |
${CLAUDE_SKILL_DIR} |
当前 Skill 目录的绝对路径 |
调用 /deploy staging v1.2.3 时:$0 = staging,$1 = v1.2.3。带空格的参数用引号包裹:/migrate "Auth Module" 则 $0 = Auth Module。
Shell 命令预执行:
发送给 Claude 之前,Shell 命令先执行,输出替换占位符:
# 内联形式(单行) 当前分支:!`git branch --show-current` 未提交文件:!`git status --short`
# 多行形式 ```! echo "Node: $(node -v)" echo "当前目录: $(pwd)"
|
Claude 收到的是执行结果,而不是命令本身。这是 Skill 能感知实时环境状态(当前分支、文件变更、系统信息)的关键机制。
### 工具权限控制
通过 `allowed-tools` frontmatter,Skill 可以**预授权**特定工具,执行时不再弹出确认框:
```yaml --- name: safe-commit allowed-tools: Bash(git add *) Bash(git commit *) Bash(git status) Bash(git diff *) ---
请分析改动并创建提交...
|
支持通配符(git * 匹配所有 git 子命令),空格分隔多个工具名。这让自动化工作流成为可能,同时把权限边界限定在最小必要范围。
手动调用 vs Claude 自动选用
通过 frontmatter 控制 Skill 的触发方式:
---
disable-model-invocation: true ---
user-invocable: false ---
|
Claude 自动选用的判断依据就是 description 字段,所以描述要写触发场景,而不只是功能说明:
description: 审查代码
description: 对代码进行专业审查。当用户说"帮我看看这段代码"、"有没有 bug"、或需要 code review 时使用。
|
Skill 与 Hook 的配合
Skill 可以在 frontmatter 里定义 Hook,在工具执行前后插入自定义逻辑:
--- name: production-deploy hooks: PreToolUse: - matcher: "Bash" hooks: - type: command command: "${CLAUDE_SKILL_DIR}/scripts/safety-check.sh" ---
执行生产环境部署流程...
|
Skill 激活时其 Hook 合并到全局 Hook 中生效,Skill 结束后自动移除——实现了作用域化的安全防护,只在特定 Skill 运行期间执行额外检查。
上下文生命周期与压缩
Skill 内容注入对话后,在整个会话期间持续有效。当对话上下文接近上限触发压缩时:
- 每个 Skill 保留最近注入的前 5,000 Token
- 所有 Skill 共用 25,000 Token 的总预算
- 从最近调用的 Skill 开始填充,旧的 Skill 内容可能被整体丢弃
这解释了为什么在超长对话里,早期调用的 Skill 有时会”失效”——重新调用一次 /skill-name 即可恢复。
完整示例:自定义”每日站会摘要” Skill
mkdir -p .claude/skills/standup
|
.claude/skills/standup/SKILL.md:
--- name: standup description: 生成每日站会摘要。当用户说"帮我写今天的站会"或"生成站会报告"时使用。 allowed-tools: Bash(git log *) Bash(git diff *) argument-hint: "[可选:额外备注]" ---
请根据今天的代码提交记录,生成一份简洁的站会摘要。
今天的提交记录: ```! git log --oneline --since="yesterday" --author="$(git config user.name)" ```
未完成的改动:!`git status --short`
请按以下格式输出: **昨天完成:** ... **今天计划:** ... **是否有阻塞:** ...
$ARGUMENTS
|
调用方式:/standup 或 /standup 今天下午要开产品评审会
构建自己的 Skill 系统
以下是一个通用的 Skill 管理框架思路,可以用在任何 Agent 项目中:
import yaml from pathlib import Path from dataclasses import dataclass from typing import Optional
@dataclass class Skill: name: str description: str prompt_template: str input_vars: list[str] model_config: dict
class SkillRegistry: """技能注册表:统一管理所有 Skill"""
def __init__(self, skills_dir: str = "./skills"): self.skills: dict[str, Skill] = {} self._load_from_directory(skills_dir)
def _load_from_directory(self, skills_dir: str): """从目录批量加载技能""" for yaml_file in Path(skills_dir).glob("**/*.yaml"): with open(yaml_file) as f: data = yaml.safe_load(f) skill = Skill(**data) self.skills[skill.name] = skill print(f"已加载技能: {skill.name}")
def get(self, name: str) -> Optional[Skill]: return self.skills.get(name)
def invoke(self, skill_name: str, llm, **kwargs) -> str: skill = self.get(skill_name) if not skill: raise ValueError(f"技能 '{skill_name}' 不存在")
prompt = skill.prompt_template.format(**kwargs) return llm.invoke(prompt)
""" name: summarize description: 将长文本压缩为简洁摘要 prompt_template: | 请将以下文本压缩为{length}字以内的摘要,保留核心信息:
{text} input_vars: - text - length model_config: temperature: 0.3 max_tokens: 500 """
registry = SkillRegistry("./skills") summary = registry.invoke("summarize", llm, text="很长的文章...", length=200)
|
实际工程中,Skill 和 Tool 的界限并不总是清晰的,大致可以这样区分:
| 维度 |
Tool |
Skill |
| 粒度 |
单一操作 |
多步骤能力组合 |
| 驱动方式 |
代码函数 |
Prompt + 代码 |
| 复用单位 |
函数级 |
模块级 |
| 包含提示词 |
否 |
通常是 |
| 典型例子 |
search_web(query) |
「竞品分析」能力 |
一个好的经验法则是:如果这个能力需要 LLM 参与才能完成,就封装成 Skill;如果是纯确定性计算或 API 调用,就封装成 Tool。
小结
Skill 是 AI Agent 工程化落地的重要抽象层,它解决的核心问题是:
- 能力复用:好的 Skill 写一次,到处用
- 协作分工:不同 Agent 配置不同 Skill,形成专业团队
- 可维护性:Skill 独立管理,便于迭代优化
- 标准化:统一 Skill 接口,降低 Agent 系统的集成难度
随着 Agent 系统越来越复杂,Skill 的管理(版本控制、测试、监控)会逐渐成为一个独立的工程方向,值得持续关注。