前阵子在折腾公司的智能客服项目,发现每次给聊天机器人加一个新能力,都要从写提示词、调接口、测边界一路重来,循环往复特别痛。后来我换了个思路,把各种能力封装成统一的“技能包”,让 Agent 自己去匹配和调用,整个开发节奏一下子就顺了。这套东西如今在圈子里有个挺火的名字,叫 agent-skills,本质上就是个“技能库”的设计模式。今天把这几个月从零搭建、踩坑、重构的经验完整写出来,给正在搞 Agent 落地的朋友做个参考。

1. 先搞明白 agent-skills 到底在解决什么问题

先说个背景。这两年大模型应用从“单轮对话玩具”走向“真干活的生产工具”,最大的瓶颈不是模型聪明不聪明,而是怎么让模型稳定地去调用工具、完成任务。今天你让它帮你查天气,明天让它订机票,后天又让它分析一份 PDF,每加一种能力,就得重新设计一套调用逻辑。混乱、难维护、换场景直接报废。agent-skills 的核心思路很简单:干脆把所有能力都写成标准化的“技能”,用统一的格式来声明,用一套机制来管理,让 Agent 像一个装了各种工具的工人,接到任务就自己挑合适的家伙。

1.1 它和普通工具调用的本质区别在哪

传统的 function calling 是把每个函数单独注册给模型,比如 get_weather、book_flight 各写各的,模型在对话里自己决定调哪个。这套方案在小规模场景下够用,但技能一多就糟心:每个函数都要单独调优提示词,函数之间的协作没法声明,复用基本靠复制粘贴。

agent-skills 则把“技能”当成一个自包含的模块。每个技能文件里不仅有代码实现,还有给模型看的说明书、示例、参数定义,甚至专门的语言提示。比如一个“生成图表”技能,里面就包括了图表类型说明、数据格式要求、多语言环境下的调用示例。模型拿到这个技能包,不仅知道"有这个功能",还知道"什么时候该用、怎么调用最稳、参数怎么给最不容易出错"。

1.2 为什么圈内都在聊 SKILL.md

聊到 agent-skills 就绕不开 SKILL.md。这是 Anthropic 在 2024 年下半年力推的开放标准,把某个技能的所有描述都写进一个 Markdown 文件里,放在项目目录中。文件本身是人类可读的说明文档,但它的价值在于:结构足够规整,模型能直接消费,而且和 Human 对齐一致。我在实践中越来越觉得,这种设计是把 Agent 的“上下文”变成可复用资产的关键。过去我们把知识都堆在系统提示词里,提示词越来越长,模型越来越“迷糊”,有了 SKILL.md 之后,知识和调用逻辑沉淀在技能库中,主提示词保持清爽,模型更容易专注与稳定发挥。

1.3 适合谁、不适合谁,先泼一盆冷水

如果你只是做个 Demo,调两三个 API 玩,建议别上 agent-skills,直接 function calling 最省事。技能库的收益要在技能的“种类和数量”达到一定规模后才明显。我大致估算过一笔账:当你的 Agent 未来可能要维护超过十几个能力,并且这些能力会被多个不同项目复用的时候,用技能库的维护成本优势立刻就能看出来。如果公司有成体系的 prompt 管理需求、多 Agent 协同场景、频繁增加和调整技能,那 agent-skills 算是长期角度看比较划算的基建。

2. 核心设计拆解:技能包长什么样、怎么写才不翻车

真正动手之前,必须先把技能包的结构摸清楚。最开始我照着 Anthropic 官方技能仓库的模板写,写完第一个技能后发现,结构看起来简单,但细节里全是坑。一个合格的技能包至少要包含三块核心内容:给模型看的声明、给系统跑的实现、给开发者维护的说明。

2.1 技能包的标准目录结构

我目前的技能库目录设计如下,管理十几个技能基本不混乱:

skills/
├── generate-chart/
│   ├── SKILL.md
│   ├── scripts/
│   │   ├── make_chart.py
│   │   └── requirements.txt
│   └── assets/
│       └── example.png
├── web-search/
│   ├── SKILL.md
│   └── scripts/
│       └── search.py
└── pdf-summarizer/
    ├── SKILL.md
    └── scripts/
        └── summarize.py

