Agent Skills技能库设计:从Prompt到可复用技能的全流程落地
2026/9/19 6:11:07 网站建设 项目流程

前阵子我接了一个内部 Agent 项目,最让我烦躁的一件事是:同一个“总结文件内容”的能力,我在三个工作流里各写了一遍。第一版用 Prompt 硬拼,第二版用 Function Calling 传参,第三版换了框架又要重新适配。当时我就想,既然 Agent 的核心价值是执行任务,为什么不把执行任务背后那套“方法”沉淀成可复用的技能?于是就有了 agent-skills 这个项目,目标是把技能的定义、注册、调度、排查做成一条完整流水线。今天我会把设计思路和落地过程完整拆开聊,整个过程不依赖任何特定的 Agent 框架,适合被工具链绑架过的人参考。

1. agent-skills 到底是什么:先搞懂它解决的三个问题

1.1 场景回顾:同一个能力为什么写了三遍

先说个真实场景。我的工作流里经常需要做文件摘要:给一个 Markdown 路径,让 Agent 返回三到五条核心要点。最早我把这个能力写进系统 Prompt,效果还行,但 Prompt 一长,模型输出的格式就开始飘。后来切换到大模型支持的 Function Calling,我把“文件摘要”拆成函数定义,用 JSON Schema 描述参数,模型按约定返回参数,由外部代码执行。这版稳定了不少,但换到另一个 Agent 框架时,对方用的是另一套工具描述格式,我又要重新写一遍适配层。

这种重复让我意识到:问题不在 Prompt 写得不好,也不在某个框架不够强,而是“能力资产”没有被单独抽出来。真正的能力资产应该是一套跟框架无关的技能包:技能描述、参数说明、执行逻辑、示例、依赖关系、版本信息都放在一起。Agent 只是技能的调用方,框架只是技能的传输通道。agent-skills 本质上就是这套技能的仓库和运行时。

1.2 技能与 Prompt、插件、工具的本质区别

很多人会把技能和 Prompt 混为一谈,但它们在生产环境里的定位完全不同。Prompt 是写给模型看的一段自然语言,它没有校验、没有版本、没有依赖管理。你今天写了一个“总结文件”的 Prompt,下周模型升级,输出格式可能就变了。插件往往绑定特定平台,比如 Chrome 插件、VS Code 插件,离开宿主环境就没法用。工具则更多指一个可被调用的函数或者说接口,重点在“能执行”,但缺少“什么场景该用、怎么用、边界在哪”这些信息。

技能更像一份带说明书的可执行手册。它同时包含三个层次:一是给模型看的语义层,包括技能名称、描述、适用场景、示例;二是给人看的工程层,包括参数定义、依赖声明、版本号、作者;三是给机器看的执行层,包括入口脚本、超时时间、运行方式。把这三层揉进一个包,Agent 才能在一个统一结构里做选择、传参和执行。我后来在团队里推 agent-skills,核心目标就是把“知道怎么做”和“实际能执行”这两件事彻底分开。

1.3 技能库的生命周期:定义、注册、调度、迭代

既然把技能当作资产,就要按资产来管理。一个技能从进到 agent-skills 到最后稳定服役,通常经过四个阶段。首先是定义,把“做什么、需要哪些参数、依赖什么环境”写成 skill.yaml,再把实现脚本放进去。其次是注册,扫描技能目录、校验格式、整理索引,让运行时能发现它。然后是调度,当用户或上游系统给 Agent 一个任务,调度器根据查询和上下文选出候选技能,交给模型决定是否调用。最后是迭代,通过日志和命中率观察哪个技能总用不上、哪个技能参数总出错,再针对性修改。

这四个阶段里,定义和调度是大家最关心的,但注册阶段往往被低估。没有注册表的话,技能一多就乱,重名、版本冲突、路径不对都是问题。我在设计 agent-skills 时,把注册表放在脚手架的最底层,后面接任何大模型、任何框架,都是在这个稳定的底座上做适配。

