Agent技能拆解:从Prompt到可复用技能包的工程实践

发布时间:2026/9/18 8:38:18
Agent技能拆解:从Prompt到可复用技能包的工程实践 你是不是也遇到过这种情况给 Agent 写了一大段系统提示词把搜索、读文件、算数据、写报告全塞进去结果模型跑着跑着就忘了该调哪个工具上下文一长就开始自己编结果每次想改一个小功能都得把一大段 Prompt 重写一遍。后来我把这套方案彻底换掉改成了现在主流的 agent-skills 思路——把能力拆成一个一个独立的技能包每个技能包自带说明、参数定义、执行逻辑和校验规则Agent 按需加载、按步骤调用。实测下来任务成功率明显提升调试效率也高了很多。这篇文章就来聊聊我在项目里是怎么设计、实现和排障的内容偏工程落地适合正在搭 Agent 应用、想给自己的智能体配一套“工具箱”的同学参考。我不会只讲概念会把技能元数据、调度器、校验逻辑、版本管理这些细节都展开每段都有可以直接抄走的代码或配置。1. 内容整体设计与思路拆解1.1 为什么要把技能从提示词里拆出来很多团队的 Agent 应用一开始都是“一个大 Prompt 一个工具列表”。工具列表通常是一堆 JSON Schema模型看到用户问题后通过 function calling 机制选择工具。这种方案在小 Demo 里跑得通一旦任务变复杂问题就出来了工具之间的协作逻辑全指望模型“灵光一现”用户说一句“你帮我分析这周的数据”模型往往只调用了一次统计函数然后就开始生成结论根本不会主动去读文件、清洗数据、做可视化。“agent-skills”的做法完全不同。它把一个完整能力封装成独立的技能模块每个技能不只是“一个函数”而是一整套包含任务描述、参数模式、执行代码、校验规则、甚至失败处理方案的单元。我习惯把它理解成“岗位说明书”传统工具列表只告诉模型“你有一个计算器”而技能包会告诉模型“你需要算数时这个计算器怎么开、什么时候不能开、算完怎么检查结果、算错了怎么办”。这样的设计带来三个直接收益。第一Prompt 大幅缩短模型不需要在长上下文里翻找工具使用规则只在需要某项能力时才把对应技能的说明加载进来。第二技能可以独立开发和测试改文件解析逻辑不会影响搜索技能。第三每个技能都能沉淀成可复用的资产新项目接入时只要注册一下就能用。1.2 agent-skills 与传统 function calling 的区别我在项目里同时接触过两种实现方式这里做个对照。传统 function calling 的核心是“一个可执行函数的声明”模型只负责根据用户输入填入参数然后等你调用后端函数。它适合一次性的、边界清晰的工具调用比如查天气、算运费。agent-skills 则偏向“一个完整的任务处理单元”它不仅包含函数签名还包含任务拆解建议告诉模型这个技能适合处理哪些子任务、不适合处理什么。内部执行流程技能内部可能有多个步骤比如先抓取再解析再输出结构化结果。结果校验器执行完后检查结果是否符合预期不符合就自动重试或报错。上下文管理策略输出是完整内容还是摘要是否需要落盘供后续技能读取。我用一个表格说明差异维度传统 function callingagent-skills粒度单个函数完整任务单元信息量函数名参数说明描述参数执行逻辑校验规则组合能力主要靠模型临场编排支持技能内部多步骤和技能间管道调试边界只看到函数入参出参可追踪技能内部各环节复用方式工具清单分散在各处技能包可打包、可版本管理一句话总结我的体会function calling 解决“让模型学会调用函数”agent-skills 解决“让 Agent 真正学会完成一项工作”。在复杂任务面前后者明显更扛得住。1.3 技能体系的分层与选型技能不是越多越好而是越“分类清晰”越好。我在项目里把技能分成三层每层承担不同职责基础技能层文件读写、执行代码、HTTP 请求、数据库查询。这是 Agent 的“手脚”大部分场景都需要。领域技能层例如 PDF 解析、网页爬取、Excel 透视、SQL 生成、情绪分析。这些建立在基础技能之上封装了某个具体领域的处理方法。编排技能层把多个技能串成一个标准化流程比如“市场调研报告生成”技能内部会自动调用搜索、爬取、摘要、排版四个子技能。选型时我会看三个点技能是否被频繁复用、是否有清晰的输入输出边界、是否具备可测试性。如果一个技能既要做数据分析又要生成图表又要发邮件那它不是一个技能而是一坨逻辑。我会继续拆拆到每个技能只做好一件事为止。对于工程框架我见过有人基于 LangChain 的 Tool 类扩展有人直接用 Python 类 JSON Schema 手写也有人用 Claude Agent Skills 这类带明确目录约定的方案。我的建议是初期别迷信复杂框架先用最朴素的“JSON 配置 Python 执行器”把一套跑通再考虑迁移到框架。2. 核心细节解析与实操要点2.1 技能元数据设计name、description、input_schema 和更多技能元数据是整个体系的地基。一个技能能不能被模型正确识别、正确调用几乎完全取决于元数据写得好不好。我在项目里给每个技能设计了一套标准化字段至少包含这几项{ name: archive_reports, description: 将指定目录下的报告文件按日期重命名并移动到归档文件夹。当用户需要整理、归档日报/周报/月报时使用。, input_schema: { type: object, properties: { source_dir: {type: string, description: 需要归档的源目录绝对路径}, target_dir: {type: string, description: 归档目标目录绝对路径}, pattern: {type: string, description: 文件名匹配模式默认 *.md} }, required: [source_dir, target_dir] }, executor_type: python, timeout_seconds: 30, enable_retry: true, post_validate: true }每个字段都有它的意义。name 必须短小且语义唯一模型靠它做第一轮筛选。description 必须说清楚“什么时候用、什么时候千万别用”这直接决定选技能的正确率。input_schema 是参数约束要求模型把用户需求翻译成结构化的调用参数我规定所有参数必须带 description否则很多模型会习惯性塞一个空字符串。除了这些基础字段我还会在技能包里放置一个guide字段用来写使用注意事项。这个字段不会每次都进上下文只有技能被选中后才会注入系统提示词。这样一来Prompt 里就不用堆一大堆规则模型在需要时才能看到上下文也更干净。2.2 技能描述怎么写模型才容易命中很多人把 description 写成一句“处理文件”然后抱怨模型不用技能。问题通常出在描述太笼统。我踩过坑之后总结了一套写法用动词开头比如“归档文件”“生成摘要”“查询数据库”。模型对动作词更敏感。写明适用场景带一些具体名词比如“当用户提到日报、周报、月报”时命中率会高很多。必须写明“不适合做什么”。比如“注意如果用户只是要统计行数请不要使用此技能”。这个负面约束非常有效。长度控制在 50 到 120 字之间。太短语义不足太长会稀释关键信息。举个例子同一个技能的两个描述描述一归档文件。模型经常在用户问“帮我备份一下这几个文件夹”时完全想不到用这个技能。描述二将指定目录下的文件按日期重命名并移动到归档目录。当用户需要整理归档日报/周报/月报时使用。不要用于删除文件或修改文件内容。这个命中率就明显高。原理其实很简单模型选择技能时本质上是在做语义匹配描述里的动词和场景名词就是匹配的“锚点”。没有锚点模型就只能靠猜。2.3 注册、发现与调度机制技能仓库建好之后还需要一个调度机制。我实现的调度器不复杂核心是一个“查询-匹配-加载-执行”的流水线系统预热时扫描所有技能包把 name、description、input_schema 建一个轻量索引。收到用户请求后调度器生成一个候选技能列表。这里有两种策略一种是完全靠模型选择把技能列表塞进 Prompt 让模型挑另一种是先用 embedding 做语义召回召回 Top 5 再让模型精挑。模型选定技能并填充参数后调度器做 JSON Schema 校验。校验通过则加载执行器执行完成后把结果交给结果校验器。校验失败则进入重试流程自动修正参数重试一次还不行就把错误信息交回给模型让模型决定是换技能还是直接向用户询问。从工程角度看我推荐第二种“语义召回 模型精排”的策略。因为当技能数量超过 20 个全部塞进 Prompt 会占掉大量 token而且模型会眼花缭乱。语义召回相当于先人工初筛一遍把明显不相关的技能过滤掉。2.4 结果校验与错误兜底技能执行完不等于任务完成。我最开始踩的坑是文件读取技能返回了一个空列表模型却直接告诉用户“文件内容是空的”。后来我意识到技能必须在结果返回给模型前先做自检。校验规则可以拆成三层结构校验返回结果是否符合定义好的输出结构比如字段是否存在、类型是否匹配。业务校验结合技能语义检查结果是否合理。比如解析 Excel 时校验表格行数是否大于 0爬虫技能校验抓取到的页面是否包含目标标题。成本校验输出内容是否过大。如果返回体超过阈值就自动压缩改成只返回摘要完整结果写到临时文件里。兜底逻辑我习惯用“三层降级”先重试再降级为最小可用结果最后把错误信息原样返回给模型。关键原则是不要让模型在不知情的情况下基于错误数据继续推导。宁可告诉模型“技能执行失败”也好过让它用空白结果自己脑补。3. 实操过程与核心环节实现3.1 最小技能实现文档归档技能从零实现一个技能包并不复杂。我来展示一个实际跑过的“文档归档”技能完整包含元数据、执行器和校验器。先看执行器实现我用 Python 写的import os import re import shutil from datetime import datetime from pathlib import Path class ArchiveReportsExecutor: def __init__(self, config): self.config config def execute(self, params: dict) - dict: source_dir Path(params[source_dir]) target_dir Path(params.get(target_dir, archive)) pattern params.get(pattern, *.md) if not source_dir.exists(): raise FileNotFoundError(fsource_dir does not exist: {source_dir}) target_dir.mkdir(parentsTrue, exist_okTrue) matched_files list(source_dir.glob(pattern)) archived [] for file_path in matched_files: mtime datetime.fromtimestamp(file_path.stat().st_mtime) date_str mtime.strftime(%Y-%m-%d) new_name f{date_str}_{file_path.name} dest target_dir / new_name shutil.move(str(file_path), str(dest)) archived.append(str(dest)) return { archived_count: len(archived), files: archived }接着是校验器。class ArchiveReportsValidator: def validate(self, result: dict) - bool: if archived_count not in result: return False if result[archived_count] 0: return False return True这个例子看起来简单但它已经具备了 agent-skills 的关键要素有清晰的输入参数、有独立的执行逻辑、有可单独测试的校验器。后续如果想让技能支持按后缀分类归档只需要改执行器不需要动调度器。3.2 技能调度器与大模型集成的骨架调度器是每个技能包的“接线员”。我实现过一个精简版本核心逻辑是把用户请求和技能仓库对接起来。关键代码如下import json class SkillScheduler: def __init__(self, skill_registry, llm_router): self.skill_registry skill_registry self.llm_router llm_router async def handle(self, user_query: str): candidates self.skill_registry.semantic_retrieve(user_query, top_k5) if not candidates: return {status: no_skill, message: 没有匹配到可用技能} selected await self.llm_router.select_skill( queryuser_query, candidates[ { name: skill.name, description: skill.description, input_schema: skill.input_schema, } for skill in candidates ] ) skill self.skill_registry.get(selected[name]) params await self.llm_router.fill_params( queryuser_query, schemaskill.input_schema ) result skill.execute(params) if not skill.validate(result): result skill.retry_or_fallback(params) return {status: ok, result: result}这个调度器承担了“召回—选择—填参—执行—校验”五个环节。实际项目中我会在llm_router里接入大模型服务的 function calling 接口也可以换成纯规则匹配看场景复杂度。有一个细节值得强调不要让模型直接执行技能而是让模型“决定调用哪个技能、填什么参数”真正的执行永远在沙箱或本地环境里。这样既能控制权限又能保证执行结果可审计。我见过不少新手把“写入文件”这种操作直接交给模型生成的代码去做一旦模型被恶意提示词诱导风险很大。3.3 组合技能完成复杂任务单个技能能解决的事有限agent-skills 的价值在多个技能组合时才会充分体现。我举一个实际跑通的场景用户上传了本周的 5 份数据文件要求生成周报。调度器收到的任务先进入“周报生成”编排技能。这个编排技能内部定义了流程调用read_table_files技能读取所有表格文件输出标准化 DataFrame 结构。调用analyze_trend技能对关键指标做环比、同比计算返回趋势结论。调用generate_markdown技能把结果渲染成 Markdown 周报。调用render_chart技能生成趋势图并把图片路径写入周报。每一步都由独立的技能执行每个技能的结果都经过校验。如果第 2 步发现某列全是空值分析技能会返回一个带警告的结构化结果而不是直接抛给模型。然后编排层决定是跳过该指标还是把警告写进周报。这里的关键是编排技能本身也是一个技能它的“执行器”是一段流程定义。我建议把流程定义写成 JSON而不是硬编码在 Python 里这样业务人员也可以调整流程步骤。3.4 技能热更新与版本管理技能开发不是一次性的随着项目演进技能会不断修改。我最开始直接把技能文件放在项目目录里改代码就要重新部署非常痛苦。后来我把每个技能做成独立目录并用一个 manifest.json 记录版本号。目录结构skills/ ├── archive_reports/ │ ├── manifest.json │ ├── executor.py │ ├── validator.py │ └── guide.md ├── read_table_files/ │ ├── manifest.json │ ├── executor.py │ └── validator.pymanifest.json 中的关键字段{ name: archive_reports, version: 1.3.0, entry: executor.py:ArchiveReportsExecutor, validator: validator.py:ArchiveReportsValidator, min_agent_version: 0.9.0 }更新策略我用的是“保留历史版本 软链接切换”。正式环境跑在 v1.2.1新版本 v1.3.0 先在测试环境验证通过后改一下软链接即可切换。如果出了问题一条命令就能回滚。这套机制最初看着重但在技能数量超过 20 个之后它的价值就体现出来了。没有版本管理的技能库上线两周就会变成“改一次坏一次”的泥潭。4. 常见问题与排查技巧实录4.1 模型总是选错技能这是我在 agent-skills 体系中最常遇到的问题。表现是用户明明要求的是“分析 PDF 里的表格”模型却调用了“网页抓取”技能。排查思路我通常分三步。第一步检查 description 是否足够具体。如果描述里只有“数据提取”这种泛词模型很难区分该用哪一个。改成“从 PDF 文件中提取表格数据支持 .pdf 格式当用户提到上传或打开 PDF 文件时使用”之后命中率会明显提升。第二步检查语义召回是不是出了问题。embedding 模型对某个领域术语不敏感时可能根本没把目标技能召回上来。我的做法是给每个技能配置 3 到 5 个“触发词同义词”比如“归档”配“备份”“整理”“移动文件”。第三步是再加上一层“排除规则”。在 description 里明确写“不要用于……”是一个很有效的方式尤其是对两个相似技能比如“读取 Excel”和“读取 CSV”互相把对方的场景写进排除规则。4.2 技能内部报错模型却像没事发生一样我遇到过最危险的情况技能执行时抛了异常但返回大模型时被包装成了一个空结果模型直接回答“文件没有内容”用户差点信了。这个问题根因在于技能执行器没有把错误信息透出。解决方法是强制约定技能返回值结构至少包含status和message两个字段。执行器内部 all 错误都要被捕获并转换成结构化的失败信息result { status: error, error_type: file_not_found, message: source_dir does not exist: /tmp/nope, suggestion: 请检查路径是否正确或改用 list_files 技能查看有效目录 }同时在 Agent 的约束提示词里加一句“如果某个技能返回 statuserror你必须如实告知用户不得根据猜测继续生成内容。”这两步加起来基本能杜绝“脑补成功”的情况。4.3 JSON Schema 填参错误频发模型填参时经常出现两个问题一是把用户原文里的某个词组直接当成参数值比如把“那个文件夹”填进source_dir导致路径无效二是漏填必填参数。我的应对策略分两层。第一层参数设计尽量宽松所有能用默认值解决的都不设为必填required数组只保留真正的硬约束。第二层调度器在真正执行前做一次参数归一化如果source_dir是相对路径或纯名称调用一套“解析用户上下文”的技能去补全绝对路径。我还会在 schema 的 description 里给模型举例子{ source_dir: { type: string, description: 源目录绝对路径例如 /home/user/reports/2025-01, examples: [/home/user/reports/2025-01] } }加了 examples 之后填参错误率肉眼可见地下降强烈推荐在小参数字段里都放一个示例值。4.4 多技能并发冲突与上下文被撑爆技能多了之后会出现两个工程层面的问题。一是并发冲突两个技能同时写同一个文件或者一个在读一个在删。二是上下文爆炸某个技能把一张 2 万行的表直接塞回给模型。对并发冲突我给技能执行器加了文件锁并在调度器里为“写型技能”维护一个串行队列。读取型技能可以并行写入型技能必须排队。判断一个技能是读型还是写型我在 manifest.json 里加了一个access_mode字段取值read、write、mutate。对上下文爆炸根本原则是“技能返回给模型的必须是加工后的摘要或结构化结果而不是原始数据”。比如读取 Excel 后技能先做聚合操作只返回行数、列名和前 5 行样例。如果后续任务需要完整数据再把完整结果写入临时文件并提供一个read_temp_file技能供后续步骤按需读取。这样一来模型上下文始终保持在一个可控范围。4.5 问题速查表现象可能原因快速处理建议模型选错技能description 不具体增加场景名词、动词、排除规则复杂场景找不到技能语义召回没命中给技能配置触发词同义词技能报错但模型正常回复错误未透出统一返回 status/message 结构参数填错schema 约束不清晰增加 examples、缩小必填范围多个技能互相覆盖文件缺少并发控制写型技能串行加文件锁上下文太大技能返回原始数据技能内先聚合返回摘要完整结果落盘技能升级后效果变差缺少版本管理保留历史版本用软链接切换最后分享一个 debug 技巧技能系统上线后的调试最怕的就是“黑盒”。我现在的习惯是给每个技能执行器加一个统一的日志装饰器记录完整的入参、出参、耗时和校验结果输出成一行 JSON 日志。排查问题时直接按skill_name和时间窗口过滤一眼就能看出是哪一步出了问题。还有一个经验调试模型选错技能时不要只看一次结果要准备 5 到 10 条相似但表达不同的测试用例。比如分别用“帮我把日报整理一下”“归档一下昨天的几个文件”“把 downloads 里的文档按日期放好”来测试同一个归档技能看它的命中率是否稳定。如果三次里有一次选错就说明 description 还有优化空间。跑了几个项目之后我最大的体会是agent-skills 不是银弹它最大的价值不是让 Agent “会更多”而是让 Agent 的每一项能力都变得可控、可测、可演进。技能数量会越来越多但接口越收敛系统就越稳定。希望这篇基于实战的记录能帮你少踩几个我踩过的坑。