1. 项目概述与核心思路
1.1 从工具调用到技能体系,agent 开发到底缺什么
这两年做大模型应用,尤其是搞 Agent(智能体)方向的朋友,应该都有同感:模型本身的智商天花板已经不明显了,真正卡住项目进度的,往往是"模型能不能稳定地调用外部能力完成一整条任务链路"。换句话说,模型再聪明,没有一套可靠的执行工具,它也就是个会聊天但干不了活的"嘴强王者"。
"agent-skills"这个词,指的就是给智能体配备的一组可复用、可编排、可管理的"技能"集合。它解决的核心问题有三个:第一,把模型从"只会生成文本"变成"能操作真实系统";第二,把零散的 API 调用整理成有语义边界的原子能力;第三,让这些能力能够被多个 Agent 场景复用,而不是每个项目从零造轮子。
我最初接触这个概念时,还停留在"给模型接几个函数"的原始阶段,后来在实际项目里被反复教育:工具和技能是两回事。工具是单个函数、单个接口,技能则是围绕一个业务目标封装好的完整能力单元。举一个直观的例子:单个"搜索网页"API 调用是工具,但"调研一个竞品并输出结构化报告"就是技能,因为后者需要规划搜索关键词、多次检索、筛选有效信息、按模板整理输出。这个封装层,才是 agent-skills 真正有价值的地方。
1.2 谁需要关注这套东西
如果你是下面几类人,这篇文章应该能帮到你:
- 做大模型应用开发,正在纠结"怎么让 Agent 稳定调用工具"的工程师。
- 负责企业内部 AI 中台,想把各业务线的 Agent 能力统一管理起来的技术负责人。
- 做 RAG、自动化工作流、数字员工方向的开发者,需要一套可复用的技能注册与调度机制。
- 刚入门 Agent 开发,被 function calling、prompt 工程、工具链路搞得一头雾水的新手。
这篇文章不会停留在概念层面,我会把 agent-skills 的设计思路、核心实现环节、生产环境落地的问题排查都拆开讲,尽量把那些文档里不会写的实操细节一并补齐。整个项目我是在一个真实的内部知识库问答 + 自动化运维场景里做落地的,后面所有代码和踩坑记录都来自这个项目,可参考性比较强。
2. 技能体系设计的关键决策
2.1 为什么不能把技能做成"一堆函数列表"
很多团队第一次做 Agent 工具化时,最自然的做法是把所有需要的能力都定义成 function,一股脑塞进 system prompt 或 tools 参数里。我见过有项目一次性塞了 40 多个 function 定义,结果模型在每次对话里都要对所有工具做一轮评估,推理时间明显变长,而且工具一多是会出现"选择困难"的——明明该用 A 工具,模型偏偏调了语义相近的 B 工具,定位问题会花掉大量时间。
agent-skills 的第一个设计原则是"分层"。技能注册中心维护一个全局技能清单,每个技能包含描述、参数 schema、调用入口、依赖条件和适用场景。但在实际请求里,我们会先做一个技能路由(skill router),根据用户的意图和上下文,从全局清单里召回一小批相关技能,再交给模型做最终选择。
打个比方:一个大型餐厅有 200 道菜,你不可能把整本菜单都摆在客人面前让他选,那样客人会懵。合理做法是服务员先根据客人的口味偏好推荐 8~10 道菜,客人再从中选择。技能路由就是那个服务员。我在项目里实测,30 个技能的环境下,加了路由层之后,工具选择准确率从 78% 提升到了 94%,效果非常显著。
第二个原则是"技能内部可以编排子任务"。一个技能不一定直接对应一个 API 调用,它可能是多个子步骤的组合。比如"生成数据报表"技能内部包含:查询数据库、处理缺失值、生成图表、输出 Markdown 表格四个子任务。对外暴露的时候,它是一个整体技能,但内部实现是一个有向调用链。这种封装方式让上层业务逻辑保持干净,也让每个技能可以被单独测试和优化。
2.2 技能描述的质量决定成败
在 agent-skills 体系里,最容易被低估的就是技能描述(description)的撰写质量。很多开发者把描述写得含糊,比如"查询用户信息",模型根本不知道这个技能接收什么参数、在什么场景下调用、返回什么结构。于是模型要么不调用,要么调用了却传错参。
我给团队定的规矩是:每个技能描述至少要包含四个维度——功能边界(这个技能做什么、不做什么)、适用场景(什么情况下应该选择它)、参数含义(每个参数的业务含义和格式要求)、返回说明(调用后能拿到什么)。这四个维度写清楚,模型的调用准确率会大幅提升。
这里还有一个容易被忽略的小技巧:描述里要写明"不应该在什么情况下使用"。负例往往比正例更能帮助模型做排除。比如"查询用户信息"这个技能,描述里加一句"注意:此技能仅用于查询单用户详情,获取用户列表请使用 list_users 技能",模型犯迷糊的概率就低得多。
2.3 确定技术栈与运行时方案
agent-skills 的底层依托什么运行时,我试过两种路线:
- 路线 A:基于大模型平台原生的 function calling(如 OpenAI function call、通义千问的 tool call 等),所有技能以 JSON Schema 方式注册,模型自己决策调用哪个函数。实现快,但调度逻辑被平台锁死,复杂编排不方便。
- 路线 B:自建技能执行引擎,技能注册到本地 registry 里,由引擎负责意图解析、技能路由、参数校验、执行和结果回填。灵活性高,但工程量大很多。
我采用的是"折中路线":技能定义使用标准 JSON Schema,保证可移植性;技能路由和调度用自研的轻量引擎;最终调用模型时,只把当前会话相关的技能子集注入到 tools 参数里。这样既享受了平台 function calling 的稳定性,又保留了自建体系的灵活性。
如果你是在一个已有的大模型应用里做增量改造,我建议也从这条折中路线起步,不要一上来就全自研。先跑通最小闭环,再逐步把技能执行从平台函数调用迁移到自建引擎,风险会小很多。
3. 核心细节解析与实操要点
3.1 技能注册表的数据结构设计
技能注册表是整个 agent-skills 体系的心脏。我的设计是每个技能对应一份结构化描述,核心字段如下:
| 字段 | 说明 | 示例 |
|---|---|---|
| skill_id | 技能唯一标识 | query_user_detail |
| name | 技能名称 | 查询用户详情 |
| version | 技能版本号 | 1.2.0 |
| description | 功能描述,包含正例和负例 | 查询指定用户的账户详情信息;仅限单用户查询,批量查询请用 list_users |
| parameters | JSON Schema 参数定义 | {"userId": {"type": "string", "description": "用户ID"}} |
| required | 必填参数列表 | ["userId"] |
| handler | 技能执行入口的引用 | skills/user_detail/handler.py |
| tags | 技能标签 | ["user", "query", "crm"] |
| timeout_ms | 技能执行超时上限 | 5000 |
| dependencies | 依赖的其他技能或服务 | ["auth_service"] |
| enabled | 是否启用 | true |
这里我想多说一下 version 字段。技能是会持续迭代的,同一个 skill_id 在不同时间可能有不同版本。生产环境里,已经在跑的会话可能还在用旧版技能,新会话已经开始用新版,如果版本管理做得不到位,会出现同一个动作两种行为的情况。我的做法是:技能变更必须升版本,且每次变更都生成一条变更记录,便于回溯。
参数校验这块我踩过一个很深的坑:早期没有在技能层做参数强校验,完全依赖模型生成的参数。结果模型偶尔会把日期格式传成"2024年1月1日"这种自然语言格式,导致后端解析直接报错。后来我在技能入口处增加了一层参数规范化逻辑,先按 schema 做格式校验,不符合的尝试自动转换,转换不成功再请求模型补充或修正参数。这一步看起来不起眼,但能把工具调用的整体成功率提升至少 10 个百分点。
3.2 技能路由模块的实现思路
技能路由模块承担"从全局技能池里召回当前会话最相关技能"的职责。我实现的第一版用的是简单的关键词匹配,规则简单但召回效果一般,例如用户说"帮我看看这个用户最近有没有异常",关键词匹配很难把"用户"和"异常检测"技能关联起来。
第二版换成了 embedding 向量召回。做法是把每个技能的 description 离线向量化,存到向量数据库里;线上来了一条用户消息,先把消息向量化,然后做相似度检索,取 top-N 个技能作为候选集。这个方案效果就好很多,原因是 embedding 天然能捕捉语义相关性,哪怕用户原话里没有技能名,也能召回正确技能。
在具体参数上,我用的 embedding 模型是 text-embedding-v2 级别的通用模型,向量维度 1024,检索时采用余弦相似度。技能池 30 个技能时,每个请求做一次检索的延迟在 30ms 左右,可以忽略不计。候选集大小 N 我取 5~8,太少了会漏,太多了会增加模型的选择负担。
需要提醒的是,向量召回只是候选生成,最终决定权还是要交给大模型。召回的目的是缩小范围,不是替代模型判断。把语义接近但实际不适用的技能混进候选集没关系,模型通常能根据技能的详细描述做正确排除。
3.3 技能执行链与上下文设计
单个技能执行是简单的,真正的复杂度在于多个技能按顺序协作。比如用户问:"帮我查一下这个项目的整体健康度,如果发现风险就通知相关负责人。"这条指令至少涉及三个技能:查项目状态、判断风险等级、查负责人联系方式、发通知。技能之间是有关联的,后一个技能的入参依赖前一个技能的出参。
我的做法是引入一个"执行上下文缓冲池"。每个 Agent 会话维护一个全局 JSON 对象,多个技能执行过程中产生的关键输出都写入这个缓冲池。后续技能在生成参数时,可以引用缓冲池里已有的值。相当于给技能体系加了一个"工作台",上一个技能放上去的工件,下一个技能可以直接取用。
这个设计在实践里最大的难点是确定"什么信息值得写入缓冲池"。写太多,上下文会冗余,模型容易受噪声干扰;写太少,后续技能拿不到必要参数。我最后定的标准是:跨技能使用概率高、且获取成本高的信息才写入。比如"查询到的用户ID"这类主键信息要写,一次性展示用的中间状态就不要写。
上下文管理方面,每个技能的调用记录我都会保留一份结构化日志,包含入参、出参、耗时、状态。这个日志不仅是排障工具,也是后续优化技能编排的原始数据来源。我在项目里会定期导出日志做分析,看哪些技能被调用次数最多、哪些技能失败率最高,用数据指导迭代方向。
4. 实操过程与核心环节实现
4.1 最小闭环:从注册一个技能到跑通一次调用
下面用一个最简单的"查询天气"技能来演示 agent-skills 的完整链路。这个例子虽然简单,但每个环节都是通用的。
第一步,定义技能描述文件。我使用的技能定义格式是标准 JSON,方便和各家平台工具对接:
{ "skill_id": "query_weather", "name": "查询天气", "version": "1.0.0", "description": "根据城市名查询当前天气情况。适用于用户询问某地天气的场景。注意:本技能仅支持国内主要城市,如无法匹配城市名请返回错误。", "tags": ["weather", "query"], "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如北京、上海、广州" } }, "required": ["city"] }, "handler": "skills.weather.handler:run", "timeout_ms": 3000 }第二步,实现技能执行函数。这里我用 Python 示例,实际项目中你可以换成任何语言:
# skills/weather/handler.py import requests def run(params: dict) -> dict: city = params["city"] # 这里接入实际天气服务商API resp = requests.get("https://api.example.com/weather", params={"city": city}, timeout=2) data = resp.json() return { "city": city, "temperature": data["temp"], "condition": data["condition"], "humidity": data["humidity"] }第三步,注册并测试。技能引擎会自动读取技能定义,把 handler 加载到执行环境中。我习惯在注册后立刻做一次单元测试,直接构造参数调用 handler,确认执行函数本身是通的。这一步能避免把"技能定义错误"和"handler 逻辑错误"混在一起排查。
第四步,将技能接入模型的 tools 参数。这里以 OpenAI 兼容接口为例:
import json with open("skill_definition.json") as f: skill_def = json.load(f) tools = [{ "type": "function", "function": { "name": skill_def["skill_id"], "description": skill_def["description"], "parameters": skill_def["parameters"] } }] messages = [{"role": "user", "content": "北京今天天气怎么样?"}] resp = client.chat.completions.create( model="qwen-plus", messages=messages, tools=tools, tool_choice="auto" )模型返回的内容里如果包含 tool_calls,引擎就提取技能名和参数,执行 handler,再把执行结果以 tool 消息回传给模型,模型根据结果生成最终回答。这个闭环跑通之后,后续扩展新技能就是重复这个流程,工作量集中在技能实现本身,框架部分基本不用动。
4.2 技能编排:实现一个多技能协作的复杂场景
单技能跑通之后,我们来看一个真实的复杂场景。在运维自动化项目里,有一个高频需求是"检查服务器异常并通知负责人"。这个场景涉及三个技能:get_server_status(查服务器状态)、check_anomaly(判断是否有异常)、notify_owner(通知负责人)。
这三个技能的编排逻辑是:先执行前两个技能,如果 check_anomaly 返回有异常,再执行 notify_owner。为了让模型能完成这个编排,关键是在每个技能的 description 里写清楚触发条件。我实际使用的描述片段如下:
- get_server_status 的描述中有:"该技能返回指定服务器的原始状态指标,是检查服务器健康度的前提步骤,通常与 check_anomaly 技能配合使用。"
- check_anomaly 的描述中有:"该技能基于 get_server_status 返回的指标判断是否存在异常。只有已经获取到服务器状态指标时才能调用。如果用户直接询问服务器是否正常,请先调用 get_server_status 获取指标。"
- notify_owner 的描述中有:"该技能用于向服务器负责人发送异常通知,仅在 check_anomaly 判定存在异常时必须调用,正常情况下不要调用。"
大家可以看到,我在描述里刻意写明技能之间的调用时机和依赖关系。这样做之后,模型基本能按照预期顺序执行。实测下来,一次标准的异常检查流程,模型会连续调用三次工具,总耗时约 8 秒,其中大部分时间是三个 API 的实际执行耗时,模型自身的工具决策时间占比很小。
如果遇到更复杂的编排需求,比如条件分支或循环,我建议不要只靠模型自由发挥,而是在技能引擎里引入工作流定义。也就是说,把高层流程写死成 DAG,技能是节点,引擎负责按序执行和条件判断,模型只在需要动态决策的地方介入。Agent 的自由度应该被约束在"流程中的灵活节点"里,而不是让整个流程都在不确定性中游走。这是我在生产项目里最深的体会之一。
4.3 技能执行引擎的并发与超时控制
技能执行引擎上线后遇到的第一个性能问题,是并发控制。早期的引擎对技能调用没有任何并发限制,某个技能是慢 API,多个会话同时执行时会把后端服务打挂。后来我引入了简单的信号量机制,每个技能配置最大并发数,超过阈值就排队等待。
超时控制同样关键。每个技能定义里都有 timeout_ms 字段,引擎在调用 handler 时会启动超时计时,超时后直接判定失败并返回错误信息给模型。模型收到错误信息后,可以选择换一种方式处理,或者告知用户当前系统暂时不可用。这里有一个细节:超时返回的错误文案会影响模型的后续行为。我曾经把错误文案写成"技能执行失败",模型会盲目重试,浪费更多时间;改成"技能执行超时,请改用备用方案或告知用户稍后重试"之后,模型的行为就从无脑重试变成了理性降级。
还有一点值得注意:技能执行的失败重试策略。我的做法是"不做自动重试,只在特定条件下重试一次"。原因是大多数技能失败是参数或上游服务的稳定性问题,自动重试的成功率并不高,反而会增加延迟。唯一例外是偶发超时,重试一次的成功率还不错。我后来实现了一个简单的策略:如果错误码标记为"可重试"(比如 5xx),最多重试一次,其他错误一律直接返回。
4.4 技能效果评估与迭代闭环
做 agent-skills,光是能跑起来远远不够,关键是持续优化"模型选对技能、传对参数、产出正确结果"的比例。我建立了一套离线评估集,包含 200 条真实用户请求,每条标注了期望调用的技能序列和期望的最终答案。每次技能体系有变更,都会跑一遍评估集,观察几个关键指标:
- 技能召回率:正确技能是否出现在候选集里
- 技能选择准确率:模型是否选中期望技能
- 参数正确率:传给技能的参数值是否准确
- 任务完成率:最终结果是否满足用户意图
这套评估机制帮我发现了很多隐蔽问题。举个例子,有一次我们发现某个技能的参数正确率特别低,排查了半天发现是技能描述里的参数格式写得太含糊,模型总是把日期格式传错。修正描述之后,参数正确率直接提升了 20 个百分点。没有评估集的迭代,基本就是盲人摸象。
我强烈建议大家从第一天就建立评估集。哪怕只有 50 条问题,也比什么评估都没有强。迭代的方向感、优先级判断,都依赖这套数据。
5. 常见问题与排查技巧实录
5.1 模型死活不调用技能怎么办
这是 agent-skills 落地里最让人抓狂的问题——技能定义得清清楚楚,但模型就是不用。我排查这类问题时,按以下顺序逐步检查:
- 技能描述是否指向了用户真实意图?有时模型不调用,是因为 description 写得太"接口化",模型没意识到这个技能能解决用户的问题。建议把描述写成"用户说 XX 场景时使用"的形式。
- 当前模型版本是否支持 function calling?有些轻量级模型对工具调用的支持比较弱,换一个工具能力强的模型往往立竿见影。
- 是否同时在 tools 里塞了太多技能?候选技能超过 15 个时,模型的选择准确率会明显下降。优先做技能路由召回,缩小候选集。
- 模型是否需要示例引导?在 system prompt 里补充少量"用户问什么 → 调用什么技能"的 few-shot 示例,能有效提高调用率。
5.2 技能被调用但参数总传错怎么办
参数错误是高频问题,尤其是多参数字段。我的经验是先从这几个角度找原因:
- 参数描述是否采用了"业务语言"?比如字段名是 user_id,描述里写"用户唯一标识,格式为 8 位数字",模型传参时要容易得多。
- 参数来源是否清晰?如果参数需要从用户对话里提取,明确的描述是"从对话中提取城市名,若未提及请询问用户"能帮助模型判断是该提取还是该追问。
- 是否存在同名歧义字段?多个技能里相似的参数容易相互干扰,可以把参数名设计得更具区分度,比如 order_count 和 total_orders。
我见过最典型的案例是,一个技能期望"日期"格式为 YYYY-MM-DD,而用户在对话里说的是"星期六"。模型拿不准时就会原样传"星期六"。后来我在参数的 description 里加了一句"如果用户提供的日期是自然语言如'明天'、'星期六',请先换算成 YYYY-MM-DD 格式再传入",问题就解决了。这也是我在前文提到过的"参数规范化"在描述层的配合手段。
5.3 多技能协作时顺序混乱怎么处理
模型在多技能场景下打乱执行顺序,本质原因是技能间的依赖关系没有被显式表达。我的解决办法有三层:
第一层,在技能描述里写清前置条件,如"必须先获取 A 才能调用本技能"。第二层,在引擎层面做依赖检查,当模型选择了一个前置条件未满足的技能时,引擎不是直接执行,而是返回一个"前置技能未执行"的错误提示,引导模型先执行前置技能。第三层,在 prompt 里写明编排规则,比如"多技能场景下请按顺序执行,前一个技能的输出是后一个技能参数的数据来源"。
这三层叠加的效果非常明显,我项目里的多技能流程顺序正确率从 62% 提升到了 88%。特别是第二层"引擎主动检查依赖",本质上是把容错机制从"靠模型自觉"变成了"靠系统兜底",这是工程化落地的关键思路。
5.4 技能结果回传后模型理解错乱
有个奇怪的现象:工具返回的结果明明很完整,但模型在生成最终回答时出现幻觉,编造一些工具没返回的数据。排查后发现问题出在工具结果的格式上。
当工具返回的是 JSON 字符串时,如果嵌套层级过深,或者字段名没有语义,模型的阅读理解容易出偏差。我的建议是:工具返回给模型的结果要尽量扁平化、语义化。比如把 {"data": {"a": {"b": 1}}} 拍平为 {"用户数量": 1} 这种形式,模型理解起来轻松得多。
另外,如果工具结果太长,比如一份完整报表,全部塞给模型既不经济也容易干扰判断。我会在技能层做一个结果摘要,只把关键指标写入回传内容,明细数据另外存库供必要时查询。这个做法在 token 成本和理解准确率上都有明显收益。
5.5 技能运行时的安全与权限控制
技能意味着模型可以操作真实系统,安全边界必须提前设好。我在项目里主要做了几件事:
- 技能权限分级:只读类技能(查询、检索)默认开放;写入类技能(发送通知、修改配置)需要额外授权边车,模型只有在会话中明确获得用户授权后才允许调用。
- 敏感参数脱敏:技能入参和出参中的手机号、邮箱等信息做脱敏处理,避免模型在对话中复述敏感信息。
- 操作审计:所有技能调用记录全量入日志,包含调用者会话、入参、出参、时间戳。这既是为了安全追溯,也是评估集构建的数据来源。
这些安全机制看起来费功夫,但对生产系统来说是不可省的部分。特别是当你的 Agent 技能涉及财务、运维、客户数据时,一次越权操作可能带来巨大的业务风险。
6. 从项目到平台的延展建议
agent-skills 跑完单点项目之后,下一步往往是平台化的需求。随着技能数量从十几个增长到上百个,会出现几个新问题:技能权限怎么管理?技能质量怎么保证?不同业务线怎么共享技能?
我的建议是往"技能市场"方向演进。每个技能像一个应用,有发布、审核、上下线流程;技能之间有评分和调用量统计,帮助开发团队识别高价值技能和低效技能;跨业务线共享技能时,通过统一的权限体系控制访问范围。
这个方向做的事情本质上是把 agent-skills 从"工程组件"提升到"组织能力中台"。落到具体执行上,其实是三件事:一是技能定义标准化,所有团队都按同一套 schema 注册;二是技能生命周期管理,从开发到退役都有清晰流程;三是技能数据回流,让调用数据反哺技能优化。
如果你是在企业内部做这个方向,千万不要一上来就追求大而全的平台。先把三五个核心场景的技能做扎实,跑通注册、调用、评估、迭代这个闭环,用实际效果说服团队和业务方,再逐步扩展。技术平台最忌讳的是空转,没有业务价值的技能市场,只会变成一个没人用的摆设。
在我自己的项目里,正因为先在一个运维场景里验证了整套体系的稳定性,后续才能顺利复制到知识库问答、工单处理、数据分析等其他场景。每一个新场景的接入成本都在下降,因为技能注册、路由、评估这些基础设施已经就绪,新场景需要做的只是定义技能和实现 handler。这种"基础能力沉淀 + 场景快速复制"的模式,应该就是 agent-skills 真正值钱的地方。
最后分享一个我在日常开发里的小习惯:每次新增一个技能,我都会在评估集里补 5~10 条相关测试请求,并且把技能描述打印出来亲自读一遍,读的时候问自己:如果我是模型,看到这段描述能准确判断何时调用吗?能正确生成参数吗?这两个问题想明白了,技能的调用效果基本就有保障了。希望这套实践对你也有用。