AI摘要
一文读懂 Agent Skills:原理、机制、架构、代码与 AI 工程化落地全解析
作者:多模态智能体
发布时间:2026年6月9日 08:21
原始链接:https://mp.weixin.qq.com/s/hWtN3cPszsVKJd94tSQM7A
你有没有遇到过这种情况:写了一个函数,需要补充单元测试。你打开 AI 助手,发了一句话过去。
AI 给了你几个测试用例。但你看了一眼,发现边界条件根本没有覆盖,Jest 框架也没用,覆盖率更是没有任何说明。
你只好重新发一条:"要用 Jest 框架,要覆盖边界情况,要生成覆盖率报告……"
下周同样的事情再发生一次,你又要从头解释一遍。
或者这个场景:团队里积累了几十个 Markdown 文档,格式乱、目录缺、链接死。你告诉 AI "帮我整理一下这些文档",AI 只是改了改标题格式,其他什么都没动。你想要的是统一格式、提取关键信息、生成目录、验证链接——这些它压根不知道。
问题不是 AI 不聪明。问题是:它没有一套稳定的、可复用的能力链来完成这类任务。
每次都要重新解释,每次执行结果都不一样,每次都是一次性的。这才是真正的痛点。
Agent Skills 要解决的,就是这个问题。
先给一个干净的定义:
Skill 是一个可被 Agent 在运行时发现、理解、调用和组合的能力模块。
这句话里有四个动词——发现、理解、调用、组合——缺少任何一个,它都不能叫 Skill。
用一个生活场景来理解:你手机里装了几十个 App。当你想导航的时候,不需要自己写路线规划算法,直接打开地图 App 就好。App 帮你把"导航这件事"封装好了——如何定位、如何规划、如何播报,全在里面。
Agent Skills 的逻辑完全一样。
- Agent 是那部手机,负责理解你的意图并调度能力
- Skill 是那个 App,给 Agent 增加一项可调用的能力
- 生态 是应用商店,让能力被发现、安装和管理
再讲一个更贴近工程场景的类比是:你刚入职一家公司。你智商很高,学东西快,但完全不懂这家公司的业务。老板让你"处理一下这批退款并生成财务报告"。
如果没有任何参考资料,你大概率会做错。
但如果书架上有一本《退款处理与财务报告生成 SOP》,你只需要按照手册里的步骤,用公司的工具(电脑、系统账号)一步步执行,结果就会是对的。
这本手册,就是 Skill。
它给 Agent 发的不是命令,而是一张图书馆借阅证——不是强迫它背下整个图书馆,而是让它在需要的时候去查。
很多人第一反应是:Skill 不就是一个很长的 Prompt 吗?写详细点,不就行了?
不一样,差别很大。
我们从三个维度来看:
发现性
Prompt 需要用户主动提供。你每次要用,就得自己把 Prompt 粘贴进去。Skill 不一样,Agent 可以根据你的任务描述,自动匹配到合适的 Skill,你不需要知道它叫什么名字。
执行性
Prompt 本质上是"告诉 AI 做什么",但执行过程是 AI 自由发挥的。Skill 内部有结构化的 Workflow,每一步要做什么、怎么做、不能做什么,全都有明确规定。
复用性
Prompt 是一次性的,散落在对话历史里,下次遇到同样的任务,你还得从头写。Skill 是独立的模块,一次定义,到处复用,还能持续迭代优化。
举个具体例子:
用 Prompt 的方式补充单元测试,你需要写:"帮我给这个函数补充单元测试,要用 Jest 框架,要覆盖边界情况,要检查覆盖率,要……"——每次都要重复,容易遗漏,结果不稳定。
用 Skill 的方式,你只需要说"帮我补充单元测试",Agent 识别到这是一个测试相关任务,自动加载 test-driven-development Skill,按照里面定义好的流程执行:理解代码 → 设计测试场景 → 编写测试代码 → 验证覆盖率。
结果一致,流程稳定,不用每次解释。
上面说的是表层体验。从工程架构的角度看,Agent Skills 解决的是三个更深层的问题。
痛点一:巨型提示词灾难
很多团队的第一反应是:把所有业务规则、操作流程、边界条件全写进系统提示词。结果是上下文越来越长,几千 Token 变成几万 Token。
这不只是钱的问题。当提示词超过模型能高质量处理的范围,会出现一个现象叫"注意力丢失(Lost in the Middle)"——中间部分的指令,模型大概率会忽略。越长的 Prompt,指令遵循能力越差。
痛点二:执行能力和专业知识脱节
现在的 Agent 系统通常配了很多工具:能执行代码、能读写文件、能调 API。但拥有工具,不等于知道如何专业地使用工具。
给 Agent 一个能执行 SQL 查询的工具,它能查。但它知道针对你这张五百万行的宽表应该怎么写查询吗?知道如何做多步关联验证吗?知道数据异常了该怎么回退吗?这些"操作智慧",工具给不了,必须单独注入。
痛点三:企业级可靠性缺失
生产环境有一个基本要求:相同输入,相同输出。但依赖动态生成的提示词,系统行为会有大量随机性。今天跑正确,明天换个措辞就跑歪了。
工程化系统需要确定性。需要把专家的隐性知识,转换成模型可以稳定执行的标准操作流程。
这三个痛点,指向同一个缺失:缺少一种"可插拔的领域专家大脑"机制。 Agent Skills 填的就是这个空。
这是一个很多人想搞清楚的问题:我说一句话,Agent 是怎么判断要不要调用 Skill,调用哪个的?
整个决策流程分五步:
第一步:识别任务类型。 Agent 先判断你的请求是什么性质的任务——测试相关?文档处理?代码审查?这一步相当于分类。
第二步:判断是否需要 Skill。 不是所有任务都需要 Skill。"Python 是什么语言"这种问题,直接回答就好,不需要任何流程。判断标准是:这个任务是否需要结构化的多步骤执行?是否有明确的验证标准?
第三步:语义匹配。 如果判断需要 Skill,Agent 会在所有可用 Skill 的描述(description)里做语义搜索,找到最匹配的那一个。
第四步:注入并执行。 找到 Skill 后,把 Skill 的完整内容注入到系统提示词,Agent 按照里面的 Workflow 一步步执行。
第五步:整合输出。 Skill 执行完成,结果返回给你。
这背后有一个精妙的设计——渐进式信息披露。
系统启动时,只加载所有 Skill 的元数据(名字和描述),每个大约一百个 Token。等到识别到具体任务,才加载完整的 SKILL.md(几千 Token)。如果任务还需要外部参考资料,才在执行过程中按需加载(可能几万 Token)。
这种设计让 Agent 能"知道"几百个 Skill 的存在,但只在真正需要的时候才"学习"具体内容。既保持了广度,又控制了成本。
理解了调用机制,我们来看 Skill 本身的结构。
从三个层次来看:
第一层:使用者视角(黑盒)
对用户来说,Skill 就是"我说一句话,Agent 按某套流程处理,然后给我结果"。不需要知道里面是什么。
第二层:文件夹视角
打开一个 Skill 的目录,通常长这样:
my-skill/
├── SKILL.md # 必需:核心文件
├── scripts/ # 可选:可执行脚本
├── references/ # 可选:参考文档
├── assets/ # 可选:静态资产
└── templates/ # 可选:输出模板只有 SKILL.md 是必须的,其他都是按需添加。
第三层:SKILL.md 的内部结构
一个典型的 SKILL.md 长这样:
---
name: test-driven-development
description: 在实现任何新功能或修复 Bug 时使用,必须在编写实现代码之前调用
---
# 测试驱动开发
## Goal
引导使用 TDD 方法论来实现具体功能。
## Workflow
1. 理解需求:阅读功能描述,识别验收标准
2. 设计测试用例:列举测试场景,按优先级排序
3. 测试先行:用项目指定框架实现测试,确保初始失败
4. 实现代码:编写最小代码使测试通过
5. 重构:在不改变行为的前提下提升代码质量
## Constraints
- 绝对不能在写测试之前写实现代码
- 代码覆盖率最低要求 80%
- 每个测试必须独立、可重复运行结构很清晰:frontmatter 是路由的入口,body 是执行的指南。
description 字段尤其关键,它直接决定了这个 Skill 会不会在合适的时机被找到。写好和写差的区别很大:
根据复杂度不同,Skill 分三种类型:
- 简单型:只有一个 SKILL.md,3-5 步线性流程,比如生成 Git 提交信息。五分钟就能写完。
- 多步骤工作流型:仍然只有 SKILL.md,但 Workflow 有分支逻辑,5-10 步,比如 TDD 开发流程。
- 资源依赖型:SKILL.md 加上外部资源目录,适合需要参考文档、执行脚本、使用模板的复杂任务。
- *
理论说完了,来看代码。我们用一个具体任务串起来:实现一个天气查询 Skill,分别用 OpenAI SDK 和 LangChain 两套框架落地。
---
name: get-weather
description: 获取指定城市的天气信息。在用户询问天气、气温或出行建议时使用。
---
# 获取天气信息
## Goal
根据用户询问,提供准确的天气信息。
## Workflow
1. 从用户请求中提取城市名称
2. 获取该城市当前天气数据(温度、天气状况、湿度、风速)
3. 以自然对话口吻返回结果
4. 如需对比多城市,分别获取后整合展示
## Constraints
- 默认使用摄氏度
- 只提供当前天气,不做长期预测
- 空气质量差时主动提醒
## Examples
用户:"北京今天天气怎么样?"
回答:"北京今天晴天,25°C,空气质量良好,适合出行。"两套框架共用同一套三层架构:
SkillRegistry(加载所有 Skill)
↓
SkillRouter(根据用户输入选择 Skill)
↓
SkillExecutor(注入 Skill 文档并执行)import os, re, json
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
)
@dataclass
class SkillMetadata:
name: str
description: str
file_path: str
full_content: Optional[str] = None
class SkillRegistry:
"""扫描目录,加载所有 Skill 文件"""
def __init__(self, skills_dir: str):
self.skills_dir = Path(skills_dir)
self.skills: Dict[str, SkillMetadata] = {}
self._load_all_skills()
def _parse_skill_file(self, file_path: Path) -> Optional[SkillMetadata]:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 提取 YAML frontmatter
match = re.search(r'^---\s*\n(.*?)\n---', content, re.DOTALL)
ifnot match:
returnNone
frontmatter = match.group(1)
name_match = re.search(r'name:\s*(.+)', frontmatter)
desc_match = re.search(r'description:\s*(.+)', frontmatter)
ifnot name_match ornot desc_match:
returnNone
return SkillMetadata(
name=name_match.group(1).strip(),
description=desc_match.group(1).strip(),
file_path=str(file_path),
full_content=content
)
def _load_all_skills(self):
skill_files = (
list(self.skills_dir.glob("*.skill")) +
list(self.skills_dir.glob("*.md"))
)
for f in skill_files:
skill = self._parse_skill_file(f)
if skill:
self.skills[skill.name] = skill
def get_all_skills_summary(self) -> str:
return"\n".join(
f"- {s.name}: {s.description}"
for s in self.skills.values()
)
class SkillRouter:
"""调用 LLM,根据用户输入选择合适的 Skill"""
def __init__(self, registry: SkillRegistry):
self.registry = registry
def route(self, user_input: str) -> Optional[str]:
prompt = f"""
你是一个 Skill 路由器。根据用户输入选择最合适的 Skill。
可用 Skills:
{self.registry.get_all_skills_summary()}
用户输入:{user_input}
只返回 Skill 名称,没有合适的返回 NONE。
"""
response = client.chat.completions.create(
model=os.getenv("DEEPSEEK_MODEL", "deepseek-chat"),
messages=[{"role": "user", "content": prompt}],
temperature=0,
max_tokens=50
)
skill_name = response.choices[0].message.content.strip()
return skill_name if skill_name in self.registry.skills elseNone
class SkillExecutor:
"""加载完整 Skill 文档,注入系统提示词后执行"""
def __init__(self, registry: SkillRegistry):
self.registry = registry
def execute(self, skill_name: str, user_input: str) -> str:
skill = self.registry.skills.get(skill_name)
ifnot skill:
returnf"Skill '{skill_name}' 不存在"
system_prompt = f"""
你现在要执行以下 Skill:
{skill.full_content}
严格按照 Workflow 执行,遵守所有 Constraints,直接返回结果。
"""
response = client.chat.completions.create(
model=os.getenv("DEEPSEEK_MODEL", "deepseek-chat"),
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
],
temperature=0.7
)
return response.choices[0].message.content
class AgentSkillsEngine:
"""系统入口,协调 Registry / Router / Executor"""
def __init__(self, skills_dir: str):
self.registry = SkillRegistry(skills_dir)
self.router = SkillRouter(self.registry)
self.executor = SkillExecutor(self.registry)
def process(self, user_input: str) -> Tuple[str, Optional[str]]:
skill_name = self.router.route(user_input)
ifnot skill_name:
return"抱歉,没有找到合适的 Skill 处理这个请求。", None
result = self.executor.execute(skill_name, user_input)
return result, skill_name
def list_skills(self) -> List[str]:
return list(self.registry.skills.keys())调用方式:
engine = AgentSkillsEngine(skills_dir="./skills")
result, skill = engine.process("北京今天天气怎么样?")
print(f"回复:{result}")
print(f"使用 Skill:{skill}")
result, skill = engine.process("对比北京和长沙的天气")
print(f"回复:{result}")LangChain 版本在架构上完全一致,换成 LCEL(LangChain Expression Language)的写法,用 | 操作符串联各组件:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
class SkillRouter:
def __init__(self, registry):
self.registry = registry
self.llm = ChatOpenAI(
model=os.getenv("DEEPSEEK_MODEL", "deepseek-chat"),
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
temperature=0
)
self.prompt = ChatPromptTemplate.from_messages([
("system", """你是一个 Skill 路由器。
{skills_summary}
规则:只返回 Skill 名称,没有合适的返回 NONE。"""),
("human", "{user_input}")
])
# LCEL 链:提示词 → 模型 → 解析输出
self.chain = self.prompt | self.llm | StrOutputParser()
def route(self, user_input: str) -> Optional[str]:
result = self.chain.invoke({
"skills_summary": self.registry.get_all_skills_summary(),
"user_input": user_input
})
skill_name = result.strip()
return skill_name if skill_name in self.registry.skills elseNone
class SkillExecutor:
def __init__(self, registry):
self.registry = registry
self.llm = ChatOpenAI(
model=os.getenv("DEEPSEEK_MODEL", "deepseek-chat"),
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
temperature=0.7
)
self.prompt = ChatPromptTemplate.from_messages([
("system", """执行以下 Skill:
{skill_content}
严格按照 Workflow 执行,直接返回结果。"""),
("human", "{user_input}")
])
self.chain = self.prompt | self.llm | StrOutputParser()
def execute(self, skill_name: str, user_input: str) -> str:
skill = self.registry.skills.get(skill_name)
ifnot skill:
returnf"Skill '{skill_name}' 不存在"
return self.chain.invoke({
"skill_content": skill.full_content,
"user_input": user_input
})LangChain 版本和 OpenAI SDK 版本的逻辑完全相同,差别只是表达方式。如果你的项目里已经在用 LangChain 的生态,直接接入就好;如果是新项目,两种都可以,OpenAI SDK 版本更轻量。
用了这么久,有一个绕不开的问题:这三个东西什么时候用哪个?
先理清各自的定位:
- Function Calling:代码级函数调用。你定义函数签名,AI 决定什么时候调用它,调用后你处理结果。执行是确定的,代码写什么就跑什么。
- MCP(Model Context Protocol):协议级能力接入。标准化的接口协议,让 AI 能接入各种外部系统(数据库、文件系统、第三方服务)。解决"如何连接"的问题。
- Agent Skills:语义级能力模块。用 Markdown 描述"做什么、怎么做、不能做什么",AI 按照描述执行。解决"如何专业地完成"的问题。
判断用哪个,看这几个维度:
确定性要求高不高? 金融交易、数据库写入、触发支付——这些场景不允许任何随机性,必须用 Function Calling。给定相同输入,一定要得到相同输出。
需不需要快速迭代? 如果你还在验证阶段,需求每周都在变,Agent Skills 的优势很明显。改 Markdown 比改代码快,也不需要重新部署。等流程稳定了,再考虑要不要换成 Function Calling。
团队里有没有非技术人员? 产品经理、运营想定义工作流,但不会写代码——Agent Skills 是唯一的选项。描述清楚流程和规则,就能用起来。
需不需要跨平台复用? 同一套能力要在多个 AI 系统里用,MCP 的标准化协议价值才体现出来。如果只在一个系统里用,MCP 的开发成本未必值得。
实际项目里,这三个通常是组合使用的:高层工作流用 Skills,底层精确操作用 Function Calling,跨系统集成用 MCP。 不是非此即彼,是各司其职。
最后说一些真正上线之后才会遇到的问题。
坑一:路由瘫痪——Skills 互相打架
当你的 Skill 库积累到几十个,如果各个 Skill 的 description 语义重叠,Agent 就会开始"选择困难"。用户说"处理这个文件",Agent 在 file-processor、document-analyzer、data-extractor 之间反复横跳。
解法:每个 Skill 的 description 要有清晰的边界,而且要加"负向触发器"——明确写出"本 Skill 不处理 X 类任务"。比如:
description: |
当用户需要提取文本或处理 PDF 结构时使用。
不用于处理纯文本文件或代码仓库扫描。坑二:上下文隐性膨胀
Skill 里定义了"读取 references/ 目录下的文档",但没有限制读取量。执行时 Agent 把整个目录的内容全加载进来,几万 Token 一下就打满了。
解法:在 SKILL.md 里明确限制:"每次最多读取 5000 Token,使用 grep 过滤后再读取,不要全量加载。"
坑三:description 写成了说明书
新手最常见的错误:把 description 写得很详细,一段话解释背景、功能、使用场景、注意事项。结果适得其反——description 太长太复杂,路由器反而判断不准了。
description 要短、精、关键词密度高。一两句话,清晰说明触发时机和核心能力。
坑四:Skill 过了保质期
Skill 里引用了某个外部接口的格式,接口升级了,Skill 没更新。或者团队的代码规范变了,Skill 里的标准没跟上。这种"静默降级"很隐蔽——系统不报错,但结果越来越偏。
解法:建立定期审计机制。每隔一段时间,人工检查 Skill 的 Workflow 和 Constraints 是否还与实际情况对齐。
Agent Skills 不是什么革命性的新概念。它做的事情,说穿了很朴素:把专家的知识和流程,结构化地写下来,让 AI 能稳定地照着执行。
就像公司有了流程手册,新人上手更快,老员工犯错更少,团队整体输出更稳定。
AI 也一样。给它一本好的手册,它就能更好地工作。
Prompt 是你告诉 AI"去做这件事"。Skill 是你提前准备好了"这件事怎么做"。
区别就在这里。

