AI Agent技能层实战:从Prompt模板到可复用技能库的工程化之路
2026/9/17 10:30:27 网站建设 项目流程

最近后台好几个朋友都在问同一个词:agent-skills。有人以为它是一个开源项目,有人把它理解成一类技能库的统称,也有人直接问“我的 Agent 是不是缺了这层东西”。其实名字怎么叫不重要,重要的是这个话题背后扎扎实实踩中了一个痛点:AI Agent 能跑通 demo,但一到真实业务就“只会聊天,不会干活”,就算会干活,也沉淀不下来。

我自己的体会是,把 Agent 能力做厚的那个“技能层”,比换更大参数的模型、堆更多的工具都更关键。一个再强的模型,如果只能用一堆零散的 function call,没有围绕真实任务把规则、工具、流程、异常处理打包成可复用的技能,那它就像一个刚入行的实习生,聪明但没有章法。agent-skills 想解决的问题,就是把这个“章法”沉淀成目录、规范和执行体,让 Agent 真正具备可维护、可组合、可复制的工作能力。

这篇内容不会讲太多空泛概念,主要分享我在技能库搭建、技能注册、技能组合和线上故障排查里的真实经验。适合正在做 Agent 应用、想把手头 prompt 工程往工程化方向推一步的开发者;如果你刚接触这个概念,也能从第二、三节的内容里把“技能到底是什么”彻底搞明白。

1. 先搞清楚:Agent 缺的不是模型,是“技能层”

1.1 从 prompt 模板到技能库的演变

早期做 Agent,大家普遍的做法是写一长串 prompt,把任务步骤一步步写在系统提示词里。比如“你是客服助手,第一步先判断用户意图,第二步查询订单状态,第三步根据状态套用话术”。这种方式在 demo 阶段没问题,可一旦任务变多,prompt 会膨胀到几千 token,模型每轮都要重新理解全部规则,改一句话就要重新调参,线上出了错也只能靠肉眼翻日志。说白了,prompt 模板把“做事步骤”写在文字里,但它既不能被程序校验,也不能被复用。

后来有了 function calling 和 MCP,Agent 可以调用外部工具了。这解决了“动手”的问题:查天气、发邮件、算汇率、读数据库,都能做成一个 API 暴露给模型。但工具是原子化的,一个工具只做一件事,不携带任何任务上下文。以“生成周报”为例,你可能需要先查项目进度、再查工时、再调历史周报模板、最后汇总刻薄语言。这几个工具可以一个个被调用,但“先干什么、后干什么、什么情况走什么分支”这件事,依然没有被固化下来。

agent-skills 这类设计,本质上是在 prompt 和工具之间加了一层“技能”:一个技能包含明确目标、适用场景、执行步骤、依赖的工具、参数协议、异常分支,以及沉淀下来的经验教训。模型拿到技能描述,不是学一段干巴巴的说明,而是拿到一个“带使用说明书和内部构造的模块”。这才是从“会调函数”到“会做事”的分水岭。

下面这张对比能帮你看清区别:

能力形态表示方式复用性可维护性典型问题
Prompt 模板自然语言步骤低,依赖复制粘贴差,改动牵一发动全身规则多了模型容易忽略
Function/Tool接口定义+执行函数中,可被多个 Agent 调中,但缺少任务上下文工具碎片化,没有流程
Skill元数据+流程+工具+策略高,可组合可版本化高,独立测试独立发布需要额外设计规范和运行时

1.2 agent-skills 解决的核心问题

如果你接手过一段时间的 Agent 项目,一定会遇到三个问题:知识分散、流程失传、质量不稳定。知识分散指的是每个人做 Agent 都在自己的 prompt 里塞规则,同样的“退款校验逻辑”可能散落在五个文件里;流程失传是指核心员工一走,他脑海里那些“碰到 XX 情况要 XX 处理”的经验也跟着没了;质量不稳定则表现为同一个任务上午成功下午失败,只因为模型换了个采样参数。