每个子目录就是一个独立的技能。系统在启动时扫描这个根目录,读取每个目录中的 SKILL.md,把文件内容作为可用能力的描述全文注入给模型。代码和资源文件按需加载,不到用时不占上下文。

2.2 SKILL.md 的写作规范,别小看这几百字

SKILL.md 可以说是整个技能库的灵魂。它不像普通技术文档,读者不止是人,还有大模型。一份好的 SKILL.md 要同时满足人和机器的阅读理解需求。以下是我总结的关键字段和写法建议:

字段 作用 写作要点
name 技能标识 简洁准确,和目录名一致,不要用歧义词
description 模型选择技能的依据 写清楚触发场景、输入要求、输出格式,最好包含正例和反例
when_to_use 使用时机 明确哪些情况必须用、哪些情况不要用,这能显著减少模型误调用
examples 给模型的示范 写2到3个完整的人机对话示例,从提问到调参到最终结果输出
template 提示词模板 定义调用该技能时,模型要按什么步骤思考和输出

description 字段尤其重要,模型就是靠它来匹配“当前任务”和“技能清单”的。写得太泛,模型会乱调;写得太窄,模型该用时又想不起来。我的经验是,复杂技能至少准备 3 个具体触发场景的描述,并在 when_to_use 里明确“无关时一定要忽略”。

2.3 每个技能都应该是一个“迷你项目”

很多教程把一个技能简单理解成“一段脚本 + 一段说明”,这远远不够。技能里的代码部分,应该是可独立运行、可测试、可调试的小型程序。比如我做“generate-pdf 报告”技能时,把 PDF 生成逻辑做成一个 Python 命令行工具,支持命令行参数传入主题、样式和数据文件,技能代码只是调用这个工具。这样模型通过自然语言生成命令参数就能成事,不需要动态写代码。我强烈建议,所有技能内的代码都优先设计成 CLI 工具形式,少用需要注入模型动态拼接的代码片段,稳定性会明显提升。

3. 实操:从零搭建一个可用的技能库

理论聊完,直接上实操。这一节我用一个“网页内容总结成结构化报告”的真实技能案例,完整带大家从目录创建、脚本编写到联调测试走一遍,拿走就能改。

3.1 先搭好基础环境

我用的技术栈是 Python 3.11 + Claude API + LangChain 做任务编排层。不同模型、不同框架影响不大,核心逻辑是通用的。目录结构就两行命令:

mkdir -p skills/webpage-report/scripts
touch skills/webpage-report/SKILL.md

技能实现部分,我选择了 Fire 来写 CLI 工具,非常轻量,参数解析自动生成。

# skills/webpage-report/scripts/report.py
import json
import sys
from urllib.parse import urlparse

import fire
import requests
from bs4 import BeautifulSoup

def fetch_text(url: str, max_chars: int = 8000) -> str:
    """抓取网页正文并清洗"""
    resp = requests.get(url, timeout=15, headers={
        "User-Agent": "Mozilla/5.0 (AgentSkills/1.0)"
    })
    resp.raise_for_status()
    soup = BeautifulSoup(resp.text, "html.parser")
    for tag in soup(["script", "style", "nav", "footer"]):
        tag.decompose()
    text = soup.get_text(separator="\n", strip=True)
    return text[:max_chars]

def generate_report(url: str, output: str = "report.md", max_chars: int = 8000):
    """生成结构化报告"""
    content = fetch_text(url, max_chars)
    report = {
        "url": url,
        "domain": urlparse(url).netloc,
        "content_preview": content,
        "word_count": len(content),
        "status": "fetched"
    }
    with open(output, "w", encoding="utf-8") as f:
        f.write(json.dumps(report, ensure_ascii=False, indent=2))
    print(json.dumps(report, ensure_ascii=False, indent=2))

if __name__ == "__main__":
    fire.Fire(generate_report)

脚本逻辑很直观:抓正文、清洗标签、截断、落盘。但几个设计细节值得提一下,这也是我踩过坑之后总结出来的:

  • User-Agent 一定要设置,很多站点默认拒绝 Python 的 urllib 请求
  • HTML 清洗顺序有讲究,先删 script/style,再取纯文本,否则打印出来全是 JS 代码
  • 截断字符数要控制,避免技能把超长网页全部塞给模型,上下文直接爆掉

