
直接说结论吧agent-skills 并不是某个花哨的框架也不是一句提示词就完事它是一整套“把大模型的泛化能力收敛成可复用、可测试、可组合的确定性技能包”的方法论。这两年我拿它做了不少 AI 代理项目从邮件自动分类、日程提取到文档问答、多代理协作踩过的坑加起来能写一本小册子。这篇文章就围绕“技能”这两个字把从概念拆解到项目落地、再到事故排查的完整路径摊开讲希望能给正在做 AI 代理、自动化任务、或者多智能体系统的朋友一些参考。agent 的底层模型依然是那个模型真正拉开体验差距的是你给它装了什么技能、怎么定义的、怎么调的。你说“帮我处理邮件”模型一脸懵但你说“调用 extract_event_skill邮件正文在这当前日期给你输出结构化日程”它就非常清楚自己该干什么。这里面隔着的就是 agent-skills 这套设计思路。1. 技能的本质与设计逻辑1.1 从“提示词”到“技能”的进化咱们先把底层逻辑搞清楚。无论是 GPT 还是 Claude 这类大模型用户打交道的方式最顶层依然是聊天。但代理不一样他是“拿着任务自己去跑”的实体。聊天的本质是“你说一句我答一句”代理的本质是“你给我任务我自己想办法完成”。这中间的跨度光改善提示词是不够的。提示词描述的是“目标”技能描述的是“怎么达成目标的路径”。我最早做的一个自动化流程想让它根据用户邮件自动整理日程。一开始就是一段很长的 system prompt请识别邮件中的时间、地点、参与者然后调用日程接口。看起来没问题但跑起来全是 bug。识别时间不准时区一团糟参与者有时候取成发件人自己。后来我把“日程识别”这件事情拆成一个技能定义了具体的输入字段邮件文本、当前时间、用户所在时区和输出结构格式化的日程提议模型在里面只做“抽取和填充”这一件事原本五花八门的幺蛾子基本消失。这就是技能的第一个核心价值它把任务边界划死了。模型在技能边界内发挥聪明的部分边界以外的部分交给确定的代码逻辑。很多失败的代理项目问题不是模型不够聪明而是你没给模型划边界。1.2 技能、工具和角色之间的关系聊 agent-skills 经常会牵扯到另外两个词工具tool和角色role。我的理解是这样的角色是“身份和行为风格”比如客服、数据分析师、日程助理工具是“能执行的外部操作”比如发邮件、查数据库、调用 API技能是“工具的编排 任务处理的流程”它是两层概念的结合体纯粹的工具调用本质还是“命中即用”——模型判断该用哪个函数就用哪个。但技能通常带流程前置条件检查、参数组装、调用工具、处理返回值、判断是否需要重试、最后汇总输出。技能可以包含多个工具的调用顺序。举一个例子一个“客户信息查询”技能可能是先查客户主数据再查订单数据最后查售后记录然后把三者聚合成一份完整视图。这不是某个单独工具能搞定的它是一个流程。把角色、工具、技能搞混的后果就是有人花了一堆时间写了一大堆工具结果代理根本不会在合适的时候调用或者给代理定义了“客服助理”的角色但没配套任何技能它只能空谈。真正的分工应该非常清晰角色负责说话的方式工具负责能干的事技能负责处理事情的步骤。1.3 为什么技能要让代理“少思考”这一节想聊一个很反直觉的点与其让代理多想不如让它少想。我们做 agent-skills 时最好把代理的“思考量”压到最低。你可能觉得代理越聪明、越会推理越好。但实际项目里推理是多步骤的每一步都有概率出错。一旦代理理解错了上下文后面全盘皆输。所以技能设计的核心思路是让每一步都有明确的输入输出让代理在每一步只需要做最简单的判断。复杂逻辑尽量用代码实现而不是靠大模型“凭空想”。我自己的经验法则是如果一个操作用代码写只要 5 行就绝对不要让大模型去“推理”。改成一个技能参数的提取和验证写死模型的自由度只保留在“识别意图”和“提取关键信息”上。模型是最贵的、最不稳定的组件能用代码解决的事情就不要让它碰。这个原则做久了整个系统稳定性会高很多。1.4 技能设计的关键原则做 agent-skills 时我会反复检查这么几条设计原则单一职责。一个技能只做一件事不要做一个“万能技能”。比如“分析销售数据”和“生成销售报告”是两个技能而不是一个技能里既分析又报告。输入输出明确。每个技能必须有明确的参数定义和返回结构拿 JSON Schema 来约束最好这样调用方和模型都清楚边界。可测试。技能必须有确定性的执行路径输入固定之后输出应该可预期这样每次回归测试才有效。可组合。小技能拼成大流程大流程本身又可以作为一个技能暴露给代理。层级化组织是后期扩展的关键。我看到很多开发者的第一个版本就是堆功能一个技能里塞十几种逻辑看起来功能强大实际上模型根本不知道什么时候用哪个逻辑最后效果稀碎。技能设计跟软件设计是同一个道理高内聚、低耦合永远是第一位的。2. 核心细节与技能定义实操2.1 技能描述怎么写才管用技能描述写得好不好直接决定了代理会不会在正确时机调用它。常见的问题是描述写得太抽象比如“处理邮件相关的事务”——代理完全不知道什么时候该用。描述一定要具体、区分场景、包含触发条件。我一般用这样的模板来写技能描述技能名称简单的动词短语比如 schedule_meeting一句话概述干什么的比如“从邮件或聊天文本中提取会议信息并创建日程邀请”触发场景什么情况下必须调用什么情况下不调用输入参数每个参数的名字、类型、是否必填、示例值输出格式成功和失败分别返回什么结构边界条件哪些情况属于这个技能处理不了要报错或转交给其他模块其中触发场景非常重要。模型判断是否调用技能靠的就是描述里的场景匹配。我见过有人写“查询天气”都不写清楚支持城市范围结果用户问“东京天气怎么样”代理调了这个技能但传参不对白白浪费一次调用。把描述当作训练模型的 few-shot 来对待。你描述得越精细模型越不容易误用描述得越含糊模型就会自由发挥然后出各种诡异的问题。2.2 参数设计比你想的重要参数设计是 agent-skills 里面最容易被低估的一环。很多人写技能时参数随便定义几个 string 类型就完事结果后期到处校验不通过。我的建议是至少做到三点严格类型。能用 integer 就不要用 string如果用枚举就显式列出来。给示例值。参数描述里带上具体示例模型填参时不容易瞎猜。做边界校验。在技能内部对参数做合法性检查不合法直接返回明确的错误码而不是带着脏数据往下跑。举个例子技能需要接收“日期”参数。如果你只定义一个 date 字段问题不大但如果用户说“下周三”模型就要算日期还要考虑时区。这时候与其让模型自己算不如在技能里接收一个“自然语言日期描述”用代码里的解析库去转成标准时间。模型只负责抽取文本日期转换交给确定性逻辑准确率会高得多。这其实也是参数设计的核心哲学把不确定的工作尽量往代码侧移动让模型侧只保留它擅长的那部分——语义理解和信息抽取。2.3 技能注册与调用机制不管用哪种框架技能注册的本质都差不多把技能的名称、描述、参数格式、执行函数注册到一个统一清单里。模型在需要时从清单中检索、匹配、调用。常见的注册结构大致是这样# 技能注册表示例 SKILL_REGISTRY { schedule_meeting: { description: 从文本提取会议信息并创建日程邀请, triggers: [会议, 预约, 安排时间, 日程], parameters: { title: {type: string, required: True}, start_time: {type: string, required: True, format: ISO8601}, participants: {type: array, items: {type: string}}, }, handler: schedule_meeting_handler, } }调用机制有两种主流方式。一种是模型直接输出一个结构化的函数调用请求由运行时解析并执行另一种是模型转成一段自然语言指令由一个指令解析器再映射到具体技能。我自己的经验是能走结构化函数调用就走结构化自然语言中间层看着灵活但引入的解析不确定性很大调试起来也费劲。代理的运行循环一般是接收用户请求 - 判断意图 - 选择技能 - 填充参数 - 执行技能 - 处理结果 - 判断是否完成或需要调用其他技能。这个循环看起来简单但每次循环都是让大模型跑一次推理性能开销和失败概率都会累积。所以技能的设计一定要减少循环次数能一次完成的步骤就别拆成三次否则系统又慢又脆。2.4 多技能场景下的优先级与冲突处理当技能数量超过十几个以后会遇到一个新问题代理到底该优先用哪个技能。尤其是多个技能描述相似时模型容易选错。处理这个问题我有几个实操建议技能描述里写清楚“不适用”的场景让模型在不确定的时候宁可选择不调用也不要硬调。相似技能尽量合并成一个内部用分支逻辑处理不同子场景。技能数量不是越多越好太多反而增大了模型的选择难度。在注册清单里按业务优先级排序模型在检索时通常对前面位置的内容注意力更高把高频技能放前面能显著减少错选概率。给技能打过一次调用失败的记录后续可以让代理先尝试备选技能而不是执着于失败的那个。我曾经在一个项目里注册了四十多个技能结果模型经常把“查询订单状态”和“查询物流进度”搞混。后来我把这两个合并成一个“订单查询”技能内部根据参数区分是查订单还是查物流问题立刻少了很多。技能数量不是 KPI能用得准才是。3. 实操从零构建一个“文档问答”技能这一节用真实场景演示怎么搞一个完整技能出来流程可以复用。3.1 场景与方案选型假设需求是给代理加一个“文档问答”的能力用户上传一份 PDF 文档代理需要理解文档内容并回答用户提出的问题。最初的直觉可能是直接把 PDF 内容塞进上下文让大模型回答。但这种方法有几个问题文件可能很大上下文塞不下直接塞入会导致模型回答不准确、引用不明。所以这是典型的技能拆解场景。我的方案是拆成三个子技能。文档解析技能接收 PDF 文件路径返回纯文本内容、分块列表内容检索技能接收查询文本和文档 ID返回相关段落问答生成技能接收问题及相关段落返回最终答案并标注引用页码每个技能各自管理自己的输入输出通过技能调度将它们串联起来。这个方案的好处是每一步都能单独测试哪里出错一目了然而且后续扩展新文档类型比如 Word、Excel不需要改问答逻辑只要改解析技能就行。3.2 文档解析技能的实现文档解析这个技能本质是“把 PDF 转成可检索的纯文本块”。核心代码大致长这样import fitz # PyMuPDF def parse_pdf(file_path: str) - dict: 解析 PDF返回分块文本列表 doc fitz.open(file_path) chunks [] for page_num, page in enumerate(doc): text page.get_text() # 简单的固定长度分块 for i in range(0, len(text), 1000): chunks.append({ page: page_num 1, text: text[i:i 1000] }) return {chunks: chunks, total_pages: len(doc)}注意真实项目中分块策略不会这么简单要根据文档结构来分尽量按标题、段落边界切避免把一个完整句子切碎。分块质量直接影响后续检索效果。我见过很多项目栽在分块上——块切得不好后面的检索和问答全崩。3.3 检索与问答的闭环检索技能最简单的实现是向量检索把每个块向量化再算查询和块之间的相似度取 top-k。但我这里想强调一个细节不要只靠向量检索很多技术文档里关键词匹配也很有价值尤其是一些专有名词和编号。所以我的检索技能通常是“混合检索”同时做向量相似度和 BM25 关键词匹配然后把两边结果加权合并。代码示意def retrieve(query: str, doc_id: str, top_k: int 5) - list: query_vec embed(query) vec_results vector_search(doc_id, query_vec, top_k) bm25_results bm25_search(doc_id, query, top_k) combined merge_by_score(vec_results, bm25_results, weights(0.7, 0.3)) return combined[:top_k]问答生成技能就简单了把检索到的段落和问题拼进一条构造好的 prompt要求模型基于给定段落回答并标注来源页码。这里的 prompt 一定要强调“引用来源”防止模型自由发挥。整个流程跑通了以后再用调度把三个技能串起来。代理判断用户意图先调用解析技能再调检索技能和问答技能。用户看起来就是上传文档、提问、得到带引用的答案体验非常自然。3.4 调试与回归的实践手法技能开发完后调试环节我总习惯保留一份固定的测试集每次改完代码就跑一遍防止回归。测试集包括各类边界情况比如空文档不含目标信息的文档超长文档几百页非文本型 PDF扫描件提问中带错别字或口语化表达跑回归测试时我希望看到技能调用的每个环节都有日志模型选择了哪个技能、输入参数是什么、技能返回了什么、最终输出是什么。只有这样才能快速定位到底哪个环节出了问题。一个我常用的调试技巧是在技能的 handler 里把每次调用的输入、输出、耗时都记录到一个本地文件中。出问题时回看这份调用日志能省下大把排查时间。这个习惯帮我避开了无数次“玄学”问题。4. 常见问题排查与工具选型4.1 模型不调用技能怎么办遇到的最多问题就是模型明明知道有技能但就是不调用直接凭自己的知识回答。这种情况通常有三个原因。第一个原因是技能描述不清晰模型不知道这个技能什么时候用。解决办法是按前面说的场景模板重写描述把触发场景写具体。第二个原因是技能在注册表里被其他技能或内容“淹没”了。如果系统里有大量上下文模型检索到技能的注意力概率会下降。可以尝试把技能列表精简、置顶高频技能。第三个原因比较隐蔽就是模型在之前的对话中已经对用户的问题做了完整的文本回答这时候它认为不需要再调用了。解决办法是调整 system prompt 的约束明确指示“凡是涉及知识库的问题必须调用技能即使你已经知道答案”。4.2 技能执行结果不稳定怎么办技能执行不稳定大概率出在参数或者依赖的外部服务上。先把技能内部所有确定性逻辑测一遍看有没有边界问题再看外部 API 的响应是否有不规范的字段比如字段名大小写、缺失值、超时等。参数方面最常见的问题是模型生成的参数格式和技能声明不一致。比如技能要求 array 类型模型输出了逗号分隔的字符串。解决办法是在技能入口做一次宽容的解析和类型转换不要带着错误格式往下走。外部服务的问题就要靠重试和降级策略了。我习惯给所有外部调用包一层带超时和重试的封装重试两到三次还不行就返回明确的错误让代理知道该换方案而不是一直卡住。4.3 框架选型的个人经验聊到框架我陆陆续续用过 LangChain、LlamaIndex、AutoGPT、CrewAI还有一些自研的轻量封装。个人体验是越重的框架越难排错越轻的框架越可控。LangChain 生态大工具链多但抽象层级也高一出现问题要穿越好几层封装去排查AutoGPT 这种偏演示性质真放到生产环境会痛苦CrewAI 的角色和任务编排逻辑很强适合多角色协作场景但前提是你要有清晰的角色定义。最终我在生产项目里往往是“轻框架 自研技能调度”用 FastAPI 起一个服务把技能注册表和调用循环自己管起来反而最省心。这不是说框架没用而是说在 agent-skills 层面核心难点在于业务逻辑和技能编排而不是框架本身。框架给的是轮子但车能不能跑看你自己的设计。4.4 常见问题速查表现象可能原因处理建议模型完全不调用技能技能描述不清晰 / 技能列表过长重写描述精简列表置顶高频技能调用了技能但参数总错参数约束不够 / 类型定义含糊加强类型和枚举约束给示例值技能运行报错但模型不知道错误信息没有反馈给模型技能返回结构化错误让代理可读效果时好时坏外部服务不稳定 / 上下文被污染加重试降级精简上下文代理绕开技能直接回答系统提示词约束不够强强制“涉及知识库必须调用技能”技能越多越混乱技能职责重叠合并相似技能缩小选择空间这张表是我在多个项目里反复遇到问题的浓缩版每次新项目启动前我都会拿出来对一遍省了很多坑。5. 进阶体验多代理协作与技能组合5.1 多代理场景下的技能分配多代理系统的核心问题是“谁负责什么”。我倾向于让每个代理拥有自己的技能集而不是所有代理共享一个巨大的技能池。比如一个团队里有“数据查询代理”和“报告生成代理”。数据查询代理的技能包括查数据库、调 API、格式化数据报告生成代理的技能包括做图表、写报告段落、汇总分析。两个代理之间通过消息传递数据而不是各自拥有对方领域的技能。这样做的好处是技能域隔离每个代理的技能集小模型选择准确率高。共享技能池听起来灵活实际上会让每个代理的决策空间变得过大很容易选错技能尤其在代理规模大了以后。5.2 技能组合成工作流单个技能是原子操作多个技能按顺序或条件组合就成了一个工作流。工作流可以继续封装成新技能这就是层级化组织。我这边常用的封装粒度是这样最底层是一堆工具函数比如“查询库存”“计算价格”“生成订单号”中层是技能比如“下单流程”组合多个工具高层是业务流程技能比如“商品购买全流程”组合多个中层技能。代理面对用户时只看到最高层的几个技能内部复杂流程完全被封装掉了。这样的好处是模型不需要关注底层细节只需要在最上层做意图判断。业务逻辑的变更是改中层代码不影响模型层。项目迭代起来非常爽改逻辑基本不用重新调 prompt。5.3 安全与边界控制技能越强大安全边界就要越严格。调用外部 API 时敏感操作必须有二次确认技能内部必须做权限检查不能因为模型说了一句“帮我删除所有数据”就真把所有数据删了。我常用的安全策略有四条对执行类技能删除、发送、支付强制加入确认步骤不能让模型一步到位执行。敏感信息不出内网传给外部大模型前先做脱敏处理。技能的执行链路全程留痕方便事后审计。给技能设置运行超时和调用频次限制防止代理在循环里无限调用外部接口既烧钱又打断系统。这些策略听起来很基础但见过太多项目忽略它们。尤其是做自动化流程的时候代理一跑飞API 费用和时间成本都是几千倍增长没有边界控制真会出大事。做 agent-skills 这几年最大感受是真正难的不是让大模型“聪明”而是让整个系统变得可控、可预测、可维护。我自己也犯过“提示词万能论”的错总觉得模型够强就能搞定一切结果一上线就翻车。后来老老实实把任务拆成技能、把边界划清楚、把参数管严格系统的稳定性才真正上来。最后分享一个小技巧每次给代理加新技能不要光看它单独运行有没有效一定要放到完整的对话流里测几轮观察它会不会被错误触发、会不会和其他技能冲突。很多问题只有在完整链路里才会暴露出来。记住代理技能不是越花哨越好而是越精准越好。场景匹配得准、边界划得清、参数管得严这套方法论够你在绝大多数 AI 代理项目里站稳脚跟。