大模型安全调用业务技能:agent-skills技能层设计与实践

去年下半年,我把大部分开发时间都投进了一个叫 agent-skills 的代码库。名字起得很简单,做的事情却不小:让大模型不再停留在“会聊天”,而是能安全、稳定地调用一堆真实业务技能完成任务。无论订单查询、退款计算还是排班调整,每个能力都被封装成独立的技能模块,智能体在回答问题时按需选择、调用并执行。这篇文章就是把我在这个项目里踩过的坑、沉淀下来的设计和实践心得完整梳理一遍。如果你正在做 AI 应用或智能体开发,或者正准备在业务系统上接入大模型能力,这篇内容应该能给你一套可以参照的落地路径。

1. 项目起源:给智能体“装手”之前,我踩过哪些坑

1.1 第一版教训:API 调用写对了,业务却总是答非所问

最早我们团队接了一个售后客服场景:用户问订单什么时候到、退款到没到账、发票怎么开。当时的做法非常朴素,把全部 API 说明塞进系统提示词,让模型自己判断该调哪个接口。结果上线第一周就出了不少问题。

最常见的翻车场景是参数混淆。比如用户说“我要查一下上个星期的退货单”,模型能识别出需要调用查询接口,却把order_id和return_id搞混,查出来一个不存在的记录,最后回复用户“未查询到相关信息”。更气人的是,这种错误不是偶发,同一个问题换几个说法就可能触发一次。原因在于提示词里的 API 描述只是“给模型看的一段文本”,它没有强约束,也没有反馈闭环。模型本质上是在靠着模糊语义猜测接口用法,猜错了它自己也不知道。

还有一类问题是模型过于自信地补全参数。用户只提供了手机号,模型却“贴心”地编造了一个订单号填进参数里,接口调用失败后又换一个编号继续试。这些错误在日志里看起来都很零散,但统计下来非常可观——我们当时粗算了一下,大约有 30% 的 API 调用错误属于参数错误或选错接口。

1.2 agent-skills 的定位:在模型和 API 之间加一层可治理的机制

踩了这些坑之后,我意识到问题不是模型能力不够,而是我们缺了一层“技能管理层”。这层东西要能做到三件事:

  • 把每个 API 封装成一个有完整契约的技能,包括名称、功能描述、输入输出格式、出错时的标准化反馈,而不是把接口说明扔给模型去自由发挥。
  • 在调用前后加校验和兜底逻辑,参数不齐就补、格式不对就拦、调用失败就换方案,避免模型在错误路径上一次又一次地空转。
  • 让所有技能可测试、可灰度、可回滚,不能像改提示词那样靠“感觉”上线。

agent-skills 这个名字里的“skills”指的就是这一层能力单元。每个技能不是一个简单的函数封装,而是一个包含文档定义、输入 Schema、执行函数、测试用例和安全策略的四位一体模块。它解决的核心问题是:当模型和真实系统对话时,怎样保证这个过程可控、可观测、可纠错。

2. 技能模块的核心设计:从原生函数到可被模型理解的描述

2.1 一份技能清单应该包含哪些字段

我见过很多初版设计,技能就是一个函数名加一句注释,然后靠提示词把函数列表扔给模型。前期没问题,技能一多就乱了。我们后来把每个技能的数据结构固定成下面这样:

@dataclass class SkillSpec: name: str # 技能唯一标识,如 "query_order" description: str # 给模型看的用法说明 input_schema: dict # JSON Schema,描述每个参数 handler: Callable # 实际执行业务逻辑的函数 version: str = "1.0.0" # 技能版本 namespace: str = "common" # 所属领域/命名空间 tags: list[str] = [] # 检索标签 examples: list[dict] = [] # 典型调用示例 security: dict = {} # 权限、限流、审计配置

这里最容易被忽略的是description的写法。很多人写“查询订单信息”,这种描述是给程序员看的,模型看了并不清楚“什么时候该用”。后来我们总结出一个写法原则:描述要写“在什么情况下用这个技能”,而不是“这个技能是什么”。对比一下:

  • 差:查询订单信息。
  • 好:当用户询问订单状态、物流进度、发货时间、预计送达时间等与订单履约进度相关的问题时使用。若用户提供订单号则直接传入 order_id,否则用手机号或用户 ID 查询。

这种描述把触发场景、参数来源和边界都讲清楚了,模型选技能的准确率明显提升。

examples字段也很重要。模型对抽象描述的理解不稳定,但给它一两个具体的输入输出例子,它就能很快对齐。我们的每个技能至少配两组示例,一组是标准调用,一组是边界情况(比如查不到记录时怎么返回)。

