1. 为什么Agent时代,"技能"成了新的编程单位
1.1 从"一句话需求"到"一套可复用能力":我遇到的实际问题
先说个我自己的经历。去年我在做一个内部数据整理Agent,初始想法很简单:用户丢进来一堆发票和报销单,Agent负责提取关键字段、校验金额、汇总成表。
第一版我走的是最常规的路子——把所有提取规则、字段清单、校验逻辑全部塞进Prompt里。效果怎么说呢?能用,但很不让人安心:模型有时候把"发票号码"和"发票代码"搞混,有时候多提取一行备注当成金额,最头疼的是每次规则微调,都要重新调试那一段越来越长的Prompt,而且同样一段Prompt在不同对话上下文里表现完全不一样。同一张发票,这个对话里提取对了,换个对话开头再问一次,结果可能就少了一个字段。
后来我换了一种组织方式:把"发票信息提取"整个流程——字段定义、提取规则、校验脚本、异常处理、输出模板——打包成一个独立的技能(skill),Agent在对话中只看到一个简短的能力描述,真正执行时才会把这个技能的内部逻辑加载进来。效果对比非常明显:字段级的准确率从大概90%出头稳定到了99%以上,而且不再受对话上下文波动的影响。同一张发票,一百次测试的结果几乎完全一致。
这个变化让我想明白了一件事:在Agent的应用里,技能的英文名"skills"之所以变成一个公认的架构概念,是因为它本质上是把"告诉模型怎么做"升级成了"给模型一套确定性的执行方案"。Prompt是慢性的、概率性的指导,技能是确定性的、可测试的能力封装。
1.2 技能和普通Prompt、Function Calling的本质区别
很多刚开始接触Agent开发的朋友会把三个概念混在一起:Prompt、Function Calling、Skill。我在早期的项目里也经历过这个混乱阶段,这里用最直白的方式捋清楚。
Prompt是一段文本,作用是给模型补充知识和约束。它适合表达"你是一个严谨的财务助理,输出必须包含以下字段"这类柔性规则。但Prompt不包含可执行的逻辑,模型怎么理解、怎么组织,仍存在大量自由发挥空间。
Function Calling是一个接口,让模型在对话中决定调用某个预定义函数。它适合"查天气""获取用户信息"这种单一动作。但函数本身不负责多步流程——你让模型"提取发票信息并校验金额一致性",那就不是一个函数调用能覆盖的,你需要串联好几个函数,还得处理中间状态的传递。
Skill则是一个完整的、可复用的复杂能力单元。它里面可以同时包含指令文本、脚本代码、参考样例、输出模板、边界说明。模型在对话中通过技能描述判断"这个任务我有对应技能",然后按技能内部定义的分步流程执行。生活化一点说,Prompt是给实习生的口头交代,Function是工位上的一个按钮,Skill是一整本岗位SOP手册——什么时候该做、第一步做什么、遇到异常怎么处理、交付物长什么样,全在里面。
我自己对这三者的选择原则很简单:单次、无状态的动作用Function;知识约束和风格控制用Prompt;凡是需要多步骤、有内部逻辑、要在多个项目里复用的,就封装成Skill。
1.3 什么样的任务值得封装成技能
不是所有东西都该做成技能。技能有维护成本,技能多了之后还有管理成本。我在跑过一段时间的技能库之后,总结出五个判断标准:
| 判断维度 | 具体要求 | 反例 |
|---|---|---|
| 高频复用 | 每周至少用几次,而不是偶尔一次 | 给文章起标题(每次创意不同) |
| 确定性 | 输入输出可以被明确定义和验证 | 聊天陪伴(没有统一输出格式) |
| 多步骤 | 需要多个操作串联,不是一次调用 | 翻译一句话(单次生成) |
| 可测试 | 有明确的输入样例和预期输出 | 性格分析(结果无法客观校验) |
| 边界清晰 | 触发场景明确,不容易误用 | 通用知识问答(几乎任何对话都能沾边) |
拿第二点举个例子,我当时要做一个"生成周报"的技能。周报有固定的章节结构,有量化指标,有格式要求,输入是本周的任务流水,输出是可提交的文档——完全符合上面的标准。但如果你想把"回答用户任何闲聊问题"也做成技能,那基本没有意义,因为触发边界太模糊,模型会乱调。
所以我的建议是:如果你现在突然有灵感想写一个技能,先拿上面这个表过一遍,至少满足前三项再动手。不然技能库会变成一个塞满"听起来有用但实际没人调用"的杂物间。
2. 拆解一份技能文件:从元信息到执行逻辑
2.1 技能的标准目录结构与每个文件的责任
目前主流的Agent开发框架对技能的组织方式大同小异,基本都是一个目录装一个技能,目录内由一个描述文件加若干附属资源构成。我习惯用的结构是这样的:
skills/ csv-cleaner/ SKILL.md scripts/ main.py validate.py assets/ sample.csv cleaned_sample.csv requirements.txt每个文件干一件事:
SKILL.md是技能的门面,里面写清楚这个技能是干什么的、什么时候该用、什么时候不该用、执行步骤是什么。模型在对话中首先要读的就是这个文件。scripts/放实际执行脚本。技能里的脚本不一定全都会被调用,模型会根据SKILL.md的指引按需选用。assets/放样例数据。比如csv-cleaner里放一份脏数据的样例和对应的清洗结果,模型可以通过对比样例来理解预期输出格式。requirements.txt声明这个技能需要的Python依赖,在技能被安装或首次执行时自动装上。
这里有一个很重要的设计原则:技能目录是自包含的。不要让你的技能依赖另一个技能的脚本,也不要假设执行环境里已经装了某个特定的库。这不光是复用性问题,也是稳定性问题——一旦技能之间产生了隐式依赖,你升级一个技能就可能弄挂另一个。
2.2 SKILL.md的核心字段:怎么写才能让模型准确理解
SKILL.md的写法是技能好不好用的分水岭。同一个功能的技能,描述写得潦草和写得严谨,在Agent实际调用时的表现差距非常大。我整理了一下自己反复打磨出来的字段结构,下面这个例子是一个CSV自动清洗技能:
--- name: csv-cleaner description: 当用户提供CSV文件且文件中存在缺失值、重复行、格式不一致等脏数据问题时使用。适用于数据预处理阶段,不接受无文件输入的纯问答请求。 when_to_use: 用户提到"清洗CSV"、"处理脏数据"、"填充缺失值"、"去除重复行" when_not_to_use: 用户只是询问CSV格式的定义或教学问题,不涉及具体文件处理 --- # CSV 数据清洗技能 ## 输入 - 一个CSV文件路径,或直接粘贴的CSV内容 - 可选的清洗规则参数(缺失值填充策略、去重字段) ## 执行步骤 1. 读取CSV并解析表头 2. 检查缺失值:数值列用中位数填充,分类列用众数填充 3. 检查重复行:基于全字段匹配进行去重 4. 检查格式一致性:日期列统一为YYYY-MM-DD,金额列统一保留两位小数 5. 输出清洗后的CSV,并生成一份清洗报告 ## 输出格式 - 清洗后的CSV文件 - 清洗报告:包含发现的问题类型、处理数量、处理前后的行数对比 ## 注意事项 - 如果CSV超过10万行,改用分块处理 - 如果某列缺失值比例超过80%,直接删除该列并说明原因 - 清洗不可逆,输出前必须保留原始文件备份重点在于两处。第一是description和when_not_to_use,这两段决定了模型在什么情况下会调用这个技能。写得越具体、越有边界感,模型就越不容易在无关场景乱调用。第二是执行步骤,它给模型提供了一张操作地图——模型在执行时不一定要逐行照搬,但有了这张地图,它的行为就不会跑偏得太远。
2.3 技能的加载机制:不是整个技能都塞进上下文
很多人第一次接触技能时以为,技能也就是把一段长Prompt塞进系统消息里。事实上不是这样。一个设计良好的技能框架,在对话层面暴露给模型的只有元信息——也就是name、description、when_to_use这些字段,可能总共几百token。只有当模型判断"这个任务需要某个技能"之后,技能内部的完整内容——步骤说明、脚本代码、样例数据——才会被加载进来。
这个设计直接决定了技能库能建多大。如果你的全部技能都常驻上下文,10个技能可能就占用了一万到两万token,Agent一开局就背着一个沉重的包袱,响应速度、推理质量都会明显下降。而按需加载的方案下,技能库可以做到几十个技能,每个Agent单次对话实际加载的内容却只有需要的两三个。
这个机制也解释了一个我早期踩过的坑:技能描述里不要写太长的细节。我一开始把SKILL.md写得跟论文一样,非常详尽,结果模型在对话阶段就要消化大量冗余信息,反而更容易做出错误的调用判断。后来我把SKILL.md精简成"一句话概述+触发词+边界说明",把详细步骤留在技能内部,调用准确率立刻上去了。
3. 从零实现一个真正可用的技能(含完整代码)
3.1 选一个场景:把凌乱的会议纪要变成结构化任务清单
空谈理论没有意义,我们动手写一个。我选择一个非常常见、又很好地体现"技能"价值的场景:把一段混乱的自然语言会议记录,转换为结构化的任务清单。
这个任务看起来简单,其实完美命中技能的核心优势:
- 它高频——几乎每个团队每周都在做。
- 它有确定性——输出要求稳定:总任务数、负责人、截止日期、优先级、状态。
- 它多步骤——需要先解析会议文本,再提取行动项,再分类和排序,最后输出表格。
- 它可测试——给定同样的输入,预期输出应该是等价的。
模型用自然语言能力提取关键信息,脚本用确定性的逻辑做格式化和规则校验,两边分工,避免模型在"责任截止日期判断"这种环节上自由发挥。
3.2 主体代码实现
import json import re from datetime import datetime def parse_meeting_notes(raw_text: str) -> dict: """从原始会议文本中解析结构化信息。""" # 分阶段处理: # 1. 模型负责语义提取,输出中间JSON # 2. 本脚本负责格式校验和规则约束 intermediate = llm_extract_tasks( raw_text, output_schema={ "tasks": [ { "action": "任务描述", "owner": "负责人", "due_date": "YYYY-MM-DD格式或null", "priority": "high/medium/low", "status": "todo/in_progress/done" } ] } ) return normalize_tasks(intermediate) def normalize_tasks(intermediate: dict) -> dict: """规则校验与规范化:日期标准化、优先级默认值、重复任务合并。""" tasks = intermediate.get("tasks", []) normalized = [] for task in tasks: # 日期宽松解析:支持"下周三"、"6月底"等模糊表达 due = task.get("due_date") if isinstance(due, str) and due != "null": due = parse_fuzzy_date(due) priority = task.get("priority", "medium") if priority not in ("high", "medium", "low"): priority = "medium" normalized.append({ "action": task["action"].strip(), "owner": task.get("owner", "待指派"), "due_date": due, "priority": priority, "status": task.get("status", "todo") }) # 按优先级排序:high -> medium -> low order = {"high": 0, "medium": 1, "low": 2} normalized.sort(key=lambda x: order.get(x["priority"], 1)) return { "task_count": len(normalized), "tasks": normalized, "generated_at": datetime.now().isoformat() } def llm_extract_tasks(text: str, output_schema: dict) -> dict: """调用底层模型完成语义提取(替换为实际模型API调用)。""" # 实际项目中,这一函数会发起模型请求 # 并确保返回结果符合output_schema的结构 # 这里给出一个示意实现 return {"tasks": []} def parse_fuzzy_date(expression: str) -> str: """把模糊日期表达转换为标准日期。""" # 实际项目里可以用 dateparser 这类库 # 这里做最小的示例处理 patterns = { r"明天": 1, r"后天": 2, r"下周": 7, } for pattern, offset in patterns.items(): if expression.startswith(pattern): target = datetime.now() + timedelta(days=offset) return target.strftime("%Y-%m-%d") return expression这里有一个关键点值得展开:脚本和模型的边界怎么切。在这个例子中,模型负责"理解"——从自然语言中找出谁是负责人、哪个日期是截止时间。脚本负责"规范化"——把模糊日期转成标准格式、把任意字符串的优先级映射到三个固定档位、按优先级排序。这样的分工让整体输出的确定性最大化,又保留了模型在语义理解上的优势。
3.3 技能描述怎么写,模型才肯在正确的时机调用
技能代码写完之后,另一件同样重要的工作是写SKILL.md里的描述字段。我见过太多人代码写得很漂亮,却在描述上翻车。
先看一个典型的反例:
description: 用于整理会议信息,帮助用户提取任务清单。这个描述有三个问题。第一,"整理会议信息"太宽泛——用户可能只是问"上次会议是什么时候开的",模型也可能调用这个技能。第二,缺少触发条件——模型不知道什么情况下应该优先想到这个技能。第三,缺少反例——模型不知道什么情况下不该调用。
我最后打磨出来的版本是这样的:
--- name: meeting-notes-to-tasks description: 当用户粘贴一段会议记录或讨论文字,并要求"整理任务"、"提取行动项"、"分配负责人"、"列待办事项"时使用。输出结构化任务清单。 when_not_to_use: 用户仅询问会议时间或参会人员等事实性问题,或要求写一份会议纪要摘要(该项目由另一个技能负责)。 --- # 会议纪要转任务清单 ## 执行步骤 1. 通读全文,找出所有包含行动指向的句子 2. 对每个行动项提取负责人、截止日期、优先级 3. 将提取结果交给 normalize_tasks 脚本进行规范化 4. 输出最终任务清单表格 ## 输出格式 - 任务表格:包含编号、任务内容、负责人、截止日期、优先级、状态 - 总任务数统计对比一下就能看出区别。把"该用"和"不该用"都写明白,模型才有足够的依据做判断。我当时记录过一个数据:把描述从宽泛版本改成边界清晰版本之后,这个技能在测试集上的调用准确率(真需要的时候调用、不需要时不调用)从61%直接提升到了87%。这是纯文字调整带来的收益,代码一行没改。
3.4 技能自测:别上线了才发现边界不对
技能开发完,我会做一组自测。下面是其中一些典型的测试用例和预期输出,你可以拿来当作自测参考:
| 测试输入类型 | 预期行为 |
|---|---|
| 一遍完整的会议记录,包含3个任务 | 输出3条结构化任务,按优先级排序 |
| 会议记录中有一句"这个先放着不做了" | 不把这句话提取为任务 |
| 用户直接提问"如何写周报" | 不调用该技能,进行普通回答 |
| 会议记录中只有一个模糊任务"尽快处理" | 仍输出1条任务,due_date为null,优先级为medium |
第四种情况尤其值得注意。它测试的是技能的"兜底"能力——即使输入信息不全,技能也应该正常输出,只是某些字段为空,而不是抛异常或者重复询问用户。我一开始的设计里没有这个兜底,结果遇到模糊信息时脚本会直接报错,模型也不知道怎么处理。后来加上默认值逻辑,整个技能才算真正稳定。
4. 多技能协同:当Agent手上有10个技能时
4.1 一次任务如何触发多个技能:以会议场景为例
单个技能用起来简单,技能一多,真正考验的是编排能力。我以一个完整的会后日常工作流为例:
用户输入了一段两小时的会议记录,希望Agent最终产出三样东西:任务清单、周报素材、给未参会同事的摘要邮件。
这个需求会依次触发三个技能:
meeting-notes-to-tasks:提取行动项,生成任务清单。weekly-report-builder:把任务清单转成周报格式,补充进度说明。email-summarizer:从会议记录中提炼一段简短的邮件摘要。
关键问题来了:这三个技能的执行顺序由谁决定?实践经验是,不要把顺序写死在任何一个技能内部。技能之间应该是松耦合的——weekly-report-builder不应该负责调用meeting-notes-to-tasks,否则你以后换了提取技能就得改周报技能。
正确的做法是:把编排决策交给Agent的调度层。每个技能只声明自己的输入输出格式,Agent根据用户需求判断"这个任务需要先拿到结构化任务清单,才能生成周报",然后按顺序调用。技能之间通过统一的数据格式连接,比如都输出标准JSON。
4.2 技能间的数据传递:统一JSON结构是必需品
如果每个技能的输出结构都信马由缰,比如一个用due_date,另一个用deadline,那下游技能就要写各种兼容逻辑,时间一长根本维护不动。
我在技能库里定了一条规矩:所有技能的输出必须遵循统一的JSON Schema,涉及任务的一律用tasks数组,涉及日期的字段一律是YYYY-MM-DD字符串。这个约定让技能之间可以直接用管道式串联,就像Unix命令的输出能接到另一个命令的输入,前提是大家都遵守相同的文本流规则。
在meeting-notes-to-tasks的输出格式里就体现这个约定:
{ "task_count": 3, "tasks": [ {"action": "...", "owner": "...", "due_date": "2025-07-10", "priority": "high", "status": "todo"} ], "generated_at": "2025-07-03T10:23:00" }下游的weekly-report-builder拿到这个JSON后,不需要做任何字段映射,直接读取tasks数组就能使用。
4.3 上下文窗口的预算管理:技能库越大越要克制
技能一多,上下文管理就成了必修课。我曾经遇到过一个问题:某个智能体加载了8个技能之后,单次响应的token开销涨了三倍,延迟从2秒变成7秒,而且中间推理质量下降,因为很多上下文被技能文本占用了。
解决这个问题,我的经验是把上下文占用分成两级:
- 常驻层:只放所有技能的元信息(每条控制在50-150 token)。这部分承载"模型知道有哪些能力可用"的功能。
- 按需加载层:技能内部详细内容、脚本、样例数据,只在调用时加载。
按这个策略,即使技能库里有20个技能,常驻层的token开销也能控制在3000以内,对大多数模型来说都是可接受的。我维护技能库时还有一个习惯,每次新增技能前先看一眼它命中触发词的概率——如果一个技能的description在常驻层里占了大量篇幅但真实调用率很低,就坚决精简描述,把空间让给调用频率高的技能。
| 上下文预算项 | 预算上限 | 说明 |
|---|---|---|
| 全部技能的元信息 | 3000 token | 超过就要精简description |
| 单次对话实际加载的技能内容 | 4000 token | 超过说明步骤/脚本可能过重 |
| 系统指令与角色设定 | 800 token | 保持精简,不做知识堆砌 |
| 留给对话历史和模型输出的空间 | 视模型而定 | 建议至少保留一半窗口 |
5. 我踩过的坑:技能在生产环境里的真实问题
5.1 乱调用:描述写得太宽泛的代价
这是我在技能开发早期踩得最狠的一个坑。做一个"会议总结"技能时,我把description写成了"帮助用户整理会议信息"。结果上线后,用户问"上周的会议纪要在哪里可以找到",模型也触发了这个技能——它没有查文件系统,也没有给出答案,反而执行了一通完全没有输入的总结流程,输出了一堆空话。
排查过程是这样的:我先把技能的调用日志捞出来,发现触发场景里有一大批根本不是"需要总结"的请求。再回到SKILL.md里看,description里确实没有任何"输入什么、什么时候不该用"的约束。问题根源清楚了:模型只能依据你给的信息做判断,你没有告诉它"不要用在查询类场景",它就全靠猜。
修复方式也直接:把边界说明写清楚,并且明确加一条when_not_to_use。修复后这类误调用基本消失。这件事让我养成一个习惯——每写一个技能描述,我都会先问自己一句:"如果变成问答场景,模型会误调用吗?"
5.2 技能内容过载:想覆盖太多场景,反而啥都干不好
另一个反模式,是往一个技能里塞太多"以防万一"的逻辑。我有个技能原本职责是判断一个文件是不是包含敏感信息,后来陆续给它加了"如果是PDF怎么办""如果是扫描件怎么办""如果是图片里的文字怎么办"——这堆分支把主流程淹没了,模型在这个技能里的决策准确率不升反降。
我对这个问题的反思是:技能不是代码库,不需要覆盖所有边界。处理边界情况是模型推理能力的职责,不需要都写死在技能内部。核心路径做到快速、稳定,异常情况让模型自己判断是否需要其他技能或求助用户,这才是常态。
拆分也是解决方案之一。如果发现一个技能确实承载了太多不相关场景,就把它拆成两个独立技能。比如上面的例子,后来我拆成detect-pii-in-text(处理文本)和extract-text-from-files(处理文件),前者只管纯文本扫描,后者负责从各种文件里抽文本,然后再把抽出来的文本交给前者。一拆开,两个技能的描述都能写得更精准,调用率也上去了。
5.3 权限边界:技能能执行代码,也意味着能闯祸
技能不仅仅是文本指令,它往往还带着能执行真实操作的脚本。这是它强大的原因,也是风险所在。
我出过一次事故:有个技能内部写了段"清理临时文件"的逻辑,用来在每次任务结束后删除中间产物。脚本本身没问题,但问题是那个技能被配置在了一个处理用户上传文件的智能体里。某次测试,用户上传的文件名碰巧包含了一个容易匹配爆破的路径,脚本误删了缓存目录里的其他文件。数据没有永久损失,但那段时间整个智能体现在某些任务上异常。
从那以后,我给所有技能定了几条安全基线:
- 技能里的脚本默认只读,任何写操作(改文件、删文件、发请求)必须在SKILL.md里显式声明。
- 危险操作需要二次确认:技能内部发现要删除或覆盖文件时,不允许静默执行,必须把待操作清单输出给用户确认。
- 执行环境沙箱化:技能脚本跑在隔离环境中,不直接接触宿主的系统和网络。
这些规则看起来多花了一点功夫,但能在你技能库规模上来之后,避免很多半夜被叫起来处理的线上事故。
5.4 版本混乱:改了一个技能,连累了一排Agent
技能是复用单元,也就意味着它是"单点"。我维护过一个技能,被三个不同的Agent引用。某次我觉得它的某个执行步骤不够好,直接改了,结果其中一个Agent的表现反而变差——因为它原本承接的上游输出格式和这个技能的新逻辑不匹配。
教训就是:技能必须带版本号,Agent的配置里必须写清楚锁定哪个版本。我把技能目录里的SKILL.md加上version字段,同时要求每个Agent在配置里声明依赖的技能版本范围。更新流程也改成"先在新Agent里灰度,确认无回归,再批量升级其他Agent的应用"。
| 版本状态 | 影响范围 | 处理策略 |
|---|---|---|
| control-1(新增未发布) | 只有测试Agent | 可以随便改 |
| control-2(已发布) | 部分Agent | 只允许向后兼容的修复 |
| control-3(大规模使用) | 多数生产Agent | 任何改动都必须先灰度验证 |
版本问题不新鲜,但在技能这种"描述即接口"的东西上尤其容易被忽视,因为你不一定能直观地看到技能的接口边界。它不像一个函数有明确的签名,一个技能的行为模式是"模型阅读文本后自由发挥出来的",所以任何微调都可能产生意外影响。
6. 从"写技能"到"设计技能":进阶前的最后一公里
6.1 量化技能成本:决定一个技能该拆还是该合
技能不是免费的。虽然按需加载能控制上下文,但每个被调用的技能都会带来真实的时间成本和token成本。我在评估技能设计时,会算一笔简单的账:
单次技能调用成本 = 技能加载token数 × 模型输入单价 + 模型输出token数 × 模型输出单价
举个例子:某个技能加载需要3000 token,输入单价0.003美元/千token,处理一次任务模型输出大约2000 token,输出单价0.015美元/千token。那单次成本就是3000/1000 × 0.003 + 2000/1000 × 0.015 = 0.009 + 0.03 = 0.039美元。如果这个技能一天被调用500次,日成本接近20美元。
这个成本视角会直接影响技能设计决策。如果一个技能经常只用到其中一小部分逻辑,说明拆分可能更划算;如果多个技能经常被同时触发,而且共享大量前置步骤,合并成一个更大的技能可能减少重复加载开销。我一般用这个思路做季度复盘:看每个技能的真实调用次数和平均消耗token,然后决定这一季要拆哪个、合哪个、精简哪个。
6.2 技能如何与记忆、工作流、多模型协同
技能不是孤立存在的。一个成熟的Agent系统里,技能要跟记忆系统配合:技能负责"做事的方式",记忆负责"具体项目的历史信息"。比如meeting-notes-to-tasks技能知道如何把会议内容变成任务,但它不知道这个团队习惯用哪个项目代号,此时记忆层提供这个背景知识。
跟工作流引擎配合时,技能充当"动手术"的那个角色,工作流负责"什么时候做手术"的编排。技能内部不要做定时触发、不要做完一件事之后自己决定做下一件事,这些都应该交给外层的工作流控制。
多模型协同也是技能设计里一个值得琢磨的方向。并非所有技能都需要最强的模型来执行。文本分类、格式校验这类确定性较强的步骤,可以让轻量模型完成;真正需要深层语义理解的任务,才留给强模型。我试过在一个技能内部配置不同的模型后端——提取阶段用快模型,校验和复杂推理阶段用强模型,成本直接降了三分之一,质量没有明显变化。
6.3 可复用技能包的设计原则:一份检查清单
做了大半年技能开发之后,我沉淀了一份设计检查清单,每次新写技能或者重构技能都会过一遍:
- 职责是否单一?如果一句话说不清楚这个技能干什么,就拆。
- 描述是否自洽?只写经常触发的场景,不需要穷举所有可能。
- 是否自带测试样例?技能目录里有样例数据和预期输出,才能保障回归不出问题。
- 输出是否符合团队统一Schema?不统一的话,下游联动迟早会出问题。
- 依赖是否声明清楚?包括Python依赖、外部工具、网络权限。
- 危险操作是否有显式确认?涉及删除、覆盖、请求发送的,必须有确认环节。
- 是否标注了版本号?没有版本号就无法做灰度发布和回滚。
按照这份清单,技能的编写过程就从一个"写代码的活"变成了"设计接口的活"。你做的不再是给模型添加一段指令,而是为你的Agent团队里增加一个能力边界清晰、可独立测试、能被多个场景共享的成员。
我自己现在的体会是,真正玩明白skills之后,Agent项目会从"一个什么都能聊、但不一定靠谱的聊天框"变成"一支各司其职、步骤可验证、能力可扩展的小团队"。技能就是给团队里每个成员写的岗位说明书。如果你准备开始搭建自己的技能库,不用想太复杂,从日常重复率最高的那个任务开始,写第一个就好。写完你会明显感觉到,Agent的行为从"凭感觉"变成了"按规程",这个转变带来的安全感,是任何Prompt技巧都给不了的。