最近大半年我一直在折腾一个叫 agent-skills 的东西说白了就是给 AI Agent 配一套可插拔的技能库把 Agent 要干的每一类具体活的执行逻辑打包成标准化的技能模块然后让 Agent 像一个装了应用市场的手机一样按需加载、动态调用。这个思路解决了我过去特别头疼的一个问题——以前写 Agent 应用习惯把所有指令全怼进系统提示词里能写多长写多长结果上下文动不动就爆模型还老是绕来绕去不知道干什么。而 agent-skills 把每个能力封装成独立技能单元Agent 在运行的时候自己决定调哪个技能复杂的任务也能拆成一个个可组合的小步骤。不管是做信息检索、文档处理还是跨系统的自动化操作这套体系都能让 Agent 的行为变得更可控、更好维护。这篇文章我就把自己从零搭建这套技能体系的过程、踩过的坑、以及沉淀下来的经验完整写出来适合那些正在做 Agent 应用、想摆脱“提示词大杂烩”的开发同学参考。1. 整体思路拆解为什么 Agent 需要“技能”而不是“提示词”1.1 Agent 技能的真正含义先得把概念捋清楚。很多人一听到“给 Agent 加技能”第一反应就是“多写几个函数让它去调用”。这其实只对了一半。函数调用Function Calling只是技能的一种承载形式真正的 Agent Skill 应该是“工具 使用规则的封装”它包含三块内容一段清晰的声明告诉 Agent 这个技能什么时候该用、什么时候不该用一段可执行的逻辑可能是调用本地函数、请求外部服务也可能是执行一段代码片段一个标准的结果反馈机制无论成功失败都要把结果整理成 Agent 能继续理解的格式。我举一个特别生活化的例子你就明白了。假设你要带一个实习生你给他一份文档说“这是全公司的业务流程手册”他大概率看完还是懵。但如果你把工作拆成一个个标准操作卡“报销走什么流程需要填什么单子”、“打印遇到卡纸怎么处理按哪几个键”他甚至不需要理解公司的整体架构就能把每一件具体的事情办好。Agent Skills 就是给大模型发了一套这样的“标准操作卡”。所以我理解 agent-skills 的核心价值不是在给模型“加能力”而是在给模型“减负担”让模型不用在每次对话时都从零开始推理怎么做一件事而是认出一个模式然后直接触发对应的技能模块。这能显著降低任务的执行方差也方便我们逐步替换和升级 Agent 的局部能力。1.2 为什么我放弃了“全部交给提示词”最开始我做 Agent思路非常简单把所有的角色设定、背景知识、操作步骤全写成一大段 system prompt然后只靠模型自由发挥。实测下来问题很多我总结几个最典型的第一个问题是上下文膨胀。一个稍微复杂点的业务提示词容易写到几千字加上历史对话和检索回来的资料非常快就能把上下文窗口吃掉一半。留给真正思考的空间就很小了模型输出的质量和稳定性都会下降。第二个问题是调试太痛苦。提示词是“非线性”的你改一句无关紧要的话可能让另一条原本好好的功能突然失效。因为所有逻辑都纠缠在同一个文本块里根本没法做单元测试。第三个问题也是最致命的——不可复用。今天这个项目里写死的“搜索本地文档”的逻辑换一个项目没法直接搬过去用得复制粘贴然后改提示词、调参数、跑回归。反过来如果每个能力都是独立的技能模块在不同项目里做一遍注册就能用维护起来完全是另一个量级。所以我后来换成了技能化架构规则很简单凡是能写清楚输入输出边界的能力一律拆成独立技能提示词里只保留真正的“决策逻辑”和“风格要求”。这样 Agent 的核心提示词可以很短短到一屏能看完剩下的全部靠技能来补齐。1.3 方案选型技能应该长成什么样子在确定技能形态之前我对比了几种主流的技术路线方案优势劣势适用场景纯函数式Function Calling执行效率高、调用链路清晰、参数可强校验Agent 缺乏灵活度任务边界必须预先定义清楚流程固定、接口稳定的场景比如查天气、算运费纯自然语言式NL Skills对任意任务都适用靠提示词描述执行结果不稳定容易让模型偏离预定逻辑开放式写作、头脑风暴类任务混合式技能既有强约束的工具路径又有自由推理空间实现复杂度高需要设计好路由规则真实业务场景强力推荐我自己最后选了“混合式”路线这也是一线做 Agent 的团队普遍采用的方案。每个技能包同时包含一份 JSON 清单文件manifest和一份可执行代码清单文件负责告诉模型“我是什么、什么时候用、参数怎么填”代码负责真正干活。模型先根据任务描述选择清单再把参数以 JSON 格式传进来技能执行完返回结构化的结果。这相当于把“决策”和“执行”彻底剥离开了。2. 核心细节解析与实操要点2.1 技能清单文件Manifest该怎么写技能清单是整个技能体系的地基。我见过太多人在这上面栽跟头——清单写得含糊Agent 选技能时自然抓瞎。我自己踩过好几轮坑之后总结出了一套“能不改就不改”的字段模板{ name: search_local_docs, description: 在本地知识库中检索关键词返回最相关的文档片段。仅在用户明确要求查找资料、笔记、合同、报告或历史记录时使用。, input_schema: { type: object, properties: { query: { type: string, description: 检索关键词使用具体名词短语不要使用疑问句 }, top_k: { type: integer, description: 返回片段数量默认3最大10, default: 3 } }, required: [query] }, output_schema: { type: object, properties: { matches: { type: array, description: 文档片段列表每项包含文档名与片段文本 } } }, tags: [检索, local, rag] }这里每个字段都不是随便写的我逐个说下背后的想法name一定要用简短、能对表意的驼峰或下划线风格。不要叫skill_1这种至少要让 Agent 从名字上就能猜个大概比如send_email、query_sql。description是我认为最关键的字段。很多 Agent 选错技能八成是 description 写得不够清楚。它要写“什么时候用”更要写“什么时候不用”这样能帮模型排除干扰项。比如上面的反例“仅在用户明确要求查找资料时使用”这句话能避免 Agent 在闲聊时也触发检索。input_schema里的字段描述本身就是“给模型看的外卖菜单”——必须写清楚每个参数的含义、取值范围、默认值甚至要说明该填什么风格的值。比如 query 字段我特别注明“使用具体名词短语不要使用疑问句”就是因为模型经常把用户的原话直接塞进来导致搜出来的东西乱七八糟。output_schema经常被人省略。但你想想如果技能返回的数据结构不固定Agent 怎么知道如何把它组织成回答所以输出也要标准化。2.2 目录结构与注册机制有了清单文件技能代码的组织方式也很重要。我建议按“领域/技能名”两级目录组织每个技能包里至少包含一份 manifest.json 和一份实现文件skills/ core/ __init__.py registry.py runner.py local/ search_local_docs/ manifest.json implement.py file_summarizer/ manifest.json implement.py web/ fetch_url/ manifest.json implement.py article_extractor/ manifest.json implement.py code/ python_sandbox/ manifest.json implement.py其中 registry.py 负责扫描目录、加载所有 manifest、构建索引并在 Agent 启动时把“技能摘要列表”注入到系统提示词里。注意注入的不是完整 JSON而是压缩过的摘要比如“search_local_docs本地知识库关键词检索参数 query/top_k”。因为完整 JSON 太占 token而我下面会讲技能描述在提示词里很贵必须精打细算。2.3 技能描述在上下文中如何做预算说到 token 预算这也是一个必须提前想清楚的问题。假设你挂了 30 个技能每个 description 平均 100 个 token那就是 3000 token 的固定开销还没开始干活呢上下文先少了四分之一。所以我做了三件事第一摘要索引。注册表给 Agent 提供的不是完整 manifest而是每个技能的一两句话摘要压缩在几百 token 以内。等模型确实选中了某个技能再把完整 manifest 拿出来用于参数生成。这就像用户搜新闻先看标题感兴趣了才点开全文而不是一开始就把全部内容堆在眼前。第二动态裁剪。如果任务明显和某些技能无关比如用户只是闲聊“你好”那些检索、执行代码类的技能摘要根本不需要注入。我会在任务入口加一个初筛步骤根据任务分类把技能池从 30 个缩减到 5 个以内。这样做还有一个额外的好处技能池越小模型选错技能的概率就越低。第三长技能降级。某些技能必须携带大量说明才能用比如 SQL 查询需要附上数据库的 schema那就不要让它的完整说明常驻上下文而是采用“延迟加载”——当模型决定调用它时再用工具拉取完整的说明和参数模板。提示技能描述是 Agent 产品的“UI”而不是“文档”。写描述时要用模型能快速理解、没有歧义的句子宁可直白不要美丽。3. 实操过程与核心环节实现3.1 技能执行引擎从任务到调用的完整闭环我把整个技能调用的核心循环整理成了一段很清晰的调度逻辑你可以直接参考这个思路去实现自己的版本。先看整体流程Agent 收到用户任务系统提示词里带有可用技能的摘要列表模型思考一轮决定“是否需要调用技能、调用哪个、参数是什么”技能引擎校验参数执行技能代码拿到标准化结果结果返回给模型模型结合结果继续组织回答或决定是否再次调用重复 2-4 步直到模型判断任务已完成、或达到最大轮数。代码核心用 Python 的话大概是这样的我简化了一部分异常处理但关键逻辑都在def run_agent(task: str, skill_registry, llm, max_rounds: int 8) - str: system_prompt build_system_prompt(skill_registry.get_skill_summaries()) messages [ {role: system, content: system_prompt}, {role: user, content: task}, ] for round_idx in range(max_rounds): # 1. 让模型决策下一步动作 response llm.chat(messages, toolsskill_registry.get_tool_schemas()) assistant_msg response[message] messages.append(assistant_msg) # 2. 判断模型是否要求调用技能 tool_calls assistant_msg.get(tool_calls, []) if not tool_calls: # 模型已给出最终回答 return assistant_msg[content] # 3. 逐个执行技能调用 for call in tool_calls: skill_name call[function][name] try: arguments json.loads(call[function][arguments]) except json.JSONDecodeError: arguments {} executor skill_registry.get_executor(skill_name) result executor.execute(arguments) messages.append({ role: tool, tool_call_id: call[id], content: json.dumps(result, ensure_asciiFalse) }) return 已达到最大调用轮数任务尚未完成。这里面有几个非常容易出错的细节我逐个展开。3.2 参数的生成与强校验模型生成的参数经常不靠谱比如类型搞错了、字段漏了、甚至凭空造出 schema 里没有的字段。我一开始不做校验直接传入函数结果技能一旦报错整个任务就断了。后来我加了一个强制校验层效果立竿见影import jsonschema def validate_args(skill_name: str, raw_args: dict, skill_schema: dict): try: jsonschema.validate(raw_args, skill_schema) except jsonschema.ValidationError as e: # 二次修正尝试将错误信息返回给模型让它重新生成参数 raise SkillArgumentError(f参数校验失败: {e.message})校验失败时我不会直接终止整个 Agent而是把错误信息原样返回给模型让它“重新解释并修正参数”。这一步很重要因为很多模型在收到错误提示后会意识到自己在参数上理解错了第二次就能生成正确结果。这个“失败 → 反馈 → 重试”的闭环比单纯设置参数更实用。3.3 技能执行的结果如何返回技能执行完的结果要返回给模型但这会带来一个问题结果太长怎么办典型的例子是“fetch_url”技能抓了一个网页内容可能有几万 token直接塞回上下文会把其他重要信息全部冲掉模型也无法有效利用。我的解决方案有两个看场景选一是截断与分段。如果结果本身是顺序结构比如文档正文就截断到前 N 个字符并附上一句“已截断如需继续查看第 N1 段请调用翻页技能”。这样既能保证模型拿到核心信息又控制了上下文占用。二是摘要化。如果结果的核心信息需要“归纳”才能被利用比如一整天的日志要总结异常我会在技能内部先跑一个压缩摘要的步骤再返回给模型。注意摘要也可以用模型来做但优先考虑规则方法比如提取关键行、统计频率、按正则过滤能省很多 token 和时间。这里我贴一个实际技能的返回示例你会看到字段全是结构化的{ ok: true, matches: [ { doc_name: 2024年度项目总结.md, snippet: 本项目全年完成三个里程碑整体满意度达 92%……, score: 0.87 }, { doc_name: 需求评审会议纪要.md, snippet: 会议决定将交付节点提前两周……, score: 0.81 } ], truncated: false }3.4 循环终止条件的设计Agent 技能循环最大的隐患是“死循环”模型反复调用同一个技能或者连续调用几个技能就是不停下来。我设置了三个终止条件最大轮数建议 8-10 轮。超过轮数还没得到最终回答直接返回“任务未完成”的提示。注意此处的“轮数”不是模型思考的次数而是调用技能的总次数。连续失败上限。如果连续 2 次技能调用都失败参数错误、超时、结果无意义就不再重试直接把错误汇总给用户。结果重复检测。如果本轮技能结果和上一轮几乎一样通常说明模型陷入循环把它视为一次失败累积计数触发终止条件。我把这些逻辑封装在引擎里而不是写在提示词里。因为提示词只能“建议”模型不要怎么干而引擎是“强制”它不能这么干。做 Agent 就是要尽可能把规则移出提示词、移进代码。注意Agent 的可靠性靠的是“代码约束”而非“模型自律”。凡是能被代码或数据结构约束的行为就不要指望提示词里的一句话能兜住。4. 常见问题与排查技巧实录4.1 模型总是选错技能怎么办这是我在搭建 agent-skills 过程中遇到最多的问题。原本有 30 个技能可用模型动不动就把“search_local_docs”当成“fetch_url”去用或者明明用户想查个天气它去调了“query_sql”。排查思路有以下几个第一检查 description 是否互相打架。我在初期把搜索类技能描述都写成“在本地知识库中检索关键词”结果检索文档和理解文档的技能边界模糊模型根本分不清。后来我给每个技能加了使用场景和禁止场景的说明问题减少了一多半。第二减小技能池。技能池越大模型的选择空间越大出错的概率也越高。我后来在接入新技能时多了一条铁律如果这个技能和已有技能的可区分度较低要么合并要么把使用边界写得更严格。技能数量宁少勿滥。第三缓存历史偏好。在一个会话内如果模型连续几次都选了某个技能我会把它提到技能摘要列表的最前面并让其他类似的技能摘要暂时隐藏。这相当于把“用户的意图已经在前几轮确定了”的信息反馈给模型能大幅减少中途换错技能的概率。4.2 参数校验失败怎么办模型生成参数失败我把它归纳为三种情况分别有不同的应对方式失败类型典型表现处理方式类型错误应该传 int 传了 string将错误信息返回给模型要求它重新生成如果第二次还失败直接退出枚举越界传入一个 schema 不存在的枚举值校验层切到“宽松模式”自动映射到最接近的合法值字段缺失必填字段没传优先使用 schema 的默认值补全再有缺失就反馈给模型需要说明的是并不是所有参数错误都值得返回给模型重试。像“top_k 传了一个字符串 ‘很多’”这种错误模型重试一百次也改不对就不要再浪费一轮了。我的经验是给重试机制设置次数上限一般 1-2 次超过就降级成规则兜底。说白了Agent 是工程师做出来的产品不是玄学该失灵就果断兜底。4.3 技能的输出导致 Agent 上下文爆炸技能返回结果太长是一种特别常见的上下文污染方式。除了前面提到的截断与摘要我还有一个非常推荐的技巧给技能结果设置“只返回结论、不返回过程”的原则。举个例子一个 SQL 查询技能如果返回字段有 500 行模型根本读不完。更好的做法是在技能内部先执行聚合统计只把“总行数、去重数、TOP 摘要”返回给 Agent模型需要查看明细时再通过分页调用另一个子技能。这个思路也符合我前面说的“技能内部做判断、只把必要的线索交给模型”的原则。4.4 技能之间的依赖与循环当多个技能需要互相调用时我一开始会直接把一个技能的实现里写死调另一个技能的代码结果出现了 A 调 B、B 又调 A 的循环。这个问题我后来是用“技能编排层”解决的技能与技能之间不互相知道对方的存在所有依赖关系都只发生在“调度引擎”层面。一个技能执行完返回结构化结果引擎根据结果决定下一步调哪个技能。这样每个技能保持独立编排逻辑放在引擎里统一管理可以喂给开发同学去排查链路、加监控、设超时。让我用表格总结一下各类异常的处理级别这样你能快速判断什么错误要暴露给模型、什么错误要自己吞掉异常类型暴露给模型重试自身兜底处理参数格式错误是最多1次使用默认值/宽松映射技能内部业务异常否返回标准化错误码外部 API 超时否自动重试2次后降级结果为空是返回“无结果”标志让模型换策略上下文超长否截断或摘要化4.5 我踩过的一个非常隐蔽的坑最后我想分享一个特别隐蔽但后果很严重的坑。一开始我注册技能时使用了一个在 Python 中叫exec的保留方法名结果技能引擎在动态注册时直接抛错了。这提醒我技能名字空间要和代码内部变量名严格隔离所有技能名在进入引擎之前必须做一次规范化。后来我在注册表里加了一个名字校验统一转成小写、去空格、避免关键字。这个坑虽然简单但如果不提出来排查的人可能浪费大半天都找不到原因。另外一个经验也很有用技能系统的日志一定要带上“路由决策”。不只是记录“调用了哪个技能”还要记录“模型为什么选择这个技能”——把模型返回的推理过程一并落盘。这样当技能选错时你能直接看到模型的思路而不是靠猜。我每次在 Agent 开发群里答疑都会建议大家尽早做这层日志。最后换个方式说几句实操心得整个 agent-skills 体系从搭骨架到跑稳我前前后后大概改了四次结构。最开始的版本把技能当纯函数结果发现模型根本不认识该用哪个第二个版本全塞提示词上下文爆炸第三个版本加了清单文件但描述写得一塌糊涂直到第四个版本才把“摘要索引 动态加载 强校验 严格终止”这套机制跑通。给我的最大体感是构建 Agent 技能体系本质是在写一套“给模型用的 API 文档”这套文档的质量决定了 Agent 能否做出正确的路由决策。另外如果你也在做类似的项目我建议从第一个技能开始就写完整清单、做参数校验、打日志不要先跑通再补后面补的代价会是前面的三倍。这套思路后面还能延伸出技能的市场化共享、自动评测回归、以及技能之间基于上下文的动态编排我手头也在继续试有了新的结论再回来分享。