2. 动手搭建技能库:目录结构、YAML 格式与版本管理

2.1 目录结构设计:让每个技能自带依赖和文档

一个技能库如果只有一个扁平目录,初期很爽,等技能超过十个就开始难受。我采用的目录结构是把每个技能做成独立的子目录,技能相关的元信息、代码、依赖声明全部放在一起。这样无论是本地开发、Git 管理还是打包分发,都能保持自洽。

agent-skills/ ├── skills/ │ ├── file_summary/ │ │ ├── skill.yaml │ │ ├── run.py │ │ └── requirements.txt │ ├── git_commit_report/ │ │ ├── skill.yaml │ │ ├── run.py │ │ └── requirements.txt │ └── web_extract/ │ ├── skill.yaml │ ├── run.py │ └── requirements.txt ├── registry.json └── agent_skills/ ├── __init__.py ├── schema.py └── runtime.py

每个技能目录里,skill.yaml 是给 Agent 和注册器读的说明书,run.py 是真正的执行器,requirements.txt 声明这个技能单独需要的外部库。agent_skills 目录放的是核心运行时:加载、校验、执行、检索。registry.json 则是对整个技能库的索引,启动时加载一次,后续调度都基于这个索引。这样做的好处是,任何一个技能都能独立复制到另一台机器,不会把全局环境搞得乌烟瘴气。

2.2 skill.yaml 的字段说明:描述、参数和示例是命中的关键

skill.yaml 是整个技能库的门面。模型在读技能列表时,最先看的是 name 和 description,然后才会去看参数细节。所以这两项必须写得像“给同事的需求说明书”,而不是技术文档。我习惯在 description 里同时写清“做什么”和“不做什么”,避免 Agent 在边界模糊的任务里乱调用。

下面是一个完整的 file_summary 技能定义:

name: file_summary version: 1.2.0 description: Summarize a local text or markdown file and return key points in bullets. Use this when the user wants to know what a file is about. Do not use it for code execution or search tasks. author: ops license: MIT arguments: - name: file_path type: string required: true description: Absolute or relative path to the target file. - name: max_points type: integer required: false default: 5 description: Maximum number of bullet points to return. examples: - input: "Summarize README.md with max_points=3" expected_output: "Bullet points 1-3 from README" executor: type: python entry: run.py:main timeout: 30 requirements: requirements.txt

有一些字段容易被忽略,但恰恰是它们决定了技能能不能被稳定调用。arguments 里的 required 和 default 必须显式声明,因为模型不是人,不会自动脑补参数。如果某个参数不是必须的,最好给 default,这样即使模型没传,执行器也不会直接报错。examples 更重要,它相当于给模型做 few-shot,让模型理解什么样的输入应该触发这个技能。我做过对比实验,带两个示例的技能,命中率比不带示例时高出约三成,这一条值得记进你的技能规范。

2.3 执行器与依赖隔离:用子进程还是线程?

技能执行方式的选择会直接影响稳定性和安全性。我最早图省事,把技能代码直接 import 进主进程,require 的第三方库也装在同一个虚拟环境里。结果两个技能一个要 requests 老版本,一个要新版本,直接把环境搞崩了。后来我改成每个技能独立用 requirements.txt 声明依赖,执行时优先在子进程里跑,通过 stdin/stdout 传 JSON 数据;只有极少数需要共享内存状态的场景才用线程和 importlib。这个改动让技能之间的依赖打架减少了八成。

子进程执行的一个好处是超时控制非常干净。主进程启动子进程后,如果超过 skill.yaml 里 timeout 指定的时间,直接 kill,不会污染整个 Agent 进程。坏处是进程启动有开销,尤其是导入一些重型库时会慢。我的处理方式是:给高频技能加一个进程池,复用 Worker,避免每次调用都重新启动 Python 解释器。如果技能本身执行很快,直接子进程跑也没问题,多少毫秒的延迟对 Agent 来说完全可以接受。

2.4 注册表与校验和:防止技能被篡改

