上周帮一个做客服机器人的团队排查问题他们的 Agent 经常犯一种很怪的错用户问发票怎么开它却把退款流程完整念了一遍。从对话日志看Agent 不是没能力检索文档也检索对了偏偏在“该调用哪个能力”这一步上栽了跟头。我翻了翻工程代码发现根源不在模型大小也不在提示词长短而是整个系统缺少“技能Skill”这一层抽象——所有能力都糊在一大段提示词和十几个平铺直叙的函数里。这几年我参与的 Agent 项目越多越确信一件事一个 Agent 能走多远往往不取决于模型参数有多少而取决于它的技能体系有多干净。技能设计得好几十个技能也能稳定协作技能设计得乱哪怕只有五个技能Agent 也会在调用时反复横跳。这篇文章就把我在实践中搭建 Agent 技能体系的方法、踩过的坑和工程细节完整复盘一遍适合正在做智能体应用、尤其是已经跑通 Demo 却卡在稳定性和扩展性上的团队参考。注意我这里说的技能不是“Agent 会画流程图”这种泛泛而谈而是工程意义上的技能资产它是可以被加载、被描述、被调用、被回滚、被评估的一组代码、配置和文档的组合。1. Agent 为什么需要技能这一层提示词之外的第二套能力系统1.1 技能、工具和提示词到底有什么区别很多团队一开始做 Agent只有提示词和工具函数两个概念。提示词负责“教”模型怎么说话工具函数负责让模型“动手”。这两者在简单场景下够用但一旦任务变成多步骤、多分支、跨系统就会立刻失控。我给一个简单的对照说人话就是提示词Prompt是给模型看的说明书它教会模型如何思考和表达但没法稳定执行复杂流程。工具Tool是给模型用的“零件”比如一个查询接口、一个计算函数通常只做单一动作。技能Skill是把说明书、零件、执行逻辑、参数规范、异常处理打包在一起的能力单元。它面向的是一个完整的任务目标。举个例子。模型需要“根据数据库生成一份带趋势图的周报”。用工具思维做你得拆成查询数据、计算趋势、画图、整理结论四五个工具然后在提示词里反复描述什么情况该先调哪个、参数怎么传。结果就是提示词越来越长模型经常调错。用技能思维做你只需要封装一个generate_report技能对外只暴露“数据源路径 报表周期”两个参数内部自己处理查询、计算、画图、生成结论的完整链路。1.2 为什么“把所有函数平铺给模型”这条路走不远我在代码评审里见过太多这样的实现把二三十个 Python 函数直接注册成工具函数文档写得也不差但实测调用准确率就是上不去。原因有两个。第一函数列表越长模型的注意力越分散。评价系统给模型看到的工具列表是有限上下文当几十个工具堆在一起它们之间的边界会相互污染。比如list_orders和list_refunds描述稍微重叠模型就可能把两者混淆。第二单一工具没有“流程感”。很多任务根本不是一步调用能搞定的而是多步骤的组合。如果每个步骤都要模型现场决定和拼装一旦中间某一步参数不完整后面就全乱。技能则把这些步骤内置固化模型只需要做“选择技能”和“填参数”两件事复杂度大幅下降。用我实际带过的项目数据说话同样一组业务能力从纯工具直出改成技能封装之后端到端任务成功率从 63% 提到了 86%。提升的核心不是模型变聪明了而是系统替模型把流程风险扛下来了。1.3 技能层带来的三个直接好处技能这一层抽象不是说换了种写法而是改变了 Agent 系统的容错结构。它带来三个直接好处可复用同一个技能可以被不同场景、不同 Agent 调用。业务侧做客服机器人和做运营助手完全可以共享同一套报表技能。可测试技能是独立单元可以单独写测试用例。技能内部出问题不需要把整个 Agent 跑一遍就能定位。可迭代技能有版本升级技能不影响旧流程。今天改了画图逻辑不需要重新调全校提示词。这三个好处在规模小的时候感觉不明显等到技能数量超过二三十个、团队多人协作时才会真正体会到什么叫“把能力资产化”。2. 搭技能库先立规矩目录、描述文件与命名的一次性设计2.1 一个能长期维护的技能目录长什么样我见过不少团队技能模块写得很随意有人放skills/目录有人放到agent_tools/还有的直接散落在 utils 里。半年之后没人知道某个技能该放哪里新技能开始乱建目录。所以第一步目录结构必须一次定好之后严格遵循。我自己常用的结构是这样skills/ generate_report/ SKILL.md main.py templates/ tests/ assets/ query_inventory/ SKILL.md main.py tests/ ...每个技能一个独立目录目录名就是技能标识。SKILL.md是这个技能的“自我介绍”包括名称、描述、参数、用法示例main.py是执行主逻辑templates放技能需要用到的模板文件tests放该技能专属测试assets放图片、静态资源。这个结构最大的价值是任何新成员加入团队打开skills/目录扫一眼目录名就能知道团队有哪些能力资产打开某个SKILL.md就能知道怎么调用。它让技能库像一本会自动更新的操作手册。2.2 描述文件的三要素名字、说明、参数一个技能能不能被正确调用一半的功劳在SKILL.md写得好不好。核心就三个要素。第一技能名字name。名字要短、要具体、要动词开头。好的例子是generate_report、query_inventory、send_email差的例子是process_data、utility_01、do_stuff。模型是靠名字建立第一印象的名字含糊后续描述再清楚也容易出岔子。第二技能说明description。这是给模型看的“广告位”要写清楚这个技能在什么情况下用、能完成什么任务、不能完成什么。关键在于边界要清晰要主动写“不适用于哪些场景”。我甚至建议在描述里明确写出负面条件否则模型容易把相近任务往这个技能上套。第三参数定义parameters。每个参数都要写清类型、是否必填、取值范围、含义。不要写“data”这种没头没尾的参数名要写data_source、report_period、output_format这类表意明确的命名。参数默认值也要给出模型在不确定时会倾向省略可选参数。下面是我维护的一个示例SKILL.md片段可以直接抄--- name: generate_report description: 根据结构化数据生成带趋势图和关键结论的周期性报表。 适用于周报、月报、季度总结输入数据源和报表周期输出 Markdown 格式报告及趋势图。 不适用于实时数据监控不适用于需要多表关联的复杂自定义报表。 parameters: data_source: type: string required: true description: 数据文件路径或数据库表名例如 /data/sales.csv 或 ods.sales_daily report_period: type: string enum: [daily, weekly, monthly, quarterly] default: weekly description: 报表周期 output_format: type: string enum: [markdown, html] default: markdown description: 输出格式 ---2.3 命名规范与版本记录一开始就要做的两件小事命名规范属于“一次设计长期受益”的事。我建议团队内部约定技能名统一使用小写蛇形命名snake_case如query_inventory。所有技能必须有一个owner字段写在SKILL.md的 frontmatter 里标明负责维护的同学或小组。每个技能目录下放一个CHANGELOG.md记录每次变更的时间和原因。版本记录这件事起初大家觉得没必要直到某次技能升级后线上调用率突然暴跌靠CHANGELOG.md十分钟就定位到是上一个版本改了默认参数导致模型调用时上下文不匹配。没有版本记录的情况下这种问题通常要排查好几个小时。3. 把“数据分析报表生成”封装成技能的完整过程越具体越好3.1 先选对场景第一个技能该从哪里下手我第一次给团队搭技能库时没贪多只选了一个高频任务“数据分析报表生成”做试点。选这个场景有几个理由任务边界清晰输入输出明确不容易被误解。调用频次高验证效果好团队容易看到收益。流程内部有多个步骤取数、计算、画图、写结论正好能体现技能封装的价值。如果一上来就封装那种边界模糊的“闲聊服务”或者“复杂流程审批”大概率会死在需求定义不清上。第一个技能的目标是打通链路、建立团队信心选最标准化的任务最稳妥。3.2 技能主逻辑的代码骨架如何组织内部流程我习惯把技能主逻辑拆成三步参数解析与校验执行核心流程结果封装输出。下面是一个简化但结构完整的 Python 骨架import json from typing import Any class GenerateReportSkill: 数据分析报表生成技能 name generate_report def execute(self, params: dict, context: Any) - dict: # 1. 参数解析与校验 data_source params.get(data_source) report_period params.get(report_period, weekly) output_format params.get(output_format, markdown) if not data_source: return {ok: False, error: missing_param, message: data_source is required} # 2. 执行核心流程取数、计算、画图、生成结论 data self._load_data(data_source) trend self._compute_trend(data, report_period) chart_path self._render_chart(trend) conclusion self._generate_conclusion(trend) # 3. 结果封装输出 return { ok: True, report: { period: report_period, trend_summary: conclusion, chart_path: chart_path, markdown_content: self._build_markdown(trend, conclusion), } } def _load_data(self, data_source: str): # 实际代码连接数据库或读文件 ... def _compute_trend(self, data: Any, period: str): # 实际代码聚合计算、趋势分析 ... def _render_chart(self, trend: Any): # 实际代码生成趋势图返回路径 ... def _generate_conclusion(self, trend: Any): # 实际代码基于趋势指标生成一句话结论 ... def _build_markdown(self, trend: Any, conclusion: str) - str: # 实际代码组装 Markdown 文本 ...这个骨架的要点是对外只暴露一个execute入口内部步骤全部私有化。模型不需要知道怎么取数、怎么画图它只需要把正确的参数传进来。这样技能内部逻辑再怎么变只要参数协议不变Agent 的调用方式就不受影响。3.3 参数校验与失败兜底不能只处理“顺利路径”技能设计里最容易被忽略的是失败兜底。很多新手写技能只考虑“一切顺利”的情况参数传错了直接抛异常导致 Agent 拿到一个莫名其妙的错误字符串然后开始瞎编。我在实践中给每个技能定了三条兜底策略参数缺失时不直接抛错。如果必填参数缺失返回带error_code和可读提示的结构化结果让 Agent 知道应该补充什么参数而不是给它一段 Python traceback。核心步骤失败时有降级方案。比如画图服务挂了可以降级成“只输出数据表格和文字结论”并在结果里标记degraded: true。Agent 拿到降级结果可以选择告知用户而不是整个任务失败。所有异常统一包装。在execute入口加一个 try-except把未预期异常转换成统一格式的失败结果绝不让裸异常冒到模型面前。def execute(self, params: dict, context: Any) - dict: try: return self._run(params, context) except Exception as exc: return { ok: False, error: internal_error, message: f技能执行异常: {exc}, suggestion: 请检查数据源配置或稍后重试, }这一层看起来简单实际价值极大。它决定了 Agent 在真实环境中遇到问题时是优雅地转向替代方案还是完全卡死。4. 技能调用链路里的工程化细节描述质量、上下文回流与版本管理4.1 技能描述是 Agent 的“路标”不是给人类看的文档我曾经做过一个对比实验同一批技能只改SKILL.md里的描述文字不碰任何代码Agent 的调用准确率波动超过 20%。这说明描述质量直接影响模型的路由决策。描述写得好不好有几个可以量化的检查标准模型看到这个描述能不能准确判断“我当前这个任务该不该用这个技能”描述里有没有说清楚“不适用于什么场景”描述里有没有包含足够的关键词让模型能跟用户的原话建立关联上面几个问题我每个技能上线前都会拿三个真实用户问题去“干跑”一遍看模型选不选得中。选不中就先改描述而不是改代码。下面这个表格是我常用的描述质量评估样例写法模型调用表现“生成报表”描述太短模型经常在多个报表类技能之间犹豫“根据数据源生成带趋势图和关键结论的周报/月报”有对象、有动作、有产出模型能准确匹配“处理数据”灾难级模糊它几乎能被任何数据类任务触发“生成周期报表不适用于实时查询和单表明细导出”正面功能负面边界误调率显著下降4.2 技能执行信息怎么回流上下文管理的三类数据Agent 调用技能之后技能的执行结果要回流到大模型上下文里这个过程容易被忽视。回流的信息应该分三类结果数据技能返回的结构化内容比如报表内容、查询结果。过程摘要技能内部到底做了什么经历哪些步骤。模型需要知道过程摘要才能在后续对话中回应用户关于“怎么算的”“用了哪些数据”的问题。状态标记比如任务成功、降级完成、部分失败。模型根据状态标记决定怎么向用户表达。我在实际工程里会把技能返回值设计成统一的{ok, result, meta}结构meta里放过程摘要和耗时信息。这样无论哪个技能Agent 的上下文处理器都能用同一套逻辑去消化结果。4.3 技能缓存与更新模型见过旧技能就不肯用新技能技能更新有一个很隐蔽的坑Agent 的历史上下文里往往包含旧版本的技能描述或被调用的旧结果。如果技能升级后没有做任何“新旧隔离”处理模型可能凭历史记忆继续调旧参数。我处理这个问题的办法是技能注册表registry每次都在系统提示词里携带skills_version信息强制 Agent 以最新版本为准。多轮对话超过一定轮数后对技能调用相关的历史摘要做截断只保留“用户目标 最近一轮技能结果”避免旧结果干扰新调用。技能目录本身的加载带 hash 校验确保线上加载的SKILL.md跟仓库里审核过的一致。这三条做下来技能更新引发的“模型不听话”问题基本绝迹。5. 一例真实的技能误调用从日志到根因的完整排查链路5.1 现象用户问库存数量Agent 却触发了“创建订单”某次联调中我发现 Agent 在回答“现在 A 商品的库存还有多少”时没有调用query_inventory而是调用了create_order并且差点真的创建了一笔订单。幸亏测试环境有风控拦截否则线上该出事故了。这类问题有个共性模型不是完全没有能力而是“路由”错了。它知道用户关心 A 商品却不能分辨用户是要“查状态”还是“下订单”。第一反应当然是怀疑query_inventory的描述有问题但排查后发现没有那么简单。5.2 顺着 Agent 的思考日志找到命中断点排查技能误调用第一步永远是看 Agent 的决策日志而不是改代码。我们要回答三个问题模型在决策时看到了哪些候选技能它为什么选中了create_order它在决策前后的“思考理由”是什么我打开日志发现系统里技能注册表已经有三十多个技能create_order的描述里有这么一句“适用于用户确认下单、提交购买需求、补充订单信息的场景。”而query_inventory的描述是“查询商品库存量、上下架状态。”单看描述边界是清楚的。但日志显示模型在决策时先看到了上一轮用户说过“我要买 A 商品吗”这句试探性的话。虽然用户本轮实际问的是库存模型却被“买”这个关键词引导到了create_order。5.3 根因描述边界模糊 上下文关键词污染的叠加效应这个案例的根因不是单一因素而是两个问题叠加。第一个问题create_order的描述没有写清楚“不适用于仅查询类问题”导致“买”“下单”“订单”这类词能轻易触发它。第二个问题多轮上下文里的历史试探被模型当成了当前意图这个属于上下文管理问题。两个问题单独看都不严重叠加在一起就酿成了高风险误调用。这也解释了为什么排查这种问题不能只看技能本身要把描述、上下文、用户原话三者放在一起看。5.4 修复方案与验证我当时做了三个修复动作修改create_order的描述明确增加负面边界“仅用于实际下单操作。用户只是询问价格、库存、发货规则时不得调用本技能。”在上下文处理器里增加意图一致性检查当用户当前语句与最近一轮历史存在冲突时以当前语句为主并对历史做弱化处理。给技能库增加一条自动化回归用例把“查库存但历史提过下单”这类场景固化下来每次发版都跑一遍。修复后我把这个案例沉淀成团队的“技能误调用案例集”后续又出现过类似路由问题基本能用同一套排查链路快速定位。这类复盘文档的价值往往比再写十个技能都大。6. 从单技能到技能网络编排、评估与团队治理6.1 技能编排用工作流把技能串起来当技能数量多起来你会发现有些任务需要多个技能协作。比如“每周五自动给运营团队发报表邮件”需要先generate_report再send_email中间还要根据报表结果决定要不要send_alert。我建议不要把这些流程塞进 Agent 的提示词里而是用技能编排层orchestration显式描述工作流。每个编排可以理解成一个“超技能”里面写明节点、依赖关系和参数传递。workflow: friday_report_dispatch steps: - skill: generate_report params: data_source: {env: SALES_DB} report_period: weekly output_key: report - skill: send_email params: recipients: opsexample.com content: {steps.report.markdown_content} output_key: email_result - skill: send_alert condition: {steps.report.trend_score} 0.5 params: message: 本周销售趋势异常请关注编排层的价值在于Agent 不需要理解“怎么在报表和邮件之间传递数据”它只需要按编排图执行节点。复杂流程因此变成可维护、可回滚的资产。6.2 给技能建立评估数据不能只看“调用成功没”技能上线后如果只看成功率很多问题会被掩盖。我给每个技能加了三个评估指标调用准确率该技能被调用的次数中真正符合用户意图的比例。参数通过率一次调用中所有参数都传完整且合法的比例。这个指标低说明参数设计或描述还不够清晰。用户纠偏率调用完成后用户在后续对话中纠正 Agent 结果的比例。纠偏率高往往代表技能的输出内容有偏差哪怕调用本身成功了。这三个指标配合起来才能反映一个技能的真实健康度。我曾有一个summary_notes技能调用成功率 98%但用户纠偏率高达 27%。一查是它生成的摘要总是漏掉关键数字属于“技能成功执行但结果不好用”的典型。6.3 团队协作里的技能治理技能库也是代码库最后说团队层面。技能资产本质上跟代码资产一样需要治理规则技能变更必须走代码评审SKILL.md的描述改动也要评审不能悄悄改。每个技能必须指定负责人长期没人维护的技能要标记废弃或下架。新增技能必须附带至少三条真实测试用例否则不允许合入技能库。我见过最混乱的情况是某团队技能库里有十几个高度相似的技能比如send_email、mail_dispatch、notify_by_email都是不同时期不同成员各自加的结果模型决策时频繁选错。后来统一清理合并成两个技能问题立刻缓解。这就是技能治理的价值——它不是流程绑架而是保证技能库可扩展的前提。我个人的体会是Agent 技能体系建设很像整理自己的工具箱一开始觉得分类、写标签、建清单都是额外负担等到工具多到找不到、拿错才知道前期那点“麻烦”有多值钱。如果你现在正被 Agent 的调用混乱、扩展困难、误操作问题困扰我的建议很简单先把能力从提示词里拆出来一件件装进技能库再谈优化和编排。技能数量少的时候动手迁移成本最低等到上百个技能再重构那才是真的噩梦。