
这两年做智能体Agent开发我发现自己反复在干同一件事把工作流拆成一块块能被模型直接调用的能力模块按统一格式注册、让 Agent 按需选用。这套东西最终沉淀成一个内部目录名字就叫agent-skills。它不是什么新框架也不是多高深的理论但如果没有它Agent 大概率只是个“会聊天的接口”而不是“会办事的系统”。这篇内容我会从实战角度聊透 agent-skills 的定位、设计、落地方案和排错经验。适合正在做智能体应用、想把 Agent 从 demo 推向生产的开发者也适合刚接触 “工具调用 / function calling / MCP” 但是被各种概念绕晕的朋友。读完之后你可以直接照着一套思路来搭自己的技能系统并避开我当时踩过的坑。1. 为什么 Agent 会突然需要“技能”这件事1.1 从“会聊天”到“会干活”中间差了一套调用骨架最早做 LLM 应用的时候大家熟悉的交互是“问一句答一段”。这种模式处理知识问答没问题但一旦涉及具体业务操作立刻会露馅要么模型一本正经地编造一个不存在的接口要么就是答非所问地给你一段“建议”。因为纯文本输出没有约束力模型说的话再好听它也变不成系统里真实的动作。后来有了 function calling模型可以通过结构化参数来请求调用外部函数。这算是一个分水岭模型第一次不只是“说”而是能“触发”。但做久了会发现函数调用解决的是“模型如何表达调用意图”的问题并不解决“你手里到底有哪些能力、这些能力怎么组织、怎么保证能力定义稳定可靠”的问题。一个聊天助手可能只需要两三个函数可一旦你做一个真正常驻运行、跨系统协作的 Agent函数会变成几十个、上百个这个时候散落四处、随意命名的函数就完全没法维护了。所以 agent-skills 在我这里不是什么新鲜概念它就是一个工程化的答案把 Agent 能执行的能力从零散的函数升级成有目录、有规范、有版本、可发现、可组合的技能体系。就像人做事靠技能而不靠“一句话描述的行为”Agent 要干活也需要一套被明确定义、被注册、被验证过的能力集。1.2 技能不是单纯的“工具列表”先厘清一个容易被混淆的点agent-skills 不等于“给模型一堆 API”。API 解决的是“系统暴露了什么”技能解决的是“Agent 在什么条件下、以什么方式、为了什么目标来使用这个能力”。同一套 API有的技能把它包装成查询动作有的技能把它包装成写操作防护策略、参数校验、返回格式都可能不同。打个比方。给一个人一本产品说明书他未必会用微波炉但给一个人一套“微波炉加热操作技能”他会明确知道什么食物适合什么火力、如何避免烫伤、加热后如何判断是否熟透。技能自带使用边界和操作经验API 只是设备说明书。Agent 也是一样一个search_knowledge_base(query: str)的函数和一个定义清晰的知识库检索技能差距很大后者会包含对结果质量的要求、对空结果的处置策略、对敏感信息的过滤规则甚至在与用户目标冲突时如何反馈。所以在设计 agent-skills 时我默认每一个技能都是一个“最小可独立运行的决策单元”。它不只是封装的代码还包括元信息也就是“什么时候该用我、不该用我”。这是让 Agent 真正可靠的分水岭。2. 设计一套可用技能集先回答四个关键问题2.1 技能的粒度多细才算合适这是最容易翻车的地方。刚开始我倾向于把技能拆得很碎打开文件、读取第一行、统计字数……这样确实灵活但实际跑起来模型经常选错技能甚至在多个步骤间来回切换平白增加大量无效调用。因为粒度过细的技能让模型承担了太多的“流程编排”责任而模型在长链路编排上的能力并没有我们想象中那么强。把技能做得太粗也不行。比如做一个处理文档技能里面塞了转换格式、提取内容、生成摘要、发送邮件模型很难判断调用之后到底会发生什么参数也臃肿到难以控制。我最终总结出的经验是技能粒度应该对应一个“成年人不需要二次思考就能完成的独立原子动作”。比如生成周报是一个完整任务不是原子动作把指定目录下的 Markdown 文件批量转成 PDF是原子动作发送邮件给指定收件人是原子动作查询某个客户在 CRM 里的基本信息也是原子动作。粒度合适还有一个量化参考技能说明最好能控制在 800 字以内参数最好不超过 5 个核心参数辅助参数可以多但必须全部带默认值。超过这个线就要考虑是不是技能本身太复杂需要拆分了。拆分的判断标准不是“这段代码能不能复用”而是“Agent 在决策时会不会在两个技能之间犹豫”。会犹豫说明边界不清晰要么粒度不对要么描述有问题。2.2 技能之间如何避免互相打架技能多了之后最典型的 A/B 冲突是查询天气和查询城市信息模型在面对“北京今天适合穿什么”时很可能在两个技能之间摇摆。解决这个问题不能指望模型“变聪明”而是要在设计层面把技能边界画清楚。我的做法是给每个技能定义一个“触发优先级”的隐式语境。具体说就是在技能描述的开头明确写“当且仅当”条件并且把容易混淆的相反场景写进去。比如查看今天天气的描述可以写成“仅当用户想了解某个城市当前或近几日的天气状况时使用包括气温、降水、风力。如果用户询问的是穿衣建议、出行舒适度请先使用此技能获取天气数据再结合建议规则回复。”这样模型就有了明确的避让关系和调用链。另外为了减少并发冲突每个技能都要声明自己的“资源占用类型”是只读型还是写转型是否需要访问网络是否需要调用模型。调度器根据这些元信息决定并发策略避免多个技能同时修改同一份状态。我见过太多团队的 Agent 在真实环境里因为两个技能同时写一个配置文件而出现数据错乱这本质上就是技能设计时没做资源隔离。2.3 输入输出格式怎样才算“能被别人接住”技能如果只在单一 Agent 内部使用参数格式可以随意但一旦要跨团队共享、发布到技能市场或者被不同语言写的服务调用输入输出契约就是生死线。我坚持用 JSON Schema 定义技能入参每个技能都必须显式声明参数类型、必填项、取值范围和默认值。这有两个好处一是模型的 function calling 输出可以被严格校验提前拦住格式错误二是同一个技能可以被多个入口复用不必为每个 Agent 重写一遍逻辑。输出格式我统一用“结构化数据 自然语言摘要”双层结构。结构化数据给程序继续调度用比如{result: 42, unit: percent}自然语言摘要是给模型用来生成用户回复的比如“当前内存使用率为 42%”。这个设计的核心原因是Agent 的下游消费方不只是人还有别的技能。如果一个技能输出的是大段自然语言下一个技能想从中提取结构化信息就会非常痛苦。输出里还要包含一个success或error状态字段调用方拿它做分支判断而不是靠抛异常来传递业务失败。2.4 技能的注册、发现与路由技能多了以后模型不可能在每个请求里都看到所有技能的全量描述token 受不了选择准确度也会下降。所以你需要一个注册中心外加一套发现机制。我见过直接靠系统提示词硬塞十多个技能描述的方法前期还行技能超过二十个之后效果就一落千丈。我的落地方案里有一张技能注册表每条记录至少包含技能名、描述、语义标签、入参 Schema、调用入口、超时阈值、健康状态和版本号。请求进来后先做技能检索把候选技能从几十个缩小到三到五个再将它们的描述注入到上下文里供模型决策。技能检索可以用关键词召回也可以用向量召回甚至混合策略。这样模型每次只需要在“几个最可能相关的技能”里做选择准确率自然高很多。路由层的职责不只是缩小范围还负责权限校验。技能标记了需要管理员权限用户角色没有对应权限就根本不进入召回列表。这样既能保证安全也能避免模型被用户误导去调用越权技能属于体感上提升非常大的一步。3. 从零搭一个最小可用的技能系统3.1 架构选型函数调用、工具描述还是走 MCP现在业界给 Agent 暴露能力的方式大概有三种。一是平台原生的 function calling比如 OpenAI 的 toolsAnthropic 的 tool use这种方式最直接但跟某个云厂商绑定比较紧。二是大家基于 HTTP 自己封装工具描述比如把工具列表写成 JSON 发给模型自由度高但每个组件都要自己维护协议。三是走 MCPModel Context Protocol这类标准化协议让技能服务独立部署通过标准 JSON-RPC 暴露工具列表和调用入口好处是生态互通坏处是协议本身还在演进有些场景的调试体验仍然不够顺手。我并不是某个协议的“信徒”。个人经验是如果你只是在一个单体项目里给 Agent 加几个工具直接用 function calling 就够了不要为了技术时髦而引入额外复杂度。如果你的团队有多个 Agent 应用或同一个技能要被不同语言的服务消费那才值得把技能下沉为独立服务再用标准化协议暴露。我在生产环境里用的是混合方案技能注册中心内部维护统一元数据对外同时提供 function calling 格式的适配和 MCP 格式的适配。内部核心不依赖任何一家厂商协议所以无论上游模型怎么换技能层都不用改。3.2 核心代码骨架技能注册表与调度器一个最简技能系统只需要两个核心组件注册表SkillRegistry和调度器SkillRouter。下面这段 Python 代码是我在实际项目中简化出来的保留了最关键的逻辑。from dataclasses import dataclass, field from typing import Any, Callable, Dict, List, Optional import json dataclass class Skill: name: str description: str tags: List[str] input_schema: Dict[str, Any] execute: Callable[..., Any] timeout: int 10 required_permission: str user enabled: bool True class SkillRegistry: def __init__(self): self._skills: Dict[str, Skill] {} def register(self, skill: Skill) - None: if skill.name in self._skills: raise ValueError(fskill {skill.name} already registered) self._skills[skill.name] skill def search(self, query: str, top_k: int 3) - List[Skill]: # 简易关键词召回生产环境可换成向量检索 query_terms set(query.lower().split()) scored [] for skill in self._skills.values(): if not skill.enabled: continue haystack skill.name skill.description .join(skill.tags) haystack_lower haystack.lower() score sum(1 for term in query_terms if term in haystack_lower) scored.append((score, skill)) scored.sort(keylambda x: -x[0]) return [s for _, s in scored[:top_k]] def get(self, name: str) - Optional[Skill]: return self._skills.get(name) class SkillRouter: def __init__(self, registry: SkillRegistry, permission: str user): self.registry registry self.permission permission def prepare_prompt_tools(self, query: str) - List[Dict[str, Any]]: skills self.registry.search(query, top_k3) tools [] for s in skills: if s.required_permission admin and self.permission ! admin: continue tools.append({ type: function, function: { name: s.name, description: s.description, parameters: s.input_schema, } }) return tools def validate_call(self, skill_name: str, args: Dict[str, Any]) - List[str]: skill self.registry.get(skill_name) if skill is None: return [skill not found] # 这里可以根据 JSON Schema 做详细校验 for field_name, definition in skill.input_schema.get(properties, {}).items(): if field_name in definition.get(required, []) and field_name not in args: return [fmissing required field: {field_name}] return []这段骨架有几个值得留意的设计点。第一SkillRegistry.search虽然只是关键词计数但它已经把“全量技能可见”变成了“候选技能可见”这一步给模型减负的效果立竿见影。第二SkillRouter.prepare_prompt_tools根据权限过滤技能不信用户输入里的“我是管理员”只认调用链上的角色上下文。第三validate_call模拟了调用前校验的动作实际项目里可以替换成jsonschema.validate提前把格式错误的调用拦下而不是让技能内部代码面对脏参数。3.3 从“一次性任务”到可持续维护的技能库很多团队做 Agent 都是从一次性脚本开始比如写个 “总结日报并发送” 的 prompt跑通就完事了。但这种脚本越堆越多最后一定失控。我建议从第一个技能开始就坚持三个习惯。第一个习惯是技能必须带 owner。每个技能挂一个负责人上线后的问题、迭代、废弃都走负责人审批。没 owner 的技能半年后没人敢动就只能躺在那里成为风险点。第二个习惯是技能描述和实际行为要有一致性测试。我们写了一个自动化用例定期用固定的 query 跑一遍技能发现检查某个典型 query 是否还能正确路由到目标技能。同时用录制的样例输入跑技能执行校验核心输出的字段完整性和状态码。这个测试成本很低但关键时刻能救命尤其是你升级了底层模型之后很多技能的“手感”会变系统必须能第一时间发现。第三个习惯是版本化演进而非原地修改。技能的行为如果有破坏性变更就新增一个版本号注册表里新旧并存路由时优先选新版本。用户或业务流程需要回退时只要靠旧版本技能还在就能快速恢复稳定性不用紧急回滚代码发布。3.4 技能调用的失败重试与降级策略技能一旦进入生产环境失败是常态。服务超时、第三方接口限流、结果不合法这些都算失败。关键是 Agent 系统怎么优雅降级而不是直接把一张错误堆栈甩给用户。我通常按错误类型分类处理。第一类是“参数不合法”这属于用户请求的问题应该反馈给模型让模型修正参数或主动追问用户而不是重试。第二类是“外部服务临时不可用”包括超时、限流、5xx这时候可以设计有限次重试比如 2 到 3 次每次退避 1 秒以上。第三类是“结果不符合预期语义”比如返回了空数据或明显不完整的结果这时可以让技能走一个备用逻辑比如从缓存读上一次结果或者调用降级技能。降级技能是 agent-skills 体系里非常好用的设计把“主技能 降级技能”组合成技能链。例如主技能获取实时股票行情降级技能获取延迟十五分钟行情当实时源挂了Agent 知道改用降级技能并在回复里明确告诉用户是延迟数据。这种透明降级既保住了任务连续性也避免了模型为了“完成任务”而编造数据。4. 用真实场景验证技能设计的合理性4.1 场景一研发协作助理我团队里有一个内部 Agent负责处理研发日常事务创建分支、提交 MR、查流水线状态、通知相关人员。这个 Agent 一开始只挂了四个技能create_git_branch、create_merge_request、get_pipeline_status、notify_im。一开始效果不错但很快遇到两个问题。第一个问题是模型经常把分支名参数和 MR 描述参数填串。后来我在create_merge_request的 JSON Schema 里把 description 字段的description写得极其详细包括示例“描述应包含需求单号、改动模块、测试结论”并且限制了maxLength模型填参准确率才稳定下来。第二个问题是流水线失败时Agent 只会机械地回复“流水线失败”用户没法定位。后来我增加了一个技能analyze_pipeline_log专门负责拉取日志并用局部摘要的方式定位失败原因再让模型基于这个摘要回复用户。加了它以后同样一个get_pipeline_status的产出使用率低了很多因为模型不再需要自己想象失败原因而是直接调用分析技能。这个场景给我最大的启发是技能设计不是一次性的而是跟着真实使用数据持续迭代的。模型在哪个环节犹豫、在哪个技能上频繁返回低质量参数都说明那里需要新的技能或者描述调整。4.2 场景二数据处理工作流另一个项目是给运营团队做的自动化数据报表。这个 Agent 要查询数据库、做聚合计算、生成图表、写入在线文档。这里技能拆分的关键是按数据动作来分而不是按业务动作来分。query_warehouse_sql只管执行只读 SQLgenerate_chart只管接收表格数据输出图表文件write_doc只管把文本和图表写入指定文档。这三个技能的分工非常干净所以模型几乎不会搞错选择。但实际运行中出现了一个有意思的问题模型写 SQL 的时候经常因为表名不规范而写错。我本来想让模型在写 SQL 前先查表结构于是加了一个get_table_schema技能。加完之后另一个问题又冒出来了模型开始频繁调用这个技能因为它是只读操作看起来总是很安全。结果是流程变慢但准确率没有提升因为在绝大多数查询中模型根本不需要完整表结构。最后我的解决办法是把get_table_schema从 Agent 的默认技能列表里拿掉只在 SQL 第一次执行报错或返回空结构时作为错误修复路径里的“小工具”出现并让模型先基于错误信息判断是否需要调用它。这个解法让我明白了技能设计同理于产品设计一个技能的存在感太强会诱导模型依赖它而依赖不一定等于效率。4.3 场景三个人知识库助手个人知识库助手是我自己平时用得最多的项目它的技能集包括网页收藏、PDF 摘要、笔记检索、标签维护。相比前两个企业级场景个人助手更考验技能定义的自然语义匹配因为没有固定命令全靠用户随口说。一个典型的请求可能是“帮我把昨天的收藏整理成一份周报”。这个请求里模型需要识别出三个技能的组合list_bookmarks_by_date、summarize_content、generate_markdown_doc。组合技能比单一技能更容易出错尤其当依赖关系隐含时。我的做法是为这类高频组合定义“编排技能”它自己不执行具体动作而是内部描述清楚步骤让模型在决策时先选编排技能再分步调用子技能从而减少漏步骤。但这里有一个值得提醒的关键点编排技能的子技能调用必须走同一个状态上下文也就是用户意图、中间结果要能透传不能让每个子技能都像第一次见到用户一样。我在代码里用一个SessionContext存储上下文数据例如当前的日期范围、收藏列表、摘要结果供后续子技能复用效果明显比让模型重读历史来恢复状态稳定得多。5. 常见问题与排错实录5.1 问题一Agent 一直选错技能怎么办选错技能的原因通常有三个。第一个原因是技能描述里“过度承诺”比如描述里写了“可以查询订单”但实际入参里缺少交易类型字段模型识别不出限制自然选错。第二个原因是候选技能之间语义重叠过高常见于历史积累的技能。第三个原因是模型本身对复杂描述理解能力有限这种情况下换参数量更大的模型常常有效但不要指望模型彻底解决描述混乱的问题。我的排查顺序是先把模型实际收到的候选技能列表打出来看路由层是否已经召回错误的技能。如果召回就错了问题在技能标签和描述如果召回对了但模型最终选错问题在技能描述与用户表述之间的对齐度。把日志打印加上之后这类问题通常半天内就能定位。5.2 问题二技能返回的内容不够结构化很多团队直接把一个 HTTP response 返回给模型模型再自己从里面抽字段抽字段就可能有幻觉风险。正确的做法是技能执行器内部就完成字段提取以结构化字典返回。比如请求一个第三方接口返回了 XML 或 HTML技能内部要用解析器预处理只输出模型真正需要的字段并加一个schema_version字段方便下游确认格式版本。另外建议技能内部加“输出审计”。返回给模型前先跑一个轻量的规则校验必填字段是否存在、数值是否在合理范围、结果是否为空。如果校验不过技能自己先标记status: error并附带简短原因模型拿到后能直接基于错误原因来调整回复或重试而不是面对一堆原始文本开始“推理”。5.3 问题三多技能并发导致共享状态错乱当一个 Agent 在复杂任务里需要并行调用多个技能时每个技能如果都去读写共享文件或数据库表冲突概率会非常高。我遇到过一次比较严重的线上事故是两个技能同时更新同一个用户配置表其中一个读到了另一个未提交的中间态最后把配置写坏了。后来做了两层防护。第一层是技能注册表里增加了资源锁声明每个技能声明自己会读写的资源 ID调度器在并发调度时检测锁冲突冲突的技能自动退化为串行执行。第二层是所有写操作统一走一个事务式的StateStore写入前比较版本号版本不一致就拒绝写入并通知上层重试。这两层加上以后共享状态错乱的问题基本绝迹。5.4 问题四技能调用链太长token 消耗爆炸组合复杂任务时Agent 为完成一个目标可能需要连续调用多个技能每次调用都要带着越来越长的历史上下文token 消耗非常惊人。这个问题更隐蔽技能之间传递的中间结果如果以全量形式保留很快会把上下文撑爆。我的优化办法是在技能链之间只传递“精炼中间结果”。比如 PDF 摘要技能输出的不是整篇文章而是一份 100 字以内的摘要和一个指向全文存储的引用 ID后续技能需要全文时再通过load_content_by_ref技能按需加载。这相当于给 Agent 的上下文做了一层缓存淘汰策略只保留当前有可能会再次用到的信息其余放回“外部存储”。实测相同任务下token 消耗能下降一半左右。5.5 一份常见问题速查表症状常见根因快速处理手段模型常选错技能技能描述边界不清或标签重叠重写描述加入“何时不该用”说明收紧检索 top_k技能报参数缺失JSON Schema 必填项定义不完整补全 required 字段给默认值字段设置default技能返回结果为 null外部接口解析失败在技能内部增加解析容错输出error状态链路长、token 超限中间结果全量保留改为引用 ID按需加载多技能并发写坏数据缺少资源锁声明注册表增加资源声明调度器串行化冲突资源升级模型后技能效果下降新模型对格式理解不一致回归测试技能选择准确率按需调整描述措辞6. 给技能系统的扩展方向留下接口如果你已经按上面的思路把一套最小系统跑起来了我建议你现在就为两件后续事情做预留。第一件事是技能质量观测和回溯。每个技能调用都应该有 trace包含入参、出参、耗时、模型路由决策链路。没有 trace技能一多就是黑盒出问题只能靠猜。第二件事是技能之间的版本依赖关系。比如生成图表依赖查询数据记录这种依赖关系后当你调整底层数据表结构时就能快速评估受影响技能范围。我个人的体会是agent-skills 本质上是在给 Agent 建立一套“能力肌肉记忆”。它不是替代模型智能而是让模型不用每次都由零开始猜测该动作怎么做、边界在哪、什么时候该拒绝任务。高质量技能库会让 Agent 从“看上去聪明”变成“真正可靠”这一步是所有想要把智能体落地的团队都绕不开的。