技能目录越来越多以后,不能每次调用都去现扫文件系统,那样慢且不可控。我在 agent-skills 里维护了一个 registry.json,启动时加载一次,把技能名映射到目录、版本和校验和。

{ "skills": [ { "name": "file_summary", "version": "1.2.0", "path": "skills/file_summary", "checksum": "sha256:2f7e...", "enabled": true }, { "name": "web_extract", "version": "0.9.0", "path": "skills/web_extract", "checksum": "sha256:8c5a...", "enabled": true } ] }

校验和有必要专门说一下。共享技能库时,最大的风险不是版本冲突,而是别人改了你本地技能脚本,让你在不知情的情况下执行恶意代码。我每次加载技能前会重新计算 run.py 和 skill.yaml 的 SHA-256,跟 registry.json 里记录的对不上就直接拒绝加载,并日志告警。这样即使某台机器上的技能目录被人动过手脚,也能在执行前拦下来。

3. 实现技能注册与调度核心:从加载到自动选技能

3.1 最小可用的技能加载与校验代码

注册核心其实不复杂,核心就是三件事:读 YAML、校验必填字段、把技能对象放进索引。我给出一个最简单可用的实现,去掉日志和缓存,只留骨架。

from pathlib import Path from typing import Dict, List, Any import yaml class Skill: def __init__(self, raw: dict, base_dir: Path): self.name = raw["name"] self.version = raw.get("version", "0.0.0") self.description = raw.get("description", "") self.arguments = raw.get("arguments", []) self.examples = raw.get("examples", []) self.executor = raw.get("executor", {}) self.base_dir = base_dir def argument_schema(self) -> dict: properties = {} required = [] for arg in self.arguments: properties[arg["name"]] = { "type": arg.get("type", "string"), "description": arg.get("description", ""), } if arg.get("required"): required.append(arg["name"]) return {"type": "object", "properties": properties, "required": required} def load_skills(skills_dir: Path) -> Dict[str, Skill]: skills = {} for skill_dir in skills_dir.iterdir(): yaml_path = skill_dir / "skill.yaml" if not yaml_path.exists(): continue with open(yaml_path, encoding="utf-8") as f: raw = yaml.safe_load(f) if "name" not in raw: raise ValueError(f"missing name in {yaml_path}") skills[raw["name"]] = Skill(raw, skill_dir) return skills

这段代码的意图很清楚:用一个目录扫描,把每个技能变成 Python 对象。argument_schema 方法会从技能参数定义里生成 JSON Schema,这个 Schema 后面可以直接塞给大模型的 Function Calling。很多框架之间的差异,就是因为这个 Schema 的拼法不同,所以我把转换逻辑集中在 Skill 类里,而不是散落在各业务代码里。

3.2 动态导入策略:安全地执行不可信技能

执行技能代码时,动态导入很容易写,但要写得安全不容易。最粗暴的方式是importlib.import_module,然后从模块里取 main 函数直接调。但这个方式有风险:技能包可能带任意代码,可能读文件、发网络请求、执行 shell。在内部可信环境里问题不大,一旦技能来自团队外部或者公共仓库,就必须加限制。

我在 agent-skills 里的做法是:默认走子进程执行,传入 JSON 参数,标准输出捕获 JSON 结果。子进程跑之前,设置资源限制,比如resource.setrlimit限制 CPU 时间和内存,并给一个绝对不可调整的超时时间。同时,进程的工作目录固定为技能目录,不让它无感读取整个服务器。若某个技能确实需要访问网络或文件,要在 skill.yaml 里显式声明 permissions,比如network: truefilesystem: /data/reports,注册器会根据声明决定是否批准。

这样做的代价是写法上要绕一点,但对“技能库被推广到多个团队”这个目标来说非常值得。毕竟 Agent 技能一旦执行,就相当于在服务器上运行了一段可能不可信的代码,你不能指望模型来保护系统安全。

3.3 技能筛选与排序:不把全部技能塞进上下文

