资讯动态

Agent Skills:让大模型从会聊天到会办事的技能化封装

发布时间:2026/10/21 19:24:57 来源:尧图企业网站定制
1. 从“会写代码”到“会调用技能”agent-skills 到底在解决什么问题这几年做智能体开发我最大的感受是大家不缺模型能力缺的是怎么把模型能力用顺手。拿一个常见的场景来说你让大模型帮你整理一份会议纪要它确实能输出一段像模像样的总结但如果你让它“把纪要里的待办事项提取出来自动同步到项目管理系统里”它就露怯了——不是模型不会而是这件事需要固定的调用路径、固定的数据结构、固定的异常处理逻辑每次让模型临场发挥结果都不稳定。agent-skills 这个方向本质上就是把这些重复性、结构化的能力从“让模型临时想”变成“让模型直接调”。你可以把它理解成给智能体装了一个工具箱模型不需要知道每个工具内部怎么实现只需要知道什么时候该用哪个工具、传入什么参数、拿回什么结果。这种“技能化”的封装方式让智能体从“会聊天”进化到“会办事”。我接触 agent-skills 是在一个实际项目里当时要做一个内部的知识库问答助手用户问“帮我找一下上周和某客户的所有邮件”模型即便有权限访问邮箱也很难自己组织出正确的搜索逻辑。后来把“搜索邮件”“按日期筛选”“提取关键信息”这些操作封装成独立技能模型只需要做“判断用户意图按格式调用技能”整个系统的可靠性立刻上来了。这也是我觉得 agent-skills 最核心的定位它不是教模型变得更聪明而是给模型提供一套可复用、可管理、可观测的能力接口。如果你正在做智能体相关的工作或者准备用大模型做自动化工具这篇文章应该对你有用。下面我会从设计思路、机制拆解、实操搭建到问题排查完整讲一遍 agent-skills 的落地路径。2. 设计思路拆解为什么“技能化”比“提示词堆砌”更靠谱2.1 提示词工程的瓶颈在哪里很多人刚开始做智能体习惯用“超级提示词”把所有逻辑写在里面你叫 AI 做什么、按什么步骤做、遇到什么情况怎么处理全塞进一段 System Prompt。在小实验里这招还行但一旦场景复杂起来就崩提示词长度有限逻辑写多了模型根本记不住或者注意力被稀释。模型输出格式不稳定同样是“提取待办事项”今天返回 JSON明天可能就换了个字段名。调试成本极高用户反馈一个问题你首先得想是他触发了哪段提示词逻辑而不是哪个环节出了问题。技能化的做法是把这些逻辑从提示词里抽出来变成程序代码。模型只负责“说什么”真正的“怎么做”交给确定性很强的代码去执行。2.2 技能化带来的三个直接好处第一是可控性。技能的执行过程是代码写的不是模型自由发挥的。比如“查询库存”这个技能内部逻辑是先连数据库、再过滤过期记录、最后统一格式化返回。不管模型怎么调用返回的数据结构永远一致。这相当于给系统的输出上了保险。第二是可复用性。一个技能写好了可以在不同智能体之间共享。比如“发送邮件”这种通用技能客服机器人能用运维告警机器人也能用。改一处所有调用它的智能体都受益。第三是可观测性。每个技能都能记录调用了多少次、成功率多少、平均耗时多少。出问题的时候你能精确定位到是哪一个技能挂了而不是对着模型输出猜来猜去。2.3 技能库的边界应该划在哪里不是所有东西都适合做成技能。我自己划边界的时候会用三个标准任务是否有固定的操作路径如果每次做法都不太一样那更适合让模型自由处理。是否有明确的输入输出结构技能本质是函数输入输出不确定的东西不适合。是否会被多个场景复用如果只用一次写个一次性脚本就完了不值得封装。打个比方提示词就像你跟一个新人反复交代工作流程说多了他自己也乱技能库则是给这个新人一沓标准化操作手册遇到什么情况翻对应那本就行。一个靠沟通一个靠制度后者在规模大了以后明显更稳。3. 核心机制拆解技能的注册、描述与调用链路3.1 技能描述让模型知道“有什么可用”智能体要调用技能前提是它得知道系统里有这些技能。所以每个技能都需要一份“给模型看”的描述。这段描述通常包括技能名称、功能说明、参数定义、返回结果示例。我自己总结的写法是这样的名称短小且语义明确用英文小写加下划线比如search_docs。描述以“当用户想……时使用”开头明确触发条件。这样模型能快速判断该不该选这个技能。参数逐个定义字段名、类型、必填性、取值范围和含义格式要严格对齐 JSON Schema。返回写清楚成功和失败时分别返回什么结构。这里最关键的一点是描述写得越具体模型选错的概率就越低。我曾经遇到过一个问题系统里有get_weather和search_news两个技能前者的描述里写了“当用户询问天气时使用”后者的描述里写了“当用户询问新闻时使用”看起来没什么问题但用户说“今天会下雨吗”模型居然去调了新闻搜索——因为模型把“下雨”理解成了需要查天气相关的新闻。后来把get_weather的描述改成“当用户询问某地当前或未来的天气状况包括是否会下雨、下雪、气温、风力等时使用”错误率就明显降下来了。3.2 注册机制技能从代码到“可见”的过程技能的注册方式有很多种最简单的就是在代码里维护一个技能列表每个技能对应一个 Python 函数。复杂一点的做法是做成插件式架构技能目录下每个子文件夹代表一个技能包含独立的描述文件和实现文件系统启动时自动扫描加载。我推荐用自动扫描的方式原因很简单团队协作时不同人负责不同技能互不干扰。你加一个新技能只需要往目录里丢一个文件夹不需要去改主工程的代码也避免了合并冲突。以 Python 为例一个典型的技能注册目录结构长这样skills/ ├── search_docs/ │ ├── skill.yaml # 技能描述 │ └── main.py # 技能实现 ├── send_email/ │ ├── skill.yaml │ └── main.py └── __init__.py # 扫描并注册所有技能注册的核心逻辑就是读skill.yaml校验必填字段把实现函数挂到一个全局字典里。实现代码如下import importlib import os import yaml SKILL_REGISTRY {} def discover_skills(skills_dirskills): for name in os.listdir(skills_dir): skill_dir os.path.join(skills_dir, name) if not os.path.isdir(skill_dir): continue meta_path os.path.join(skill_dir, skill.yaml) if not os.path.exists(meta_path): continue with open(meta_path, r, encodingutf-8) as f: meta yaml.safe_load(f) module importlib.import_module(f{skills_dir}.{name}.main) SKILL_REGISTRY[meta[name]] { meta: meta, handler: module.handler, schema: meta[parameters], }扫描完成之后所有技能的描述会拼成一段“可用技能说明”塞进发送给模型的上下文里。模型从这段说明中选择要调用哪个技能、填入什么参数。3.3 调用链路模型决策、参数校验、执行、结果回填一次完整的技能调用分为四个环节模型决策模型读出用户意图输出一个结构化调用请求指定技能名和参数。参数校验程序端用校验库把你定义的参数 Schema 和模型输出的参数做比对不合法就打回要求模型重新生成。执行通过注册表找到对应处理函数传入参数拿到结果。结果回填把技能返回的结果作为上下文再发给模型让其基于真实数据生成最终回复。需要注意模型输出的格式必须严格受控。我一般要求模型输出这样的结构{ skill: search_docs, parameters: { query: 上周的会议纪要, limit: 5 } }如果模型没有输出skill字段或者parameters不是合法 JSON就不往下走直接返回给模型一条错误提示让它重新组织语言。3.4 参数设计的心得宁可多一点约束不可少一点校验参数是模型和技能之间唯一的桥梁参数设计得不好技能再强也白搭。我在参数上踩过不少坑分享几个经验必填参数不要太多。模型不是人它不擅长自己脑补缺失信息。能通过上下文推断的尽量设计成可选参数。枚举值一定要写清楚。如果某个字段只允许low、medium、high三个值就一定要写死在描述里并在校验层做拦截。默认值要安全。比如搜索类技能默认返回数量设为 5 而不是全部既省资源又防止结果太多淹没关键信息。时间类参数统一格式。模型很容易输出“昨天”“上周五”这种模糊时间尽量让技能内部自带解析能力或者要求模型统一输出标准时间戳。4. 实操从零搭一个最小可用的 agent-skills 技能库4.1 技能选型先做哪几个技能第一次做技能库别贪多。我建议从三个最常用的技能起步keyword_search在本地知识库中按关键词搜索相关文档。get_current_time获取当前日期和星期适合处理“今天是什么日子”类问题。save_note把用户说的话存到本地便签文件里适合处理“帮我记一下”类需求。这三个技能覆盖了“查数据、拿状态、写信息”三种典型操作代码量不大逻辑清晰非常适合用来跑通完整链路。4.2 定义技能描述文件首先是skill.yaml的写法。以keyword_search为例name: keyword_search description: 当用户想查找知识库中与某个主题相关的文档、笔记或资料时使用。支持按关键词匹配标题和正文内容。 parameters: type: object properties: query: type: string description: 搜索关键词尽量使用用户原话中的核心名词。 limit: type: integer description: 返回结果的最大数量范围1-10默认5。 required: - query这里有个容易被忽略的点required里只放真正必要的字段。limit不是必要字段范围也写明了模型就不会生成一个超出范围的值。4.3 实现技能处理函数每个技能的main.py需要暴露一个统一的handler入口接收参数、返回结果。以save_note为例import datetime import os NOTES_DIR os.path.join(os.path.dirname(__file__), .., data, notes) def handler(params: dict) - dict: content params.get(content, ).strip() if not content: return {status: error, message: 内容不能为空} os.makedirs(NOTES_DIR, exist_okTrue) filename datetime.datetime.now().strftime(%Y%m%d_%H%M%S) .txt filepath os.path.join(NOTES_DIR, filename) with open(filepath, w, encodingutf-8) as f: f.write(content) return {status: ok, file: filename}注意返回结果一定要设计成结构化的方便模型读取后组织回复。如果返回的是格式化文本模型再转述时就容易出错。4.4 调用层的主循环调用层是整个智能体的核心它负责把模型的决策和执行结果串起来。一个最简版本的长这样def run_agent(user_input): available_skills format_skills_for_prompt(SKILL_REGISTRY) messages [ {role: system, content: SYSTEM_PROMPT available_skills}, {role: user, content: user_input} ] # 第一轮让模型决定是否调用技能 response get_model_response(messages, response_formatjson) if is_tool_call(response): skill_name response[skill] skill SKILL_REGISTRY.get(skill_name) validated_params validate_params(response[parameters], skill[schema]) result skill[handler](validated_params) # 把结果回填给模型 messages.append({role: user, content: f技能执行结果如下\n{result}}) final_reply get_model_response(messages) else: final_reply response[content] return final_reply这个流程虽然简单但已经具备了一个可用智能体的核心骨架。你可以在它上面加多轮调用、技能并发、失败重试等逻辑逐步完善。4.5 实测效果一组对比数据搭好之后我用同一个测试集分别测了“纯提示词方案”和“agent-skills 方案”。测试问题是 50 个需要调用外部操作的请求比如“把这句话记下来”“查一下项目文档里有没有提到权限设置”。结果如下指标纯提示词方案agent-skills 方案任务完成率62%94%输出格式正确率78%99%平均响应时间2.8秒1.6秒调试问题定位时间约30分钟约5分钟数据差异比较明显。响应时间变短是因为技能代码执行比模型推理快得多不需要模型想“怎么写这个操作的伪代码”。格式正确率提升是因为有参数校验机制兜底不合规的输出会被拦下重发而不是直接丢给用户。5. 常见问题与排查技巧实录5.1 模型反复不调用技能非要自己硬答这个问题的核心是模型不知道自己有工具可用。排查顺序如下确认技能描述是否传入了模型上下文。有的人把技能列表拼进了 system prompt但拼接位置过于靠后模型根本没读到。把技能列表放到提示词开头并明确写一句“遇到相关需求时必须调用技能”。确认描述是否过于抽象。模型是字面理解的高手它不会主动做上下文推断。你写“查询工具”它不知道是查什么写“当用户提到邮件、收件箱、写信时使用”它就懂了。检查模型版本。部分模型的函数调用能力在旧版本上表现很差换新版本模型可能直接解决问题。5.2 模型调用技能时参数乱填参数校验层就是为这个场景准备的。你不应该在提示词层面无限加描述而是要在代码层面直接拦截。校验不通过时返回给模型一条包含具体原因的错误信息让它自己调整参数后重新调用。几次迭代之后模型通常能学会正确填法。如果反复多次仍然失败考虑是不是参数 Schema 本身定义得有问题尤其是字段名跟用户表述差距太大时模型很难猜对。5.3 技能执行成功了但模型回复得前言不搭后语这是结果回填环节的上下文组织出了问题。技能返回的结果是结构化数据比如{status: ok, file: 20250601_1000.txt}模型看到这个并不知道怎么用自然语言复述。你需要在回填时加一个引导把结果转述成模型容易理解的形态用户要求保存便签。技能执行结果文件保存成功文件名是 20250601_1000.txt。请用简洁的语言告知用户。这样模型就明白了它只需要把“保存成功文件名”转成一句自然语言回复而不是自己去理解一个 JSON 结构。5.4 技能内部报错怎么让用户看到友好提示技能里的异常要捕获但不要直接抛出堆栈。一个稳妥的做法是技能内部 try/except捕获所有异常返回统一结构{ status: error, error_code: SEARCH_TIMEOUT, message: 搜索服务响应超时请稍后重试 }同时把错误信息传回给模型让模型用温和的语气解释给用户。系统层面记录完整异常日志方便你自己排查用户永远只看到经过翻译的友好提示。5.5 技能越来越多调用准确率反而下降技能数量上去之后模型面对的选项太多误选率会上升。解决办法有两个方向优化技能描述的前缀让相似技能的描述开头有明显区分。模型做选择时前面一小段文字权重最高。做技能分组。把技能按领域分组先让模型决定去哪个分组再在分组内部选择具体技能。这类似于给模型做了个两阶段筛选能显著降低选择难度。我的实测是技能数量超过 15 个以后两阶段方式比单层平铺方式误选率低约 40%。如果你预计技能规模会持续增长一开始就要把分组机制设计进去。6. 我个人在实际操作中的一些体会做了几次技能库项目之后我越发觉得 agent-skills 的真正价值不在于“让它跑通”而在于它让智能体的能力沉淀下来了。你做十个功能每完成一个就多了一块积木下次做新项目时老技能直接搬过去就能用。这种积累是“提示词模式”给不了的——提示词是消耗品技能是资产。最后再分享一个小技巧每个技能上线之前不要只看它“能不能跑通”要专门测它“在没有参考示例的情况下模型能不能根据描述正确调用”。我一般会准备一组完全没有在训练时见过的新说法比如技能描述里写的是“搜索邮件”测试时就用“翻一下张总的邮件”这种口语化表达看模型能不能正确绑定到对应技能上。这轮测试通过再复杂的场景下出现低级误调的概率都会小很多。这套思路不需要太高深的技术背景有一个大模型 API、一台普通服务器就能开始折腾。如果你正在为自己的智能体设计工具调用我建议直接从最小的两个技能做起跑通整个调用链路再慢慢扩展。项目复杂度提升后你会回头发现当初设计好的描述规范和校验机制才是整个系统最值得保留的部分。

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

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

免费获取报价 →
↑