2.2 输入参数 Schema:决定模型会不会用错的菜谱

输入 Schema 是技能契约里最关键的一环。我们使用 JSON Schema 定义,因为主流模型厂商的工具调用格式基本都兼容它。一个典型的订单查询技能长这样:

{ "name": "query_order", "description": "当用户咨询订单状态、物流进度、发货/送达时间时使用", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,格式通常为 16 位数字。若用户只提供了手机号,该字段可以不传" }, "mobile": { "type": "string", "description": "用户下单手机号,用于通过手机号反查订单,格式为 11 位手机号" }, "page": { "type": "integer", "description": "页码,默认 1", "minimum": 1 } }, "required": [], "oneOf": [ {"required": ["order_id"]}, {"required": ["mobile"]} ] } }

这里用oneOf约束“订单号和手机号至少传一个”,是一个很实用的技巧。模型有时候会贪多,两个参数都编一个出来,加上这种约束后它能明白“只需满足其中一条路径”。

写参数描述时,不要用缩写,不要把内部字段名直接暴露给模型。我们曾经有个技能叫getOrderByUID,参数叫u,模型永远猜不出u是什么。改名为query_order、参数改为user_id后,准确率一下子飙升。还有枚举值一定要列全,不列全模型就会开始幻觉出一些不存在的取值。

2.3 结果返回结构的标准化

技能执行后的返回结果同样需要统一。我们定了一套结构化返回格式:

{ "status": "success" | "failed" | "retryable", "data": { ... }, # 业务数据(成功时) "error_code": "ORDER_NOT_FOUND", # 错误码 "user_message": "没有查询到该订单,请确认订单号是否正确" # 可直接回复用户的话 }

user_message是很多人会漏掉的设计。当技能内部已经知道“查不到”时,它可以直接生成一句用户能看懂的话。模型拿到这句话,就不需要再编理由了。retryable状态则告诉模型:这次失败是临时的,你可以换个参数再试,而不是鲁莽地不断重试。这套结构让下游的 Agent 行为可预测了很多。

3. 运行时调度与异常恢复:模型说错话时,系统怎么兜底

3.1 function calling 链路中常见的三类失败

技能定义好了,真正的挑战在运行时。模型调用工具这个过程,看起来是一个标准的 function calling,但实测中最常翻车的是三类:

第一类是参数幻觉。模型会生成一个看起来合理但实际不存在的参数值,比如用户说“查一下我上个月的订单”,模型直接编一个order_id: "202502001",但这个号码根本没存在过。第二类是参数缺漏。模型识别出要调用某个技能,却漏掉了必填参数,导致下游接口 400。第三类是死循环重试。技能抛错后,模型不换策略,而是用同样的参数再调一遍,连续失败四五次后才放弃。

这三类问题不是靠调提示词能根治的,必须在技能调用层加硬校验。

3.2 参数校验与自动修复

我们实现了一个前置校验器,核心逻辑是:模型生成工具调用参数后,先不直接执行,而是先跑一遍 JSON Schema 校验。如果校验失败,系统做两件事。

首先尝试自动修复:根据缺失参数的名称,从全局上下文里找候选值。比如模型漏了order_id,但对话历史里用户提到过“订单号 2025020112345678”,那我们就把它补进去。这种“上下文补参”的成功率不错,比让模型自己发现缺参再补要快得多。

如果无法自动修复,就把校验错误返回给模型,并附上明确的补充指引。我们的返回消息大概是:

调用技能 query_order 时缺少参数 mobile。用户最近一条消息中的手机号是 13800138000,是否使用该值?请重新生成完整的调用参数。

这里有一个关键点:返回给模型的错误信息必须指出“缺了什么、应该怎么补”,而不是简单抛一个validation error。模型对模糊报错的处理能力很差。

3.3 三级降级链路:深度技能、轻量技能、固定话术

即使参数校验通过了,技能执行时仍可能失败,比如下游系统超时、数据源返回异常。我们设计了三级降级策略,用一张决策表来管理:

级别触发条件处理方式示例
L1 深度技能主技能调用成功返回结构化数据给模型调订单中心查询订单详情
L2 轻量技能主技能失败但存在替代数据源调用简化版接口,只返回关键字段查缓存/只读库,仅返回状态和日志时间
L3 固定兜底连续失败或数据不一致返回确定性话术,停止工具调用“受系统影响暂时无法查询,请稍后重试”

降级不是无限尝试的代名词。我们在技能调用层加了一个熔断器:同一个技能在 30 秒内连续失败 3 次,就自动切换为 L3;在 5 分钟内错误率达到 50%,则直接停止该技能的自动调用,避免模型在故障系统上反复空转浪费时间和 token。

这套机制上线后效果立竿见影。之前那种“模型在同一接口上死磕五次”的情况基本消失了,平均单轮对话的工具调用次数从 2.7 次降到了 1.4 次,响应时间也快了很多。

4. 技能仓库的组织方式:权限、版本、冲突与冷热分层

4.1 命名空间划分:别把技能全部平铺在一起

技能数量到 30 个以上的时候,模型在选择技能时已经会出现“选择困难”。把所有技能平铺在一个大列表里,不仅增加模型的理解负担,还容易让它在语义相近的技能之间犹豫不决。

我们最终按业务域划分命名空间,每个命名空间是一个独立子模块:

agent-skills/ ├── order/ # 订单域 │ ├── query_order.py │ ├── cancel_order.py │ └── change_address.py ├── payment/ # 支付域 │ ├── query_refund.py │ └── apply_refund.py ├── aftersales/ # 售后域 │ ├── create_return.py │ └── track_return.py └── common/ # 通用域 ├── transfer_human.py └── faq_search.py

这听起来像最简单的代码组织,但在 Agent 场景里有额外意义:模型在收到用户请求后,会先经过一层“意图路由”,预测它属于哪个命名空间,然后只把该命名空间下的技能列表注入提示词。这样上下文窗口里技能数量少、语义相近、模型选择压力小。

4.2 版本管理与灰度:改一个技能不拖垮整个对话

技能本质上和一段线上代码没有区别,改坏了会影响真实用户体验。最初我们很随意,直接改代码上线,结果某个同事把“查询订单”的技能改成了默认只返回最近一单,导致用户查历史订单时永远返回错误结果。好在那次只是测试环境。

之后我们建立了技能版本管理机制。每个技能可以同时存在多个版本,线上流量按比例分配:

版本功能变化灰度比例回滚策略
v1.0.0基线版本20%全量回退
v1.1.0新增物流轨迹字段30%失败率超过 8% 自动回退
v1.2.0优化描述文案50%参数解析错误率超过 5% 自动回退

灰度期间我们重点看两个指标:技能调用成功率和用户侧满意度。一旦发现某项指标下降,就把流量切回稳定版本,整个操作改配置即可,不需要重新发布代码。

4.3 冷热技能分离:技能太多时先检索再注入

当技能总数超过 60 个以后,光靠命名空间还是不够。十几个技能虽然不多,但如果每个都带长长的 JSON Schema,上下文窗口会被迅速吃掉。我们的做法是把技能分为冷热两档,热技能(最近 7 天调用次数高的)直接随提示词注入,冷技能则通过一个检索层按需加载。

检索层的实现不算复杂:把每个技能的name + description + tags + examples拼成一段文本,做 embedding,然后和用户当前问题计算相似度,召回 top-5 候选技能。同时保留一组规则映射(比如用户消息里包含“退货”就强制召回create_return)作为兜底。这套“向量召回 + 规则强制”双通道,保证了技能数量再大,单次对话真正暴露给模型的最多也就十来个。

5. 真实效果复盘:用数据说话

5.1 离线评测集:让每个技能改动有据可依

技能层面的改动如果没有评测集,回归成本非常高。我们做了一个固定的离线评测集,包含 100 条典型用户问题和 50 条边界问题,覆盖订单查询、退款申请、发票、地址修改等主要场景。每跑一次版本,都把这些问题重新过一遍,然后记录三个指标:

  • 技能选择准确率:模型是否选中了正确的技能。
  • 参数准确率:生成参数是否能被 Schema 校验通过。
  • 任务完成率:最终是否给出了用户可以接受的答复。

下表是我们某一次从“纯提示词方案”切换到 agent-skills 框架后的对比数据,虽然不是业界标准,但能看出量级差异:

指标纯提示词方案agent-skills 方案
技能选择准确率71%92%
参数准确率64%89%
任务完成率58%84%
平均工具调用次数/轮2.71.4
单轮平均耗时3.2s2.1s

这里要说明,这套评测集的问题难度是固定的,数字只能反映我们业务场景的相对变化,不能外推成通用标准。但趋势是确定的:当模型不再“自由猜测接口”后,准确率的提升非常直接。

5.2 两个印象最深的调试案例

第一件是金额精度问题。某个退款相关技能返回浮点数金额,模型在回复用户时偶尔会写成 9.999999 这类值,虽然罕见,但发生在退款金额上会出大事。查了半天发现是技能内部把以分为单位的整数转成了 float 再返回,丢失了精度。修复很简单,全部统一成“以分为单位的整数”作为传输格式,再由前端做格式化。这个教训让我们规定:涉及金额的技能一律用整数分存储和传输,不允许出现浮点数。

第二件是技能误匹配问题。用户说“我不想用这个订单了,帮我取消”,我们的意图路由把“取消”匹配到了create_return(创建退货单),导致系统问用户要退货原因,用户很恼火。后来我们在两个技能的 description 里明确做了“区分提示”,并且在examples中加入相似问法的反例,这类误判率才降下来。给技能写清楚“和哪些技能容易混淆、边界在哪”,比单纯把描述写长更有用。

5.3 线上日志里的意外收获

接入 agent-skills 后,我们发现一个很意外的收益:排查问题变得容易多了。以前模型直接调用 API,出错后只有一行请求日志,很难定位是模型理解错了还是接口参数错了。现在每个技能调用都有完整的“意图→候选技能→选中技能→参数校验→执行→结果返回”链路日志,复盘一个问题只需要几分钟。这也是技能化设计带来的隐性价值——可观测性天然提升了。

6. 后续规划与给同行的一些建议

6.1 正在探索:让技能自己“长出来”

agent-skills 目前还是“人写技能、模型调用”的模式,我在想的是能不能让流程更加自动化。比如给定一个新的 API 文档,系统自动生成技能描述、输入 Schema 和基础测试用例,再通过离线评测来自动验证。这个方向我在一个小范围实验里跑通了:把某新接口的 OpenAPI 规范喂给一个专用生成流程,产出的技能描述质量已经接近人工写的 80%,后期只需要调整边界描述。

另一个方向是技能的评估自动化。每个技能升级后,自动用线上样本生成变体问题(改写法、换数字、加口语词),然后跑一遍评测,和旧版本对比。这套流程如果打通,技能迭代就能像 CI/CD 一样发布。

6.2 什么情况下别急着上技能框架

一个很常见的问题:是不是所有 Agent 项目都需要一套 agent-skills 这样重的技能框架?我的答案是看规模。如果整个应用只需要调用三五个核心接口,且这些接口非常稳定、参数简单,那你直接在提示词里写清楚也够用,强行上一套框架反而增加维护成本。

建议的触发条件是:一个智能体需要管理超过十个业务技能,或者接口参数经常变、出错影响大,或者你希望在多个项目里复用技能能力。满足任何一条,就值得拆出独立的技能层。另外还有一个心理上的判断标准——如果你已经出现过“这次改动不知道会不会影响上一个场景”的担忧,那说明技能之间已经出现耦合,该上框架了。

我在整理这些内容时,翻到了自己最初给 agent-skills 记录的 TODO,第一条写着“把技能定义做规范”。现在回头看,这一步确实是最关键的。如果你也正在搭自己的智能体,我建议你不要急着写调用代码,先花一个下午把技能接口、返回结构和测试样例定下来。这个习惯帮我省掉了无数次的返工和线上翻车。实际上,后面我们新同学上手项目时,首先看的也是技能定义文档,而不是业务代码——技能层已经成为了整个系统最稳定的锚点。

相关新闻

SatDump 完整指南:3 步搭建免费的卫星数据解码环境

SatDump 完整指南:3 步搭建免费的卫星数据解码环境

SatDump 完整指南:3 步搭建免费的卫星数据解码环境 【免费下载链接】SatDump A generic satellite data processing software. 项目地址: https://gitcode.com/GitHub_Trending/sa/SatDump SatDump 是一款通用的卫星数据处理软件,帮你把 SDR 录制…

2026/10/21 4:00:36 阅读更多 →
LDC1000金属探测程序完整版:从校准到动态阈值的嵌入式实现

LDC1000金属探测程序完整版:从校准到动态阈值的嵌入式实现

简介:这份资源是面向电子设计竞赛参赛者与嵌入式开发者的LDC1000金属探测程序完整工程,围绕TI杯金属位置探测器赛题展开,解决从传感器驱动到目标检测的完整实现问题。压缩包共40个文件,约101KB,以C语言源文件、头文件、…

2026/10/21 4:00:36 阅读更多 →
PyQt与深度学习的急性胰腺炎智能辅助诊断系统:医工结合毕设全解析

PyQt与深度学习的急性胰腺炎智能辅助诊断系统:医工结合毕设全解析

带“计算机毕设”标签的项目,大家最关心的往往不是“能不能跑通”,而是“答辩能不能过”“创新点够不够”“导师问到原理时能不能扛住”。今天我要拆的这个题目,是典型的多技术栈综合题:PyQt基于深度学习的急性胰腺炎智能辅助诊断…

2026/10/21 4:00:36 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/21 9:32:40 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/21 9:32:40 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/21 9:32:40 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/21 9:32:40 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/21 9:32:40 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/21 9:31:32 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →