ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent技能化重构实战:从堆提示词到可复用技能包

Agent技能化重构实战:从堆提示词到可复用技能包 前阵子有个做运营的朋友拿着两份Excel来找我说想让AI帮她自动核对门店对账单。我打开对话窗口把文件拖进去交代两句模型很快就把对不上的记录列出来了。她挺兴奋追着问“那以后这类活儿是不是都能交给它了”我笑了笑没急着回答。因为我知道一次对话里灵光乍现的成功和一套能稳定复用的能力体系中间隔着十万八千里。后来跟几个做AI应用的朋友聊起这事大家都有同感现在的Agent智能体演示阶段个个惊艳可一旦放到真实场景里跑几周问题就全冒出来了——该调工具的时候不调、同样的需求问法稍微变一下结果就跑偏、上下文越长行为越不稳定。折腾到最后有人选择给模型写几千字的系统提示词有人把业务逻辑全塞进函数调用里还有人放弃了Agent改回传统流程编排。但今年社区里逐渐形成了一个共识与其在提示词里堆规则不如把能力“打包”成一个个可复用、可分发、可测试的技能模块——这正是agent-skills这类项目想解决的核心问题。我花了大概一个月时间把手头几个Agent项目往技能化方向重构了一遍过程中踩了不少坑也总结出一些可复用的方法论。这篇文章就把我在这个过程中的设计思路、实操步骤、参数取舍和排查经验完整写出来希望能给正在做Agent工程化的朋友省点时间。1. 为什么需要技能体系Agent开发里绕不开的三个坎先说痛点。如果只是写个demo现在的模型能力已经完全够用你甚至不需要设计什么体系。但一旦涉及生产环境我遇到的三个坎是绕不过去的。第一个坎是上下文膨胀。项目初期我习惯把业务规则、工具用法、输出格式全都写进系统提示词。一开始还能跑随着业务规则越来越多提示词从500字膨胀到3000字甚至更多。模型需要处理的信息量越来越大不仅响应变慢而且注意力会被稀释——用户问一个简单问题模型反而因为“知道太多”而过度发挥答非所问或者自作主张调用了不该调用的工具。后来我学到一个关键心法**系统提示词只保留“身份和边界”把具体能力放到Agent可以按需加载的技能库里。**模型在一轮对话里只需要看到技能的名字和一两句摘要真正用到某个技能时再读取详细说明这就像图书馆只让你看书目卡片而不是把整本书都搬到你面前。第二个坎是行为漂移。同一个Agent上午测试的时候一切正常下午换了个问法它就开始自由发挥了。比如我做过一个代码仓库分析Agent最初它能正确调用统计脚本但当我问“看看这个项目进展怎么样”时它没有去跑脚本而是自己根据文件名猜了个结论。后来我意识到模型非常擅长“看起来合理地糊弄你”它会根据对话上下文脑补缺失信息。要对抗这种漂移不能靠在提示词里反复强调“你必须调用工具”而要把“什么情况必须调用什么技能”定义成一种可被模型识别的结构化信号。第三个坎是能力无法沉淀。今天花一个下午调好的一个数据清洗流程下周换个新项目又要从头写一遍。如果是传统的编程你可以把这段逻辑抽成公共函数放进工具库但Agent的技能不光包含代码逻辑还包含模型在调用这个逻辑时需要说清楚的各种元信息——什么时候用、参数怎么填、输出怎么解析、有哪些禁忌。这些“使用说明书”才是技能真正值钱的部分。agent-skills的意义在于它给这种沉淀定了一套标准格式和目录约定。理解了这三个坎你就明白为什么社区里越来越多人在做“技能化”而不是继续堆提示词了。提示词更像口口相传的经验而技能包是可安装、可卸载、可测试的软件资产。这个转变本质上是从“教模型做事”走向“给模型配工具”。2. 技能包的标准结构目录、清单、脚本与资产2.1 一个技能包由哪些部分组成我重构过的技能包基本都遵循统一目录结构。以agent-skills风格为例一个名为csv-inspect的技能包大致长这样csv-inspect/ ├── SKILL.md ├── scripts/ │ ├── inspect.py │ └── summary.py ├── assets/ │ └── report_template.md ├── requirements.txt ├── tests/ │ ├── test_inspect.py │ └── fixtures/ │ └── sample.csv └── version.txt逐个解释一下这些文件的作用。SKILL.md是技能的“门面”模型首先读取的就是这个文件里面用结构化的格式写清技能的名称、适用场景、参数定义、输出规范和示例。scripts/目录存放真正执行的脚本它接收参数、处理数据、输出结构化结果。assets/放模板、参考文件这类辅助资源。requirements.txt声明依赖tests/放回归测试。这套结构的关键设计原则是**模型只读SKILL.md不读脚本源码。**这意味着你在写SKILL.md时的措辞质量直接决定模型能不能在正确的时机调用它。代码写得再漂亮如果描述文件没说清楚“这个技能是干嘛的、什么时候该用”那模型依然会忽略它。2.2 SKILL.md 正面战场元信息怎么写SKILL.md是我的重点调试对象。它通常分为两块文件头部的YAML元信息区和正文说明区。元信息区至少包含以下字段--- name: csv-inspect description: 分析CSV文件质量检测缺失值、重复行、类型不一致等异常。 when_to_use: 当用户提供CSV文件并要求检查数据质量、清洗数据、或生成质量报告时。 version: 1.2.0 parameters: file_path: type: string description: 待检查的CSV文件路径。 required: true report_format: type: string enum: [short, full] default: short description: 报告详细程度short只输出异常摘要full输出完整统计。 output_format: JSON对象包含total_rows、missing_values、duplicate_rows、type_issues四个字段。 ---每个字段都不是随便写的背后有讲究。description字段要精炼且具体目的是让模型在技能索引中快速判断“这个技能和用户当前请求是否相关”。when_to_use字段是我后来加上去的它把触发条件写得更明确显著提升了调用准确率。这就像搜索引擎的摘要信息——摘要写得越贴近用户的真实搜索意图点击率越高。parameters字段用JSON Schema形式定义了技能所需要的输入。这里的技巧是**参数越多模型犯错的可能性越大。**能设计默认值的就设默认值能合并的参数尽量合并。我曾经设计过一个技能给了模型8个参数结果它调用时总是漏填或者填错后来压缩到3个必填参数成功率一下提了上来。2.3 正文区是给模型读的操作手册元信息下面就是自由文本区。这里写什么、写多少是技能设计里最需要拿捏的部分。我的经验是正文区不要写长篇大论的算法讲解模型不需要理解你代码里用了什么数据结构它需要知道的是三件事第一这个技能的详细执行步骤让它在调用时心里有数。第二输出规范的具体示例尤其是JSON结构长什么样这样它才能正确解析结果并继续后续对话。第三边界和禁忌也就是“哪些情况不适合用这个技能”。比如“文件超过100MB时不建议直接分析请先进行采样”这能避免模型在一堆坏数据上浪费时间。还有一条重要经验**正文区内容不必追求短但关键信息必须放在最前面。**模型在读取文件时对开头内容的注意力权重更高。我有一次把一个重要提示写在文档最后结果模型全程都没注意到后来把它挪到开头问题立刻解决。3. 实操从零设计并实现一个“仓库体检”技能3.1 需求定位为什么选仓库体检做例子理论说多了容易飘我用一个我自己做过的完整案例来演示。前阵子我需要定期检查公司几个Git仓库的健康状态包括代码行数变化、TODO数量、未合并分支数量、最近提交活跃度等。传统做法是写个脚本跑一下但我想让团队同事直接用自然语言问Agent“帮我看下order-service这个仓库最近有没有异常”。这就需要把体检能力做成一个技能包。需求场景明确之后技能边界也要划清楚它只负责“读取仓库静态数据并输出统计报告”不做代码审查更不自动修改代码。这个边界我需要写进SKILL.md否则模型很容易在用户提出“顺便帮我把这个bug修了”的时候越权行动。3.2 实现核心脚本技能的核心逻辑其实不难关键在于输出格式要极其规整方便模型做后续处理。下面是我实际使用过的脚本骨架#!/usr/bin/env python3 仓库健康度统计脚本 import argparse import json import subprocess from pathlib import Path from datetime import datetime, timedelta def count_lines(repo_path: Path) - int: 统计仓库内代码文件的总行数忽略.git目录和常见构建产物 total 0 for p in repo_path.rglob(*): if p.suffix in {.py, .js, .ts, .java, .go, .rs}: total len(p.read_text(encodingutf-8, errorsignore).splitlines()) return total def count_todos(repo_path: Path) - int: 统计代码中TODO/FIXME注释数量 count 0 for p in repo_path.rglob(*): if p.suffix in {.py, .js, .ts, .java, .go, .rs}: content p.read_text(encodingutf-8, errorsignore) count content.count(TODO) content.count(FIXME) return count def git_recent_commits(repo_path: Path, days: int 7) - int: 统计最近days天内提交数量 since (datetime.now() - timedelta(daysdays)).isoformat() result subprocess.run( [git, -C, str(repo_path), log, --since, since, --oneline], capture_outputTrue, textTrue ) return len(result.stdout.strip().splitlines()) def main(): parser argparse.ArgumentParser(description仓库健康度统计) parser.add_argument(repo_path, typestr, help仓库本地路径) parser.add_argument(--window, typeint, default7, help提交活跃窗口天) parser.add_argument(--output, typestr, defaultjson, choices[json, text]) args parser.parse_args() repo Path(args.repo_path).resolve() if not (repo / .git).exists(): print(json.dumps({error: 路径不是有效的Git仓库})) return report { repo_path: str(repo), total_lines: count_lines(repo), todo_count: count_todos(repo), open_branches: len(subprocess.run( [git, -C, str(repo), branch, -r], capture_outputTrue, textTrue).stdout.strip().splitlines()), commits_last_7d: git_recent_commits(repo, args.window), generated_at: datetime.now().isoformat(), } if args.output json: print(json.dumps(report, ensure_asciiFalse, indent2)) else: print(f仓库: {report[repo_path]}) print(f代码总行数: {report[total_lines]}) print(fTODO/FIXME数量: {report[todo_count]}) print(f远端分支数: {report[open_branches]}) print(f最近{args.window}天提交数: {report[commits_last_7d]}) if __name__ __main__: main()这个脚本故意设计得不需要第三方依赖因为Agent技能的执行环境往往比较受限subprocess调git是最稳的方案。--output参数允许模型根据需要选择输出格式如果用户只是简单问一句“仓库怎么样”模型可以要求text格式直接读如果后续还要做数据分析那就要求json格式。3.3 编写 SKILL.md 元信息与正文写SKILL.md时我特别注意让描述语和真实用户提问习惯吻合。不能说“检查代码质量”因为用户不这么说话要说“查看仓库状态/健康度/活跃情况”这才贴近真实表达。--- name: repo-health-check description: 统计Git仓库的代码行数、TODO数量、分支数量、最近提交活跃度输出健康度摘要。 when_to_use: 用户询问仓库状态、活跃情况、健康度、TODO积压、代码规模时使用。当用户提到一个明确的本地仓库路径时优先考虑调用。 version: 1.0.0 parameters: repo_path: type: string description: 仓库在本地磁盘上的完整路径或相对路径。 required: true window: type: integer default: 7 minimum: 1 maximum: 90 description: 分析最近多少天的提交活跃度默认7天。 output: type: string enum: [json, text] default: json description: 输出格式。 ---正文区我这样写此技能用于快速评估Git仓库的健康状况。执行时将运行 repository-check 脚本脚本会返回结构化数据包括总代码行数、TODO/FIXME注释数量、远端分支数量和指定时间窗口内的提交数量。 执行后请根据结果向用户提供摘要例如“order-service最近7天有23次提交代码规模约12万行有45处TODO标记。整体活跃度正常。”如果仓库路径不存在或不是Git仓库请如实告知用户并提供合理的路径建议。 注意本技能仅为评估工具不提供代码修改或仓库管理操作。如果用户提出修改代码、合并分支等需求请明确说明不在本技能范围内。写完之后我的验证方式是模拟10种用户提问方式检查模型是否能在正确的时机调用技能并正确解析输出。这10种提问里既有直白的“看下order-service仓库状态”也有模糊的“后端那个项目最近是不是没人维护了”还有负例“这个仓库里代码写得怎么样”其实问的是代码审查不该调用本技能。实测下来调用了本技能的准确率从最初没写when_to_use时的60%左右提升到了90%。3.4 注册与加载如何让Agent发现技能技能包写好了怎么让它被Agent用起来目前主流做法是给Agent配置一个“技能目录”启动时扫描目录里的所有SKILL.md把技能名和描述汇总成索引塞给模型。我在实践中通常采用两种加载方式。一种是本地目录加载把所有技能包放在/agents/skills/下启动Agent时扫描并生成索引。这种方式适合个人项目简单直接。另一种是远程仓库加载技能包存放在Git仓库里Agent启动时拉取最新版本。这种方式适合团队协作因为每个技能都有版本记录更新和回滚都很方便。加载配置大致长这样agent: name: dev-helper model: llm-default skills: source: git repo: gitinternal:ai-skills.git local_path: /data/skills auto_update: true system_prompt: | 你是开发助手可以调用技能完成代码分析、仓库检查、文档生成等任务。 用户提出需求时先判断是否需要调用技能需要的话选择最匹配的技能。这里有一个重要参数system_prompt里那句“先判断是否需要调用技能”不是可有可无的。因为如果你不主动引导很多模型倾向于直接凭已有知识回答问题而非调用工具。加上了这句之后模型会形成一个“要不要动用技能”的决策习惯。3.5 如何衡量一个技能包是否有效技能上线不等于完事。我给团队定了一套简单的度量指标每个技能都要记录三个数字调用率、成功率、返工率。调用率是指该技能被模型选择的比例太低说明描述写得不够吸引人模型没意识到该用成功率是指调用后脚本正常执行并输出合法结果的比率太低说明代码健壮性不够或者参数定义有问题返工率是指同一个请求在第一次调用后模型还需要额外追问或重复调用的比率太高说明输出契约定义不清晰。我用一张表记录每个版本的指标变化指标定义目标值采集方式调用率相关提问中模型主动调用技能的占比85%日志分析成功率脚本返回非错误结果占比95%执行日志返工率一次请求触发多次调用的占比10%会话分析这三个数字组成了一套核心效果指标比在对话里问“你觉得这个Agent聪明吗”要客观得多。技能包迭代的依据就是这三个数不是感觉。4. 把技能变成团队资产版本管理、测试与分发4.1 语义化版本与变更管理技能包本质上是一个软件包天然需要版本管理。我采用语义化版本号主版本号变化说明技能行为不兼容比如输出JSON结构变了次版本号变化说明新增了功能比如新增统计维度补丁版本号变化说明修复Bug或优化描述。版本信息并不只写在version.txt里更重要的是写在SKILL.md的YAML元信息区。如果元信息区没更新模型就不知道版本变化可能出现“Agent以为用的是旧技能实际跑的是新代码”的错位。每次调整技能描述后我也建议顺手在description末尾加上“v1.2”这样的标记这能让你在日志里快速判断当前Agent用的是哪个版本的技能描述。4.2 回归测试既要测代码也要测模型行为传统工程里测试是测代码逻辑但技能包的测试要比这多一层还要测模型在给定提示下能否正确选择技能并遵守技能说明。我称之为“双回归测试”。代码层的测试很常规用 pytest 针对核心函数写断言就够了。你只需要将参数和预期结果固定下来尤其注意异常分支——仓库路径不存在、文件权限不足、git仓库处于合并冲突中这些情况都要覆盖。模型行为层的测试就更有意思了。我会把历史上收集到的真实用户提问整理成“触发样例集”每个样例标注预期行为该调用/不该调用。然后每次改完SKILL.md就拿样例集跑一遍看模型的选择结果是否发生变化。例如我收集过这样一组样例用户提问预期行为“看下order-service仓库最近有没有异常”调用repo-health-check“这个仓库的代码review一下有问题吗”不调用转人工审查“order-service有多少行代码”调用repo-health-check“帮我把order-service的README翻译成英文”不调用直接处理为什么一定要保存这个样例集因为你对SKILL.md描述的每一次“优化”都可能带来正反两面的效果——描述写得更具体可能提升触发率但也可能让模型在边界场景下“过度触发”。没有回归集你根本发现不了这些细微变化。我吃过这个亏有一次把技能描述改得更详细了结果模型在用户询问完全不相关的问题时也因为看到了关键词而调用了技能白白浪费了一次执行开销。4.3 分发的三种方式技能包的分发方式我尝试过三种各有适用场景。第一种本地文件目录。自用场景最简单目录一放就行。缺点是没法多人在一个共享源上协作。第二种Git仓库Python包管理器。这是团队协作的标配方案。将技能包仓库放在代码托管平台上使用者通过一条命令安装git clone gitgithub.com:internal/agent-skills.git skills cd skills python -m venv .venv source .venv/bin/activate pip install -r requirements.txt第三种内部技能市场。这是理想状态公司内部搭建一个简单的HTTP服务提供技能包的检索和安装接口。Agent启动时可以先从市场服务拉取“技能索引”再根据用户请求按需下载技能包。这种方式最接近软件包管理器的用户体验但实现成本也最高。我目前实际采用的是“本地目录Git仓库”两步走个人电脑上开发好技能后推送到公共仓库服务器上定一个拉取计划自动更新。这样不需要额外搭建服务成本很低。5. 常见问题与排查技巧实录5.1 模型就是不调用技能怎么办这是最常遇到的问题。排查顺序我建议从简单到复杂来。第一步检查技能的description是否包含了用户真实会用到的词汇。比如卖点是“Git仓库巡检”但用户在对话里说的往往是“看一眼仓库”或“这个项目现在什么情况”如果你的描述没有包含这些口吻的表达模型很难把它和用户意图对上。第二步看技能的数量是否太多了。当索引里同时存在七八个技能时模型的选择准确率会下降。我的经验是把技能按场景分组每个场景内不要超过3个技能。超出就要考虑做技能合并或拆分。第三步反省when_to_use是否写明确了。我发现很多人只写“用户询问仓库时使用”这叫说得不够精确更有效的写法是直接列举用户可能使用的自然语言模式比如“当用户说‘看看仓库’‘检查项目状态’‘最近有提交吗’等日常表达时”这会极大提高匹配率。第四步检查系统提示词是否给了模型选择支持。如果你的system_prompt一直告诉模型“你是一个博学的助手直接回答所有问题”那它确实不会意识到自己还能调用工具。需要明确写上“在回答前先考虑是否需要调用技能”。5.2 技能执行了但它“没按规矩来”有几次日志显示技能确实被调用了但模型没有遵守脚本输出的JSON结构而是在对话里自己“编造”了一番解释。这个问题的根源通常在于SKILL.md的正文区没有给出足够的解析指引。我的解决方法是在正文区写一段“使用示例”脚本输出示例 {repo_path: /data/repos/order-service, total_lines: 125000, todo_count: 45, commits_last_7d: 23} 拿到结果后请直接引用其中的数据向用户汇报不要自行推测或补充未包含在输出中的信息。这相当于告诉模型“你的工作不是分析JSON结构而是把JSON里的内容翻译成人话。”如果你不明确要求模型会忍不住做多余的事情比如自行计算一个“健康评分”这个评分往往和脚本的真实结果不一致闹出乌龙。5.3 多个技能边界模糊怎么处理当技能库慢慢变大之后新技能的设计者很容易造出一些职责重叠的技能。比如我已经有“repo-health-check”统计仓库规模又有“code-quality-report”做代码质量检查二者都涉及“统计代码行数”。结果模型就会经常选错。处理思路有两个。一个是明确互斥规则在各自的SKILL.md里写上“本技能不负责XX如需XX请参考其他技能”。另一个是考虑合并如果两个技能有大量重叠就说明边界没划好不如合成一个技能用参数来区分模式。我比较推荐后者因为技能包数量越少模型的选择负担越轻。另外在技能索引层面也可以做“推荐联动”当用户的问题命中了技能A但可能也需要技能B的信息时在技能A的输出里提示“建议同时调用技能B获取更多维度的数据”。但注意别在技能描述里过度交叉引用否则模型可能形成“所有技能都要一起调用”的坏习惯白白增加执行开销。5.4 技能包越来越“重”之后的问题技能执行时间越来越长、响应越来越慢是很多技能库膨胀之后的通病。这里要对技能拆分成更细粒度的子技能而不是在一个技能脚本里塞进所有功能。比如“repo-health-check”如果加入了分支比较、提交历史分析、代码审查建议等能力单次执行可能要跑几十秒这样用户在问一个简单问题时也会被迫等待很久。参考建议是一个技能包只有一个核心职责。用户问“仓库活跃度”和“代码审查”是两件事不要混在一个技能里。另一点是缓存与增量计算统计类技能如果输入数据没有变化可以直接缓存上次的执行结果不需要每次都重新扫描整个仓库。比如执行时间从10秒缩短到几十毫秒体验完全不同。5.5 踩坑记录你在实际操作中才有机会慢慢积累的经验最后分享几个我在实际运维中踩过的小坑希望你不要再踩。第一个坑是我在SKILL.md里写了“输出JSON”但忘了定义JSON的具体字段结构然后模型就自己创造了一组字段名。脚本输出的是total_lines模型却念成line_count虽然人一眼能看出来是一个意思但下游自动化解析就乱了。字段名从脚本到解析必须完全一致这不只是规范问题是要测试覆盖的。第二个坑是升级技能时不小心改了脚本输出格式但忘了同步更新SKILL.md结果模型还在按旧格式解析。后来我养成了一个习惯任何技能改动都提交到同一个代码仓库并执行双回归测试只改代码不改说明书的情况一律禁止合并。第三个坑是远程技能仓库的requirements.txt出现了版本冲突。某个依赖被更新后脚本启动直接报错。为了快速响应这类问题我脚本内部增加了一个依赖自检启动后先检查关键依赖版本如果不符合预期则打印清晰错误信息。一个小改动却省下了很多调试时间。6. 从技能库走向技能生态我的几点后续规划把核心技能包跑稳定之后我最近开始关注两个新的方向它们让技能的数量和价值都呈现出指数级增长的趋势。第一个方向是技能编排即让一个Agent在完成复杂任务时按顺序调用多个技能。比如用户说“帮我对order-service做一次每周例行检查并生成报告”Agent需要先调用repo-health-check获取数据再调用report-builder生成Markdown报告。这个流程如果能够稳定跑通技能的价值就不再局限于单一能力而体现在流程自动化上了。我目前的做法是先定义好简单的步骤清单在技能里预留“前置/后置技能”的钩子然后逐步增加编排能力。这里有一个重要经验技能编排不要试图在模型提示里同时给出太多技能否则它会迷失我倾向于分成两轮对话来推进或者用任务队列的方式逐条执行。每步只展示一个技能用户也能看得更明白出问题也更容易定位。第二个方向是我开始尝试让技能反向学习。所谓反向学习不是让模型微调而是在每次技能被成功调用后把用户的实际问法和技能的调用结果记录下来定期回填到SKILL.md的description和when_to_use中。比如有一个用户反复把“看看后端那个项目”理解为单位要统计代码行数这个说法原本在我的描述里没有我把它加到“when_to_use”里之后新用户再这么说也能触发正确技能。这个循环操作虽然简单但效果立竿见影——描述的覆盖面会越来越接近真实用户的语言习惯。另外一个值得投入的方向是“技能市场”的搭建。我在前面提到过内部HTTP服务方案最近我把这个方案推进成了正式版本。具体来说核心是把技能包的检索逻辑比如按技能名、场景、依赖标签做成接口让Agent在配置之后能够根据用户请求动态发现并加载新技能。本地实验跑通后我发现它带来的不只是分发效率的提升更是技能的“发现”效率——一个藏在技能库里的能力如果描述得好、检索得准它就能在团队里被反复利用而不是永远躺在Git仓库里吃灰。7. 写在最后的小体会做了这一轮技能化重构之后我个人最大的感触是Agent能力的上限不取决于模型本身而取决于你给它配了什么技能、技能说明书写得好不好。模型永远是那个聪明的实习生技能包则是你手把手教给它的操作手册和工具箱。一个只有聪明大脑但没有工具的实习生和另一个既有大脑又有完备工具的实习生能交付的成果天差地别。所以如果你在开发Agent的过程中遇到了类似的问题——行为不稳定、能力难沉淀、提示词越写越长——不妨从今天开始把单个技能包装进目录给SKILL.md起一个好描述再加上一条回归测试。这并不难做成之后你会很快感受到Agent的开发方式正在悄悄发生变化。
返回列表