技能少于十个时,可以把全部技能描述直接放给模型;但技能库超过二十个后,你会发现上下文被技能说明挤爆,模型的注意力也被稀释。这个问题我在 agent-skills 里用了两级策略解决。

第一级是粗筛,用关键词标签过滤。每个技能在 YAML 里可以带 tags,比如文件处理、网络请求、数据分析、邮件等。任务进来时,先根据用户问题里的关键词或实体词,把完全不相关的技能从候选列表里剔除。第二级是精排,用 embedding 做语义相似度。我把每个技能的 description、arguments 和 examples 拼成一段文本,预先算好向量;任务查询来了以后,也算一条查询向量,然后取 TOP-K 个最相似的技能。

def rank_skills(query: str, skills: list[Skill], top_k: int = 3): # 真实项目里这里会调用 embedding 模型 vectors = {skill.name: skill_vector(skill) for skill in skills} query_vec = embed(query) scored = sorted( skills, key=lambda s: cosine_similarity(vectors[s.name], query_vec), reverse=True, ) return scored[:top_k]

注意,向量拼接时不要只拼 description,一定要把 examples 拼进去。原因是 examples 往往包含了实际触发场景的措辞,比如用户可能说“把这篇 README 浓缩一下”而不是“使用 file_summary 技能”,例子里的自然语言能显著提升召回率。最终真正交给模型去决策的技能只有三到五个,既省 token,又降低误调用概率。

3.4 加一层“技能指挥官”:让多个 Agent 共用一套调度逻辑

当项目里不止一个大模型 Agent 时,如果每个 Agent 各自实现一遍技能选择,维护成本会直线上升。所以我在 agent-skills 之上加了一层“技能指挥官”,它本身不直接回答用户问题,只负责两件事:先根据用户任务选择候选技能,再把这些候选技能的结构化定义返回给上层 Agent。上层 Agent 可以决定直接调用其中某个技能,也可以组合多个技能来完成复杂任务。

这层抽象的收益在重构时特别明显。某个 Agent 从 GPT 换成其他模型,只需要保持 skill 列表输出格式不变,模型侧怎么解析、怎么调用,由上层 Agent 自己负责。技能指挥官不需要理解每家模型的 prompt 模板。它只暴露一个接口:输入任务文本,输出候选技能和参数 Schema。这样一来,技能库成了团队里所有 Agent 共用的“公共设施”,而不是某条业务链路上的私有代码。

4. 实战案例:三个技能从 0 到 1 接入 Agent

4.1 文件摘要技能:参数校验与结果回传

先挑最简单的 file_summary 完整走一遍流程。我在技能目录 run.py 里写了一个最小实现:读文件、取前几行作为要点。真实项目里这里通常会接一个大模型做摘要,但为了做单元测试,我用纯文本逻辑先跑通链路。

import argparse import json from pathlib import Path def extract(file_path: str, max_points: int = 5) -> dict: content = Path(file_path).read_text(encoding="utf-8") lines = [line.strip() for line in content.splitlines() if line.strip()] points = lines[:max_points] if len(lines) > max_points else lines return {"file": file_path, "points": points} def main(file_path: str, max_points: int = 5) -> str: result = extract(file_path, max_points) return json.dumps(result, ensure_ascii=False) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--file_path", required=True) parser.add_argument("--max_points", type=int, default=5) args = parser.parse_args() print(main(args.file_path, args.max_points))

调用时,通过命令行传参而不是读桌面路径,主要为了跟 skill.yaml 里的参数定义一一对应。执行器最终只输出 JSON 字符串,主进程再用json.loads解析,不需要在子进程里做复杂通信。这一步看似简单,却能避免很多编码问题。我的经验是:所有技能的执行结果必须是一个标准 JSON,结构为{"data": ..., "error": ...},这样上层 Agent 才能统一处理成功和失败。

4.2 提交纪要技能:给 Agent 增加上下文输入

第二个技能是“生成昨日 Git 提交纪要”。这个技能和文件摘要不一样,它需要依赖外部命令git log,并且需要接收仓库路径作为上下文。我在 skill.yaml 里为它增加了一个 context_fields 字段,表示这个技能在执行前需要 Agent 自己先准备哪些信息。

