资讯动态

从Prompt工程到技能工程:打造可复用的Agent能力单元

发布时间:2026/10/20 15:30:09 来源:尧图企业网站定制
最近有做 Agent 开发的朋友问我说感觉自己写的提示词越来越长模型反而越来越容易跑偏到底有没有更工程化的组织方式。我给他的回答很简单去看看现在讨论度很高的 “skills” 机制。“skills” 不是什么专利概念也不是某一家产品的专属功能而是 Agent 能力组织方式的一次通用进化。它把一条条散落的提示词连同配套脚本、参数声明、示例输出和触发条件打包成一个个独立的“能力单元”。一个 Agent 可以挂载几十个彼此独立、按需调用的技能而不是把全部指令硬塞进一个巨大的系统提示词里。这篇内容不绑定任何特定平台背景不管你在用什么 Agent 框架只要它支持技能加载这套方法论基本通用。适合正在做智能体应用、内部工具自动化或者想把 AI 工作流沉淀成可复用资产的人。我会用一套实际可跑通的日志分析技能作为主线带你过一遍技能目录怎么建、说明书怎么写、脚本怎么配套、踩过的坑怎么排。1. 从 prompt 工程到技能工程为什么大家都在聊 skills1.1 prompt 越长Agent 越“回”不到点子上最早我调试 Agent 的方式很朴素把所有能想到的规则、工具说明、输出格式全部写进系统提示词指望模型一次读完全部约定然后完美执行。刚开始任务简单还行等业务复杂起来问题就来了。举个例子一个客服会话分析 Agent既要判断用户情绪又要检索订单信息还要生成总结工单。我起初把所有功能说明、输出模板、调用限制全部堆在一个 3000 字的 system prompt 里。结果模型经常在“生成总结”的任务里突然跑去“检索订单”甚至在应该输出 JSON 的时候输出一长串无关解释。后来我意识到一个核心问题大模型的能力边界并不取决于提示词总字数而在于每个任务指令能否在恰当的时机被准确唤醒。指令越多互相稀释越严重。这就好比一个工具箱里所有工具没有分格分类找一把螺丝刀要翻半天还容易拿错。skills 机制解决的正是这个痛点把工具分格分类用标签和触发条件让 Agent按需取用。1.2 技能是 Agent 时代的“插件系统”我们说一个“技能”本质上是由四部分组成的封装单元元数据、指令文本、工具配置、示例校验。元数据技能名、版本、触发描述、依赖环境。指令文本告诉模型“你要做什么、怎么做、做到什么程度”。工具配置配套脚本、API、外部命令的调用方式。示例校验输入输出样例以及常见异常的兜底处理。这就像浏览器插件浏览器本身只提供基础能力具体功能靠插件按需加载。Agent 也一样模型本身只负责推理对话真正干活的还是这些被封装好的技能。我在自己的项目里验证过这种设计的好处。之前一个数据清洗流程前前后后在多个对话里重复执行每次都靠重新写一遍 prompt 来指挥既慢又不稳定。后来我把数据清洗做成了技能挂载到 Agent 上之后只需要说“帮我把这份 CSV 去重补全一下”模型就会自动识别到对应技能调用脚本执行再返回结构化结果。整个过程的体验完全不同。1.3 哪些场景适合先引入技能机制不是所有任务都需要技能化。我建议优先把下面几类任务改造成技能重复执行的固定流程比如日志分析、日报生成、数据清洗、格式转换。需要外部工具配合的任务调用脚本、查询接口、操作文件系统这类任务纯粹靠 prompt 很难稳定。多人共享的标准操作团队内部经常要做的统一动作比如监控告警归类、发布检查清单。对输出格式有严格要求的任务必须输出 JSON、Markdown、特定表格结构的场景。不太适合的技能化场景也有高频实时交互比如用户在聊天窗口和 Agent 来回聊天、需要严格人类审批的操作、短平快的问答。这类任务保持轻量配置反而更灵活。我的经验是第一版技能库不要贪多选那类“做了能省一小时”的流程型任务。技术上是把提示词工程升级为能力工程本质上更像是在给 Agent 建一套自己的功能清单。2. 一个可落地的 skills 目录体系结构与配置2.1 推荐目录结构和文件职责一套清晰的目录结构是技能工程的地基。我不太建议把所有技能文件堆在一个扁平目录里那样后期维护容易乱套。我常用的结构是这样my-skills/ ├── SKILLS.md ├── log-anomaly-analyzer/ │ ├── SKILL.md │ ├── scripts/ │ │ ├── analyze_logs.py │ │ └── config.yaml │ ├── examples/ │ │ ├── sample_input.log │ │ └── sample_output.md │ └── assets/ │ └── patterns.md ├──>--- name: log-anomaly-analyzer version: 1.2.0 description: 分析服务端错误日志和访问日志统计错误级别分布、TOP N 接口失败量、异常堆栈聚类并生成 Markdown 格式报告。 when_to_use: 当用户提供日志文件路径或粘贴日志文本且任务目标是排查错误、分析失败原因、统计异常趋势、生成日志分析报告时使用。注意不要用于处理非日志类的普通文本分析比如聊天记录分析或合同审查。 input_args: - name: log_file type: string required: true description: 日志文件路径支持 .log/.txt/.json - name: top_n type: integer required: false default: 10 - name: severity type: string required: false default: ERROR enum: [DEBUG, INFO, WARN, ERROR, FATAL] output_format: markdown depends_on: - python3 --- # 日志异常分析技能 ## 角色定义 你是日志分析助手只负责阅读日志并输出结构化报告...模型在决定调用哪个技能时主要读的就是 YAML 头里的描述和触发条件。正文部分则是调用之后要遵循的具体流程。两者职责不同缺一不可别把触发规则全写进正文里。2.3 命名规范与触发设计技能命名和触发设计非常容易踩坑。命名上我建议用小写字母加短横线kebab-case。Log Anomaly Analyzer、日志分析这类命名在不同系统里很容易出格式问题。短横线命名在所有命令行环境、Git 仓库、YAML 配置里都安全也方便做文件路径引用。触发设计上最典型的问题有两个描述太宽或者描述太窄。描述太宽比如“处理日志”模型可能把任何日志相关任务都往这个技能上调哪怕用户只想简单问一句“今天有没有 warning”这种轻量问题。描述太窄比如“读取 /var/log/app/error.log 文件并统计 ERROR 关键字数量”模型遇到类似但路径不同的任务就不会调用重新去写随机 prompt导致行为飘忽。正确的做法是给出任务类边界而不是字面路径。拿日志技能举例可以写“分析日志文件或文本片段中的异常信息包括错误级别分布、失败接口聚合、根因线索提取”这样既覆盖同类任务又把非日志任务排除在外。我还会在when_to_use里加反面澄清这是很多人容易忽略的细节。写一句“不要用于处理聊天记录、合同文本或普通文章”能显著减少误触发。2.4 版本管理与依赖隔离当技能数量多起来版本管理就变得重要。我采用的是类似代码库的管理方式技能库用 Git 管理每个技能子目录独立变化技能的version字段手动维护。在SKILLS.md索引表里给每个技能记录当前版本号一旦有重要行为变化升级版本号并清理变更日志。技能之间的依赖也需要明确。比如日志分析技能依赖 Python 环境和config.yaml里的正则模式depends_on字段就是用来声明这些的。另一个场景是技能之间的调用主技能可能调用子技能。这种依赖如果不声明清楚部署环境一变就全崩了。依赖隔离的建议是技能脚本尽量使用相对路径读取配套资源重要的外部依赖避免直接写在全局环境中最好用虚拟环境或者容器方案隔离。妄图“一个环境通吃所有技能”的做法后期会付出代价。3. 实操从零创建“日志异常分析”技能3.1 目标与方案选型纸上谈兵没什么用我以一个真实场景为例带你完整建一个技能日志异常分析。我的原始需求很朴素每天要人工查看生产环境的错误日志统计出错最多的接口、错误级别分布、异常堆栈的相似片段然后写一份日报。这是典型的重复劳动而且人工统计容易漏。目标明确为输入一个日志文件路径输出一份 Markdown 报告内容包括错误级别分布、失败 TOP N 接口、典型错误样例、根因线索。技术选型上我选择 Python 3 和系统自带正则不引入额外重量级框架。原因很简单日志分析的逻辑不复杂Python 的标准库足够覆盖。等将来需要并发处理、接入更多数据源再逐步替换也不迟。3.2 写主文件 SKILL.md我完整写一份SKILL.md你可以直接参考修改--- name: log-anomaly-analyzer version: 1.2.0 description: 分析服务端日志中的异常信息统计错误级别分布、TOP N 接口失败量、异常堆栈聚类输出 Markdown 报告。 when_to_use: 当用户提供日志文件路径或日志文本且目标是排查错误、分析失败原因、统计异常趋势、生成日志报告时使用。不要用于聊天记录、合同、文章等非日志文本。 input_args: - name: log_file type: string required: true description: 日志文件绝对路径支持 .log/.txt/.json - name: top_n type: integer required: false default: 10 - name: severity type: string required: false default: ERROR enum: [DEBUG, INFO, WARN, ERROR, FATAL] output_format: markdown depends_on: - python3 --- # 日志异常分析技能 ## 任务说明 当你接收到日志分析请求时按以下流程处理 1. 读取日志文件忽略空行和非标准日志行。 2. 识别日志中的级别关键字DEBUG、INFO、WARN、ERROR、FATAL。 3. 默认筛选 ERROR 及以上级别除非用户另行指定。 4. 根据请求的接口 URI 聚合同类错误。 5. 从每条错误日志中提取关键信息时间戳、错误类型、接口、错误短句。 6. 输出 Markdown 格式报告。 ## 输出模板 - 错误级别分布 - TOP N 失败接口 - 典型错误样例 - 可能的根因线索 ## 注意事项 - 日志读取出现编码问题时跳过无法解析的行不要中断。 - 没有匹配任何级别关键字的行不纳入统计。 - 如果日志文件不存在或为空明确告知用户不要编造统计结果。 ## 示例 输入日志片段 [2025-05-01 10:11:22] ERROR /api/order/create 500 连接超时 [2025-05-01 10:13:04] ERROR /api/order/create 500 数据库连接池耗尽 [2025-05-01 10:15:00] WARN /api/pay/notify 200 重试次数偏高 报告片段 ### 错误级别分布 - ERROR: 2 - WARN: 1 ### TOP 接口 - /api/order/create : 2 条这里的正文部分对模型来说是一份带具体步骤的说明书。我特意加入“不要编造统计结果”这条限制是因为模型在没有数据的时候倾向于用幻觉补全这是实测中非常常见的问题。3.3 配套脚本设计技能不能只有提示词得有一个真正能独立运行的程序。配套脚本我用 Python 实现核心逻辑分为三块日志解析、异常聚合、报告渲染。#!/usr/bin/env python3 日志异常分析技能配套脚本 import argparse import re from collections import Counter SEVERITY_LEVELS re.compile(r\b(DEBUG|INFO|WARN|ERROR|FATAL)\b) API_PATTERN re.compile(r(?:GET|POST|PUT|DELETE|PATCH)\s(/[^\s,;])) def parse_log(file_path, severity_filter): records [] with open(file_path, r, encodingutf-8, errorsignore) as fh: for line in fh: line line.strip() if not line: continue match SEVERITY_LEVELS.search(line) if not match: continue level match.group(1) if severity_filter and level ! severity_filter: continue records.append({level: level, text: line}) return records def aggregate(records, top_n): level_counter Counter(r[level] for r in records) api_counter Counter() api_samples {} for r in records: api_match API_PATTERN.search(r[text]) if api_match: uri api_match.group(1) api_counter[uri] 1 if uri not in api_samples: api_samples[uri] r[text] return level_counter, api_counter.most_common(top_n), api_samples def render_report(level_counter, top_apis, api_samples): lines [## 日志异常分析报告, ] lines.append(### 1. 错误级别分布) for level, count in level_counter.most_common(): lines.append(f- {level}: {count}) lines.append() lines.append(### 2. TOP N 失败接口) for uri, count in top_apis: lines.append(f- {uri}: {count} 条) lines.append(f - 样例: {api_samples[uri][:200]}) lines.append() lines.append(### 3. 可能的根因线索) lines.append(- 结合高频错误短句聚类定位共性链路。) return \n.join(lines) def main(): parser argparse.ArgumentParser(descriptionLog anomaly analyzer) parser.add_argument(-i, --input, requiredTrue, helplog file path) parser.add_argument(-t, --top, typeint, default10, helptop N) parser.add_argument(-s, --severity, defaultERROR) parser.add_argument(-o, --output, helpoutput markdown file) args parser.parse_args() records parse_log(args.input, args.severity.upper()) level_counter, top_apis, api_samples aggregate(records, args.top) report render_report(level_counter, top_apis, api_samples) if args.output: with open(args.output, w, encodingutf-8) as fh: fh.write(report) print(f[done] report written to {args.output}) else: print(report) if __name__ __main__: main()这段代码我刻意保持精简。errorsignore是处理日志乱码时的防御性手段API_PATTERN抓取常见 REST 接口路径collect阶段同时记录每个接口的样例日志这样报告不只是干巴巴的数字还能看到具体错误长什么样。注意一点脚本不做过多业务假设。日志格式千奇百怪技能说明书里写的那些字段脚本不一定都能解析出来。最稳的做法是脚本尽量通用具体业务逻辑放在配置或者更高级的分析步骤里。3.4 加载与验证写完脚本和说明书把它挂到 Agent 上我习惯先用命令行直接验证脚本本身再做端到端测试。验证脚本python3 scripts/analyze_logs.py -i examples/sample_input.log -t 5这一步如果没有报错说明脚本逻辑没大问题。接下来在 Agent 配置里登记技能目录然后测试触发。我会准备几条不同的用户请求来验证触发稳定性“帮我分析一下这份日志/tmp/app.log”“统计生产环境最近一天的错误按接口排序”“这个日志文件里有哪些 WARN 级别信息”第一条和第二条都应该能触发日志分析技能但第三条可能会触发失败因为它在语义上偏向“查询”而不是“分析”。遇到这种边界情况我会修改when_to_use补充“统计 WARN/INFO 信息也属于该技能范围”。端到端测试时我还会让模型展示它执行了哪些步骤。如果模型处理中途改了逻辑没有按说明书来说明指令文本还需要更明确的约束。3.5 结果检查与迭代技能不是一次性开发完就行的我会定期复盘它生成的报告质量。复盘维度有三个触发率该调用的时候有没有被正确调用。报告准确性统计结果和人工核对的结果是否一致。输出可用性直接拿报告去发日报还需要额外加工多少。有一次我发现模型生成的报告里接口 URI 把查询参数也带了进去导致/api/order/list?page1和/api/order/list?page2被当成两个接口。修正方式是在脚本里对 URI 去掉查询参数后再聚合。这种问题不跑真实数据是发现不了的。技能更新之后记得把version号往上提然后在SKILLS.md里同步更新。如果不做这一步后面排查问题时会分不清当前到底跑的是哪个版本。4. 常见问题与排查技巧实录4.1 技能没被触发或者总调用错误技能这是最常遇到的问题。现象是模型明明看到了用户需求却没有去调用挂载的技能而是自己现场组织一段逻辑来回复。排查顺序我通常是这样的看description是否覆盖了用户可能使用的自然语言表述。比如用户说“看看日志里啥报错”你的描述里只有“分析日志文件路径”可能就不命中需要补充“查看报错、排查异常”这类近义表达。看when_to_use是不是太窄或者太宽。太窄就把边界放宽太宽就加反面排除。看技能是否和其他技能冲突。比如有两个技能都描述“处理日志”模型就很容易猜错。这种情况建议合并成一个技能或者显著区分触发条件。看上下文里是否堆了太多技能导致模型“选择困难”。实测中Agent 上下文里技能数量超过 15 个以后误触发概率会明显上升。解决方案是按用户场景动态加载技能而不是把所有技能一次性全塞进去。这里有一个判断技巧如果模型总是能正确处理任务只是没有调用技能问题多半出在描述层面如果模型调用技能后行为异常才需要去查脚本和指令正文。4.2 参数解析失败与工具调用报错模型在调用技能时需要按照input_args的声明传入参数。但大模型不会像程序一样严格遵守类型约束经常出现把数字传成字符串、漏传必填项、传了非法枚举值等情况。我在脚本里做了三重防护参数类型做防御性转换比如top_n传成字符串时解析前先int()并捕获异常。设置默认值top_n不传就默认 10。对severity做枚举校验不在合法范围内就回退到ERROR。另外一个有效的思路让技能接收一个 JSON 文件路径而不是在工具调用参数里传一堆散字段。比如分析任务需要“日志路径时间范围过滤条件”时把这些打包成一个 JSON脚本读取后统一处理。这样显著减少模型传参时的错误率。4.3 技能循环调用进入死循环在技能协作场景中模型可能陷入一种循环主技能调用子技能A子技能A执行时又去调用主技能或者两个技能互相调用停不下来。如果平台没做步数限制这会白白消耗大量 token。我在配置技能协作时定了几条硬规则每个技能默认不允许调用自己。主技能和子技能之间设计为单向依赖H2 范围内不能出现环形引用。在 Agent 平台层面一般有最大调用步数限制我会默认设置一个较小的值比如 6 步。排查循环问题时重点看平台提供的调用链路日志。通常能看到技能A、技能B反复交替出现这时候直接在对应子技能的触发描述里加上“该技能不应该再次触发日志分析主技能”之类的限制即可。4.4 更新技能后行为没变化有时候我改了SKILL.md或者更新了脚本但重新测试时发现 Agent 行为和之前一模一样。第一反应不是脚本没改对而是检查有没有缓存。很多 Agent 框架在加载技能时会缓存解析结果尤其是 YAML 头信息。如果你直接替换了文件框架可能还拿着旧的解析结果。解决方法是看平台有没有清理缓存的命令或者主动重启加载进程。更彻底的办法是在技能版本号里加一个递增标识比如1.2.0改成1.2.1然后强制重新加载。另一个容易忽略的点脚本更新之后之前已经部署到容器或远程环境里的旧版本还留在那里运行的依然是旧脚本。验证前先确认执行路径别改完本地就跑测试而线上跑的还是老版本。4.5 技能质量自检清单我给自己整理了一份技能上线前自检清单每次新增或修改技能都过一遍。列出来给你参考检查项说明描述是否能被目标用户表述覆盖把用户可能说的话写进测试集是否有反面澄清明确哪些任务不该用该技能参数声明是否完整类型、必填、默认值、枚举值脚本是否有防御性处理编码、异常输入、空数据输出格式是否稳定能直接被下游消费是否有示例数据和期望输出用于回归测试依赖是否声明环境、脚本、配置版本号是否正确维护和索引一致是否配置权限最小化技能只访问必要数据是否有循环调用风险检查技能依赖图技能质量差的典型特征是看着能跑但换个环境、换个输入风格就不稳定。自检清单的价值就是把这些不稳定因素提前掐掉。5. 一些实操体会说了这么多最后分享几个我在实践中的真实感受。第一一套技能库的早期核心目标不是追求数量而是追求稳定性。你就是把 3 个常用技能打磨到“谁用都说准”也比做 30 个用不上、还互相干扰的技能强得多。我见过很多团队热情高涨地规划了几十个技能最后维护成本高到吓人反而拖垮了整个 Agent 项目。第二SKILL.md是一件需要持续迭代的“半产品”。初始化版本永远不是最好的版本。每次跑真实数据、看真实输出后都会发现触发条件该调整、描述该补充、示例该丰富。我自己的经验是一个技能至少经过三轮真实数据打磨才算达到“合格”状态。第三技能和代码一样要纳入团队的管理规范。哪次改了脚本、为什么改、影响哪些下游任务这些信息不记录清楚一个月后就会变成一团乱麻。我一般会在技能目录里放一个简短的CHANGELOG.md每次改动加一行说明。最后一个小技巧你的第一个技能别挑复杂的。找一个“数据转 Markdown 表格”或者其他特别简单的转换型任务先跑通从编写到加载、从触发生成到校验结果的完整链路。等你吃完这一遍流程剩下的思路就顺了。技能工程这条路本质上是在给 AI 建一套可维护的能力仓库前期慢一点后面会省下非常多的时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