3.2 写一份模型看得懂、人也看得懂的 SKILL.md

现在写核心的 SKILL.md。这份文件直接影响模型能不能正确使用上面的脚本:

---
name: webpage-report
description: 将任意公开网页的内容提炼成结构化报告。当用户给出一个网页地址并希望了解该页面的主题、关键信息或总结内容时使用本技能。不适用于本地文件分析、PDF分析或不涉及具体网址的通用问答。
---

# Webpage Report

将用户提供的公开网页地址抓取并提炼成结构化摘要报告。

## 使用步骤

1. 提取用户问题中的 URL,确认是一个 http/https 链接
2. 参考下述示例构造参数,调用 scripts/report.py
3. 根据抓取的内容生成结构化摘要:页面主题、核心内容、关键结论

## 参数说明

| 参数 | 必填 | 说明 |
|------|------|------|
| url | 是 | 网页完整地址,必须包含 http:// 或 https:// 前缀 |
| max_chars | 否 | 抓取正文的最大字符数,默认 8000 |

## 示例

用户问题:帮我总结一下 https://example.com 这篇文章的主要内容

调用方式:

python scripts/report.py --url https://example.com --max_chars 6000

最终输出结构(Markdown):

## 页面主题
(一句话概括页面主题)

## 核心内容
(3-5 个关键要点,每个要点附带简短说明)

## 关键结论
(2-3 条主要结论或要点)

## 补充信息
(如页面中有特殊数据、表格、统计数据,在此列出)

description 里我特意写了“不适用场景”,这样做的好处是模型在遇到“帮我看看这个PDF”的时候就不会误调用网页技能。这一段“排除描述”价值很大,实测能明显降低误调用率,刚开始做技能库的朋友容易忽略。

3.3 把技能库挂到 Agent 上:注入策略很关键

写完单个技能还不够,要把整个技能库接入运行中的 Agent。代码层面核心就两步:扫描技能目录、注入上下文。

from pathlib import Path
from typing import List

def load_skills(skills_dir: str) -> List[str]:
    """扫描所有技能目录并提取 SKILL.md 内容"""
    skill_docs = []
    base = Path(skills_dir)
    for skill_path in base.iterdir():
        if not skill_path.is_dir():
            continue
        md_file = skill_path / "SKILL.md"
        if md_file.exists():
            skill_docs.append(f"# {skill_path.name}\n\n{md_file.read_text(encoding='utf-8')}")
    return skill_docs

skills_context = "\n\n---\n\n".join(load_skills("./skills"))
system_prompt = f"""
你是一个智能助手,拥有以下技能库。请根据用户需求,选择并调用合适的技能完成任务。

<skills_available>
{skills_context}
</skills_available>
"""

运行时会发现一个关键问题:技能一多,系统提示词疯狂膨胀,十几个技能全塞进去,可能一次请求就要多烧几千 token。这里必须引入“按需注入”机制,我的方案是先用轻量级索引让模型判断该用哪些技能,再通过工具调用把具体的 SKILL.md 内容加载进来。

SKILL_INDEX = """
可用技能列表:
- webpage-report: 网页内容结构化总结
- generate-chart: 图表生成
- web-search: 联网搜索
- pdf-summarizer: PDF文档总结
- csv-analyzer: CSV数据分析

根据用户问题,选择最相关的1-3个技能,输出技能名称的JSON数组。
"""

def route_and_load(question: str, skills_dir: str):
    """通过索引完成粗粒度技能路由"""
    # 这里实际调用模型判断 route_result 为技能名列表
    route_result = llm_call(SKILL_INDEX, question)
    active_skills = load_specific_skills(skills_dir, route_result)
    return build_execution_prompt(active_skills)

经过这样改造,每轮对话模型只需要看“技能索引”和与当前任务真正相关的“技能详情”,上下文占用和响应速度会有本质性改善。这也是我从“全量注入”到“按需加载”的一个很有价值的优化迭代。

4. 常见问题与排查技巧实录

这部分是纯实战经验。我用 agent-skills 模式后,遇到了一些非常典型的问题,单个拎出来都能写篇文章,这里全部集中分享。这些问题有些是设计初就埋下的,有些是技能数量到一定规模才暴露的,但都是必坑。