name: git_commit_report version: 0.1.0 description: Generate a daily commit report from a git repository. Use this when the user asks for commit history, daily report, or changelog in a local repo. arguments: - name: repo_path type: string required: true description: Path to the git repository root. - name: days type: integer required: false default: 1 description: Number of days to look back. executor: type: python entry: run.py:main timeout: 20

run.py 里用 subprocess 调 git 命令,再对提交记录按作者和时间分组。这个技能的难点不是代码,而是让 Agent 知道“去哪找 repo_path”。如果用户只说“给我昨天的提交记录”,Agent 可能不知道仓库在哪个目录。所以我在 description 里明确要求:如果用户没有给出仓库路径,Agent 应该先判断当前工作目录或者明确向用户询问,不能瞎猜。这件事听起来很基础,但实际跑起来,模型经常自己猜一个不存在路径,导致技能执行失败。提前在描述里写清楚,能显著减少报错。

4.3 网页正文提取技能:处理依赖和网络超时

第三个技能是网页正文提取。这个技能会发起真实的网络请求,所以比前两个更依赖环境。我在 requirements.txt 里声明了 requests 和 beautifulsoup4,然后在 run.py 里做三件事:下载页面、解析 HTML、提取正文核心段落。

import requests from bs4 import BeautifulSoup def extract_article(url: str, timeout: int = 10) -> dict: resp = requests.get(url, timeout=timeout, headers={"User-Agent": "agent-skills/0.1"}) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") for tag in soup(["script", "style", "nav", "footer"]): tag.decompose() paragraphs = [p.get_text(strip=True) for p in soup.find_all("p") if len(p.get_text(strip=True)) > 20] return {"url": url, "paragraphs": paragraphs[:20]}

这里有两个容易踩的坑。第一个是依赖冲突,requests 版本经常跟其他技能要求不一致,所以这个技能必须独立声明,不能复用全局环境。第二个是网络超时,技能执行时如果不设 timeout,requests 可能挂几分钟,最后被 agent-skills 的运行时强杀,输出为空,反而不好排查。我的做法是:在 skill.yaml 的 timeout 设为 30 秒,而在 requests.get 内部再把单次请求超时设成 10 秒,网络层超时比进程级超时要早触发,错误信息也更友好。

4.4 接入 Function Calling:同一个技能库复用给不同框架

技能库和框架之间的适配,核心就是把 Skill 对象转成框架要求的工具描述。以 OpenAI SDK 为例,它的 tool 结构是一个 function 对象,包含 name、description、parameters。我只要调用 skill.argument_schema(),再把 skill.description 和 examples 拼进去,就能直接生成可用的 tools 列表。

def to_openai_tools(skills: list[Skill]) -> list[dict]: tools = [] for skill in skills: tools.append({ "type": "function", "function": { "name": skill.name, "description": skill.description + "\nExamples:\n" + format_examples(skill.examples), "parameters": skill.argument_schema(), }, }) return tools

模型返回 tool_calls 后,我的调度器就接管执行:根据 tool_call 里的函数名找到技能,拿到参数 JSON,调用技能执行器,再把结果作为新消息返回给模型。整个过程和具体模型无关,所以同样一套技能库,你还能把它接到自定义 Agent 上。跨框架复用的关键点不是写很多适配器,而是让技能本身不依赖任何框架的专用名词。skill.yaml 里不出现 openai、anthropic 等字样,所有框架适配都放在运行时外面。这个边界我划得很清楚。

5. 踩坑记录:agent-skills 常见的五个坑与排查思路

5.1 Agent 宁可胡说也不用技能,怎么办

最常见的现象是:你明明给 Agent 注册了技能,但它就是不用,自己凭空编答案。这种情况我碰到过很多次,根因多半出在技能描述上。描述里只写了“Summarize a file”,但没说清“什么时候用、什么时候不用”,模型无法判断是否该触发。后来我在每个技能 description 里都加上一句“Use this when... Do not use it for...”,命中率立刻提升。另一个原因是 examples 太少,尤其是对于需要多参数组合的技能,最好给两个完整输入输出示例,模型才有样本可模仿。

