AI助手用了一段时间后,你有没有这种感觉?

你需要自己判断:先干什么、后干什么、哪个工具负责什么、结果怎么传给下一个。
这件事听起来像是"编排"的问题,但更深层的问题是——工具和Agent之间,缺少一套标准的"连接协议"。
今天聊的Skill生态,就是来解决这个问题的。
01 问题的本质:不是工具不够,是"连接"不够

为什么?
因为每个工具都是一个"入口",Agent需要在上下文中感知:现在该用哪个工具?这个工具怎么用?结果怎么处理?
工具越多,感知成本越高。上下文窗口就那么大,全用来装"工具清单"了,真正做任务的空间反而被压缩。
Skill生态的设计思路是:不是让Agent看到所有工具,而是让Agent在需要的时候,恰好知道该用什么。
这就是"渐进式披露"的核心思想。
02 渐进式披露:两阶段加载的艺术
SkillLoaderTool的代码里,有一段注释非常关键:
1 2 3
# 💡 核心点:渐进式披露第一阶段
# 只读 frontmatter,构建轻量 XML 注入 description。
# 主 Agent 看到工具 → 知道"什么场景用什么 Skill",但不加载完整指令。
翻译成人话就是:
第一阶段,Agent只知道"有哪些技能",每个技能是干嘛用的,但不加载具体操作步骤。
这些信息被压缩成一个轻量的XML描述,注入到工具的description里:
1 2 3 4 5 6 7 8 9 10 11 12
<available_skills>
<skill>
<name>pdf-processor</name>
<type>task</type>
<description>将PDF文件转换为Word文档,支持格式保留...</description>
</skill>
<skill>
<name>mailbox-ops</name>
<type>task</type>
<description>发送邮件,支持附件和HTML格式...</description>
</skill>
</available_skills>
Agent看到这个清单,就知道:"用户要处理PDF,我应该调用pdf-processor这个Skill。"
第二阶段,才是在真正需要的时候,读取完整的SKILL.md文件。
1 2 3
# 💡 核心点:渐进式披露第二阶段
# 读取完整 SKILL.md,剥离 frontmatter,拼接沙盒路径替换指令。
# 结果写入 _instruction_cache,同一 Skill 只读一次文件。

03 参考型 vs 任务型:两种不同的"干活方式"
在Skill生态里,Skill分两种类型,这个设计非常有意思:
1 2 3 4
class SkillLoaderInput(BaseModel):
skill_type: str = Field(
description="skill类型:reference(参考型)或 task(任务型)"
)
参考型Skill(reference)
返回操作规范文档,Agent自己消化、自己执行。
适合的场景:
• 查文档、看攻略 • 理解某个领域的知识 • 需要Agent自己综合多个信息源
1 2 3
if skill_info["type"] == "reference":
# 参考型:直接返回指令文本,不启动 Sub-Crew
return f"<skill_instructions>\n{instructions}\n</skill_instructions>"
任务型Skill(task)
触发独立的Sub-Crew,在沙盒中隔离执行,上下文完全隔离。
适合的场景:
• 需要执行文件操作 • 需要运行脚本 • 可能产生副作用的操作
1 2 3 4 5 6 7
# 任务型:启动独立 Sub-Crew,在沙盒中执行
crew = build_skill_crew(
skill_name=skill_name,
skill_instructions=instructions,
mount_desc=self.sandbox_mount_desc,
mcp_url=self.sandbox_mcp_url,
)

04 沙盒隔离:安全与自由的平衡
说到任务型Skill,就不得不提沙盒隔离的设计。
1 2 3 4 5 6
# 💡 核心点:默认沙盒挂载描述
DEFAULT_SANDBOX_MOUNT_DESC = (
"1. 所有的操作必须在沙盒中执行,不得操作本地文件系统\n"
"2. 如果需要读取本地文件,则需要本地文件在./workspace/data/目录下\n"
"3. 任务预期输出的文件,必须写在沙盒绝对路径的/workspace/output/目录下"
)
核心约束:Sub-Crew能操作文件、跑代码,但它活在沙盒里,出不来。
这个设计解决了一个经典的Agent安全问题:

主Agent把任务扔给Sub-Crew,Sub-Crew在沙盒里干活,干完把结果交给主Agent。
中间环节是隔离的、安全的。
05 异步双通道:FastAPI和命令行的兼容设计
代码里有一段很有意思的设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
async def _arun(self, skill_name: str, task_context: str) -> str:
"""
💡 核心点:FastAPI 异步调用链的主路径,直接 await Sub-Crew
CrewAI 在 arun() 内部调用 _arun(),框架自动选路
"""
return await self._execute_skill_async(skill_name, task_context)
def _run(self, skill_name: str, task_context: str) -> str:
"""
💡 核心点:用 ThreadPoolExecutor 在新线程中运行独立 event loop,
规避主线程已有 event loop 时 asyncio.run() 报
'cannot run nested event loop' 的问题。
"""
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
future = pool.submit(ctx.run, _run_in_new_loop)
return future.result(timeout=300)

1 2 3 4 5
if __name__ == "__main__":
if "--async" in sys.argv:
asyncio.run(main_async())
else:
main() # 走同步路径
06 和MCP协议的关系:各司其职
之前我们聊过MCP协议,它是"AI的USB-C",让不同模型、不同工具之间能互相通信。
那么Skill和MCP是什么关系?
简单来说:
| 工具级别的标准化 | |
| 任务级别的封装 |
打个比方:

MCP解决的是"怎么连",Skill解决的是"拿来干嘛"。
07 实际应用:一个真实的workflow
说了这么多设计理念,来看看实际怎么用:
1 2 3 4 5 6 7 8 9 10 11 12 13
USER_REQUEST = (
"请将./workspace/data/quarterly_report.pdf里的关键数据提炼出来,"
"生成一份格式规范的 Word 文档"
)
def build_main_crew() -> Crew:
skill_loader = SkillLoaderTool()
orchestrator = Agent(
role="skill使用助手总管",
goal="根据用户需求进行分析,拆解,分发任务,最终保证任务的完成",
tools=[skill_loader, IntermediateTool()],
)
用户说:"把PDF转成Word。"
主Agent拿到请求,开始拆解:
1. 分析需求——用户要处理PDF文件,需要用到pdf-processor这个Skill 2. 加载Skill——调用SkillLoaderTool,按需读取pdf-processor的完整指令 3. 分发任务——把具体任务描述(task_context)传给Sub-Crew 4. 沙盒执行——Sub-Crew在隔离环境里完成PDF处理 5. 汇总结果——把最终产物路径返回给用户
整个过程,主Agent不需要知道"PDF怎么转Word"的具体步骤,它只需要知道"有pdf-processor这个Skill能搞定这件事"。
这就是"让AI管理AI的调度"的具体实现。
写在最后
Skill生态给我的最大启发,不是某个具体的技术实现,而是一种分层解耦的思维。
• 工具层:MCP统一接口标准 • Skill层:把工具组合成可复用单元 • Agent层:调度Skill完成任务
每一层只关心自己的事,层与层之间通过清晰的契约连接。
这和微服务架构的设计哲学是一脉相承的——
当你的Agent系统变得越来越复杂,这种分层解耦的思维会越来越重要。
📌 互动话题:
你在给Agent接工具的过程中,遇到过最大的坑是什么?是工具调用失败、上下文爆炸、还是不知道该用什么工具?
(全文完。更多AI智能体实战,关注公众号,我们下期见。)
夜雨聆风