从Prompt到Skill:Agent技能设计方法论与实战避坑指南
先说我做这块的真实感受2024年之前我给模型写prompt本质上是写“一次性剧本”2024年之后我基本只在做一件事——把大量“一次性剧本”重构成“可被智能体按需调用的技能包”。agent-skills这个名字听着像某个项目的代号但在我看来它代表的是Agent工程化里最值钱的那层抽象技能体系。如果你还在用一坨几千字的system prompt去约束智能体换一个业务场景就全乱套那你确实需要认真看看这篇文章。我下面要讲的是一套我从多个实际落地项目里总结出来的Agent技能设计方法论。不含空话全部是能直接抄回你项目里的思路、模板和避坑经验。无论你是刚接触Agent开发还是已经在生产环境里维护着一堆工具函数这篇文章都会让你重新想清楚“技能”到底该怎么设计。1. 先把“给Agent写Prompt”升级成“给Agent造技能”这一节我想先掰扯清楚一个核心认知为什么prompt和skill是两种完全不同的东西。这个认知不建立起来后面所有工程手段都是空中楼阁。1.1 一次性Prompt的真实痛点很长一段时间里团队里同学做Agent功能方式是这样的接到一个需求“让助手帮用户写周报”于是打开一个prompt文件开始写“你是一个周报助手你需要在每周五下午总结用户本周的工作内容输出格式为……”。写完塞进系统里测几个case感觉还行上线。两周后产品过来说用户还想让它自动把周报同步到飞书文档。于是又把飞书文档的上传说明继续塞进同一个prompt里。再过两周用户觉得周报语气太官方希望可以调节于是prompt里又加了语气档位。这种做法的结局我见过太多次prompt越来越长模型开始忘掉关键指令不同功能互相干扰最终只能整个推倒重来。本质原因在于我们是在给模型写一段“一次性表演剧本”而不是在给Agent造一个“可被调度和编排的能力单元”。一次性的prompt和可复用的技能区别在四个维度一次性的prompt是静态的技能是带接口的。技能拥有明确的名称、描述、输入参数、输出契约Agent可以根据用户意图动态决定是否调用。一次性的prompt混在总上下文里技能是隔离的。技能只在被调用时加载对应逻辑不影响其他无关对话。一次性的prompt无法治理技能是可观测的。技能能记录调用频率、成功率、耗时可以被版本管理和回归测试。一次性的prompt靠运气技能靠契约。输入输出有结构错了能定位是哪一步的问题。1.2 技能的本质是什么我习惯把技能定义为“一个能被智能体感知、判断、调用的能力封装单元”。它由五个核心部件组成意图域什么时候该用这个技能什么时候不该用。这一部分直接决定智能体会不会乱用工具。输入契约调用这个技能需要哪些参数、参数的类型和约束。执行器真正干活的逻辑可以是模型推理、代码执行、外部API调用或者三者的混合编排。输出契约返回给智能体的结果长什么样是纯文本还是结构化JSON是拿到数据还是执行完成的状态。观测与反馈每次调用的日志、耗时、token消耗、成功与否这些数据要回流用于迭代。你可以把技能理解成给智能体提供了一种“乐高积木”。整体回复能力是基础底盘技能就是那块带标准卡槽的积木。只有底盘没有积木什么也搭不起来积木没有标准卡槽互相接不上也白搭。如果你已有现成的Agent应用可以先做一件事把当前的system prompt按功能块画出来然后问自己哪些功能块是在任何对话里都必须存在的哪些是仅在特定场景才需要的。后者就是天然的技能候选。2. 技能长什么样一个可落地的技能骨架下面给出一份可以直接抄回去用的技能定义模板。它不依赖任何特定框架无论你用的是LangChain、Coze、Dify还是完全自研的调度器这套骨架都能平移过去。2.1 技能的元信息层技能元信息是给智能体“看”的作用是让路由器能在正确的时机选中这个技能。很多人忽略这部分结果辛辛苦苦写好的技能永远不被调用。字段作用设计要点name技能唯一标识统一小写加下划线比如webpage_extract别用带空格的描述性句子display_name人类可读名面向测试和运营人员比如“网页正文抽取”description技能的触发条件说明这一段是最重要的路由依据写的是“什么时候用”不是“能干什么”version技能版本每次修改prompt或执行逻辑都必须升版author / owner负责人技能出问题能找到人tags标签分组比如information_extraction、reporting方便技能集市检索visibility可见范围private / team / public控制谁能调用这里我特别想强调description的写法。大多数人写“这个技能可以用来抽取网页正文”这在智能体路由时几乎等于没写。正确写法是描述“触发场景输入约束典型用法”例如当用户提供一个URL并要求获取该页面的正文、标题或主要内容摘要时使用。适用于新闻文章、博客和技术文档页面。如果用户要求的是整个网站的爬取或批量采集不要使用本技能改用site_crawler。这种写法其实是在给路由模型划边界既告诉它什么时候该用也告诉它什么时候不该用。我见过太多案例就是因为description里没写负向场景导致Agent在用户要求批量采集时也去调单页抽取技能跑了几百次才发现。2.2 技能的输入输出契约输入输出契约决定技能能否被编排。弱类型、自由文本的参数会让后续的一切组合都变得不可控。输入参数建议用JSON Schema描述最小的字段包括参数名、类型、必填与否、描述、示例值。下面是我常用的写法{ name: webpage_extract, description: 当用户提供URL并要求提取页面正文、标题或摘要时使用。仅适用于单个页面的内容提取。, parameters: { type: object, properties: { url: { type: string, format: uri, description: 待提取页面的完整URL必须包含协议头。, example: https://example.com/blog/agent-skills }, extract_type: { type: string, enum: [full_text, summary, title_only], default: full_text, description: 提取内容的粒度 }, max_length: { type: integer, description: 正文提取的最大字符数, default: 5000 } }, required: [url] }, output: { type: object, properties: { title: { type: string }, content: { type: string }, content_length: { type: integer }, extract_status: { type: string, enum: [success, partial, failed] } }, required: [title, extract_status] } }注意输出原则上要结构化至少在结果里包含一个状态字段。extract_status这个字段非常关键它告诉上层调用方这次提取是完整成功还是部分成功。假如你后面要让Agent基于这个结果生成摘要它看到partial状态就会知道内容可能不完整摘要质量需要降级处理而不是傻乎乎的继续加工。2.3 技能的执行器与上下文窗口策略执行器是技能真正干活的部分。三种形态纯模型推理、纯代码执行、混合执行。我强烈建议优先做混合执行即能用代码做的预处理用代码做需要语义理解的环节再交给模型。为什么举个实际例子做网页提取技能的时候最蠢的做法是直接把整个网页HTML塞给模型说“帮我提取正文”。HTML里大量脚本、样式、广告噪音会显著摊薄模型的注意力又加倍消耗token。正确做法是先做两步代码预处理用readability算法抽离正文框架用正则去掉script和style标签。只有经过清洗后的文本才进模型做最后的语义抽取。同一条逻辑换成任何技能都成立能用规则解决的问题不要花模型成本去推理。上下文窗口策略同样容易被忽视。技能内部使用模型时怎么控制窗口如果所有技能都把完整上下文塞进同一个对话窗口多个技能叠加后必然溢出或互相干扰。我建议每个技能定义自己的“上下文视野”全局上下文只放用户身份、业务背景、长期目标这是所有技能都能看到的。局部上下文技能执行时需要临时获取的资料执行完就清理不进全局记忆。隔离上下文技能内部多步推理时的中间结果外部不可见。很多出问题的Agent项目问题都出在技能最终被调用时读取了太多与其任务无关的全局信息。我在做竞品分析Agent时就遇到过网页抽取技能在提取一个产品页面时模型居然参考了对话历史里另一个跑车评测的结论导致提取结果出现严重偏差。这就是上下文污染。后来我把抽取技能的局部上下文强制隔离历史对话只有经过用户明确授权才会注入问题基本消失。3. 从需求到上线一个技能的完整开发链路确定了技能骨架接下来就是具体的开发流程。我这里把经验浓缩成五个阶段拆解、建模、实现、验收、注册。每个阶段都有容易翻车的细节。3.1 拆解把大需求切成技能原子技能设计的第一个难点是粒度。以“自动生成周报”为例新手会把它直接定义成一个技能。这其实是个大杂烩内部藏着数据采集、事项汇总、行文组织、格式转换等一堆子能力。正确思路是先拆解成不重叠的技能原子。task_query从项目管理系统拉取本周任务只输出结构化任务列表。worklog_summary把任务列表和备注压缩成周报素材按时间或项目分组。report_formatter把素材组装成目标格式的周报支持模板切换。doc_publisher将最终文本发布到指定文档平台。为什么要这么拆因为可复用性在原子粒度上才能最大化。task_query不只服务于周报它还能服务于“今天有什么待办”或者“哪个人任务积压最多”。如果你把它们糅进一个“生成周报”技能里后面这些需求就都得重写一遍。拆解时有个经验法则如果一个技能内部的执行步骤可能被其他技能重复使用赶紧拆出来。如果你发现一个技能里出现了“如果……就调用另外一套逻辑”的分支也考虑拆。3.2 建模用单测思维定义输入输出建模阶段要完成技能的核心文件。目录结构建议从第一天就规范化不要等技能多了再治理。我当前项目的技能目录大致是这样的skills/ webpage_extract/ skill.yaml execute.py prompt.md tests/ cases.json main.py README.md report_formatter/ skill.yaml execute.py prompt.md tests/ cases.json main.py README.mdskill.yaml放置第一节里说到的元信息和输入输出契约。execute.py是执行器入口如果技能内部需要调用模型就在这个文件里组装prompt并处理返回结果。prompt.md是技能内部使用模型时的提示词模板注意它是被隔离加载的不参与主对话。tests/cases.json是典型输入输出对用于验收和回归测试。建模阶段有个细节给每个输出字段都设计“异常时的默认行为”。例如网页提取失败时content是返回空字符串还是返回“提取失败”的提示我的建议是永远不返回空字符串至少返回一个状态码加说明否则上层Agent会因为拿不到任何信息而开始胡乱猜测编造不存在的网页内容。大模型在没有信息时会倾向“幻觉填充”这一环必须靠契约堵死。3.3 实现写执行器时的三个优先原则实现执行器时我有三个优先级排序第一稳定优先于聪明。执行器里少用奇技淫巧优先选择社区验证过的库和工具。网页抽取就优先用readability和beautifulsoup不要自己造HTML解析器与外部API交互就用官方SDK。你的目标是让技能100次调用有99次表现一致而不是偶尔超常发挥。第二失败可恢复。技能内部任何一步出错都要能返回可以被上层理解并继续处理的错误信息。捕获异常后转成契约里定义的状态码不要直接抛出底层traceback。上层Agent看到一坨traceback时基本是懵的它没法从这里恢复对话只能硬编一个答案。第三限流和大响应保护。给技能调用加超时给模型输出加最大长度限制。我曾经有个技能在某个小概率分支里不断循环调用模型一路烧掉了几百块token额度。后来所有技能都强制加上超时和重试上限。3.4 验收让每个技能都有回归测试集验收阶段很多人觉得“demo跑通了不就完了吗”但技能类的功能后面改prompt、换模型版本、换依赖库都会让行为悄悄漂移。一份覆盖典型场景的回归测试集是必须的。tests/cases.json的格式建议如下[ { id: extract_001, input: {url: https://example.com/blog/agent-intro, extract_type: summary}, expected: { extract_status: success, content_contains: [agent, skill] }, tags: [happy_path] }, { id: extract_002, input: {url: , extract_type: full_text}, expected: { extract_status: failed }, tags: [invalid_input] } ]测试断言不要只写“输出成功”还要写“输出必须包含某些关键信息”。因为大模型输出的不确定性全等断言很难稳定但包含性断言一般能做到。你还可以加“非包含性断言”比如输出里不得出现特定幻觉词。回归测试要纳入日常开发流程。每次改prompt、每次升级模型版本、每次改第三方库都跑一遍全量回归任何一个历史case挂了就要停顿排查。没有这套保障技能迭代就是裸奔。3.5 注册技能上线的最后一公里注册阶段把写好的技能登记到技能仓库或注册中心。注册不仅仅是上传文件要确保技能描述通过验收让一个不了解内部实现的同事看description看他能否在给定场景里正确选出该技能。权限申请完成技能需要的API密钥、存储权限、数据权限均已开通且记录在技能元信息里。上线后用一小部分流量灰度不要一次性全量放开给所有用户先放5%的流量观察调用成功率和反馈。灰度的价值在Agent技能上尤其明显。很多问题只会在用户多样性输入下暴露灰度期能拦下大批翻车事故。比如同样一个网页抽取技能处理英文技术博客没问题但遇到中文图片为主的电商详情页时抽取到的正文几乎为空。这种真实分布差异不在灰度期观测你根本发现不了。4. 技能仓库与组合从单技能到技能体系技能多了之后单点设计问题开始让位于体系治理问题。目录组织、命名规则、技能之间的依赖关系都会成为能不能持续演进的胜负手。4.1 技能仓库的目录与命名规范我的团队目前维护了四十多个技能分散在产品助手、运营后台和数据分析三类场景。如果没有统一规范第三天就开始乱。命名上我强推三段式领域_动作_对象。例子crm_query_contact客户查询crm_create_followup跟进记录创建web_extract_content网页内容抽取report_format_weekly周报格式化这种命名最大的好处是技能在一两百个时依然可以通过前缀快速定位。目录则按业务域划分不按技能类型划分。按类型划分比如全放“工具类”、“数据处理类”的问题是跨域复用会很痛而且新业务接入时无法判断这个技能跟自己的关系。按业务域划分更贴近调用方的思考习惯。4.2 技能组合上层编排器与子技能嵌套我坚持“聪明在上层简单在底层”的组合原则。底层技能都保持单一职责只干一件事真正复杂的编排逻辑放在上层流程里。编排放置的载体可以是流程、Agent实例或者决策树但不能是底层技能自身。举一个仪表盘助手的例子。用户说“帮我分析这两个渠道的获客数据差异”上层编排器接到请求后依次调用data_query_channel_metrics查询两个渠道的关键指标。data_compare_segments对比各指标差异输出显著差异清单。report_format_analysis把差异清单组织成段落化分析报告。这三个技能本身都足够简单任何一个出问题可以直接单独替换。组合的复杂度全部在上层编排器里管理。这样可以避免“技能内部调用技能”带来的循环引用和隐式依赖也方便排查问题。要特别克制“技能内嵌技能”的冲动。底层技能A内部调用技能BB调用C链路一旦拉长现场排查会让人抓狂。你根本不知道是A的参数出了问题还是B的输出没传给C。技能调用的深度我建议尽量控制在两层以内。超过这个深度就开始怀疑你的编排层是不是失去了作用。4.3 技能版本与模型版本强绑定技能迭代过程中最隐蔽的坑是“同样的技能换个模型版本效果完全不同”。同一个抽取prompt在GPT-4上稳定换到小尺寸开源模型上可能频繁漏字段。所以版本管理里不能只记录技能自身的version还要记录依赖的模型版本、prompt版本、依赖库版本。我的技能元信息里有这么一段runtime: model_provider: azure_openai model_name: gpt-4o-2024-05-13 model_temperature: 0.2 prompt_version: 3.1 dependencies: readability-lxml: 0.8.1这么做有几个直接好处技能出问题时可以迅速对比“上次正常时用的什么模型什么prompt”升级模型时能一眼看出哪些技能绑定了旧模型需要重点回测新同学接手时不用靠猜就知道这个技能实际跑在什么环境下。5. 几个我真实踩过、花了大代价才爬出来的坑这一节写下我认为最值得反复看的实战教训。每个坑都来自真实生产环境不是理论推演。5.1 粒度过细技能数量爆炸最初做Agent助手时我秉持“每个子动作都做成技能”的理想结果两周后技能数量到了八十多个其中“获取用户名”“获取用户头像”这种只有一行代码的逻辑都独立成了技能。这带来了两个问题一是路由质量急剧下降。Agent面对八十多个相似技能时描述稍微模糊一点就选错。二是维护成本爆炸改一个公共逻辑要波及十几个技能因为它们各自拷贝了一份实现片段。后来我划了一条粒度边界如果一个技能没有独立的输入契约、没有独立的业务含义、没有独立的失败模式它就不配成为一个技能充其量只是技能里的一个步骤。细化到函数级别的“技能”是把工具函数当技能用会让整个技能体系失去抽象价值。5.2 上下文污染导致幻觉和错误引用这个坑前面提到过但值得展开说。我做过一个法律文本问答助手技能A负责检索法条技能B负责根据检索结果撰写解释。起初技能B在执行时能看到完整的对话历史包括用户之前聊过的无关内容和模型之前说过的话。结果出现了什么一次用户问“劳动合同解除的补偿标准”模型生成的解释里居然引用了对话早期用户随口提过的一句经济补偿金数字把那个数字直接当成了标准答案。排查后发现技能B在生成时确实随手把对话历史里出现过的数字当成了法条内容。我们的修复方式是把技能执行时的上下文彻底隔离技能B只接收结构性输入一个法条检索结果列表和一个明确的问题描述。如果模型不确定必须输出“信息不足”而不是猜测。这个改动让错误引用率下降了80%以上。经验说透一点模型天然会把上下文里出现的所有信息都当作可参考依据哪怕这些信息只是用户的闲聊。技能设计者必须用上下文隔离机制堵死这条幻觉来源。5.3 评测缺失prompt微调直接带崩线上还有一个更常见的坑就是prompt迭代没有回归验证。我们有个促销文案生成技能接需求的同学觉得“文案要更有感染力一点”直接改了prompt里的一句话没有跑测试集上线当天促销文案里频繁出现“错过今天再等一年”这类夸张表述被运营投诉。跑了一下回归测试才知道原来测试集里有明确要求“不得使用时间紧迫性话术”。这件事之后我把技能prompt的任何修改都纳入评审并强制要求跑通全部回归测试case后才能发布。无论改一个词还是一个标点都算一次变更。规则本身可能严苛了一点但面对模型输出的不确定性没有这种“宁可麻烦不可失控”的态度线上翻车是迟早的事。5.4 权限边界模糊技能被滥用最后一个是安全和治理层面的。技能通常伴随数据访问权限。我在做数据分析Agent时设计了一个“按省份查询销售数据”的技能权限是查询所有省份的数据。测试时没问题上线后运营同学发现这个技能能被套用来查询任何区域的明细包括一些敏感业务线的数据。原因是Agent在理解请求时把权限控制完全交给了模型判断而模型并没有那么强的边界意识。后来我们给所有技能加上了强制权限校验层技能在数据层、API层而不是在模型层做权限拦截。技能的输入参数会被后端服务再次校验用户不在授权名单内直接拒绝。这个经验也推荐给所有做Agent技能设计的人技能权限不是写进prompt里让模型去自觉遵守而是在执行器代码里硬性拦截。6. 关于技能安全与边界控制我坚持的几个底线谈到安全很多人的第一反应是“防提示词注入”但在技能体系里安全远远不止这一层。我把这里必须坚守的底线归纳为以下几条。6.1 技能权限最小化每个技能只申请完成自身任务所需的最小权限。如果一个技能只需要读取某个数据库的特定表就不要给它整库只读权限如果一个技能只需要发送站内信就不要给它邮箱发送权限。尤其要注意的是技能之间的组合放大效应。单个技能权限很小但Agent在编排时可能把多个技能串联起来形成一条绕开权限限制的链路。比如技能A能读取客户列表技能B能创建营销任务单独看问题不大组合起来就变成了“给全量客户群发营销消息”。所以在权限设计时必须考虑组合后的能力不能只看单个技能。6.2 技能调用的可审计性所有技能调用都必须记录审计日志谁在什么时间调用了什么技能输入参数是什么输出结果是什么以及调用链路是怎样的。这个日志不仅要服务排查还要能服务追责和合规审计。我见过不止一个项目在出了事故之后才发现没有任何技能调用日志只能靠用户描述去猜发生了什么。技术演进再快可审计性是不可让步的底线。它也是一种对技能设计者的保护一旦有问题日志会告诉你到底是模型决策错了还是技能执行错了。6.3 用户确认机制不能省涉及发送消息、创建订单、删除数据、对外发布这类高影响动作必须有用户确认机制。确认机制应当放在技能执行链路里而不是依赖模型自觉去问用户。自动化的价值不是取消用户控制而是把低价值环节省掉高影响环节依然保留人的决策权。我目前设计高影响技能时会在技能返回里加一个“预执行状态”返回即将执行的动作摘要只有上层收到用户确认后才真正调用执行函数。这确实让流程多了一步但是这一步换来的信任和安全绝对值回票价。6.4 敏感信息的脱敏和隔离技能在处理用户数据时要遵循数据生命周期的最小化原则。只保留完成任务所需的数据处理完即清理不写入日志不进长期存储。尤其是涉及个人身份信息、财务信息、联系方式时能脱敏的尽可能脱敏。Agent技能的未来一定在“可复用”与“可治理”两个词上。你今天造技能时给它设计的接口是否清晰描述是否准确边界是否强硬权限是否最小都会在几个月后技能数量翻倍时十倍百倍地体现出来。最后说一个我自己的体会技能的工程化没有想象中那么玄乎它更像是在Agent应用里建立一套“谁在什么条件下可以拿什么数据做什么事”的契约体系。把契约想清楚执行器反而是最简单的一环。如果你正在规划Agent项目我建议从今天起把“给Agent写提示词”改为“给Agent造技能”。这不仅是命名习惯的变化更是认知框架的转变。两者的差别会在你的Agent第一次面对复杂真实任务、第一次需要多人协作维护、第一次接受安全审计的时候给你截然不同的结果。