把这些沉淀成一个技能库之后,逻辑就变了。每一项经验都是一个独立技能,有版本、有测试、有调用入口。Agent 不再靠“临场发挥”完成任务,而是从技能库里检索最合适的技能去执行。技能本身是代码和文档的结合体,可以被 review、被评审、被回滚。

我见过一个很典型的客服场景:退款处理原来是一段 800 字的 prompt,逻辑里混着规则、话术、工具调用格式。后来把它拆成“退款资格校验”“退款金额计算”“退款话术生成”三个技能,每个技能都有独立的输入输出结构,发现问题的速度从小时级降到分钟级。这就是技能层带来的工程收益。

2. 技能定义长什么样:从元数据到执行体的设计

2.1 一份技能描述该包含什么

我习惯把每个技能放在独立目录里,整体结构大概是这样:

skills/ web-to-markdown/ SKILL.md run.py requirements.txt tests/ test_basic.json

其中SKILL.md是技能的“门面”,模型能不能正确调用它,全靠这份描述写得好不好。常见字段包括:

--- name: web-to-markdown description: 当用户需要从指定 URL 抓取网页正文并转换为 Markdown 时使用。 version: 1.3.0 entrypoint: run.py input_schema: type: object required: [url] properties: url: type: string description: 需要抓取的公开网页地址 ---

这里最容易被忽略的是description。它不是写给人看的文档标题,而是写给模型看的“触发条件说明”。我见过太多的项目把描述写成“网页转 Markdown”,结果模型遇到任何跟网页沾边的任务都调它,遇到真正需要转 Markdown 的任务反而又因为描述太宽泛而犹豫。

一份合格的技术描述应该包含三件事:什么场景下用、输入是什么、有什么限制。比如这样:当用户希望把某个公开网页的正文内容提取出来并转成 Markdown 格式时使用。输入为 URL,输出为结构化 JSON,包含标题和正文。仅支持静态页面,不处理需要登录的页面。这样模型就能非常精准地匹配意图。

2.2 技能依赖与运行时隔离

技能是代码,就会引入依赖。依赖一旦不隔离,A 技能升级了 requests 库,B 技能可能直接崩。所以我在技能目录里强制要求写明依赖文件和锁版本。Python 技能就用requirements.txtrequirements.lock,Node 技能就配package-lock.json。如果有条件,更推荐每个技能跑在独立容器里,或者至少用独立虚拟环境。

比依赖更隐蔽的是权限边界。技能一旦能被 Agent 调用,就相当于给模型开了一个执行代码的口子。比如“网页转 Markdown”技能,它其实需要网络访问权限,不需要文件删除权限,更不需要读取环境变量的权限。我会在技能元数据里声明 permissions:

permissions: network: allow: ["*"] filesystem: read_workspace: true write_workspace: true shell: allow: []

这个设计看起来有点重,但线上事故往往就出在“顺手放权”上。我早期写过一个小技能,内部用os.system执行命令,参数来自模型生成的 JSON,结果用户构造了一个恶意 URL,把环境变量打印出来了。从那以后,凡是能走专用库做的事,绝对不让技能直接拼 shell 命令;必须走 shell 的,就白名单命令和参数格式。

3. 手写一个技能并接入 Agent 的完整过程

3.1 技能代码骨架

直接用“网页转 Markdown”这个例子,带你看一个技能执行体到底长什么样。这个技能在我的项目里跑得最多,代码很短,但足够说明接口约束。

import json import sys from urllib.parse import urlparse from trafilatura import fetch_url, extract INPUT_SCHEMA = { "type": "object", "required": ["url"], "properties": { "url": {"type": "string"} } } def run(params: dict) -> dict: url = params["url"] parsed = urlparse(url) if parsed.scheme not in ("http", "https"): return {"ok": False, "error": "unsupported_protocol"} html = fetch_url(url) if not html: return {"ok": False, "error": "fetch_failed"} markdown = extract(html, output_format="markdown") if not markdown: return {"ok": False, "error": "extract_empty"} return {"ok": True, "content": markdown} if __name__ == "__main__": params = json.loads(sys.stdin.read()) result = run(params) print(json.dumps(result, ensure_ascii=False))