还有一点容易被忽略:模型能力差异。小模型在 Function Calling 上的稳定性明显弱于大模型,如果你必须用小模型,就要接受它可能不会严格按照工具列表执行。这时候可以考虑用一个“能力更强的模型做技能路由,能力更弱的模型做具体生成”的两级架构,至少能保住场景识别这一环的准确率。

5.2 参数是字符串还是对象?解析要包容

模型返回的参数经常不按 schema 来,明明定义的是 integer,它给你返回字符串 "5";明明是对象,它输出成 JSON 字符串。如果你用严格类型校验,技能会频繁报错。我的解决方案是:在运行时写一个宽松的 normalize 函数,把参数尽量转换成可用类型。

def normalize_arg(value, expected_type: str, default=None): if value is None: return default if expected_type == "integer": return int(str(value).strip().strip('"\'"')) if expected_type == "number": return float(value) if expected_type == "boolean": return str(value).strip().lower() in ("true", "1", "yes") if expected_type == "array": if isinstance(value, str): return [v.strip() for v in value.strip("[]").split(",") if v.strip()] return value return value

这种宽松处理不是要纵容模型,而是因为生产环境里模型输出格式的抖动是常态。技能执行器的职责是尽量让调用成功,严格校验可以放在技能测试阶段,而不是运行时。不过也要注意,normalize 不能过度,比如路径类参数绝对不能帮你乱拼字符串,否则很容易踩到文件系统隐患。

5.3 技能数量一多上下文就爆炸

技能库从五个涨到二十个的时候,如果还把全部技能定义都塞给模型,一次任务可能吃掉几万 token。我试过最夸张的时候,一个 Agent 的 system prompt 里技能描述比业务指令还长,导致模型频频选错技能。解决方法是前面提到的向量检索 + TOP-K 候选。但还有一个额外经验:一定要给技能索引建“冷热分层”。

我在 registry.json 里给每个技能加了一个 enabled 字段,平时只把高频技能保持 enable,长尾技能在按需检索时临时打开并注入。每周末我会跑一次调用统计,把七天都没被触发过的技能标为“冷技能”,不再默认参与排序。这样做之后,随随便便几十个技能也不会影响响应速度,而且真正被调用的技能命中率反而更高,因为候选集更干净。

5.4 日志分级:三招定位技能执行失败

技能执行失败的定位,最忌讳的是只看到“执行失败”四个字。我把 agent-skills 的日志以技能执行为单位做了三段式记录:第一阶段是技能选择日志,输出命中了哪些候选技能、最终选了哪个、模型传入的原始参数是什么;第二阶段是进程启动日志,记录执行器入口、超时时间、工作目录;第三阶段是结果摘要日志,输出返回 JSON 的前几百个字符和整体耗时。这样任何一次异常,我都能从日志里立刻看出是模型选错技能、参数传错,还是脚本本身抛异常。

如果日志也不够,就做“最小复现”。直接把某个技能 run.py 当作命令行工具调用,绕过模型,用一个固定参数测试。比如git_commit_report报错,我先手动跑python run.py --repo_path /tmp/repo --days 1,如果脚本本身有问题,问题大概率在执行器而不是调度器。这个思路放之四海而皆准,不要让大模型成为调试依赖项。

5.5 升级技能的兼容性坑

技能也有版本迭代。把 file_summary 从 v1.1 升到 v1.2,新增了一个 max_points 参数,如果旧场景传了 max_count,新技能直接报 unknown argument。所以我在加载参数时加了一个别名机制:老字段名和新字段名做映射,匹配不到就忽略。这样升级对上层 Agent 是透明的。

