ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

大模型应用落地关键:Agent Skills技能封装与工程实践

大模型应用落地关键:Agent Skills技能封装与工程实践 这些年做大模型应用我越来越觉得Agent 能不能真正落地干活关键不在模型本身多聪明而在于你往它手里塞了多少“趁手的家伙”。模型是大脑Agent Skills 就是手和脚——这句话我常跟团队讲。今天借“agent-skills”这个项目把我在这块积累的设计思路、实现细节和踩过的坑一次性摊开聊聊。这个项目做的是一套面向 Agent 的模块化技能体系通俗点说就是把你希望 Agent 能做的事——比如查天气、读文档、操作数据库、调内部 API——封装成一个个标准化的技能包让 Agent 在跑任务时按需调用。它解决的痛点是没有技能体系的 Agent 每次对话都在“自由发挥”结果飘忽不定有了 Skills 之后Agent 的行为可预期、可复用、可维护尤其适合做垂直场景的落地。适合谁看正在做 Agent 应用开发、做 AI 自动化工具、或者准备把大模型接进业务流程的开发者这篇内容应该能帮你少走不少弯路。1. 项目整体设计与思路拆解1.1 为什么 Agent 需要一套“技能封装”我先说一个观察。很多人做 Agent 的第一版就是把 API Key 一接扔给模型一个 system prompt 就开始聊天。Demo 阶段确实能跑但一旦进入真实业务问题马上冒出来模型给出的回答时对时错工具调用逻辑混乱同一个功能换个场景就不能用了。根本原因在于你没有给 Agent 提供结构化的能力边界它根本不知道自己“会什么”。Agent Skills 的本质是把“能力”从模型参数里外置出来。举个例子你让 Agent 读一份 PDF 报告并总结如果你不提供任何技能模型只能瞎猜——它可能尝试直接读文件路径可能编造一个不存在的 API也可能干脆拒绝。但如果你给它注册一个read_pdf技能把文件解析、文本提取、摘要生成整个流程封装好Agent 拿到任务就知道“先调这个技能把文本取出来再交给我的语言能力去总结”。这就好比新员工入职你不给他流程手册和工具清单他再多聪明才智也使不出来。而且这套设计有个额外好处——模型无关。今天你用的是 GPT 系明天换成国产开源模型只要 Skills 层的接口没变Agent 的核心逻辑就不用动。我在项目里把技能定义和模型调用完全解耦底层模型换过三轮上层业务代码一行没改过。1.2 技能分层架构从“原子操作”到“业务流程”在设计 agent-skills 的初期我见过很多项目把技能设计成“一个大函数”——比如一个run_business_process包含几十步逻辑。这种设计的维护成本相当高任何一个环节改动都可能引发连锁问题。我采用的拆分原则是分层、原子化、可组合。最底层是“原子技能”相当于工具函数职责单一比如http_get、db_query、send_email。往上一层是“任务技能”把多个原子技能按固定流程编排起来比如generate_report内部要调用数据查询、模板渲染、文件导出三个原子技能。最顶层才是“流程技能”通常对应一个完整的业务场景比如“周报自动生成”“客户投诉处理”它内部可以编排多个任务技能并且允许 Agent 根据上下文动态决定调用顺序。分层设计带来的直接好处是复用性。原子技能是全局共享的任务技能是可配置的流程技能才是绑定具体业务的。我统计过后续新增业务场景时大约 60% 的场景不需要从零开发用现有技能组合就能覆盖。这种设计还让排查问题变得简单——出错时你能快速定位到具体是哪一层出了问题而不是对着一个几百行的“万能函数”发呆。1.3 为什么选“描述驱动”而不是“硬编码驱动”技能描述这件事我纠结过很长时间。最开始的版本是硬编码每个技能写死函数签名Agent 通过 model function calling 直接调用。但这个方案有坑第一模型输出的参数经常不符合预期格式你不得不在代码里堆大量校验逻辑第二技能的“可用条件”“副作用”“边界限制”这些东西硬编码没法描述清楚模型经常在错误的场景调用错误的技能。项目最终采用的是“Schema 描述 模型理解”的混合方案。每个技能都有一份结构化的描述文件包含技能名称、用途说明、输入参数定义、输出格式、约束条件和适用场景。这份描述会被注入到模型的上下文里模型基于描述来决定是否调用、如何调用。硬编码只负责最终的参数校验和执行不负责“判断”。这个设计跑下来效果很明显。模型对技能的理解准确率提升了不止一个档次——原因其实简单你在描述里明确写出“该技能只适用于处理用户明确提供文件路径的场景如果未提供路径请先调用文件检索技能”模型就不会在缺条件时硬上了。描述驱动还有一个好处就是新技能上线不需要改代码只要新增一份描述文件Agent 次日就能学会使用它。2. 核心细节解析与实操要点2.1 技能 Schema 的设计演进从 JSON 到 JSON Schema技能描述文件我不建议用单纯的 JSON强烈推荐直接用 JSON Schema 标准。原因很现实JSON Schema 本身就是为描述数据结构和约束设计的工具链成熟很多模型在训练时就见过大量 JSON Schema 样本理解门槛低。我的 schema 设计经历了三个版本。v1 版本只定义参数类型和必填字段结果模型经常把字符串传进整数字段——调教成本很高。v2 版本补上了描述信息给每个字段加了 verbose 说明情况好了很多但仍然存在格式报错。v3 版本引入了枚举约束、条件约束和示例值这三个关键要素比如一个状态字段我可以声明“仅允许取值为 pending / running / done / failed 之一”模型基本不会再犯低级错误。给个实际的 schema 片段这是execute_sql技能的描述我稍微简化一下{ name: execute_sql, description: 在指定的业务数据库上执行只读 SQL 查询。仅适用于数据查询场景禁止执行写入操作。, parameters: { type: object, properties: { sql: { type: string, description: 完整的 SQL 查询语句必须为 SELECT 开头, pattern: ^SELECT\\s.* }, db_name: { type: string, enum: [orders, users, inventory], description: 要查询的目标数据库名称 }, limit: { type: integer, default: 100, minimum: 1, maximum: 5000, description: 返回结果的最大行数 } }, required: [sql, db_name] } }注意几个细节。pattern字段用正则约束 SQL 必须以 SELECT 开头这比任何提示词都管用——模型见过这种约束后基本不会尝试 INSERT 或 DROP。enum限制了数据库范围避免模型凭空捏造一个不存在的库名。default值给模型提供了“不传也行”的容错空间。方案落地后需要人工修正参数的比例下降了七成以上。2.2 描述语言的写作技巧告诉模型“什么时候别用”技能描述里最容易忽略的是“负面条件”——也就是告诉模型不该在什么情况下调用。大多数人的描述只写“能做”不写“不能做”结果模型在边界场景反复试探。举个例子get_weather技能大多数人会写“获取某个城市的天气信息”。但我建议这样写获取某个城市当天的天气信息。仅当用户明确指定城市名称且意图为查询天气时调用。若用户询问“适合穿什么衣服”“要不要带伞”等衍生问题仍需先调用本技能获取基础天气数据。若用户未指定城市请勿调用应主动向用户询问城市名称。这段描述包含了三重信息触发条件明确指定城市 天气意图、衍生场景处理间接问题也要先调技能、拒绝条件缺参不调用改为追问。这种精细化的描述直接把模型误调用的概率按住了。经验是描述信息里“不做什么”和“做什么”同样重要。描述长度也有讲究。太短模型理解不到位太长会挤占上下文空间。我踩过的平衡点是 200~400 字之间重点信息密集、覆盖边界情况即可不需要把每个使用案例都列用。技能过多时超过 50 个描述精简化更是刚需否则上下文根本塞不下。2.3 参数校验与执行沙箱最后的防线在代码里模型再聪明也可能给出不合规的调用参数。所以参数校验一定不能省。我之前在execute_sql上吃过亏——一次内测中模型把 DELETE 语句包装成 SELECT 用子查询发了过来虽然没造成损失但也吓出一身冷汗。从那以后技能执行器的第一道工序一定是运行时校验流程分三步校验参数格式类型、枚举约束直接参考 JSON Schema 校验库手写。校验安全边界比如 SQL 技能判断语句前缀文件技能判断路径是否在允许目录内。校验调用频率同一技能短时间内的调用次数是否超过阈值防止模型陷入死循环。这些校验必须在技能执行器层面完成不能依赖模型自律。更进一步危险类技能我建议直接跑在沙箱里。数据库操作走只读账号、文件操作限制目录可写范围外部命令执行尽量用容器隔离这是底线不是可选项我踩过的坑太多不想让读者再踩一遍。执行器捕获异常后返回结构化错误信息模型读到错误信息能自行修正参数重试这在 Agent 里是闭环的一环。3. 实操过程与核心环节实现3.1 技能注册流程从新建技能到 Agent 可调用我按项目里成熟的做法走一套标准流程定义 schema 文件、实现执行函数、注册技能管理器、编写测试用例、注入 Agent 配置。第一步在skills目录下新建以技能名命名的子目录里面放schema.json和main.pyschema 按 2.1 的规范写。第二步实现执行函数。这里给出一个文件读取技能的完整示例# skills/read_file/main.py import os import json from pathlib import Path ALLOWED_ROOT Path(/data/workspace) def execute(params): # 参数校验 file_path params.get(file_path) if not file_path: raise ValueError(缺少 file_path 参数) # 路径安全检查 target (ALLOWED_ROOT / file_path).resolve() if not target.is_relative_to(ALLOWED_ROOT): raise PermissionError(f路径 {file_path} 超出允许访问范围) if not target.exists(): raise FileNotFoundError(f文件不存在: {file_path}) if target.stat().st_size 5 * 1024 * 1024: # 5MB raise ValueError(文件超过 5MB请配合 read_large_file 技能分段读取) # 执行读取 content target.read_text(encodingutf-8, errorsreplace) # 输出截断保护防止上下文溢出 max_chars params.get(max_chars, 3000) truncated len(content) max_chars return { content: content[:max_chars], truncated: truncated, file_name: target.name, file_size: target.stat().st_size }注意里面的细节ALLOWED_ROOT限制访问目录5MB 大小保护max_chars截断防上下文溢出errorsreplace防乱码导致编码报错。这些都是真实调用场景里坑过我的点。is_relative_to是 Python 3.9 才有的方法如果你还在用老版本用os.path.commonpath替代效果完全一样。第三步把技能注册进技能管理器。我用的注册方式是装饰器简单直接# skill_manager.py skill_registry {} def register(name): def decorator(func): skill_registry[name] { handler: func, schema: json.loads(Path(fskills/{name}/schema.json).read_text()) } return func return decorator def list_skills(): return {name: info[schema] for name, info in skill_registry.items()} def execute_skill(name, params): if name not in skill_registry: raise KeyError(f未注册的技能: {name}) skill skill_registry[name] # 入参校验直接用 jsonschema 库验证 import jsonschema jsonschema.validate(params, skill[schema]) return skill[handler](params)这段代码把“注册”“列出”“校验执行”三个核心能力串起来了。第四步测试用例至少覆盖正常输入、非法参数、找不到技能三个场景。第五步在 Agent 的 system prompt 尾部追加一段自动生成的技能清单格式类似“可用技能read_file读取指定文件内容支持文本文件— execute_sql对业务数据库执行只读查询…”模型就能感知到技能库的存在并在决策时主动选用。3.2 模型调用与技能编排让 Agent 学会“用”技能技能都注册好了接下来是 Agent 调度层也就是 Agent 拿到用户需求时怎么选技能、怎么传参、怎么编排多步调用。控制流程我用标准的 ReAct 思路思考、决策、行动、观测循环往复。具体实现上我设计了一个解析函数把回复拆解成一个调用列表agent-skills 的实现如下import json import re from typing import List, Dict def parse_skill_calls(text: str) - List[Dict]: # 匹配形如 SKILL_CALL: {skill: read_file, params: {...}} 的调用块 pattern rSKILL_CALL:\s*(\{.*?\}) matches re.findall(pattern, text, re.DOTALL) calls [] for match in matches: try: data json.loads(match) calls.append({ skill: data.get(skill), params: data.get(params, {}) }) except json.JSONDecodeError: # 解析失败就跳过不影响主流程 continue return calls为什么用SKILL_CALL:关键词而不是直接用 function calling 系统核心原因有两点。其一是模型无关我项目里要兼容 OpenAI 格式、Claude 格式还有开源模型每家 function calling 的实现方式不同统一走文本协议后上层 Agent 逻辑不用分叉。其二也是因为它灵活Agent 可以一次输出多个并列的技能调用——比如同时读两个文件做对比这在纯 function calling 里需要多轮交互才能实现。配套的 Agent 编排循环大致是读取用户输入 → 构造上下文system 技能描述 历史 需求→ 模型输出 → 解析技能调用 → 逐个执行并写回结果 → 再给模型继续决策循环最多 5 轮以免死循环。我试过一到三轮根本不够用一些需要先结果再决策的任务压根没法结束五轮是比较平衡的经验值。3.3 技能组合实战一个多步骤任务的完整链路光说理论容易飘直接展示一个“自动生成数据分析周报”的技能组合。Agent 收到指令“生成本周订单周报”实际调用链路如下第一轮Agent 调用get_current_week获取本周一至周日的日期范围返回{start: 2025-06-09, end: 2025-06-15}。第二轮Agent 调用execute_sql查询订单数据SQL 形如SELECT date, SUM(amount) FROM orders WHERE date BETWEEN 2025-06-09 AND 2025-06-15 GROUP BY date。第三轮Agent 看到返回的数据观察到部分日期缺失比如周四没有订单此刻它在上下文里自主判断应该调用append_report_note技能写入“下划线提示六月十二日暂无订单记录”这个判断如果你没给技能描述里写上“观察数据完整性”这条约束模型一般都发现不了但写好描述后它就能补上这个思考环节。第四轮调用generate_html_report把数据渲染成周报 HTML 文件并返回文件路径给用户。整个流程里没有强规则硬编码链路Agent 每次都会根据实际数据“随机应变”。这就是技能编排的魅力——你给它足够的组件和清晰的描述它自己就能规划路径。你真正要操心的只是每个技能的边界是否清晰、描述是否准确。4. 常见问题与排查技巧实录4.1 技能调用了但结果不对先查描述再查代码这是遇到最多的状况。Agent 确实调了技能但拿到的结果不是想要的或者干脆跑偏。我的排查顺序是固定的按频率排序描述歧义、参数传递错误、模型输出格式异常、执行器 bug 这四类。描述歧义是最隐蔽的。比如你有一个search_products技能描述里只写了“根据关键词搜索商品”模型可能把“查找用户名下订单”也调用了它。排查方法比较简单——把技能的 schema 描述和实际调用日志打印出来对照看模型到底理解成什么样。修复方式是在描述里增加明确的“目的对比”比如“搜索商品仅用于用户寻找可购买商品场景不用于查询已购记录查询历史订单请使用 search_orders 技能”。这类问题修完效果立竿见影。参数传递错误也常见模型理解了该调哪个技能但传参不对。比如read_file需要传file_path模型总传成path。这就是 schema 的description不够直白或者属性命名不直观。我习惯把属性名设计得和前几个自然语言关键词强相关——比如把file_path的 description 写成“文件的路径字符串例如 /data/workspace/report.docx”附上示例模型几乎不会再用错。输出格式异常就是模型生成的内容不符合约定的result标签或 JSON 结构。这种情况多半是 prompt 里对输出格式的约束不够强或者是温度参数设太高。技能调用的生成温度我固定在 0.2 以下逻辑决策类任务温度太高基本必出错。4.2 技能多了就“瞎选”上下文优化的三种思路技能数量超过 30 个后新的问题又来了Agent 选择技能的正确率开始下降经常把不相关的技能也列进调用计划。这是上下文过载和选择困难症的叠加效应。一个直接的办法是分组路由。把技能按领域分组比如“数据查询组”“文件处理组”“消息通知组”Agent 先根据需求选一个组再在组内选具体技能。这等于把“50 选 1”变成了“5 选 1 10 选 1”准确率提升明显。我用一个简单的group处理先让模型用关键词匹配找到最相关的组描述再从该组返回技能做二次匹配组内匹配失败则回退到全局匹配保证可用性兜底。另一个是给技能增加“热度权重”。高频使用的技能放在描述列表的前面低频技能放后面或折叠。模型对上下文靠前的信息权重更高这个排序调整带来的收益很低成本值得每个人都试试。第三个思路就是动态裁剪——对于那些明确包含“不要调用技能 X 完成该任务”的指令在上下文里直接剔除 X 的描述。也可以在任务开始时先做一次意图识别只把相关组的技能描述注入上下文其他组的描述留到需要时再加载。这种方式适合技能库非常大的场景上下文瘦身后选择准确率和推理速度都上去了。4.3 技能调用的性能与稳定性日志、超时和降级Agent 技能调用有三件容易被忽略的事。第一件事技能执行一定要有超时控制。我之前有过一个 Python 技能在处理大文件时卡了 20 分钟用户端看起来就是 Agent 彻底失联。现在所有技能执行统一套超时外部 API 类技能默认 10 秒本地计算类默认 30 秒超时直接返回错误信息给 Agent让它换策略。第二件事全链路日志必须有。对技能调用记录至少包含调用时间、技能名、入参、出参摘要、耗时、错误信息、模型决策前文。其中包括模型观察片段对排查那些“模型突然调用奇怪技能”的问题帮助太大了。有了这些日志你可以回溯每一步决策的因果关系——我遇到过模型在一个查询技能失败后连续重试五次同样的调用看了日志才发现 prompt 里没有约束重试逻辑导致死循环。后续在描述里加了“技能执行失败时更换参数或更换技能连续两次失败请停止并告知用户”。第三件事要有降级方案。核心技能挂掉时 Agent 至少要说人话。我的做法是给每个关键技能配一个“替代技能链”比如数据库查询失败降级为读取已生成的离线数据快照文件文件解析失败降级为转交用户手动处理。降级逻辑必须在描述里写明Agent 才不会在异常情况下一问三不知。4.4 常见问题速查表现象直接原因排查方法解决方案Agent 不调用任何技能技能描述未注入上下文 / 描述与任务意图不匹配查看发给模型的 system prompt 是否包含技能清单确认技能描述注入逻辑在 prompt 末尾明示“可直接调用的技能有…”反复调用同一技能直到报错缺少失败重试约束查看调用日志中错误代码在技能描述中加入失败处理指引设定重试次数上限技能被“张冠李戴”多个技能之间场景差异不够清晰对比相似技能的 description 文本增加“使用场景”和“禁用场景”专门段落必要时合并技能参数格式频繁报错缺少入参校验 / 未使用 JSON Schema 约束检查执行器的校验逻辑用 jsonschema 库做严格校验schema 中补充枚举、约束、示例上下文长度不够放技能列表技能数量过多统计技能列表的 token 开销分组路由 动态裁剪只注入当前任务相关技能模型自己“编造”技能结果模型幻觉 / 执行器未返回结构化错误检查生成时的温度参数和技能输出格式约束把温度降到 0.3 以下在描述中强制要求“必须先调用后回答”5. 更多实操心得与后续想法5.1 从零搭建技能包三种适合起步的通用技能如果你打算在自己的项目里把 Skills 这套跑起来除了项目自身的业务技能我建议优先搭三个通用的“地基级”技能。第一个是web_search让 Agent 能检索外部信息而非全靠模型记忆连搜索引擎的 API 地址、参数、超时和错误处理都封装好。第二是web_fetch抓取指定 URL 的正文内容并转成 Markdown很多 RAG 场景都依赖它。第三个是current_datetime返回当前时间和日期——别觉得这个技能low模型训练数据根本没有实时时间概念没有这个技能Agent 连“今天星期几”都可能答错更别提和日期相关的业务逻辑了。这三个技能每次新项目开箱即用占据了技能调用量的很大比例。“没多少‘高技术含量’但却是 Agent 日常运转的基础设施。”5.2 技能数量会随着业务增长而膨胀规模化时要做的事当技能库膨胀到几百个除了分组路由和动态裁剪还有两件事要提前想清楚。其一是技能间互相调用的权限管理我的做法是把技能分成基础层和业务层业务技能可以调用基础技能但反过来禁止避免依赖混乱。其二是技能版本管理一旦多个 Agent 共享技能库升级技能必须带版本号和变更日志否则一个技能更新可能导致所有下游 Agent 行为突变——这个问题我在实践里踩过现在每个技能目录下都会维护一份简单的CHANGELOG.md。做一次技能库全量审查也是必要的至少每个迭代做一轮找出两个月以上未被调用的技能要么删除要么合并。技能不是越多越好——无用的技能描述不仅增加 token 开销还会干扰模型的决策判断。精简二十来个描述后调用准确率整体提升了好几个百分点这是实打实的收益。5.3 最后的一个小技巧让技能会说话我最后想分享一个项目里真正落地有效的设计——给技能加上“返回意图”字段。很多技能执行完只是干巴巴地把数据返回给 Agent比如查库存返回 JSONAgent 还得自己组织语言。但如果技能在设计时就带上一个user_message字段比如execute_sql返回{user_message: 本周累计销售额为 128,500 元较上周下降 3.2%, data: [...]}Agent 可以直接把这个话术微调后发给用户。用户在体验上会感觉这对话很自然“像是真人助手在汇报”而不是冷冰冰的 JSON 输出。需要注意的是user_message的内容也必须让模型评估可信度技能执行器只提供事实性陈述模型负责润色和补充别让技能直接替模型“定调子”这个边界要守住。这个技巧虽然简单但对提升智能体产品体验的帮助非常明显。这套 agent-skills 的设计实践从拆解技能到底层实现踩过的坑一次比一次深刻。说实话Agent 开发没有银弹Skills 也不过是让模型能力具象化的手段之一。但把技能描述写得精细点、边界划得清晰点、日志留得充分点你会明显感觉到 Agent 的表现从一个“碰运气的聊天气泡”逐渐变成一个“按规矩办事的同事”。我个人的体会是你不必一开始就追求完美的架构哪怕只从两三个原子技能起步把循环跑通再逐步扩充这比设计一堆花哨的能力却跑不通要强得多。希望这篇记录能给你一些启发。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表