AI Agent 中的 Skills 深度解析:从概念原理到工程实践
目录
- 1. 引言:为什么 Agent 需要 Skills
- 2. 什么是 Skills:一个比 Tool 更高层的抽象
- 3. Skills 与 Tools 的本质区别
- 4. Skills 的核心原理
- 5. 工程实践:如何实现一个 Skills 系统
- 6. Skills 的设计模式
- 7. 常见坑与最佳实践
- 8. 总结
1. 引言:为什么 Agent 需要 Skills
2024 年以来,AI Agent 从「能聊天」快速进化到「能干活」。早期的 Agent 框架把能力抽象为 Tools(工具),比如搜索、计算器、数据库查询。但随着任务越来越复杂,工程师们逐渐发现:一堆零散的工具,拼不出一个可靠的智能体。
真正的差距不在工具数量,而在于 Agent 是否知道「什么时候、按什么步骤、以什么约束」去组合这些能力。于是,Skills(技能) 的概念被提出并迅速普及——它把「工具 + 使用工具的流程 + 领域知识」打包成一个可复用、可组合、可观测的单元。
本文不局限于某一个框架(如 LangChain、Semantic Kernel、Claude Skills、OpenAI Agent SDK),而是从底层抽象出发,讲清楚 Skills 的概念原理,再落到可落地的工程实践。
2. 什么是 Skills:一个比 Tool 更高层的抽象
2.1 从 Function 到 Tool,再到 Skill
要理解 Skills,先厘清三层抽象:
- Function(函数):最底层的代码单元,如
search_web(query: str) -> str。 - Tool(工具):对 Function 的一层封装,加入 JSON Schema 描述、输入输出约定、错误语义,让 LLM 能理解并调用。例如把上面的函数声明成
web_search工具。 - Skill(技能):一组 完成特定任务的完整能力包,通常包含多个工具、一个有状态的流程、以及领域知识(提示词、示例、约束)。
用一个例子对比:
| 抽象层次 | 例子 | 粒度 |
|---|---|---|
| Function | requests.get(url) |
一行代码 |
| Tool | http_request(method, url, headers, body) |
一次调用 |
| Skill | 「抓取并解析某电商商品页」——先请求、再解析、再清洗、最后结构化输出 | 一个完整任务 |
2.2 Skills 的典型定义
工程上,一个 Skill 通常包含以下要素:
- 元数据:名称、版本、描述、触发条件(何时该用这个技能)。
- 工具集合:该技能可以调用的底层工具或函数。
- 流程 / 状态机:定义任务如何分步完成,允许 LLM 在步骤间携带状态。
- 提示词模板:把领域知识、输出格式、注意事项注入到 LLM 的上下文中。
- 输入输出 Schema:技能的对外契约,保证可组合、可测试。
换句话说:Tool 是「能力原子」,Skill 是「能力分子」。
3. Skills 与 Tools 的本质区别
很多人问:直接把所有工具都丢给 Agent,让它自己选不就行了?为什么还要 Skills?
答案在四个维度:
3.1 上下文窗口与选择成本
把 50 个工具的描述全部塞进 system prompt,会显著占用上下文,也会降低模型选择正确工具的概率。Skills 提供了一种 分层路由 机制:先选「技能」,再在技能内部选具体工具,把决策空间从「50 选 1」变成「5 选 1 然后再 10 选 1」。
3.2 状态的显式管理
单个 Tool 调用通常是无状态的:输入进、输出出。但真实任务是有状态的——「上一轮解析失败,这一轮应该换一种选择器」「已经翻到第 3 页,继续翻」。Skills 天然允许在内部维护状态,而 Tool 不行。
3.3 领域知识的封装
Tool 只说「我是做什么的」,Skill 还能说「在这个场景下应该怎么做」。例如同样的 sql_query 工具,在「数据分析」Skill 里会附带「先看表结构、再写只读查询、禁止 DELETE」的约束;在「数据清洗」Skill 里则附带完全不同的玩法。
3.4 复用与组合
一个 Skill 可以作为另一个 Skill 的子步骤被调用,形成 技能树。这比工具的直接组合更容易管理依赖和边界。
4. Skills 的核心原理
4.1 分层决策架构
一个典型的 Skills 型 Agent 架构如下:
规划器只负责「该用哪个技能」,技能内部负责「具体怎么做」。这种双层结构把顶层推理和底层执行解耦,显著降低单次 LLM 调用的复杂度。
4.2 技能的声明式描述
每个 Skill 都需要一段 声明式描述,告诉规划器:
- 这个技能解决什么问题;
- 什么情况下不该用它;
- 输入输出长什么样。
描述写得越好,路由越准。工程上的经验法则是:描述要写「边界」而不只是「能力」。例如:
name: web_research
description: >
用于对互联网公开信息进行多轮检索、交叉验证与摘要。
适用:需要查询实时信息、多来源比对、整理成报告。
不适用:内部数据库查询、执行本地代码、生成图片。
4.3 技能内部的渐进式执行
一个 Skill 内部往往不是一个固定脚本,而是一个「LLM 驱动的小型工作流」。常见的两种模式:
- 固定流程(Deterministic):步骤由代码写死,LLM 只在关键节点做决策。适合稳定性要求高的场景。
- 自由探索(Agentic):LLM 在工具集内自主决定下一步,直到满足终止条件。适合开放性任务。
工程上推荐 混合模式:关键步骤固定,中间环节留给 LLM 一定自由度。
5. 工程实践:如何实现一个 Skills 系统
下面给出一套不依赖特定框架的最小实现,语言采用 Python。
5.1 定义 Skill 基类
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Dict, Optional
@dataclass
class SkillContext:
"""技能执行期间携带的共享状态。"""
data: Dict[str, Any] = field(default_factory=dict)
class Skill(ABC):
"""所有技能的基类。"""
name: str = "base_skill"
description: str = ""
@abstractmethod
def run(self, task: str, context: SkillContext) -> str:
"""执行技能,返回最终结果。"""
raise NotImplementedError
5.2 实现一个具体技能:网页研究
import requests
from bs4 import BeautifulSoup
class WebResearchSkill(Skill):
name = "web_research"
description = (
"对互联网公开信息进行检索、抓取与摘要。"
"适用:查询实时信息。不适用:本地文件操作。"
)
def search(self, query: str) -> list[str]:
# 这里替换成真实的搜索 API
return [f"https://example.com/result?q={query}"]
def fetch(self, url: str) -> str:
resp = requests.get(url, timeout=10)
soup = BeautifulSoup(resp.text, "html.parser")
return soup.get_text()[:2000]
def run(self, task: str, context: SkillContext) -> str:
urls = self.search(task)
pages = [self.fetch(u) for u in urls[:3]]
context.data["raw_pages"] = pages
# 实际中这里调用 LLM 做摘要
return "\n\n".join(pages[:1])
5.3 技能注册表与路由器
class SkillRouter:
"""根据任务描述选择最合适的技能。"""
def __init__(self, skills: list[Skill]):
self.skills = {s.name: s for s in skills}
def route(self, task: str) -> Skill:
# 简化版:基于关键词路由;生产环境交给 LLM 决策
if "搜索" in task or "查" in task or "最新" in task:
return self.skills["web_research"]
return self.skills["default"]
5.4 组装 Agent
class Agent:
def __init__(self, router: SkillRouter):
self.router = router
def execute(self, task: str) -> str:
skill = self.router.route(task)
context = SkillContext()
result = skill.run(task, context)
return f"[Skill: {skill.name}]\n{result}"
运行示例:
agent = Agent(SkillRouter([WebResearchSkill()]))
print(agent.execute("帮我查一下今天 AI 领域有什么最新新闻"))
6. Skills 的设计模式
6.1 技能分层
把技能按粒度分成三层:
- 原子技能:只做一件事,如「读取 PDF」「发送邮件」。
- 组合技能:编排多个原子技能,如「简历解析并入库」。
- 领域技能:面向特定行业的高层技能,如「金融研报生成」。
分层之后,新增一个领域技能往往不需要写新代码,只需重新组合原子技能。
6.2 技能的手动与自动编排
- 手动编排:用代码或 DSL 明确写出调用顺序,可预测、可调试。
- 自动编排:让 LLM 根据任务动态组合技能,灵活但容易失控。
推荐的工程实践是:高频、高风险的任务手动编排;长尾、低风险的任务交给 LLM 自动编排。
6.3 技能的可观测性
每个 Skill 至少要暴露三类日志:
- 输入:任务原文与关键参数;
- 中间过程:每个子步骤调用了什么、耗时多少、成功与否;
- 输出:最终结果与置信度。
这样当 Agent 出错时,能快速定位是「路由选错」还是「技能内部执行失败」。
7. 常见坑与最佳实践
- 技能描述写得像广告:只写「我能做 X」,不写「我不该用于 Y」。结果是路由器频繁误选。补救方法是给每个技能写清晰的「不适用」清单。
- 技能内部塞入过多工具:一个技能调用 20 个工具,等于把选择难题又搬回了技能内部。建议单个技能暴露的工具不超过 5~7 个。
- 忽略输入校验:LLM 传给技能的参数可能千奇百怪,技能入口处必须做 Schema 校验和默认值填充。
- 状态泄漏:复用同一个
SkillContext跨任务执行时,务必在任务开始时清空状态,否则上一轮数据会污染下一轮。 - 错误吞掉不显式化:技能内部捕获异常后返回一句「失败了」,外层无法判断是重试、降级还是终止。正确做法是把错误分类后抛出或结构化返回。
- 为了 Skills 而 Skills:如果任务只有两步且永远固定,直接写个函数即可。Skills 的价值在于「可组合、可路由、可观测」,不要过度设计。
8. 总结
Skills 是 AI Agent 工程化进程中一次重要的抽象升级:它把注意力从「模型能调什么工具」转移到「智能体如何可靠地完成任务」。一个设计良好的 Skill 应该做到:
- 边界清晰:知道什么该做、什么不该做;
- 自包含:工具、流程、知识打包在一起;
- 可观测:每一步都可追踪、可调试;
- 可组合:既能独立运行,也能作为更大的技能的子模块。
从概念到实践,Skills 的核心不是复杂的技术,而是 用工程的确定性,去约束模型的不确定性。当你开始把 Agent 的能力拆成一个个边界清晰的技能时,你的智能体就已经从「玩具 demo」走向了「生产可用」。
更多推荐


所有评论(0)