资讯动态

AI Agent技能工程化:从零散函数到生产级Skills实践

发布时间:2026/10/16 7:38:35 来源:尧图企业网站定制
给 AI Agent 配技能这件事我刚接触时以为很简单——让模型能调用几个函数而已。直到有次我把一个内部数据查询 Agent 从 Demo 推到生产才被现实狠狠教育了一课模型调用技能的失败率高得离谱、两个技能之间职责重叠互相抢活、参数边界模糊导致脏数据直接打进数据库、技能抛了异常整个对话就僵死在那里。我把这次经历复盘之后把零零散散的函数集合重构成一套带目录规范、元信息注册、校验机制和可观测性的工程工作流SkillsAgent 的稳定性才真正立住了。这篇文章不是什么高深理论就是我这段时间给 Agent 配技能的实操记录。我会从设计思路讲到核心细节再给你一套可以直接照抄的落地流程最后把那些模型不听话的排查技巧一次性说清楚。适合正在做 Agent 开发的工程师、准备把原型推向生产的团队也适合那些对 Agent 底层机制好奇、想自己搭一套技能系统的朋友。1. 一次重构让我重新理解了 Agent 的 Skills1.1 给 Agent 配技能到底是什么先说结论Skills 就是模型自身参数之外按照约定暴露给模型的一组外部能力接口。模型本身不知道今天的天气也不懂怎么查数据库但它可以通过技能的名称、描述、参数定义来感知这些能力的存在并在规划任务时决定调用哪个、怎么调。我用一个类比来帮团队新人理解这件事把大模型想成一个刚入职的实习生Skills 就是给他的一套工作手册和工具清单。实习生不需要懂每个工具的电路原理但他必须知道什么情况下用哪个工具、工具需要什么输入、输出是什么格式。技能系统要做的就是把这些信息对模型可见、可用、可验证。很多人把 Skills 简单理解成函数的集合这是第一个认知误区。函数只是执行逻辑Skills 包含的其实是四层东西能力声明这个技能是干嘛的、参数契约模型怎么传参进来、执行逻辑实际干活的那段代码、反馈机制成功怎么返回、失败怎么报错。任何一层缺位这个技能在生产环境中都会变成定时炸弹。1.2 为什么零散函数撑不起生产级 Agent我在第一个 Agent 项目里技能就是一堆普通函数直接挂到模型能调用的列表里。刚开始一切正常Demo 跑得飞起但一上生产就露馅了。第一个问题是无规范。团队里几个人各写各的有人用驼峰命名有人用下划线命名模型在预测参数名的时候经常猜错。第二个问题是无边界。查询用户和获取用户信息两个技能并存看着好像有点区别但模型的语义理解让它俩频繁冲突经常出现用户问 A模型调了 B 的情况。第三个问题最致命——无观测。技能抛异常的时候整个对话就僵在那里没人知道是参数错了还是下游服务挂了只能翻原始日志大海捞针。后来我意识到Agent 要变成可靠的生产系统技能必须从代码片段升级成工程构件。工程化之后有几个直接的好处技能是独立模块换模型、换框架都能复用每个技能可以单独跑单元测试修改一个不影响全局调用链、耗时、成功率都有记录出问题能回溯新增能力不用改业务逻辑注册一个新技能就行。1.3 四个设计原则先定边界再谈能力重构之前我花了整整一天想清楚了一套原则后面所有技能都照着这个标准来。原则一技能要原子化。一个技能只做一件事粒度尽量小。宁可把处理订单拆成查询订单修改订单状态计算订单金额三个技能也不要写一个万能函数。原子化的好处是每个技能的描述边界清晰模型一眼就能判断这件事是不是该用这个技能。原则二描述是写给模型看的不是写给人看的。代码注释写给同事技能描述写给模型。要写清楚触发条件、适用场景、参数含义、禁令越像训练语料里的 API 文档越好。这一点我在后面会展开讲它是整个技能系统的灵魂。原则三参数必须严格 Schema 化。每个参数都要明确类型、取值范围、默认值、是否必填运行时还要做真实校验。别指望模型每次都能传对参数你要在入口把它拦住。原则四失败也是一种反馈。技能执行要返回结构化错误比如参数缺失city_name、上游服务超时而不是一句笼统的出错了。模型拿到结构化错误还能尝试补救拿到一句废话就只能对着用户干瞪眼。这四个原则看着简单但每一条背后都有踩坑的代价。接下来我把技能系统的核心细节完整拆一遍。2. Skill 核心细节拆解一个技能的完整生命线2.1 目录结构与元信息声明一个生产级技能我推荐用独立的目录来组织每个技能都是描述 代码 校验规则的三件套。我自己常用的目录结构长这样skills/ weather/ skill.md main.py schema.json order/ skill.md main.py schema.json logistics/ skill.md main.py schema.jsonskill.md是给模型看的能力声明也是整个技能的注册入口。main.py是实际执行逻辑。schema.json是参数校验规则。为什么要这么做因为普通代码工程里文档是配角但 Agent 技能场景里描述信息和代码同样重要甚至更重要。把描述独立成文件方便发版时做对比模型版本一升级描述可能需要同步调整这时候独立文件的价值就体现出来了。然后说注册。目前主流 Agent 框架都提供了技能注册机制核心就是把这套目录里的元信息读出来动态注入到模型的提示词上下文中。手动维护一份技能清单的做法我强烈不推荐因为加一个技能要改两三个地方迟早漏改。注册环节要做的是启动时扫描目录、加载描述、校验 Schema、注册可调用函数任何一个环节失败就直接报错宁可 Agent 起不来也不要带病运行。2.2 描述文本模型能不能调对全看这几行字技能描述是整套系统里投入产出比最高、也最容易被忽视的部分。我可以很直接地说模型能不能在正确的时候调用正确的技能七成靠描述写得好不好。这不是玄学因为大模型的调用决策本质上是在做概率预测描述越接近它训练语料中见过的 API 文档风格它越容易按你的意图走。先看一个反面案例。我早期写过这样一个描述获取天气数据。就六个字。结果模型在用户问明天会不会下雨下周气温趋势的时候全都试着调这个技能而它根本不支持这些场景。原因很简单描述太泛没有给模型任何判断依据。后来我按下面这套模板重写了技能名称get_weather 职责获取指定城市实时天气信息。 触发条件当用户询问今天天气当前气温降雨概率风力等实时气象问题时调用。 禁止条件不要用于历史天气查询、未来天气预测、气候统计分析。 参数说明 - city_name城市中文名如北京上海必填。 - unit温度单位取值为 celsius 或 fahrenheit选填默认 celsius。 示例用户问上海现在冷吗 → 调用 get_weather(city_name上海, unitcelsius)模板的核心要点有五个第一句二十字以内说明职责让模型三秒钟判断是不是我;然后给触发条件用用户可能的问法来举例给禁止条件这个很多人不写但它能有效减少误调用参数说明要写单位、格式、枚举值而不是只写类型最后给一个调用示例模型对示例的敏感度远高于抽象描述。实际效果提升非常明显。我统计过同一个测试集重写描述前模型准确调用率大概六成重写后稳定在九成以上。这个改造成本是零收益比什么调参都大。2.3 参数模型与校验机制参数是模型和真实服务之间的契约这段契约一旦含糊坑的是下游所有人。参数命名要注意语义。模型从用户话术里提取参数值时city_name比c好猜target_date比d好猜。命名越贴近自然语言模型提取准确率越高这一点我在实践中反复验证过。Schema 校验这块我直接用结构化表达。一个典型的参数定义长这样{ city_name: { type: string, required: true, min_length: 2, max_length: 20, description: 城市中文名如北京、上海 }, unit: { type: string, required: false, enum: [celsius, fahrenheit], default: celsius } }除了类型和枚举我强烈建议加用更细粒度的规则字符串的长短限制、整数的取值范围、数组的最大元素数、对象内部嵌套字段的约束。校验失败时不要直接丢异常给模型而是返回结构化的错误消息告诉模型哪个字段出了问题、期望什么格式。模型读完错误消息可以自己纠正后重试这样整个调用链条就更顺。参数这一关我特别强调是因为模型从口语里提取参数经常出乱子。用户只说了一百块模型可能传成100元如果你的金额字段只接受整数这个元就能让整个技能崩掉。入口校验 参数清洗是技能系统里性价比最高的两道防线。3. 实操落地从零搭一套可复用的技能工作流3.1 技术选型先选骨架再填血肉动手之前先选骨架。目前市面上的做法大致分三类通用代码框架、可视化工作流平台、自研轻量方案。通用代码框架适合深度定制技能就是函数加注册装饰器编排逻辑全用代码控制调试方便、可测试性强适合有工程能力的团队。可视化工作流平台适合快速验证想法拖拽节点就能连出一条链路对非技术人员友好但深度定制和版本管理往往受限。自研轻量方案是用一个配置文件 调度循环自己实现技能注册和调用适合技能规模小、框架满足不了需求的情况。我的建议是第一期直接用通用代码框架的技能注册机制别一上来就上复杂平台。因为技能系统的核心是模型-技能-执行三个环节之间的契约质量代码模式最容易逐个环节调试。等技能数量超过二十个、需要多人协作维护时再考虑引入可视化编排层。3.2 实操步骤一写一个可被正确调用的基础技能我拿一个最常见的业务场景来演示完整落地流程查询订单物流状态。这个技能在生产里非常典型涉及参数提取、外部服务调用、状态映射三个核心环节。先用代码把元信息和执行逻辑写在一起这里我用伪代码演示具体语法按你用的框架调整即可register_skill( namequery_logistics, description查询订单物流状态。当用户询问快递到哪了物流更新包裹进度时调用。参数 order_id 为用户订单号字符串。 ) def query_logistics(order_id: str) - dict: # 从订单系统获取物流信息 result logistics_client.track(order_id) if result is None: return { status: failed, error_code: ORDER_NOT_FOUND, error_message: f订单 {order_id} 的物流信息不存在请确认订单号是否正确 } # 把内部状态码映射成用户能理解的文案 status_text { pending: 待揽收, in_transit: 运输中, delivered: 已签收, exception: 异常件 }.get(result[status], 未知状态) return { status: success, order_id: order_id, logistics_status: status_text, latest_trace: result.get(latest_trace, ) }这里有两个细节值得注意。第一函数签名用了类型注解它不只是给人看的很多框架会读注解来生成参数 Schema类型明确能让模型少犯错。第二失败返回里带了error_code和error_message不是简单地返回None或者抛异常。这样模型在收到ORDER_NOT_FOUND时可以向用户追问订单号而不是丧气地说我查不到。写完代码之后注册到技能中心。启动时框架会自动扫描这个装饰器把技能名、描述、参数信息注入模型上下文。我建议在注册前后打印一条日志确认技能真的进去了。这种小习惯能帮你省掉后面定位模型为什么不调用的大把时间。3.3 实操步骤二将多个技能编排为复合工作流单技能跑通只算热身多个技能按顺序协作才体现真正的工程价值。我举一个高频场景用户问我最近哪个快递还没到。这个任务天然要拆成三步先查出用户的订单列表再逐个订单查物流状态最后筛选出未签收的订单。有两种编排方式我交替使用。第一种是模型自主编排。把query_user_orders和query_logistics都注册给模型系统提示词里写一句你可以通过调用工具分步获取信息模型会自己决定先调 A 拿列表再循环调 B 查状态。这种方式灵活适合需求开放的任务。第二种是预定义工作流。用代码把固定步骤写死模型只负责在关键节点选择调用。workflow ( query_user_orders() .for_each(orderquery_logistics) .filter(status ! 已签收) .summarize() )两步一比就能看出来预定义流程稳定可控响应时间也可预期但灵活性差一些模型自主编排灵活但链路一长就容易中途出错。我现在的实践是涉及外部服务强依赖、步骤固定的任务全部用预定义工作流比如订单查询、工单流转开放探索类的任务保留模型自主编排比如知识库问答、多条件筛选。两条路并用才算是把编排能力用到位。3.4 实操步骤三加入可观测性与降级策略没有任何一个外部服务敢说自己永远稳定所以技能系统从第一天起就要设计观测和降级。可观测性最少要覆盖三件事结构化日志、成功率统计、调用追踪。结构化日志不是随便 print而是用 JSON 格式把技能名、入参、出参、耗时、错误码打出来便于接入日志平台检索。成功率统计按技能维度聚合按小时粒度看趋势哪个技能异常率变高了不用等用户投诉就能发现。调用追踪是把一次用户请求里所有技能调用串成一条链路A 调用了几次、B 调用了几次、哪一步最慢一目了然。降级策略我按三层来搭。第一层是重试技能失败后自动重试一到两次用指数退避间隔 0.5s、1s、2s解决临时性抖动。第二层是错误兜底重试仍失败时返回结构化错误给模型让模型决定换个技能还是直接告诉用户服务暂时不可用。第三层是熔断某个技能连续失败超过三次短时间内直接禁掉该技能避免它拖垮整个工作流。这层是救命用的比如短信服务故障时与其每条消息卡一分钟超时不如快速熔断让用户知道发不出去。这三个落地步骤走完一套带基础工程保障的技能工作流就成型了。接下来我把上线后遇到的那些模型不听话的问题集中做一个整理每一条都是真金白银换来的经验。4. 常见问题与排查技巧实录4.1 模型不调用技能或调错技能这是上线后被问得最多的一个问题用户问天气模型却回答我暂时无法获取实时数据明明技能注册的好好的。先按流程排查。第一确认技能真的注册成功看启动日志或者用一个简单的测试对话去触发。第二检查描述里的触发条件是不是和用户真实问法差太远。比如你写的是当用户询问天气信息时调用但用户的实际说法是今天出门要不要带伞模型可能理解不了这里需要天气技能。这种情况要把用户口语化的问法写进描述里。第三确认一次调用时塞给模型的技能数量。技能一多模型选择困难相关性低的技能可能直接被忽略。我的经验是正常情况下塞给模型的技能控制在十个以内超过十个必须做动态选择按任务类型只加载相关技能子集。还有一个很隐蔽的坑系统提示词里如果写了如果你不知道答案可以直接告诉用户模型的默认倾向就是躺平不调工具。这类句子要与技能说明放在一起表述强调当有可用工具时优先使用工具获取信息。4.2 参数类型与边界问题模型从对话里提取参数经常把口语内容原样塞进来。城市名还好数字类型的字段最惨。用户说最近一百块钱左右的订单模型把一百块钱左右整个传给了金额参数校验不通过技能直接报错。我在实战中总结了一套处理方法首先架严格校验不合格的参数绝不进入业务逻辑然后在技能描述里写清楚如果无法从上下文中提取到准确参数值请使用 unknown 而不是自行猜测最后建一个参数清洗层做别名映射和单位换算。比如用户说北京清洗成北京用户说上礼拜清洗成上周的具体日期。清洗规则用简单的映射表就能覆盖八成场景不需要做复杂 NLP。这类问题的排查技巧是把技能实际收到的原始参数打出来看而不是看模型端日志。因为你自己测试时模型传的参数可能和你预期的不一样直接看入口日志最容易定位。4.3 技能膨胀与命名冲突随着业务增长技能列表会越来越长。技能一多问题就来了两个技能描述相似模型随机选一个一个场景有三个技能都能干模型频繁挑错。我自己踩过很典型的坑商城团队注册了一个查询订单客服团队又注册了一个获取订单详情两个技能实际调用的是同一个服务但参数定义不一样。模型有时候调这个有时候调那个返回格式还不一致下游逻辑直接崩了。后来我把这两个技能合并成一个参数统一描述合并问题消失。长期维护的技能系统建议用三项机制来对抗膨胀。第一是技能分级核心技能常驻边缘技能按任务类型动态注入不让模型每次都面对全量清单。第二是统一命名规范格式定为动词对象领域比如query_order_status_ecommerce既能避免冲突又方便检索。第三是定期清理统计技能调用次数连续三十天零调用的直接下线或合并不要心软。技能清单是资产也是负担只留真正有用的。4.4 安全与权限控制最后说安全这部分最容易在早期被忽略。给 Agent 挂技能的时候别默认授予全部权限尤其是写库、删数据、发消息、调用支付这类高危操作。我早期犯过一个错误写了个数据库查询技能参数里接受任意表名和任意字段本意是让模型灵活查询。结果测试的时候模型把内部元数据表都翻了出来虽然没造成损失但这个风险如果是生产环境就太吓人了。后来我改成白名单机制参数只能从配置好的表清单和字段清单里选模型传了白名单外的东西直接拒绝。这个改动之后同类问题再没出现过。权限控制至少要覆盖三层技能层谁能调用、参数层哪些值合法、审计层谁在什么时间调用了哪个技能、传了什么参数。高权限技能在执行前加一道二次确认让用户明确授权再执行这个交互成本不能省。安全不是上线之后才补的要在技能设计阶段就刻进去。一路上踩了这么多坑我最大的体会是Skills 不是把代码打包给模型那么简单它本质上是在模型和应用服务之间建立一套可信任的契约。先跑通两三个核心技能把注册、描述、校验、观测这几块基础设施搭扎实再逐步扩容是性价比最高的推进方式。另外一个小技巧始终值得记住你花在技能描述上的每一分钟都会在模型调用准确率上获得至少十倍的回报——这大概是我在 Agent 工程化这件事上最划算的一笔投入了。

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

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

免费获取报价 →
↑