
1. 为什么我开始做 agent-skills智能体最容易被低估的一块拼图先说个我自己的经历。大概在几个月前我在折腾一个能自动整理会议纪、跟进待办事项的个人助理型 Agent刚开始所有逻辑都堆在 System Prompt 里定义角色、给示例、描述工具怎么用、告诉它什么时候该调用哪个函数…… Prompt 越写越长从 800 字膨胀到 3000 字最后到了 5000 字。模型的表现却越来越差经常漏掉关键动作偶尔还会自作主张地“忘记”某个规则。最崩溃的一次是它把“发送会议邀请”这个动作理解为“给用户发一封摘要邮件”整个流程完全走偏。排查下来问题出在“技能”这个维度上。过去我们设计 Agent 的时候喜欢把“能力”理解为“工具”比如一个 function、一个 API 调用但这远远不够。一个真正的技能应该是一套完整的行为模式它包含触发条件、输入参数、执行步骤、校验规则、失败兜底甚至还有对输出质量的自我评估。它比“一个函数”大得多也比“一段 Prompt 指令”结构清晰得多。这就是我后来把整个项目命名为agent-skills的原因。这个项目不是做大模型训练也不是做 RAG 框架它要解决的是“智能体如何规范地获取、组织和执行一项完整能力”的问题。更直白地说它是一个 Agent 的“技能操作系统”让能力可以被定义、注册、调度、升级和复用。如果你也在做 Agent 类应用尤其是那些需要复杂决策链路的场景比如客服、办公助手、代码生成、数据分析助理你会发现自己迟早会撞上同样一面墙功能越来越多系统越改越乱模型该学会的“本事”到底是放在 Prompt 里、函数里还是代码逻辑里边界越来越模糊。agent-skills 这套实践就是要把这面墙拆掉让智能体的能力构建回到一条清晰可控的主线上。这篇文章我会把这套方案的设计思路、核心细节、实操过程和踩坑记录一次性写透。内容适合两类人看一类是自己在搭 Agent 应用的独立开发者另一类是团队里负责 Agent 工程化的技术人员无论你用 LangChain、LlamaIndex 还是自己手写逻辑都能从中找到直接可落地的参考。2. 核心设计思路技能库不是工具列表而是一套能力管理范式2.1 为什么“技能库”比“工具列表”更适合复杂 Agent拆解 agent-skills 之前我们先把一个基础概念掰清楚什么是“技能”它和“工具”到底有什么区别一张对比表格能说明问题维度工具Tool技能Skill粒度一次函数调用一组有序的动作组合输入结构化参数目标导向的自然语言意图 结构化参数执行逻辑通常是确定性的可能包含中间判断、分支、重试失败处理抛异常有兜底策略与降级方案前置条件通常无可能依赖其他技能或上下文状态输出原始结果经过校验、规范化后的结果工具解决的是“能做某个原子动作”技能解决的是“能完成一件完整的任务”。比如“发送邮件”是工具而“给客户写一封跟进邮件并发送、同时抄送直属领导、还要在 CRM 里标记联系记录”就是一个技能。这个技能背后至少涉及三次工具调用生成邮件内容、调用发送接口、写入 CRM 记录。如果中间某一步失败还得决定是重试、跳过还是通知用户。有了这层理解再回头看 agent-skills 的设计目标就很清楚了我需要为 Agent 定义一套技能管理框架让它把“工具”组织成“技能”把技能作为独立的模块来开发、注册和调用。这个思路和微服务改造非常像。早期单体应用把所有业务混在一个工程里后来按领域拆服务每个服务有自己的接口、存储和部署边界。Agent 的能力系统也需要这样的拆解否则随着玩法变复杂Prompt 会失控函数列表会膨胀模型根本不知道该优先关注什么。2.2 技能的四层抽象我在实际设计中把技能归纳为四层表现层Manifest技能的名字、描述、触发场景、所属领域、版本号。这层负责让 Agent和大模型知道“何时该用这个技能”。契约层Contract技能的输入 Schema、输出 Schema、异常类型、执行前置条件。这层负责让技能“可被正确调用”。执行层Executor具体的执行逻辑可能是编排多次模型调用、多次工具调用也可能是调用一个外部脚本。评估层Evaluator技能执行完以后如何判断结果是否符合预期。这一步很关键很多 Agent 系统烂就烂在“做了”和“做对了”分不清。我就拿一个我自己项目里的真实技能来举例技能名叫“会议纪要与任务提取”。它的 Manifest 大概长这样{ name: meeting_minutes_extractor, description: 从会议录音转写文本中提取会议纪要并识别待办事项、负责人与截止时间, triggers: [会议结束, 转写文本, 待办提取], domain: office-assistant, version: 1.2.0 }Contract 层定义输入是一个字符串转写文本输出是一个 JSON包含summary、action_items、decision三个字段。执行层内部会先对转写内容做分段再调用一次大模型生成纪要然后调用一个规则引擎提取时间相关的短语最后合并输出。Evaluator 会校验action_items 是否为空如果为空就要触发二次处理——因为一个没有待办事项的会议纪要在办公场景里几乎等于没写。这套四层抽象是我在实践里反复打磨出来的它有一个非常明显的好处Agent 的主循环不关心一个技能内部做了多少次模型推理、调了几个工具它只关心怎么匹配技能、怎么传入参数、怎么校验输出。整个系统的复杂度被控制在技能内部不会外溢到主流程上去。2.3 为什么选择“显式声明”而不是“模型自主涌现”有一种流派认为Agent 的规划能力足够强的时候你不需要显式定义技能模型看到工具列表自然就会组合出来。这条路听起来很美但实际跑下来问题很多。模型自主编排的最大问题是不确定性。你可以让模型在 10 个工具里自己选但当你面对 200 个工具时选择的准确率会断崖式下跌。而且模型的组合逻辑往往不稳定同一类任务今天走 A 路径明天走 B 路径出了问题的排查成本极高。agent-skills 的方式是显式声明每个技能都经过人工设计、注册和测试Agent 要从一个已注册的技能库里去“匹配”任务而不是每次都在工具层面自由发挥。匹配过程和工具调用类似但粒度更大、意图更明确模型做这个判断的负担要小得多。实际测试下来在一组 15 个技能的技能库里模型准确匹配技能的比率在 90% 以上而且几乎不需要在 Prompt 里堆大量示例。这比让模型直接面对 80 个工具的选择准确率高出一大截。3. 技能的组织与编排从“有个技能”到“可用、好管、能迭代”3.1 技能注册与仓库结构讲完了抽象层次再落回工程实现。任何一个技能要能被 Agent 使用都需要注册。注册的本质是把技能从“开发环境”产物变成“运行时”可发现的对象。我在 agent-skills 项目里设计的技能仓库结构是这样的skills/ ├── meeting_minutes_extractor/ │ ├── manifest.json │ ├── contract.json │ ├── executor.py │ ├── evaluator.py │ ├── requirements.txt │ └── tests/ │ └── test_executor.py ├── email_draft_and_send/ │ ├── manifest.json │ ├── contract.json │ ├── executor.py │ └── evaluator.py └── ...每个技能都是一个独立目录自带元信息、代码和测试。这样的好处是依赖隔离、责任清晰。你甚至可以给不同技能指定不同版本的 Python 运行环境或者用不同模型供应商的 API互不干扰。注册流程上我并没有走“运行时扫描目录”这条捷径而是建了一个注册中心启动时读取一个registry.yaml文件skills: - name: meeting_minutes_extractor path: ./skills/meeting_minutes_extractor enabled: true version: 1.2.0 max_retry: 2 - name: email_draft_and_send path: ./skills/email_draft_and_send enabled: false version: 0.9.1为什么不用自动扫描因为自动扫描虽然省事但当技能数量超过 20 个之后你根本不知道哪些技能是可以上生产环境的哪些还在实验阶段。显式配置让你一眼看懂当前系统的能力集而且可以做灰度——先让 5% 的流量启用新技能观察反馈再逐步放开。3.2 技能之间的依赖关系与编排复杂任务很少由一个技能独立完成大部分情况是多个技能协作。比如处理一封“请求改期”的邮件Agent 可能需要先调用“邮件理解”技能判断意图再调用“日程查询”技能看时间冲突最后调用“邮件起草与发送”技能完成回复。技能编排有两种做法主 Agent 编排主 Agent 的 ReAct 循环里每步都去技能库匹配一次技能动态决定下一步执行哪个。灵活度高但每次都要调用大模型做路由决策延迟和成本都会上升。技能内编排一个高层技能内部直接指定依赖多个低层技能按预设顺序执行。稳定、可预测、调试方便。我两个都在用。对于探索性任务走主 Agent 编排的路线对于高频且链路固定的任务直接把它封装成一个复合技能内部把链路写死。比如“会议纪要与任务提取”在实测稳定之后我就把“分段预处理 → 纪要生成 → 待办识别 → 通知发送”全部固化内部不再需要大模型做路由判断速度快了大约 40%还大大降低了模型误判的风险。这个取舍背后的原则是一直在变化的链路交给模型动态决策已经跑通的链路用代码固化下来。这也是 agent-skills 的核心哲学——让模型做它擅长的事理解不确定的输入让代码做它擅长的事执行确定性的流程。3.3 技能的版本管理与灰度发布前面提到版本号就会引出升级问题。技能迭代是一个非常容易被低估复杂度的事。模型在变、外部 API 在变、用户需求也在变技能不可能不变。但技能一旦更新就可能破坏依赖它的上层流程。我采用的办法是给技能加上语义化版本主版本号不兼容的变更比如输入 Schema 变了。次版本号向后兼容的功能新增。补丁版本号内部实现修正。技能的 Manifest 里会记录依赖的最低版本运行时有一个依赖检查器如果发现冲突直接拒绝加载并给出报告。举个例子meeting_minutes_extractor依赖text_splitter技能的 1.0.0 以上版本如果当前注册的是 0.9.x注册中心会在启动阶段就报错而不是等运行时才爆雷。灰度发布方面我在注册中心加了一个简单的流量染色机制新版本技能先标记为candidate只有带有指定 session 标识的请求才会路由到新版本其余全部走旧版本。聚合几天的评估数据后再决定是全量切换还是回滚。这个机制实现起来大约一天时间却省掉了后面无数的线上事故。4. 核心环节实操技能从 0 到 1 的完整落地流程4.1 定义技能的输入输出契约写技能的第一步不是写代码而是先写契约。我吃过不写契约的亏早期图省事直接把技能做成“传一个字符串进去返回一个结果”的自由格式结果下游解析时各种踩坑字段时有时无类型说变就变。现在我坚持用 JSON Schema 定义每个技能的契约并且写进了团队规范。拿会议纪要技能的 Contract 举例{ name: meeting_minutes_extractor, input_schema: { type: object, properties: { transcript: { type: string, description: 会议转写文本支持中英文混合, minLength: 20 }, language: { type: string, enum: [zh, en], default: zh } }, required: [transcript] }, output_schema: { type: object, properties: { summary: { type: string }, action_items: { type: array, items: { type: object, properties: { task: { type: string }, owner: { type: string }, deadline: { type: string, format: date } }, required: [task] } }, decisions: { type: array, items: { type: string } } }, required: [summary, action_items] } }这个契约的价值不只是约束输入输出它还可以被用来做自动校验。我在 Executor 执行完以后会跑一遍输出校验如果某个字段缺失或者类型不对技能立即标记失败而不是把脏数据继续往下游传。有一个小坑要提醒模型的输出很容易不遵守你定义的 JSON Schema尤其是required字段。我的经验是不要完全依赖“提示词约束”而是要在代码里做一次显式校验必要时用 “One-shot 纠错”机制——把不符合 Schema 的输出连同错误信息一起送回给模型让它修正一次。这个办法的成功率非常高基本能解决九成以上的格式问题。4.2 执行器的设计把“不靠谱”变成“可重试”技能的执行器是整个系统里最核心也最容易出问题的部分因为每一次可能涉及多次模型调用、多次外部 API 请求。任何一个环节都可能波动设计执行器时必须有“容错”的本能。我总结了一套执行器的推荐流程参数预处理把外部传入的参数做归一化补全默认值。上下文装配从会话上下文或全局上下文中补充必要信息。子任务拆解如果需要调用内部规划器拆解步骤。执行子步骤按序执行每个子步骤都复用同一个错误处理机制。结果聚合与规范化汇总各子步骤的结果按输出契约加工。自我评估调用评估器判断结果是否合格不合格触发重试或降级。以邮件起草技能为例它的执行器伪代码大致长这样def execute(input_data: dict, context: dict) - dict: # 1. 参数预处理 body input_data[body] recipient input_data[recipient] tone input_data.get(tone, professional) # 2. 调用模型生成邮件草稿 draft llm_generate( system_promptYou are an email assistant..., user_promptfWrite an email to {recipient}. Body: {body}. Tone: {tone} ) # 3. 校验草稿是否合规比如长度、敏感词 validation validate_email_draft(draft) if not validation[passed]: # 带错误信息重试一次 draft llm_generate( system_promptYou are an email assistant. Fix the issues: ..., user_prompt... ) # 4. 写入发送队列或直接发送 send_result email_api.send(draft, recipient) return { draft_id: send_result[id], recipient: recipient, status: sent }每个技能的执行器都不太一样但核心原则一致把每个外部调用都视为可能失败、可能返回无效数据然后针对性地做校验和重试。千万别假设外部 API 和大模型第一次就能给你完美结果那是灾难的开始。4.3 技能召回与路由如何让 Agent“找对技能”技能库建好了定义清楚了剩下的问题就是Agent 收到一个用户请求怎么找到正确的技能我尝试过三种方式直接说结论纯文本描述匹配把每个技能的 description 拼接后和用户输入一起做相似度匹配。简单但精度有限遇到语义接近的技能容易混淆。向量检索召回给每个技能的 manifest 生成向量用户请求也向量化用余弦相似度做召回。效果比纯文本好不少尤其适合技能数量多的场景。重排序 阈值过滤向量召回 Top 10再由一个大模型做一次精细选择选出最终要用的技能。准确率最高成本也可控。我目前在线上用的是第三种。具体来说先做向量召回然后让模型从候选列表里选一个并且必须输出“选择理由”。如果模型的判断跟向量召回的第一名不一致系统会记录一次日志用于后面优化学法。路由信息除了技能名还包括一个置信度分数。如果分数低于某个阈值我会让 Agent 主动向用户澄清而不是硬猜。比如用户说“帮我发个通知”这个意图至少可以匹配“邮件通知”“短信通知”“IM 群通知”三个技能硬猜很可能选错不如问一句。别小看这个细节——让 Agent 学会“承认不确定”比让它硬着头皮猜重要得多。4.4 评估器的落地结果好不好要拿数据说话很多 Agent 系统跑着跑着就失控了核心原因是没有一套反馈闭环。执行完一个技能之后系统并不知道这次执行到底算成功还是失败。倒不是没有信息而是信息被浪费了。我在每个技能的 Executor 之后接了一个 Evaluator它干两件事结构化校验检查输出是否符合契约。质量评估对任务完成质量打分。质量评估的实现方式五花八门我实际用过两种有效的规则评估比如会议纪要技能如果完全没有提取到任何action_items直接记为失败。模型评估用一个专门的评估模型按你提供的标准给结果打分。比如邮件技能评估标准可以是“逻辑是否通顺、语气是否专业、是否包含必要的信息要素”。设计评估器有一个关键原则评估标准必须在写技能的时候就同步定义而不是事后补。很多朋友是先写了执行逻辑跑起来再琢磨“怎么判断结果好不好”这时候再来定标准已经晚了因为执行结果已经影响了下游评估器形同虚设。另外评估结果一定要落盘。我每天会拉一次所有技能的执行评估报告用这些数据决定哪些技能要升版本哪些技能要下线哪些技能的 Prompt 需要改。如果没有这套数据闭环你做任何优化都是拍脑袋优化完也不知道是否真的有效。5. 常见问题与排查技巧实录那些坑能避一个是一个5.1 技能匹配错乱把“发邮件”理解成了“更新日程”这个问题在技能数量超过 10 个之后特别容易出现。排查步骤我建议按顺序来第一步检查向量检索的召回结果看用户输入和技能 Description 的相似度分布。很多时候是技能的 Description 写得太泛导致多个技能分数接近。第二步检查重排序模型的 Prompt确认它是否能看到候选技能之间的区别性信息。如果候选技能的描述都差不多模型压根没法判断。第三步手动高亮技能的“触发场景”。比如邮件技能加上“当用户提到发信、回复、抄送、附件等关键词时优先选择”日程技能则强调“改时间、预约、会议冲突”等场景。最有效的长期解法是把线上误匹配的案例回流到技能库里给技能逐步补充典型的触发示例。这不是一次性工作而是持续维护的过程。5.2 技能执行超时一次调用全家等待智能体常见的性能杀手就是串行调用多个外部 API任何一个慢都会拖垮总耗时。我实测下来一个复合技能如果包含 4 次大模型调用和 2 次外部 API在最差情况下可能耗时 30 秒以上这几乎不可接受。几个实用的优化手段并行化无依赖的子任务并发执行。比如“提取待办”和“提取决策”可以同时发两个大模型请求再合并结果。能省掉近一半时间。超时熔断所有外部调用统一设置超时时间比如 10 秒超过直接走降级逻辑而不是无限等。轻量探测对于外部 API可以先发一个极轻量的健康检查请求或者直接用最近缓存确认可用再执行。我在某个版本的迭代中把“会议纪要”技能从全串行改成“并行超时”平均耗时从 22 秒降到了 11 秒效果立竿见影。这个收益比换更好的模型还明显。5.3 输出严重偏离预期动作做了结果不对这种情况最考验排查功力。表面上看技能执行成功但结果完全不是用户想要的。比如“生成一封简短的确认邮件”结果生成了一封 1000 字的长文。我的排查路径是这样的先看中间结果把 Executor 内部每个子步骤的输出都打点记录定位问题出在模型生成阶段还是后处理阶段。再看模型的输入有没有把用户的原始意图完整传进提示词。很多时候问题出在参数预处理阶段把关键信息搞丢了。然后检查评估器如果评估器没有把“简洁”纳入评估标准这个偏差永远不会被发现。这个经验的核心是日志要留足中间状态一定要可追溯。Agent 调试比传统代码调试难得多因为每次模型输出都不一样无法靠断点单步调试。唯一的办法就是让整个执行链路的所有中间状态都记录下来出问题才能回溯。5.4 技能版本升级后下游崩了这是个典型的依赖管理问题。技能 A 升级了输出格式删掉了一个旧字段结果依赖技能 A 的技能 B 直接解析失败。我每次都强调那两点Semantic Versioning 和注册中心的依赖检查。如果你的项目里还没有做建议至少先做依赖检查——每个技能声明它依赖的其他技能及其版本范围。注册的时候做静态检查不满足就不允许加载。这条规则看起来简单但能拦住绝大多数升级引发的问题。5.5 频率限制耗尽技能一跑起来 API 配额当天见底最后提一个工程层面的坑。技能内部的大模型调用会被计费外部 API 调用有频控限制。你一不小心写了递归重试或者在执行器里循环很多次API 配额很快就会用光。我的做法是在技能层加一个“预算控制”if context.get(request_count, 0) MAX_MODEL_CALLS: raise SkillBudgetError(技能调用次数超限)这是在保证技能不失控的最底线。没有这层保护一个错误的 Prompt 就可能让一个技能在一个小时内调用上千次大模型。别问我怎么知道的。写在最后的实操体会做 agent-skills 这段时间我最大的一个感受是Agent 工程难的从来不是跑通一个 Demo而是把系统的边界定义清楚。模型的自由度要控制技能的边界要清晰每一层都要有明确的责任和校验。市面上讨论 Agent 的火热话题都是「能不能够解决的问题」但真实工程里更关键的往往是「什么时候该停下来、什么时候该问人、什么时候该认错」这些不性感但必须做好的事情。对想尝试 agent-skills 这套思路的朋友我建议从一个小场景切入比如一个复合技能——“会议纪要提取”或“邮件自动归档”把你现有的一个工具升级成完整技能加上契约、评估和日志然后跑两周看看。我敢说你再回去看之前那种“所有逻辑都堆在 Prompt 里”的方案肯定不想回头了。技能库越用越厚Agent 就越用越稳。这一步跨出去了后面的路就顺了。