还有依赖升级。某个技能锁了 requests 版本,新技能想用另一个版本,如果全局环境安装,必然冲突。我的习惯是每个技能目录放一个 requirements-lock.txt,精确到小版本,CI 里构建独立虚拟环境或镜像。虽然这样初始成本高一点,但遇到跨团队分享或部署到新机器时,你会庆幸当初没把所有依赖堆在一个 requirements.txt 里。

6. 把它变成团队能力:共享、权限与后续演进

6.1 内部共享时的安全评审与权限控制

技能库在团队内推广后,安全问题会比单机使用时更突出。你在自己电脑上可以信任所有代码,但别人写的技能不一定靠谱。我做了两层控制:第一层是“代码评审”,每个技能合入主分支前,必须过一遍评审清单,重点看有没有网络请求、子进程调用、文件路径拼接等危险动作;第二层是“声明式权限”,skill.yaml 里新增 permissions 字段,运行时会拦截未声明的操作。这两层结合,既保留了技能共享的灵活性,又避免引入不必要的风险。

权限控制的粒度不需要太细,按资源类型分就好:文件、网络、进程、环境变量。比如 web_extract 技能声明permissions: network: true,那它的子进程就可以发网络请求,而一个纯文本处理技能没有声明网络权限,即使代码里被塞了 requests 调用,运行时也能直接拒绝。这个能力天然适合团队内部私有技能市场,比每个人互相信任要稳得多。

6.2 让 AI 自动生成技能草稿的可行性

技能库积累多了,最耗时的不是写 run.py,而是把元信息写好。尤其 description 和 examples 需要大量斟酌。后来我试过用一个生成 Agent 自动产出技能草稿:给它一个需求描述,让它输出 skill.yaml 和 run.py。我用这个方式给团队做过一批内部工具技能,效率确实高,但准确性只能到七成左右。最常出问题的点是参数类型,模型经常把路径参数定义成 object,把可选项写进 required,所以生成后的技能必须过一轮自动化测试。

我后来把流程改成:AI 生成草稿 -> 人工修改 YAML -> 自动测试用例跑通 -> 合并进技能库。这个流程比完全手写省很多时间,也比完全自动生成更安全。如果你也想试,建议先把每个技能自动生成后只跑“参数校验”和“基础路径成功”两组测试,能拦住大部分问题。

6.3 多 Agent 编排:技能即是接口

当 Agent 从单体变成多个角色时,技能库可以顺理成章成为它们之间的接口。一个数据采集 Agent 暴露web_extract技能,另一个报告 Agent 调用这个技能拿网页正文,而不是自己再实现一遍。通过 agent-skills 的注册表,Agent 之间只要互相知道对方技能的名字和参数 Schema,就能完成协作,不需要引入复杂的消息通信协议。这种模式简化了系统设计,也避免了 Agent 之间直接用自然语言传递大段数据造成的混乱。

我在这块的实际经验是:不要让 Agent 直接调用另一个 Agent 的完整对话接口,而应该只调用它暴露的最小技能单元。否则两个 Agent 容易在相互理解上产生偏差,还会把上下文越聊越长。技能即接口,能让多 Agent 的交互像函数调用一样清晰可测试。

6.4 最后几点个人建议

如果现在有人让我总结 agent-skills 落地最容易忽略的三件事,我会说:第一,不要一开始就追求完美框架,先用一个目录、一个 YAML、一个脚本把第一个技能跑通,再逐步加注册和调度;第二,技能粒度宁可小也不要大,一个技能只做一件事,组合交给上层 Agent 去做;第三,日志和测试要从第一天就建立,等技能数量超过十个再补,成本会翻几倍。

另外,技能描述里的“Do not use it for...”这种否定边界,真的值得多写几句。模型翻车的概率会下降一大截。你可能觉得这是在给模型上课,但实际维护过的都知道,这比调十次 prompt 都顶用。agent-skills 对我来说不是一个固定成品,它更像一套不断生长的工程习惯:每遇到一个重复劳动,就把它沉淀成技能;每次踩坑,就把边界条件写进描述里。这个循环跑起来以后,你的 Agent 项目会越用越顺,而不是越加代码越乱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询