注意我看重的是输入输出都走 JSON。这个约定不是拍脑袋定的,而是为了兼容不同语言的技能。无论技能内部是 Python、Node 还是 Go,对外只需要保证“吃进 JSON,吐出 JSON”,主框架就不用关心每个技能的实现了。很多 Agent 项目死在“技能接口不统一”上,最后只能靠胶水代码拼凑。

3.2 注册与动态发现

技能写好了,接下来要让它被 Agent 发现。我的做法是在框架启动时扫描技能目录,读取每个技能目录里的元数据文件,然后把它转换成模型能理解的 tool schema。这样新增技能时完全不用改主程序,扔进目录、重启服务(或者触发动态加载)就完事。

async def register_skills(repo_path: str) -> list[dict]: tools = [] for skill_dir in Path(repo_path).iterdir(): if not skill_dir.is_dir(): continue meta = parse_skill_meta(skill_dir / "SKILL.md") tools.append({ "type": "function", "function": { "name": meta["name"], "description": meta["description"], "parameters": meta["input_schema"], } }) return tools

这里有几个容易踩的坑。第一,技能名必须全局唯一,否则后面的技能把前面的覆盖了,排查起来像鬼打墙。第二,description 一定不要拼接太多动态内容,否则 token 消耗会非常夸张。第三,如果技能数量超过几十个,不能全部塞给模型,要做检索召回,这块我在第五节细讲。

3.3 一次调用链路演示

我们模拟一个真实用户请求:“帮我把这篇网页内容转成 Markdown 存到本地”。

完整的处理链路是这样的:用户请求进入主 Agent,主模型看到“转成 Markdown”这个意图,会去匹配已注册的技能。匹配到web-to-markdown后,模型按输入结构生成一个 JSON 参数,比如{"url": "https://example.com/article"}。框架拿到这个参数直接调run(),脚本执行完把结果 JSON 回传。主模型看到返回结果后,再负责跟用户对话:“已经转换完成,共生成 2356 字,需要我保存到本地吗?”

这个链路里,技能本身不负责“理解用户”,也不负责“对话”,它只负责把一件事做扎实。理解用户的职责在主 Agent,执行的职责在技能,这种分工一旦清晰,系统的每一层都变得可优化。模型太笨就换模型,技能出错就修技能,互不干扰。

4. 技能不只是单点能力:组合与编排

4.1 两种编排方式:Agent 自主编排 vs 工作流硬编排

单个技能是积木,真正有价值的点在于组合。比如“生成项目周报”这样的任务,至少需要三个技能的协作:查询项目进度、查询团队的工时记录、根据模板生成报告。问题是这三个技能怎么串起来?

我见过两种极端思路。一种是全交给 Agent 自己编排:模型自己决定先调哪个技能、再调哪个技能。这种方案灵活,但缺点也很明显,模型可能会漏掉关键步骤,或者在上一步失败后硬着头皮继续走,产出一份错误百出的结果。另一种是用工作流引擎硬编码流程:第一步必须调 A,第二步必须调 B,失败就终止。这种方案稳,但每新增一个场景就要写一段代码,Agent 的“智能性”完全没发挥出来。

我的建议是折中:对于关键路径,用 workflow 硬编排;对于非关键路径,允许 Agent 自主发挥。比如周报生成,先查项目进度和工时这两个动作必须严格按顺序执行,不能乱,也不能跳;但最后生成报告的措辞、详略、风格,可以让模型自己决定。agent-skills 这种“技能目录”模式天然适合这种折中——每个技能保持独立,外部用轻量级编排器把技能串起来。

4.2 技能之间的数据契约

多个技能协作时,最让人头疼的是输出格式对不上。A 技能返回的是列表,B 技能期望的是字典;A 技能里的字段叫content,B 技能里叫text。这种字段错位在写 demo 时还能忍,一旦进入生产环境,就是事故温床。

所以我给每个技能都定了输出 schema,并且要求下游技能直接声明它接受哪些上游输出。以“生成项目周报”为例:

  • query_project_progress输出:[{ "project": "agent-skills", "status": "dev", "updated_at": "2025-03-20" }]
  • query_worklog输出:[{ "user": "张三", "hours": 6.5, "date": "2025-03-19" }]
  • generate_weekly_report输入:{ "progress": [...], "worklog": [...] }

