做Agent开发这两年里,我听到最多的一句吐槽就是:“我家Agent就是个智障。”说这话的人往往不是外行,而是已经能把链路跑通、Demo能演示的开发者。模型是同一个模型,算力是同一份算力,为什么有的Agent能像个熟手助理一样把事办得干净利落,有的却连最简单的指令都执行得七零八落?我自己从被气得摔键盘,到后来老老实实研究Agent Skills配置,才慢慢意识到:问题从来不在模型智商,而在于我把太多需要“临场发挥”和“随机应变”的任务直接丢给了模型。这篇东西不聊空泛的概念,就聊聊Agent Skills配置里那些真正决定Agent是“智障”还是“智能”的细节。
1. 先把“智障Agent”的症状拆开看:不是模型笨,是配置缺位
1.1 症状一:指令一多,Agent像突然失忆
做Agent的人应该都经历过这种尴尬:系统提示词里明明写了三条规定、五个步骤,你满心期待模型按顺序执行,结果它只记住了开头两句话。你让它“先读取文件,再提取关键字段,最后按JSON格式输出”,它偏偏给你一段散文式的回答,字段还少了两个。很多人的第一反应是换更大的模型,或者把提示词再写长一点。但实测下来,问题往往恰恰相反——你把所有事情都堆在一个巨型提示词里,模型在执行时根本没有清晰的优先级。上下文越长,模型对“哪些规则是当前任务必须遵守的”判断就越模糊。这就好比你让一个新员工进公司第一天同时背十本手册,他大概率只能记住你最后强调的那一句。
1.2 症状二:该调用工具的时候,它偏要硬编
Agent的很大一部分价值在于它会调用外部工具,搜索、读文件、执行脚本、调API。但一个没配置好Skills的Agent,常常在该用工具时选择“硬编”——比如让它统计分析某份数据,它不打开文件,反而根据文件名猜测了一个数字;让它检索资料,它不去调用搜索接口,而是凭训练数据里的记忆给你编一段。这不是模型故意偷懒,而是它根本不知道“在这个任务里,工具是强制依赖项”。如果系统提示词里只写了“可以使用工具”,模型会把它理解成可选项。可选项意味着它有权决定这次用不用,而一旦决定不用,结果就是一本正经地胡扯。
1.3 症状三:步骤能走完,输出永远不合规范
还有一种更隐蔽的“智障”:步骤确实走了,结果也能看,但格式永远差一口气。你要JSON,它给你Markdown代码块;你要三个字段,它给你五个,其中一个字段名还是它顺手发明的;你要日期格式统一成YYYY-MM-DD,它偏写成“2025年3月12日”。问题出在输出端没有约束。很多开发者把注意力全放在输入提示词上,却忘了在配置里明确输出Schema,也没写“最后一步必须做格式自检”。模型是概率生成,你不给它强约束,它就按最自然的统计习惯输出,而自然语言习惯恰恰不符合程序化规范。
1.4 症状四:同一个任务,上午能成,下午就崩
最让人崩溃的是不稳定。上午跑得好好的流程,下午换了一段新输入就崩了;昨天还能正确触发的技能,今天莫名其妙不触发;同一个Prompt,换一个温度参数结果天差地别。这种随机性看起来像是模型问题,其实很大一部分原因是你的Agent缺少“工程化兜底”——该用确定性代码处理的部分让模型临场发挥,该做校验的地方完全没有,更没有测试样本集把行为固化下来。模型负责的是理解和生成,而流程控制、格式处理、异常分支,这些本该由配置和脚本接手。
这四个症状单独看都像模型抽风,合在一起就是典型的“配置缺位”。想解决,靠的不是求模型变聪明,而是把Agent的能力从“自由发挥”扭转到“按契约执行”,这正是Agent Skills存在的意义。
2. Agent Skills到底是什么:从“人肉调提示词”到“行为契约”
2.1 提示词是说服,Skills是SOP
很多教程会教你写精美的Prompt,但Prompt的本质是“说服模型按你的意图行事”,结果天然不确定。而Agent Skills本质上是一套“行为契约”:什么时候该用这个能力、执行时按哪几步来、每一步有哪些硬性要求、输出必须长成什么样,全部提前定好。模型接到任务后,如果判断当前场景匹配某个Skill,就会像拿到一份SOP一样按部就班执行,而不是重新在脑子里现场编排。
我见过一个很贴切的比喻:普通Prompt像你口头交代下属“把这个事弄一下”,下属理解成什么样就做成什么样;Skills像你丢给他一本操作手册,里面写明第几步干什么、什么情况找谁、输出用什么模板。后者当然更稳定。所以真正成熟的Agent玩法,不是在系统提示词里把所有能力全塞进去,而是维护一个技能库,每个技能负责一个闭环任务,Agent按需选用。这种模式也顺带解决了上下文碎片化的问题——技能指令不用常驻提示词,只有触发时才按需加载。
2.2 Skills的三层结构:触发条件、操作指令、执行工具
拆开来看,一套完整的Agent Skills配置至少包含三层。第一层是触发条件,也就是技能描述(Description),它决定了Agent在什么情况下会想到调用这个技能。触发条件写得越清晰,误用和漏用的概率越低。第二层是操作指令,即技能正文里的执行步骤,它决定了模型进场之后按什么顺序干活。这层最考验功力,步骤既要给模型留出必要的推理空间,又要卡死关键节点。第三层是执行工具,包括脚本、外部API、数据文件等。工具层负责处理那些模型不擅长、但代码很擅长的事,比如精确计算、格式转换、数据校验。
这三层缺一不可。只写描述不写步骤,模型知道该用技能但不知道怎么落地;只写步骤不挂工具,模型在需要精确处理时依然会硬编;只挂工具不写触发条件,技能就变成了一堆没人调用的死代码。很多初学配置的人最容易犯的错,是把Skills当成“高级提示词”,只堆文字不上工具,最后发现只是把原来那段系统提示词搬了个位置,并没有变得更聪明。
2.3 边界判断:什么任务适合做成Skills
并不是所有任务都适合塞进Skills。我在实际配置时有一条很粗的筛选标准:任务必须有明确的起点、终点和可复现的步骤,且执行结果可以被客观校验。比如“把多段零散文本整理成结构化归档条目”“将会议纪要按固定模板转成周报”“对一批商品评论做情感粗分类并统计占比”,这些都适合做成Skills。反之,像“帮我 brainstorm 几个创意方向”“分析一下这篇小说的人物弧光”,这类需要发散讨论、没有唯一正确答案的任务,做成Skills反而会束缚模型,效果还不如直接对话。
适合做成Skills和不适合做成Skills的界限,可以用下面这张表快速判断:
| 判断维度 | 适合做成Skills | 不适合做成Skills |
|---|---|---|
| 流程确定性 | 有固定步骤,可复现 | 高度发散,依赖灵感 |
| 输出标准 | 有明确模板或Schema | 开放、无统一格式 |
| 工具依赖 | 需要脚本/API/文件操作 | 纯语言理解和生成 |
| 校验方式 | 可通过规则、字段、格式自动校验 | 只能靠人工主观评价 |
一个技能只干一件事,这句话怎么强调都不过分。你如果试图把“整理资料+写周报+生成图表+发邮件”塞进同一个Skill,那它很快就会退化成一个大号Prompt,所有问题卷土重来。正确的拆法是拆成一串技能节点,由Agent在上层按编排依次调用。
3. 配置Agent Skills的四道关口:一次讲透完整流程
3.1 第一关:把任务拆到“一个技能只干一件事”
配置Skills前,我习惯先写一份人肉版的SOP:如果我是人工处理这个任务,我会先做什么、再做什么、遇到什么情况怎么办。然后把SOP里可以独立成环的动作切出来,每个动作对应一个技能。比如“客户反馈归档”这个任务,可以拆成“反馈文本清洗”“字段提取”“归类打标”“表格生成”四个环节,但通常不需要拆成四个技能,因为前三个环节紧密耦合,拆太碎反而增加调用次数和出错可能。我的经验是一个技能内部控制在5到10个步骤,步骤之间要呈现线性依赖,避免出现“如果……那么……否则……”这种复杂分支,分支逻辑尽量交给代码去判断。
拆完之后,给每个技能取一个一眼能看懂的名字。名字不是给模型看的,是给你自己的看的。技能一多,如果名字全是“技能A”“技能B”,维护的时候绝对想不起来它是干什么的。目录结构通常长这样:
skills/ feedback_archive/ SKILL.md scripts/ clean_and_extract.py reference/ fields_schema.json其中SKILL.md是主文件,scripts放可执行脚本,reference放按需加载的参考文档。这个目录结构本身不复杂,但它天然形成了一种约束:一切跟该技能相关的资源都收拢在一起,不会散落在系统提示词里。
3.2 第二关:触发描述怎么写,Agent才不会瞎调用
触发描述是Agent判定“当前任务该不该用这个技能”的唯一依据,它直接决定技能会不会被正确命中。很多人写触发描述时喜欢写“处理客户反馈”,这种写法太泛,Agent看到“用户问了个问题”都可能觉得像客户反馈,于是误触发。正确写法要回答三个问题:这个技能处理什么输入、什么场景下必须用、什么场景下千万别用。我一般会写成类似这样的语气:
--- name: feedback_archive description: 当用户一次性提供多条客户反馈、评价或聊天记录,并要求整理、归档、去重或统计时使用。适合处理原始反馈文本、客服对话记录、问卷开放题。不适用于单条问题的即时解答,也不适用于生成新的营销文案。 ---注意后半句“不适用于”,也就是负例。这个太重要了。模型在判断技能匹配度时,正向描述只能让它知道“这像”,反向描述才能帮它排除“那也像”。加了负例之后,误触发率能明显降下来。还有一个小技巧:如果框架支持,在description里写两三个典型的用户语料示例,比如“当用户说‘帮我理一下这些反馈’‘把这几段记录归档’时优先考虑”,效果会比抽象描述好很多。
3.3 第三关:操作步骤的编写,要让模型“照着执行而不是发挥”
SKILL.md里的操作步骤是技能执行的核心。我在写步骤时有一条铁律:凡是需要模型理解的节点,用自然语言交代清楚目标,并提供判定标准;凡是需要模型执行的节点,不要让它自由发挥,必须给出强制要求。比如不要写“提取客户反馈里的关键信息”,要写“从每条反馈中提取:客户ID(若有)、反馈类别(投诉/建议/咨询/表扬)、情绪倾向(正向/负向/中性)、原文摘录(不超过50字)”。字段列得越具体,输出越可控。
同时,步骤要写清楚执行顺序和“什么时候可以提前终止”。比如某条数据缺少关键字段,是补一个空值继续,还是中止整个流程?这在配置里一定要写明。我最开始写Skills时,忽略了异常分支,结果模型遇到缺字段的数据时要么自己瞎编一个ID,要么直接放弃整批数据。后来我在指令里加了一条“若某条记录缺少可提取内容,保留原文本并标记为待人工复核,不得编造缺失字段”,情况才好转。
3.4 第四关:工具、参数与输出校验
如果技能涉及文本清洗、格式转换、字段校验这类机器擅长的事,一定要把这个环节交给代码。一个常见的做法是让模型把原始文本按段落切好,再传给一个Python脚本做清洗和初步解析,脚本返回结构化数据,模型再基于该数据做语义归类。用代码兜住格式,用模型处理语义,各自干各自擅长的事,这才是“智能”的底气。
下面是一段很朴素但实用的清洗脚本示例,按幂等方式输出分块后的文本列表:
# scripts/clean_and_extract.py import sys import json import re def split_blocks(text: str) -> list[str]: # 按连续空行切分,过滤纯空白块 raw_blocks = re.split(r"\n\s*\n", text.strip()) return [b.strip() for b in raw_blocks if b.strip()] if __name__ == "__main__": raw = sys.stdin.read() blocks = split_blocks(raw) print(json.dumps(blocks, ensure_ascii=False, indent=2))这个脚本本身不复杂,但它代表了一个原则:把“切分文本”这个动作从模型手里拿走。因为模型做这样的精确切分时,经常出现多切、漏切、把标题当正文的问题,而正则表达式一次搞定。脚本跑完,模型再对每个block做语义判断和字段提取,精准度会直线上升。除了脚本,技能配置里还要包含输出Schema的定义,并在最后一步明确要求“输出必须是合法JSON,且包含全部必填字段”。模型一旦被告知“输出前先检查字段完整性”,它的自检行为会显著增加,字段丢失率下降不少。
4. “从能跑到好用”最容易翻车的五个点
4.1 触发失灵:description写得像散文
我见过最典型的触发翻车,是把description写成了一段抒情文案:“本技能用于帮助用户更好地整理归纳他们海量的反馈信息,提升处理效率,让每一条反馈都被妥善对待。”这种描述对模型来说几乎等于什么都没说。它没有给出任何具体的输入形态、触发场景和排除条件。结果就是Agent要么在用户随便说一句“我现在头很大”的时候触发归档技能,要么在用户明确要求归档时压根不调用。description不是文章,是给模型看的“路由规则”,可以干,但不能散。
说到底,description本质上是一种判别式任务。模型读一段用户请求,要在多个技能里选一个命中的,或者决定不调用任何技能。你把路由规则写得越像分类器的分类条件,命中率就越高。所以我每次写description都会问自己:如果现在来了一批用户请求的测试集,单凭这段描述,我能判断出哪些该命中、哪些不该命中吗?如果我自己都犹豫,模型一定更犹豫。
4.2 上下文过载:把整本手册塞进了Skill
有些人觉得技能正文写得越详细越好,于是把操作流程、背景知识、历史版本、注意事项全写进SKILL.md。结果一份技能文档动辄三四千字,每次触发都等于往上下文里塞一大坨内容,把本可以用来处理用户输入的空间挤掉了。模型到后面操作时注意力涣散,反而连基本步骤都记不全。
正确做法是给SKILL.md做一次“瘦身”:核心指令只保留不可省略的步骤和约束,尽量控制在800字以内;背景知识、字段Schema、参考示例这些内容,拆到reference目录下按需引用。也就是说,只有当模型发现某一步需要查更细的规则时,才通过工具/搜索去读对应文档,而不是一开始全部加载。这个设计跟人做事是一样的:SOP手册放桌上,但不用每做一步都翻一遍整本,遇到不清楚的章节才去翻。
4.3 容错缺失:中途失败直接卡死
技能执行是串行的,只要中间某一步出错,后面的步骤全废。但Agent不像程序,它不会主动抛异常然后终止,它更容易做的是“想办法糊过去”——字段提取不了就编一个,文件读不了就说读完了。所以Skills配置里必须显式写好容错分支。我在每个技能文档里都会加一节“异常处理”,写明:哪类错误可以重试一次,哪类错误必须终止并如实告知用户,哪类错误可以跳过当前数据继续处理。
举一个实际场景:归档技能里有一调读取网页的操作,如果网页打不开,模型可能告诉你“提取到0条信息”。但你其实希望它在读不到时尝试备用入口,或者明确报告“链接失效,已跳过”。这两种结果对用户的价值完全不同。配置里没写,模型会默认选一个“看起来体面”的处理方式,而这个方式往往并不体面。
4.4 输出不稳定:没有Schema和自检
配置Skills时如果只写“请输出处理结果”,那模型就会输出一段排版随缘的Markdown。解决输出不稳定,只有两条路能走:一是给出明确的输出模板或JSON Schema,二是强制模型在生成最终答案前做一次自检。以JSON输出为例,不要只写“输出JSON”,要写“输出JSON数组,数组元素包含字段:id、category、sentiment、quote,其中id为整数,quote为字符串且不超过50字”。字段类型都写清楚,然后要求“在最终输出前检查每个字段是否存在,类型是否匹配,缺失则输出空字符串而不是自行编造”。
我会把这句话原样写进SKILL.md的末尾:“验证你的输出:所有必填字段必须存在,字段类型必须符合定义,不要添加Schema之外的字段。确认后输出。”别看只是一句话,它对模型行为的影响很大。模型在收到明确的“输出前自检”指令后,会进入一种近乎“复核模式”的状态,能够把不少低级错误拦截在输出之前。
4.5 不做回归:调好新技能,弄坏老技能
Agent Skills配置是一个持续迭代的过程,最怕的是你为了修一个新问题,随手改了一下全局配置,结果老技能全崩了。今天你为了提升归档技能,在系统提示词里加了一句“所有输出都要严谨”,明天分类技能可能就变得过度谨慎,连“这是投诉”都不敢下结论。这类问题靠肉眼很难发现,因为每个技能单独跑都正常,组合在一起就出鬼。
我的习惯是给每个技能配一个微型测试集,至少包含5个正向样本和3个负向样本。正向样本是“应该触发并正确执行”的输入,负向样本是“不应该触发”的输入。每次调整完配置,把所有技能的测试样本整体跑一遍,谁崩了立刻暴露。这个习惯看起来麻烦,但长期下来省下的排查时间远超投入,尤其当技能数量超过10个之后。
5. 一次完整的翻车修复实录:某开发者把“客户反馈归档Skill”从不能用调到稳定
5.1 第一版配置:思路对,细节全错
这个例子来自一位开发者的真实调试过程,我拿它当模板拆给大家看。需求很简单:用户把一堆聊天记录、问卷反馈、留言截图里的文字丢给Agent,Agent需要整理成一张归档表,包含来源、反馈类型、情绪、原文、处理状态。第一版SKILL.md写得相当精简:
--- name: feedback_archive description: 整理客户反馈信息。 --- # 任务 把客户反馈整理归档。 # 步骤 1. 提取每条反馈。 2. 判断类型和情绪。 3. 输出表格。这版配置总共不到50个字,触发描述、字段定义、异常处理一概没有。第一次测试就被打脸:用户贴了三条反馈加一句闲聊“今天天气不错”,Agent把闲聊也当成一条反馈归档了,表格里出现了一条“反馈内容:今天天气不错,类型:其他,情绪:正向”。这还不算完,因为步骤里没写字段来源,模型在“原文”那一列顺手把反馈给概括了一遍,根本不是原文摘录。
5.2 第一次翻车:该归档时闲聊,该闲聊时归档
第一版跑完,开发者首先修的是触发描述。把原来的“整理客户反馈信息”改成了更明确的条件句,并加上负例:“当用户提供两条或以上独立反馈片段并要求整理、归档或统计时使用。如果用户只是提出一个问题、表达一个观点或闲聊,不要使用本技能。”同时,在步骤里增加了一条硬性要求:只处理明确属于反馈内容的文本块,若某段文本无法判断是否为反馈,标记为“待确认”而不是直接归类。这个调整之后,闲聊被归档的问题基本消失,但新的问题又来了。
5.3 第二次翻车:字段丢失,模型自己“润色”数据
在归档准确性上来之后,开发者发现输出表格里的字段仍然不稳定。有时候“处理状态”这个字段丢了,有时候“原文”是模型改写过的内容。根因是配置里从头到尾没有定义Schema。于是开发者直接重构了SKILL.md,在步骤里写明每个字段的含义、来源和格式,并且把最终输出格式锁定为JSON数组。为了不让模型去猜字段,配置里直接内置了示例:
[ { "source": "投诉工单", "category": "抱怨", "sentiment": "负向", "quote": "客服响应太慢,等了一天没有人理", "status": "待处理" } ]作者还在脚本层加了一道校验:用Python遍历模型输出的JSON,检查必填字段是否存在。如果缺失,脚本返回错误信息,把缺失字段名反馈给模型,要求补全后重新输出。这样一来,输出格式问题被代码强制兜住了,模型即使想偷懒,也绕不过脚本这一关。迁移到纯代码校验之后,这个技能的稳定率才算是真正达标。
5.4 稳定版本长什么样
最终稳定版的核心结构大致是:触发描述明确了输入形态和负例;步骤从“切块、清洗、提取、归类、自检、输出”六个环节逐步推进;异常处理写了三条强制规则——缺失字段不编造、无法分类的文本标记待确认、单条数据处理失败不影响整体;输出段加了JSON Schema示例,并强制要求“先自检后输出”。这个版本跟最初那版相比多了一倍文字,但每一步都有具体指向。该作者之后又把这套模板复制到了周报生成、会议纪要和简历筛选三个技能上,效果都很稳定。
6. 我在Skills配置上养成的几个习惯
6.1 先写人肉SOP,再写机器SOP
每次配置新技能,我第一步不是打开编辑器写SKILL.md,而是拿一张纸,把“人工处理这个任务”的完整过程一步步写下来,包括遇到不同情况怎么分支。写不出来的环节,说明你对任务本身的理解还不到位,强行配置只会把错误流程自动化。人肉SOP越细,机器SOP越好写。
6.2 最小闭环:一个技能配三个测试样本
技能写完第一版,不要急着上生产。我会先准备3个最少输入样本跑一遍,看能不能正确触发、正确执行、正确输出。跑通后再把样本扩到8个,加入边界情况和异常输入。这套最小闭环每次改动后都跑一遍,耗时可能不到五分钟,但能拦住大半的回归问题。
6.3 给Skill加版本和变更记录
SKILL.md本质上是一份会持续演进的配置,不是一次写就的文档。我会在文件头部维护一个版本号和变更记录,哪怕只是三行:哪天改了什么、为什么改、上次版本是什么。版本记录的价值不只是追溯,它让我在某个改动引发事故时能快速回滚到已知正常版本,而不是对着全是无效修改的记录干瞪眼。
6.4 把“失败样本”当作一等资产
每个让Agent翻车的输入,在我这里都是宝贝。我会把失败样本单独放到一个sample/failure目录里,等集到一定数量,再分析它们的共性。很多时候,三五个失败样本放在一起,你会发现它们根本不是模型问题,而是同一个触发歧义、同一个缺失字段、同一个模糊步骤造成的。修复一个根因,往往能同时解决四五个症状。
Agent Skills配置这件事,说穿了就是一句话:把Agent当成一个能力不错但容易走神的同事,你要做的是给他一份足够清晰的工作手册,而不是一边骂他笨一边把所有期望都押在他的临场发挥上。如果你的Agent现在还经常“智力不在线”,回头检查一下它的Skills配置有没有过这几道关口——触发条件写没写清楚,操作步骤是不是只有形容词没有动词,输出端是不是全靠自觉,异常分支是不是全都交还给了模型自由发挥。把这些补上,你会明显感觉到,它从“智障”到“智能”的距离,其实并没有你想象中那么远。