4.1 模型选错技能或者根本不调用技能

这是最常遇到的头号问题。现象是模型面对用户的“帮我分析这份财报里的营收数据变化”,却调用了一个“网页搜索”技能,或者明明库里就有“数据可视化”技能,模型偏偏自己生成了图表代码,不用现成的。

排查下来,原因基本集中在 SKILL.md 的 description 写得有问题。我总结过一个“三查一改”的排查流程:

查 description 是否包含了核心触发词。分析类任务,description 里必须出现“分析”“营收”“财报”“趋势”这些和任务强相关的关键词。模型是靠语义相似度来做匹配的,你的描述里如果全是“生成”“搜索”“报告”这种宽泛词,模型自然会乱。

查技能之间是否存在 overlap。比如“网页总结”和“PDF总结”的技能,如果 description 都有“把内容总结成报告”,模型就很容易混淆。解决办法是把触发条件区分到“网址”和“文档文件”这两个层面,并在排除描述里明确说“不是处理网址的”“不是处理 PDF 的”。

查 when_to_use 是否写了反例。我早期写技能包的时候,只在 description 里写正面触发条件,效果总是波动。后来加上“无关时必须忽略”的 when_to_use 反例,误调用率直线下降。模型真的非常吃这一套。

最后一步就是改描述,改完用真实的用户问题跑 20 组回归测试,观测技能调用准确率。

4.2 上下文爆炸问题

技能库数量多起来后,很容易出现“把所有技能说明全塞给模型”的偷懒写法。我试过在系统提示词里直接拼接了 15 个 SKILL.md,马上遇到两个问题:token 消耗飞涨,模型注意力分散,技能描述里的关键信息经常被忽略。热组的“按需加载”已经是标准答案了,没有更优雅的方案,就是上面说的两步走,索引路由加延迟加载。另外还要注意 SKILL.md 本身的篇幅控制,单份技能说明最好控制在 500 字以内,不要把冗长的实现细节写进去,模型给技能本身留出余量会更有效。

4.3 技能内部 Python 依赖冲突与环境隔离

当你有十几个技能时,依赖治理问题根本躲不开。技能 A 要 requests 2.28,技能 B 要 requests 2.31,技能 C 还需要一堆第三方库,全装进同一个 Python 环境,迟早会出事。我的方案是把“通用库”和“专用依赖”分开管理。通用库如 requests、BeautifulSoup 直接装进环境里;专用依赖则在该技能目录下放一个 requirements.txt,用到时再现场装,首选是通过管道命令安装到隔离的虚拟环境中。你也可以用 uv、conda 环境管理,不必纠结工具。关键是:不要让某个依赖版本问题把整个 Agent 服务拖垮。

4.4 技能静默失败问题

技能脚本可能在中间环节出错,比如网页请求超时、PDF 加密、API 返回空。最恶心的状态是:脚本吞掉异常,返回一个看似正常的空结果,模型基于这个空结果“一本正经地胡说八道”。我规定所有技能脚本都必须遵循“成功或报错,没有中间态”的原则。正常结果输出 JSON 格式并附带 status: ok;出错直接抛异常,退出码非 0,并输出能从错误信息描述中看明白的错误文本。这样 Agent 才知道“这个技能没搞定”,而不是拿一个残缺输出硬编故事。

5. 工程化落地:从个人玩具到团队基建设施

当你的技能库从两三个技能长到二十多个技能,参与的同事也从一个人变成三四个人的小团队时,仅靠“写个文件夹放那里、谁要谁复制”的方式根本没法维护。你必须把技能库当成一个正经的代码仓库来管理,而且要比普通代码仓库更讲究版本控制和并发协作。

5.1 技能库的目录组织要像“技术雷达”一样分阶段

我现在的技能库分成三层管理:

skills/
├── stable/          # 稳定技能:通过联调测试,可被多项目复用
├── beta/            # 测试技能:功能可用,但边界还没摸透
└── deprecated/      # 废弃技能:保留记录,不加载到生产环境

这种分层虽然简单,但解决了一个关键问题:Agent 加载时只加载 stable 目录,避免 beta 技能的不稳定行为影响线上任务;同时新技能可以安全地在 beta 中试错迭代,不用一次开发就要求完美。从实际结果看,这个机制很好地管理了技能质量,让团队能愉快地并行开发。

