Agent Skills工程化:给AI智能体装上可复用的专业技能

发布时间:2026/9/24 23:11:11
Agent Skills工程化:给AI智能体装上可复用的专业技能 最近几个月我一直在打磨一个叫 agent-skills 的工程化项目它解决的问题很直接怎么给 AI 智能体配上真正能用的“专业技能”。做 Agent 开发的朋友应该都有同感——大模型本身的推理能力再强如果没能把业务动作挂到它身上那它始终只是一个很会聊天的空壳问它“帮我查一下报表里的合同号”它只会抱歉地说自己做不到。agent-skills 这套东西就是把散落的工具函数、提示词和接口调用组织成一套 Agent 能理解、能调用、能维护的技能体系。如果你正在搭自己的智能体或者想把现有 Agent 从“演示能用”推到“业务真用”这篇文章应该能给你一些可以直接抄的作业。1. Agent Skills 到底是什么为什么值得单独做一层1.1 从“会聊天的 Agent”到“会干活的 Agent”差的是一整层技能很多人刚接触 Agent 开发时会有一个误解模型够聪明提示词写得够细它就自然会调工具、自己干活了。真上手你会发现完全不是这么回事。模型最擅长的是文本生成它本身不会发 HTTP 请求、不会操作数据库、不会读写文件所有真实世界的动作都必须由外部代码完成。问题在于代码执行和模型决策之间需要一条桥。Agent Skills 就是这条桥上的标准化货柜。一个技能至少包含三样东西告诉模型“什么时候该用它”的描述、声明“需要哪些参数”的输入约束、以及真正干活的执行函数。有了这层封装模型才能像人一样“看到工具箱里有什么工具然后选择合适的工具把事办了”。我把 agent-skills 做成独立项目的原因也很简单早期我试过把工具定义、调用逻辑、参数说明全部写在系统提示词里结果提示词越写越长模型触发逻辑越来越乱改一个函数要翻遍整段 prompt。把技能抽成独立模块之后每个技能都是独立的文件包新增一个能力不用动主流程删除一个能力也不会影响其他逻辑。这不只是代码洁癖问题是 Agent 工程能否规模化复制的前提。1.2 Skill、Tool、Plugin、Workflow 的边界到底在哪这四类概念经常被混着说实际选型时容易踩坑。我自己的划分方式是这样的Tool 是最小的可调用单元通常就是一个函数映射负责一件没有上下文、无状态的事情比如“查询城市天气”“发送一封邮件”。Skill 是面向一类问题的完整解决包内部可以组合多个 Tool同时携带描述、参数校验、错误处理甚至前置条件。Plugin 一般代表对一个外部系统的整体接入比如“接入飞书文档”它能暴露出一组 Tool 或 Skill。Workflow 则强调预定义的执行流程有明确的先后顺序、分支判断和状态流转。区分它们的关键是看抽象粒度。如果你的 Agent 只需要调两三个 API直接用 Tool 就够了。但一旦业务复杂起来——同一个功能要处理多种输入格式、要调用多个接口、还要根据中间结果决定下一步——那就应该升级成 Skill因为你需要一个地方统一管理描述、校验和异常处理。agent-skills 这套项目里我刻意把 Skill 作为一等公民所有能力都先封装成 Skill再根据需要暴露成外部可调的工具这样的好处是内部可以复用对外又是统一的调度视图。概念粒度典型特征使用场景Tool最小函数单一动作无状态查天气、发消息、执行 SQLSkill问题域封装描述 参数约束 执行逻辑财报结构化提取、工单自动分类Plugin外部系统接入封装第三方整套能力云存储、项目管理软件集成Workflow流程编排固定顺序与分支审批流、异常处理链路实际做下来我的体会是不要一上来就设计大而全的 Workflow先把核心动作做成 Skill等流程稳定了再考虑编排。技能层的抽象粒度太小模型选择成本高粒度太大一个技能管的事情太多参数校验和错误处理就会变得很复杂。找到一个“只解决一类问题”的边界是最理想的状态。2. 设计一个 Skill 前要把这四件事想清楚2.1 技能边界一个技能只做一件事但得“完整地做成一件事”技能边界是最容易被低估的设计决策。我团队最早把“查询订单”和“生成订单报表”做进了同一个技能里当时觉得都是订单域的东西放一起方便。结果模型触发率极不稳定用户问“上周的订单量是多少”模型一会儿调这个技能一会儿调那个技能日志里全是混乱的工具调用记录。后来拆成两个技能每个技能只回答一类问题模型的选择准确率一下就上来了。但边界也不能切太碎比如“查询订单物流”和“查询订单状态”如果拆成两个模型在两者间区分也会犹豫。正确的做法是围绕用户最终要的结果来切分一个技能负责产出一种完整的结果类型。判断标准很朴素——技能名字说出来用户能立刻理解它是干嘛的比如 pdf_report_extractor、customer_sentiment_analyzer这就是合格的边界。还要考虑技能之间的依赖关系。有些技能天然需要前置条件比如“生成财务摘要”必须依赖“读取财报文件”的结果。我的处理方式是在技能描述里显式声明前置依赖同时把依赖技能的 ID 写进参数说明模型在决策时会自动把两个技能串起来调用。这比强行把所有逻辑塞进一个技能里要清晰得多。2.2 输入输出设计JSON Schema 是给大模型看的“用户手册”输入参数的定义直接决定模型能不能正确调用技能。这里的核心工具是 JSON Schema很多框架都原生支持但你得知道哪些字段真正影响模型行为。type、required 只是最基本的要求真正起作用的是每个字段的 description 和 enum。举个例子我们有个技能需要接收语言参数一开始 schema 里只写了language: {type: string}结果模型有时传Chinese有时传zh-CN有时传简体中文后端解析逻辑直接崩溃。后来我在 schema 里加了 enum[zh-CN, en-US]模型就规规矩矩按枚举值传参了。还有日期格式必须写明format: date并且给一个 example模型才会乖乖传2025-06-01而不是6月1日。输出结构同样重要。建议所有技能都返回结构化的 JSON不要返回一串格式随意的文本。比如 PDF 提取技能就返回固定字段page_count、fields、raw_text_length。模型拿到的结果越规整它后续组织回答就越稳定。如果技能执行失败也要返回统一格式的错误对象包含 error_code 和 error_message这样模型才能理解失败原因而不是把报错文本原样抛给用户。我在实际项目里见过太多次“模型把 traceback 当答案直接念给用户听”的场面根因就是技能的错误输出没有结构化。2.3 描述怎么写描述是模型判断“要不要调你”的唯一依据技能描述写得好不好直接影响触发率。这类描述和普通函数注释完全是两码事——不是写给程序员看的是写给大模型看的。模型会拿用户当前问题和你每个技能的 description 做语义匹配描述写得越贴近真实用户问法触发越准。很多人的描述写成这样“读取 PDF 文件并提取信息”。问题在于它只描述了技能“是什么”没告诉模型“什么时候该用”。下面这个写法触发率高得多当用户上传或指定 PDF 格式的财报、合同、业务报告且需要提取合同编号、总金额、签发日期、页数等结构化字段时使用。注意本技能不支持扫描件 OCR扫描件请调用 ocr_document_skill 处理。这段描述做了三件事说明了适用场景、指明了能力边界、还给出了连带推荐。这种写法能让模型在碰到文件类请求时第一时间想起这个技能。描述里还应该尽量避免其他技能名称造成干扰如果你发现两个技能频繁被搞混优先检查它们的描述是不是有大量重叠短语。把描述当成产品的“路由说明”来写模型的技能选择准确率会有肉眼可见的提升。2.4 参数校验永远不要完全信任模型生成的内容大模型生成工具参数出错是常态不是异常。模型可能漏掉必填字段、把类型传错、甚至自己编造一个值。所以技能执行入口处必须加一层严格校验。我通常用 pydantic 的 BaseModel 来定义技能入参在 execute 最开始做一次 model_validate。校验失败时不要默默吞掉错误也不要在技能内部抛异常完事而是返回一个结构化的校验失败结果让模型知道“这次调用缺了 file_path 字段请补全后重试”。这样做的好处是模型能在下一轮自动修正自己的参数整个调用链路看起来很像是技能在引导模型成长。实际测试里加了校验反馈后某些技能的参数正确率能从 60% 拉到 85% 以上效果非常明显。# 每个技能入口都做一次参数校验 try: params SkillInputModel.model_validate(input_data) except ValidationError as e: return { success: False, error_code: INVALID_PARAMS, error_message: f参数校验失败: {e.errors()} }3. 手写一个完整技能包从注册到调用3.1 项目结构与技能包的目录规范agent-skills 项目里每个技能都存放在独立的目录下包含四个核心文件SKILL.md 负责描述schema.json 负责参数约束main.py 负责执行逻辑requirements.txt 负责依赖声明。目录结构长这样agent-skills/ ├── skills/ │ └── pdf_report_extractor/ │ ├── SKILL.md │ ├── schema.json │ ├── main.py │ ├── requirements.txt │ └── tests/ │ └── test_extract.py └── runtime/ ├── registry.py ├── adapter_openai.py └── client.py这个规范是我踩过几次坑之后定下来的。早期我把技能描述和参数定义直接写在代码里改描述要重新发版非常痛苦。后来拆成独立元数据文件运营同学也能直接修改描述、调整参数说明不用碰代码。技能目录挂在 skills 下每个技能自带测试目录改动任何一个技能不会互相污染。3.2 技能注册中心让所有技能有一个统一的入口技能注册中心是整个框架的枢纽。启动时它会扫描 skills 目录下的所有技能包加载 SKILL.md 里的元数据、schema.json 里的参数结构并把 main.py 里的 execute 函数注册进来。我这里的注册表用了一个最简单的实现但已经能应付大部分场景from dataclasses import dataclass, field from typing import Any, Callable, Dict dataclass class Skill: name: str description: str parameters: Dict[str, Any] execute: Callable[[Dict[str, Any]], Dict[str, Any]] timeout: int 60 class SkillRegistry: def __init__(self): self._skills: Dict[str, Skill] {} def register(self, skill: Skill) - None: self._skills[skill.name] skill def list(self) - list[Skill]: return list(self._skills.values()) def get(self, name: str) - Skill: if name not in self._skills: raise KeyError(f技能 {name} 未注册) return self._skills[name] registry SkillRegistry()注册中心还有一个容易被忽略的作用做技能的冲突检测。如果两个技能名字相同启动阶段直接报错如果两个技能的描述过于相似也能在注册时给出告警。这些检查看似多余但在技能数量超过十个之后就会变成刚需否则模型会频繁选错技能排查起来相当头疼。3.3 让模型自动选择技能工具适配与调用闭环模型不是天然知道你有哪些技能的你需要把技能列表转成大模型能理解的 tools 格式传递过去。以 OpenAI 兼容接口为例一个技能就是一个 function 定义name、description、parameters 一一对应。我把这个过程做成了 adapterdef to_openai_tool(skill: Skill) - Dict[str, Any]: return { type: function, function: { name: skill.name, description: skill.description, parameters: skill.parameters, } }调用时的完整链路是这样的先把技能列表通过 tools 参数传给模型模型在回答用户问题时如果认为某个技能匹配就会在返回的 message 里带上 tool_calls运行时解析 tool_calls从注册中心取出技能执行函数再把结果以 roletool 的形式追加到消息列表重新发给模型让模型基于技能返回值生成最终回答。response client.chat.completions.create( modelmodel_name, messagesmessages, tools[to_openai_tool(s) for s in registry.list()], tool_choiceauto, temperature0.2, ) if response.choices[0].message.tool_calls: for tool_call in response.choices[0].message.tool_calls: skill registry.get(tool_call.function.name) args json.loads(tool_call.function.arguments) result skill.execute(args) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) # 把带有工具结果的完整消息重新发回模型得到最终答案这里有两个细节非常影响稳定性。第一system prompt 里必须补一句路由策略比如“当技能命中时优先调用技能不要编造参数不要猜测字段值”。第二temperature 建议保持在 0.2 以下否则模型的选择会带上随机性同样的问法时而调技能时而不调这在生产环境里是致命的。实测下来temperature 设为 0 到 0.2 之间工具调用的确定性最高。3.4 实测一段技能调用PDF 报告结构化提取我用一个实际技能来说明执行函数怎么写。假设我们要做一个 pdf_report_extractor负责解析 PDF 财报和合同报告提取合同编号、总金额、签发日期等结构化字段。核心逻辑大致是这样import re import pdfplumber from typing import Dict, Any PATTERNS { contract_no: r合同编号[:\s]*([A-Z0-9\-]), total_amount: r总金额[:\s]*([0-9,.]), sign_date: r签发日期[:\s]*([0-9]{4}[-/年][0-9]{1,2}[-/月][0-9]{1,2}日?), } def run(input_data: Dict[str, Any]) - Dict[str, Any]: file_path input_data[file_path] report_type input_data.get(report_type, generic) text with pdfplumber.open(file_path) as pdf: for page in pdf.pages: page_text page.extract_text() if page_text: text page_text \n fields {} if report_type in (finance, financial): for key, pattern in PATTERNS.items(): match re.search(pattern, text) if match: fields[key] match.group(1) return { success: True, page_count: len(pdf.pages), raw_text_length: len(text), fields: fields, }这个实现刻意保持简单有两个点值得说明。一是 pdfplumber 的 extract_text 在扫描页上会返回 None代码里必须用空字符串兜底否则整段文本拼接会因为 None 直接中断。二是正则提取字段失败时要保持 fields 为空字典而不是报错这样技能返回结构始终稳定模型也能理解“这份报告里可能没有合同编号”。执行的时候还有个关键决策技能要不要在线程池里跑。我的建议是一款技能带一个超时控制比如默认 60 秒超时强制取消返回超时错误。Agent 的调用链路通常面向对话场景用户等不了太久一个卡死的 PDF 解析进程会把整个 Agent 拖住加超时是性价比最高的保护。4. 常见问题与排查技巧实录4.1 模型压根不调用技能或者总是乱调用这是最常被问的问题也是整个 Agent 系统里最让人头大的故障。我接手过好几个项目第一反应都是去查日志看模型的 tool_calls 到底有没有触发。如果完全没触发八成是技能描述和用户真实问法的语义距离太远模型不觉得它和当前问题有关。解决办法不是写更多提示词而是去翻真实用户对话记录把高频问法提炼出来写进描述里。如果模型频繁乱触发可能是技能之间边界模糊或者描述里出现了太多共同关键词。还有一个容易被忽略的原因system prompt 里对技能使用的约束太弱。模型默认倾向多说多做既然不远处摆着工具它想秀一把操作结果就问了一句“几点了”它也可能调一次天气技能。这时候需要在系统提示词里明确“只有用户请求明确匹配技能能力时才调用不确定时先澄清需求”。4.2 参数永远是错的模型怎么都传不对参数错误分两类一类是字段缺失另一类是值不对。字段缺失通常是因为 schema 里没有把 required 写全或者模型不理解字段含义。值不对最常见的是枚举值自由发挥比如语言、币种、地区这类字段。我的排查顺序非常固定先看是不是 schema 缺 description 和 example再看是不是缺 enum 约束最后看校验失败时有没有把错误原因反馈给模型。这里我一直强调参数校验失败后的反馈机制是整套链路里最容易出效果的一环。模型一次传错并不可怕可怕的是系统不告诉它哪里错了它就会不断用同样的错误重试。只要把 ValidationError 的信息原样返回给模型大多数情况下模型下一轮就能修正。4.3 技能执行很慢甚至把整个 Agent 卡死了技能执行耗时是上线之后才真正暴露的问题。本地调函数很快但一旦技能变成“读文件 调外部 API 数据加工”的复合操作几十秒的延迟非常正常。解决思路是分级把技能分为快速技能几秒内和重技能可接受数十秒对话链路里优先让模型选用快速技能重技能则走异步任务模式先返回一个“任务已受理”的提示用户稍后主动查询结果。同时所有技能执行都要有超时和并发限制。我在 runtime 层给每个技能加了 timeout 配置默认 30 秒到 120 秒不等还限制了同时执行的技能数量避免用户连续触发多个重任务把进程资源打满。日志里记录每次执行的开始时间、结束时间、耗时和失败原因这些数据对后续优化非常重要。4.4 技能之间的先后依赖处理不好多技能协作看起来很美实际做起来比想象中麻烦。比如“生成季度财务摘要”这个技能需要先调用“读取财报文件”拿到数据再调用“调用摘要生成”得到结果。如果两个技能的定义里没有显式关系模型可能会跳过第一步直接拿一个不存在的文件路径去调用第二个技能然后报错。我目前的解法是在技能描述里写明依赖关系同时把依赖技能的调用方法写进参数说明。更稳妥的还能做一个“技能编排层”在代码层面定义好顺序而不是把主动权全交给模型。实际业务里凡是关键链路我都不建议完全依赖模型临场发挥固定编排加上模型判断才是高可靠性的组合。4.5 技能排查速查表症状大概率原因首选解法技能完全不触发描述与用户问法语义距离远用真实对话样本重写描述频繁误触发技能边界重叠、描述关键词重复拆分技能、互斥描述参数缺失或类型错schema 缺少 description / example补全字段说明加枚举约束技能执行卡死无超时、文件过大、外部 API 慢加 timeout 与资源限制失败循环重试错误反馈非结构化返回 error_code error_message技能间依赖混乱未声明前置依赖描述里写明依赖关键链路代码编排这张表的每条都来自实际踩坑。技能系统的排查通常不用查业务逻辑先看描述、再看参数约束、最后看错误反馈80% 的问题都能定位。5. 把一套技能体系放到真实业务里5.1 技能库的组织方式按领域归档按场景取名技能数量上来之后组织方式就很重要了。我建议把技能目录按业务领域划分finance、hr、operations、content 等。命名用domain-action的结构比如 finance_report_export、hr_leave_balance这样从技能名就能看出它的归属和能力。技能目录再配合统一的元数据规范新增一个技能时照着已有模板复制一份就好几乎没有学习成本。每个技能的 SKILL.md 头部写清楚版本号和负责人。技能也有“开发—测试—灰度—上线”的生命周期改描述、改参数、改执行逻辑都要记录版本。我早期吃过一次亏某天运营同学改了一个技能描述没同步测试第二天模型触发率直线下降找了一天原因才发现是描述里的关键词被改没了。从那以后技能文件的任何改动都走 commit描述变更必须有对应测试用例。5.2 用离线测试集保住技能触发的准确率技能模型本质上是“描述驱动的代码”所以最值得投入的是一套固定测试集。我维护了一个高频问法集合包含三类应该触发技能 A 的样例、应该触发技能 B 的样例、以及不该触发任何技能的干扰样例。每次修改技能描述或参数定义后就跑一遍这套测试集记录技能触发正确率、参数正确率和平均耗时。这套测试不需要引入庞大的测试框架就是一组文本输入和预期结果的映射。跑一次大概几十秒却能在上线前拦截大量问题。效果最明显的一次是新增了一个与现有技能高度相似的技能离线测试立刻暴露了混淆率我照着失败样例改描述把两个技能的路由区分开上线后才没出事故。可以说离线测试集是技能体系里性价比最高的基础设施。5.3 技能调用全程可观测日志里要有什么技能不是写完就结束的它需要持续看护。我要求每个技能调用都记一条结构化日志至少包含技能名称、模型版本、输入参数、返回结果、成功与否、耗时。字段比这还多一两个也行但核心字段一个都不能少。有了这些数据每天都能看到哪个技能被调用最多、哪个技能失败率最高、哪个技能参数错误最频繁这些指标直接决定下一轮迭代优先级。有一次我们发现某个技能失败率从 5% 突然涨到 30%查日志发现是外部接口改了响应格式。因为技能把解析逻辑写死在返回结构里没有做容错接口一改就全挂了。后来所有技能的外部调用全部加了一层响应适配即使外部改动也能在适配层拦截而不是把错误炸到用户面前。这类问题的提前规避只能靠日志和监控暴露出来。5.4 跨模型迁移技能元数据与厂商格式解耦最后聊一个所有 Agent 开发者都会碰到的问题模型厂商换了技能要重写吗答案是不应该。技能的核心资产是 SKILL.md 里的描述、schema.json 里的参数约束、以及 main.py 里的执行逻辑这些都是与模型无关的。你也可以为不同模型写适配层例如将统一的技能 schema 转成不同的工具格式。vendor 与技能解耦之后换模型只是一个适配器的事。我实际迁移过一遍感受非常深。旧方案里技能定义和调用代码强绑定迁移到另一个模型平台时光改工具格式就花了两天。新方案把技能统一封装后适配器一天内就写完了。顺带一提这种解耦还有一个好处同一个技能库可以同时供多个 Agent 使用比如客服 Agent 和数据分析 Agent 共用财务报告提取技能不用各写一遍。agent-skills 这套体系做到现在我最深的体会是技能体系最怕的不是代码写得差而是描述和实际能力脱节。模型再聪明也只是在“你告诉它能做什么”的范围内做选择。所以每个技能落地之后多花点时间采集真实用户问法、跑离线测试、记录调用日志这些基本功比任何花哨的编排框架都管用。先在两三个核心技能上找到手感再逐步扩展到整个技能库这个节奏走下来Agent 才能真正从“玩具”变成“工具”。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询