这个设计是受到函数式编程启发:每个技能都像纯函数,输入输出可预期,组合时才不会出岔子。实际落地时,我会在技能的测试目录里放一批“契约测试用例”,专门校验给定输入时输出结构是否符合预期。这样任何一个技能改了输出,CI 第一时间就会报错,而不是等到线上跑挂了才发现。

4.3 用“元技能”控制编排流程

除了普通技能,我还会设计一类“元技能”——它不是直接处理业务,而是用来调度其他技能。比如router技能负责判断用户请求该走哪个业务技能;guardrail技能负责在技能输出返回给用户之前做一次合规检查;planner技能负责把一个复杂目标拆成多个技能调用的序列。

元技能的好处是把控制逻辑也变成可维护的资产。过去你在主 Agent 的 prompt 里写“如果用户想投诉,先查订单,再查客服记录”,现在你可以做成一个customer_complaint元技能,它内部声明了子技能清单和调用顺序。主模型只需要决定调用哪个元技能,而不需要自己临场发挥一套流程,决策负担小了很多,效果也更可控。

5. 保姆级踩坑记录:技能化路上最常见的五个问题

5.1 技能描述写得太抽象,模型根本不会调用

很多团队把技能当普通函数写,描述一句话带过。比如“读取 Excel 文件”,这看起来没问题,但模型遇到“帮我看看这个表格里哪几个城市的销售额超过 100 万”时,会把任务拆成“先读取 Excel,再计算筛选”,然后它去技能库里找,发现只有“读取 Excel 文件”,没有“Excel 数据筛选”,于是只能返回一个“读取结果”让用户自己看。

我后来把“读取 Excel”升级成“读取 Excel 文件并返回行记录列表,支持按列名筛选、按数值条件过滤”,模型就能直接用它完成筛选任务了。写描述时一个有效的方法:写完自己先问一遍“如果我是模型,看到这个描述,知道它适合处理我这个问题吗?”不确定就重写,直到描述里能看到触发条件和边界。

5.2 环境依赖不一致,换个机器就挂

技能本质是程序,程序的第一死因就是环境依赖。我踩过一次很蠢的坑:某个技能在本地跑得好好的,部署到服务器后所有请求都失败,查了半天才发现服务器环境缺少 CA 证书,requests库发起 HTTPS 请求时直接报 SSL 错误。这种问题在单体应用里很容易发现,但到了技能库这种“一个技能一个环境”的架构里,问题会被放大。

我的对策很简单:每个技能必须有独立的依赖清单和固定版本;核心技能要在干净的 CI 环境跑冒烟测试,不能只在开发者自己的机器上测。对于跟外部网络打交道的技能,我会在测试用例里加一条“检查系统信任证书是否存在”。这些看似琐碎的检查,恰好是线上可靠性的基石。

5.3 上下文膨胀,技能说明太多

当技能库超过几十个,把所有技能的描述一次性塞给模型会带来两个问题:一是 token 成本飙升,二是模型反而“挑花了眼”。我有一次往主 Agent 里塞了 40 个技能描述,结果模型开始频繁调用错误技能,调用成功率不升反降。后来我把技能库改成两级索引:第一级是技能分类列表,第二级是每个分类下的技能明细。主模型先决定“该走技术类还是业务类”,再在对应分类里检索。

如果不想自己写分类器,也可以用向量检索:把每个技能的描述和输入输出结构 embedding 化,用户请求进来后先做相似度检索,取 top 5 技能注入给模型。实测下来,检索召回的方式比全量灌输在准确率和成本上都有明显优势。

5.4 权限与安全边界:技能是最近的特权入口

技能最大的隐患是它给了模型“动手”的能力,而模型并不真正理解安全边界。用户可能在对话里诱导模型去执行一个危险操作,而模型只是忠实调用了技能。比如一个“网页内容分析”技能,内部有爬取功能,如果它在爬取时把用户提供的 URL 直接拼进命令行,攻击者就能通过构造 URL 执行任意命令。