5.2 给每个技能加测试:这里说的不是 Pytest 单测

技能的最核心保证,不只是代码能跑通,而是模型能正确理解和调用它。我在每个技能目录下加了 tests 文件夹,里面放了几组用户提问和期望调用结果的“回归用例”。每次改动技能代码或调整 SKILL.md 描述,都会用这些用例跑一遍“模拟用户提问 + 查看 Agent 是否选中该技能并输出预期结果的调用”。回归测试通过,技能才能往 stable 目录迁移,这一套已经跑了两三个月,很稳。如果你们用 GitHub Actions 或 GitLab CI,把这套用例集成进流水线,效果会更好。

skills/generate-chart/
├── SKILL.md
├── scripts/
│   ├── make_chart.py
│   └── requirements.txt
└── tests/
    ├── case1_user_chart_request.txt
    ├── case1_expected_call.json
    ├── case2_aggregate_request.txt
    └── case2_expected_call.json

测试用例文件里存的都是真实用户可能问的问题和期望的工具调用 JSON,跑起来之后很快就能发现“改了个描述,别的技能被带偏了”这类回归问题。

5.3 多 Agent 场景下的技能分配策略

如果你做的不是一个单体 Agent,而是多个 Agent 协作的系统,技能库的组织方式要再加一层“路由维度”。比如同一个技能库里,有“客服 Agent”和“数据分析 Agent”,它们的技能调用范围应该不同。我在技能设计中增加了“适用 Agent”字段,加载时根据当前 Agent 身份过滤技能索引。这个方法虽然增加了一点点规划量,但能避免客服 Agent 突然调用数据库查询技能这类安全事故发生。

5.4 技能库的版本演进与兼容

最后说版本。技能会随着需求变化不断调整,一个技能改了参数说明,可能会导致部分已经在用的 Agent 失效。我的经验是技能接口设计上不要一开始就写死参数数量,更不要频繁改名。为了兼容旧版,SKILL.md 里可以附加一个 version 字段,并在 description 中说明“旧参数已废弃,请使用新参数”。同时在技能目录中保留旧版本说明文件,供故障时检索。如果你的技能库要嵌入到商业产品中,这个版本兼容设计尤其不能省。

6. 现在就可以开始的落地路径

agent-skills 不是某种需要“学习全套理论”才能上手的重型框架,它更像一套工程约定。全部做完,你随时可以落地,并且可以先从团队里最小的一个场景开始。

6.1 第一步,选一个高频且边界清晰的功能

比如“网页抓取总结”“CSV 数据分析”“PDF 转文本”“舆情搜索”。边界清晰的意思是:输入和输出都是明确的,不依赖过多前置判断,这能让首个技能包的理论价值快速被验证。

6.2 第二步,按照前面写的方法,创建目录和 SKILL.md

先不要追求技能数量,一个技能先跑通全流程:写脚本、写 SKILL.md、接入 Agent、用真实问题测。这一步最容易卡在 SKILL.md 描述上,如果你第一次写出来调用不准,别纠结,按前面 4.1 的“三查一改”流程调,两个小时内基本上能调到满意的状态。

6.3 第三步,跑通后再按需新增

一个技能跑顺了,你对整套模式就有了手感。再新增技能时,你会开始主动思考“这个技能和新技能的边界怎么界定”“它们的触发场景会不会重叠”“技能之间怎么配合”。当你有五六个技能跑出来以后,这套架构的价值就很明显了:加一个新能力,基本就变成了“写脚本 + 写描述 + 跑回归测试”三个动作,半小时左右能完成,再也不用从零开始调提示词和流程了。

根据我个人做了一整轮这种基建的经验,最花时间的不是代码实现,也不是模型调参,而是把每个技能的使用边界想清楚。一个技能的多份说明里,最有价值的不是“能做什么”,而是“什么情况下不要用”。把注意力放在这里,你会看到 Agent 整体的稳定性和可控性都上一个台阶。后续如果有精力和场景,我建议你进一步往“技能间协同”去扩展,也就是让多个技能像流水线一样串联配合。那套玩法就是把单技能工具推向多技能工作流的下一步演进了。

更多推荐