1. 从会聊天的机器人到能干活的工作流agent-skills解决的核心矛盾先说个我最近的真实感受。以前我给智能体写功能脑子里默认的思路就是把提示词写长一点再多给它几个例子。结果是什么呢提示词膨胀到两三万字模型一长就乱改一个需求得从头调一遍不同场景之间逻辑互相串味。最崩溃的是同一个动作——比如从PDF里抽表格——在A任务里能用换到B任务里因为请求格式不一样又得重写一遍。后来我尝试换了个思路不再把能力写死在提示词里而是把能力拆成一个个独立、可复用、有明确输入输出定义的技能skills。这个思路现在是我构建智能体的核心方法论我管它叫agent-skills。通俗点说就像软件开发里函数和模块的概念你写一堆小工具函数然后让智能体在需要的时候自己选择调用哪个。区别在于这些技能不是给程序员调用的而是给大模型判断和调用的。这件事的价值在于它把智能体从一个黑盒变成了一个可以被拆解、测试、维护的系统。你可以单独验证翻译技能好不好使也可以让它在多个工作流里共享甚至可以让非技术同事通过配置来扩展新技能不用改一行提示词。如果你也在做Agent类的产品或者经常被提示词越写越长但效果越来越不稳困扰这篇文章就是为你准备的。我会从为什么、怎么设计、怎么实现、怎么测一路聊到踩坑经验。2. 技能拆分的边界从任务到子技能的方法论2.1 别把技能做成超能力很多人第一次设计技能时容易走向两个极端。一个极端是技能粒度太粗比如文档处理技能——这根本不是一个能力而是十几个能力的集合另一个极端是粒度太细比如将字符串转为小写——这种基础操作不需要模型来判断直接在代码里做就行。我在设计技能时脑子里有个简单的标准一个技能必须是一个可以被自然语言描述、且输入输出边界清晰的原子操作。什么叫原子就是你很难再把这件事拆成更小且更有意义的步骤。当然具体粒度取决于你的场景但有一个通用判断标准如果你发现在多个任务里某个能力总是连着出现或者换个参数就能复用那它就该独立成一个技能。打个比方。你家里有工具箱技能就是里面的螺丝刀、扳手、卷尺而不是一个修家具工具箱。工具箱本身是智能体的系统提示词和候选技能列表里面的每个工具必须能被单独抽出使用。2.2 从任务描述反推技能清单具体怎么拆我的做法是拿着一个典型任务从头到尾走一遍把每一步涉及到需要模型做一些特定动作的地方标出来。举个例子一个企业周报生成任务拆出来可能是——读取数据源、按指标维度汇总、生成趋势描述、选择图表类型、格式化输出。其中读取数据源生成趋势描述选择图表类型这三个动作可以提炼为技能按指标维度汇总如果数据格式固定可以直接写在代码流程里未必需要模型参与。这样一拆你不仅得到了技能清单还顺带明确了哪些步骤该交给模型、哪些步骤该走规则逻辑。对了还有个重要的反向操作合并技能。如果两个技能总是被同时调用且先后顺序固定就把它们合并成一个复合技能减少一次模型决策的调用也能降低出错的概率。比如解析PDF和抽取表格如果总是连着用干脆合成解析PDF并抽取表格一个技能。2.3 技能描述写给模型的使用说明书一个技能的灵魂不是它的实现代码而是它的描述文档。因为模型是靠着描述来决策的描述写得好不好直接决定了调用准确率。我一般会为每个技能写结构化描述包含以下几部分字段作用示例name技能唯一标识extract_tables_from_pdfdescription一句话说明这个技能做什么、在什么场景下用从PDF文件中提取所有表格返回结构化Markdown文本。适用于含数据表格的财务报告、研究论文等input_schema输入参数定义说明每个参数的名字、类型、必填性、含义file_path: string, page_range: int[]?output_schema输出格式定义markdown_table: stringusage_notes使用注意事项包括什么情况下不要用它如果PDF是扫描件请先调用OCR预处理技能当初我把description写成解析PDF并返回内容模型就经常在不需要这个工具的时候乱调用。后来改成上面这种带适用场景禁止场景的描述精确度大幅提升。所以记住描述不是给人看的注释是给模型看的指令写得越专业调用越准确。3. 接口即契约定义技能时的关键决策3.1 参数类型越严格后期越省心技能接口设计中我最想强调的就是参数类型。很多demo里都用kwargs字典自由字符串来传参这在原型阶段很爽但一旦进入生产环境就变成灾难。模型在自由文本参数里填错格式的概率远比你想象的高。比如你定义了一个发送邮件技能要求传入recipient字段如果你不规定它是string还是array模型就可能填一个逗号分隔的字符串又或者填一个列表前端代码两个都得兼容。因此我在设计输入schema时会严格采用JSON Schema规范枚举值、正则、最小最大长度都写得清清楚楚。模型虽然不能保证百分之百遵守但有了约束之后出错的概率会大幅下降而且出错时可以很方便地通过schema校验并把校验错误回传给模型让它自己纠正。这一招在实践里非常管用。3.2 返回值结构化让模型少做猜测题技能的输出同样要结构化。常见错误是技能返回一大段纯文本让模型自己去找关键信息。结果就是模型可能漏看、瞎猜。正确做法是技能返回值里直接给模型完全精确的数据结构。比如一个查询库存技能不要返回还有123件而是返回{sku: ABC, quantity: 123, warehouse: shanghai}这种标准JSON。这样模型后续无论是要做判断还是做摘要都有据可依。3.3 错误信息的自我修复闭环技能不可能永远成功。文件不存在、网络超时、权限被拒这些都是家常便饭。关键区别在于你的错误处理方式。你可以在错误时返回{success: false, error_code: FILE_NOT_FOUND, message: ...}。这个结构本身不稀奇更重要的技巧是在message里写上模型可以执行哪些补偿动作。举个例子技能读取文件失败时返回的消息可以是文件不存在请检查file_path是否正确。你可以调用list_dir技能查看目录内容或者询问用户重新提供路径。 这样一来模型就知道下一步该干什么而不是含糊地说抱歉我遇到了问题。这个小设计让我的智能体自主修复成功率提升了非常多也大大减少了用户对话轮数。4. 注册、发现与调用把技能跑起来的代码骨架4.1 技能注册表聊完设计来看实际工程。我做的agent-skills是一个轻量级框架核心思路就三件事注册、发现、执行。代码不算复杂但够用。注册部分我用的是装饰器模式。给每个技能绑定name、description、输入schema和实现函数然后塞进一个全局注册表。类似这样# registry.py SKILL_REGISTRY {} def skill(name, description, input_schema, output_schemaNone, usage_notesNone): def decorator(func): SKILL_REGISTRY[name] { name: name, description: description, input_schema: input_schema, output_schema: output_schema, usage_notes: usage_notes, func: func, } return func return decorator然后具体的技能实现长这样# skills/pdf_skills.py from registry import skill skill( nameextract_tables_from_pdf, description从PDF文件中提取所有表格返回Markdown格式。适用含数据表格的财务报告、研究论文等。如果PDF是扫描件请勿使用。, input_schema{ type: object, properties: { file_path: {type: string, description: PDF文件的完整路径}, page_range: {type: array, items: {type: integer}, description: 页码范围如[1,3]表示第1页到第3页默认全部页} }, required: [file_path] }, output_schema{ type: object, properties: { tables: {type: array, items: {type: string}}, count: {type: integer} } } ) def extract_tables_from_pdf(file_path, page_rangeNone): # 这里是解析PDF的具体实现 ... return {tables: table_list, count: len(table_list)}4.2 模型如何与技能列表交互目前的智能体通常通过function calling机制或tool calling机制来调用技能。训练模型时系统提示词会带上技能列表的JSON描述。在执行流程里我的主循环大致是从注册表里取全部技能元信息转成模型API要求的tools格式。把用户请求 当前上下文发给模型。模型如果认为需要调用某个技能会返回一个tool_call包含技能name和参数。代码侧按参数调用相应的函数拿到结果。把结果作为新的消息回传给模型让模型继续。循环直到模型给出最终回答。这套流程本身不特殊但有几个细节我踩过坑。第一不要一次性把所有技能全塞给模型尤其是技能超过十个以后模型的选择准确率会下降。我会先让一个轻量级的路由器模型根据用户意图筛选相关技能子集再把子集传给主模型。你也可以在注册表里给技能打上标签按域domain动态加载。第二调用函数时要对参数做schema校验不允许模型传什么就信什么校验不通过就把错误信息回给模型让它重新生成参数。4.3 一个可复用的执行器封装为了方便集成我封装了一个执行器它负责把模型返回的tool_call映射到注册表里的函数同时做异常兜底# executor.py import json from registry import SKILL_REGISTRY from validator import validate_input def execute_tool_call(tool_call): name tool_call[name] arguments json.loads(tool_call[arguments]) if name not in SKILL_REGISTRY: return {success: False, error: fSkill {name} not found} skill_meta SKILL_REGISTRY[name] validation_error validate_input(skill_meta[input_schema], arguments) if validation_error: return { success: False, error_code: INVALID_PARAMETER, message: f参数校验错误: {validation_error}。请重新生成参数 } try: result skill_meta[func](**arguments) if success not in result: result[success] True return result except Exception as e: return { success: False, error_code: EXECUTION_FAILED, message: f技能执行异常: {str(e)} }这个执行器不是万能但胜在简单可靠。你完全可以照着这个骨架改造成适合自己项目的工具链。5. 技能生命周期管理测试、版本与灰度5.1 给每个技能写专门的评测集一旦技能变成了独立组件你就能像给后端接口做测试一样给技能做评测。这一步我在早期做Agent时完全没做所以频繁翻车。后来我给每个技能维护一份评测集包含三类用例标准用例正常输入期望输出符合预期结构。边界用例空值、超长文本、缺失字段、错误类型。对抗用例故意给出模糊或冲突的描述看模型是否调用错误技能。评测集不是给函数跑单元测试——那是另一部分——而是让模型用自然语言描述调用场景再由模型或人判断技能调用决策是否正确。比如我把一堆用户query丢给评测系统让带技能列表的模型去决策调用哪个技能然后和正确答案比对。这样测的不只是技能实现还包括技能描述的清晰度。5.2 技能版本与仓库目录技能一定会迭代。我习惯把每个技能实现为一个独立函数并采用语义化版本。注册表里存储版本号模型可见的技能版本则固定一个。当新版本上线后先在仿真环境里跑评测集通过率达标才更新注册表。同时保留历史版本的回滚入口。这样能避免那种上一个技能逻辑变了老用户流程突然崩掉的问题。5.3 动态加载与技能商店如果你希望系统具备扩展性可以把技能注册表从代码里抽出来放到配置中心或数据库里让运营人员通过配置界面新增技能而不用改代码。这本质上是一个技能商店的思路。每个技能条目包含name、description、schema、是否启用、路由标签等信息。模型调用时系统动态读取启用的技能列表并执行。这套机制让非技术人员也能扩展智能体的能力扩展新技能变成了填一张表单而不是写一段提示词。6. 实战案例把多轮对话变成标准化流水线为了让你更直观理解agent-skills的落地效果我分享一下最近搭建的一个投标文档初稿生成流程。以前这活儿靠人整一套流程下来至少要几小时。现在我把它拆成6个技能上传并解析文档、提取关键技术指标、检索历史案例库、生成合规性检查清单、撰写章节草稿、输出规范化文档。每个技能都是独立函数有各自的输入输出。比如检索历史案例库技能模型首先调用上传并解析文档得到需求文档的文本结构再从中提炼出技术要点然后调用检索历史案例库传入关键词数组返回相关案例的引用。整个流程由模型自主决定调用顺序而不是预先写死。好处是两个项目就算流程顺序不同模型也能组出来。我有一次故意把调用顺序打乱只给定技能列表模型依然能自己安排出一条合理路径。这就是技能化相比固定工作流的最大优势具备动态编排能力同时每个环节可独立测试。过程中也发现有些步骤单靠技能还不够。比如生成合规性检查清单技能返回的清单中间有缺漏后来我在技能的usage_notes里补充了必须逐条比对招标文件中的否决项如任一条不满足需在结果warning字段中标明。再跑评测集正确率上升了不少。这说明技能描述本身就是一个可以通过评测集持续微调的对象。7. 那些文档里不会写的坑以及我的最终建议最后聊几个真正的经验教训。第一个坑过度抽象。我最初热衷于设计一套跨任务通用的基础技能结果每个任务用起来都要传一堆奇怪的参数模型犯晕代码也难维护。后来我接受了一个现实技能可以有一些业务相关性通用型和专用型技能混着来别强求所有技能都通用。第二个坑忽略上下文占用。技能描述和调用记录都会占用上下文窗口。技能很多时每次把调用结果全部塞回去没两轮对话就把窗口撑爆了。我后来给输出schema加上了summary或compressed字段让技能在返回大量数据的同时提供摘要版本模型默认读摘要需要细节时再触发另一个技能取详细数据。这一招能显著延长多轮对话的稳定长度。第三个坑忽略了技能失败后的引导信息。一开始我的错误返回就是简单的{error: failed}模型只会跟你道歉解决问题毫无进展。后来改成带错误码和修复提示的结构情况才好转。记住大模型是靠着你给的信息在做推理错误信息里不给对策它真的就无脑道歉。如果你正打算给智能体搭建能力体系我的建议很直接不要在提示词里堆砌万能话术从最小的技能拆起把接口当契约把评测当质量门禁让每个技能能独立进化。agent-skills不是一个固定的现成工具而是一套组织智能体能力的思想。而且这套思想跟具体的大模型厂商无关无论底层换成什么模型技能层都能保留下来这大概是它最值得投入的原因。最后分享一个小技巧在技能描述的开头加上这个技能是什么的简短说明在末尾加上什么情况下不使用的句子。这两句话加起来不过几十个字却能让技能调用的准确率上一个台阶。我试过多次屡试不爽。你也试试看。