我的安全底线是:命令行参数绝不直接拼接用户数据,一律用白名单或专用库;技能需要访问网络时,尽量限定协议和域名;需要访问文件系统时,限定在指定工作目录内。另外,高危技能必须在执行前加一道人工确认,不要全自动放行。这不是小题大做,一旦技能被恶意利用,背锅的还是自己。

5.5 没有评测体系,技能越改越不靠谱

技能是代码,代码最怕没有回归测试。我见过团队反复调技能描述,这次把 A 场景调好了,下次某次改动又把 B 场景搞坏了,但大家靠感觉找不到原因。真正的解法是建立一套最小评测集:准备 20 到 50 条带标准答案的测试用例,每次改动技能后都跑一遍,看调用成功率、输出结构正确率、用户反馈等指标。

我给技能库做了很简单的评测脚本,每次变更触发 CI 跑一遍:

pytest tests/ --junitxml=report.xml

测试用例不止包含输入输出,还包含“这种请求不应该调用该技能”的负样例。比如web-to-markdown的负样例是“帮我把这段剪贴板里的文字转成 Markdown”,它没有 URL,所以不应该命中。负样例能有效防止技能描述范围过大导致的误召回。

6. 把 agent-skills 落地到自己的项目:从 0 到 1 的路线

6.1 先别追求多,挑高 ROI 的三个技能开始

如果你现在还没搭建技能库,别急着把所有功能都技能化。我的经验是先挑三个“高频、窄边界、结果易校验”的场景。高频保证投入产出比合理;窄边界保证模型容易理解触发条件;结果易校验保证你能快速判断技能是否正常工作。比如“网页转 Markdown”“Excel 转 JSON”“订单状态查询”都是很好的开局选择。

这三个技能跑顺之后,你会自然积累出技能目录设计、描述撰写、测试模板、参数校验的一整套经验。有了这套经验,再往业务深处扩展就会快很多。反之,一上来就做“全能助理”这种大杂烩技能,大概率会在调试模型调用的泥潭里挣扎一个月。

6.2 团队协作:技能评审像 code review

技能不是一个人的玩具,而是团队资产。我所在团队现在把技能库当成代码仓库来管:新增或修改任何技能都要走 PR,有模板、有评审、有测试。评审人是技能库维护者,不是业务负责人——因为他能看出技能描述是否清晰、依赖是否越界、输出是否破坏兼容性。

一个很容易忽略的细节是“僵尸技能”:如果某个技能 30 天没被调用,它的描述可能已经过时,参数也可能失效。建议在技能元数据里记录 seen 时间和 last_used 时间,定期清理。技术债不只是代码,还有无人维护的“沉睡技能”。

6.3 后续扩展:版本化、私有技能库与社区共享

技能做到一定规模后,版本化是必然要求。我推荐使用语义化版本号,技能 A 依赖技能 B 的 v1.2,B 升级到 v2.0 时如果不破坏兼容,要能在依赖声明里显式体现。再往下走,你可以把技能打包成 OCI artifact,推送到内部私有仓库,让不同项目通过 registry 拉取。这样技能库就变成了整个组织的基础设施,而不是某个项目里的一堆文件夹。

我最近也在关注社区里共享技能库的做法。同一类问题,比如“生成会议纪要”“抓取网页正文”“格式化 JSON”,谁都可以做一个技能,关键是谁的边界定义更好、谁的错误处理更细致。技能库的生态一旦形成,Agent 的能力就不再取决于团队里某几个人的经验,而取决于整个社区沉淀下来的方案。

我个人在实际操作中的体会是:技能化这条路没有终局,它更像是对团队隐性知识的一次持续整理。每次把一段经验固化成技能,就是把一次偶然的成功变成可复现的能力。如果你刚准备动手,我的建议只有一条——不要迷信“全自动”,先把一个技能从描述到执行体完整走一遍,那种从“模型随机发挥”到“按套路办事”的转变,你会上瘾的。

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

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

立即咨询