
这套“agent-skills”的目录我反复看了很多遍最后确认了一件事市面上讲Agent的文章绝大多数在讲怎么调Prompt、怎么选模型、怎么搭RAG真正把Agent当作一套“有岗位分工、有技能边界、有治理规则”的工程体系来聊的很少。前阵子我带团队做内部客服助手一开始直接拿大模型裸跑业务结果被一堆莫名其妙的问题干懵了。后来把整个系统重构为以技能Skill为核心的组织方式模型还是那个模型效果却完全不一样。这篇就把我在这个过程中的思考、踩坑和最终沉淀下来的方法完整拆开给正在做Agent应用、想摆脱“玩具级Demo”状态的同行一些可直接落地的思路。1. 先想清楚一个问题Agent为什么需要“技能”这个概念1.1 裸用大模型跑业务失败是必然的我先讲几段真实经历。第一次做客服助手的时候我们的方案很“朴素”把公司知识库塞进Prompt再加上一堆“你要遵守以下规则”的指令然后让大模型直接回答客户问题。Demo阶段确实惊艳随便问什么都能答得头头是道。但一放到真实流量上问题全冒出来了。三类问题最有代表性。第一类是“答非所问”。用户问“我的订单为什么还没发货”模型能洋洋洒洒写五百字从物流行业聊到天气影响最后也没有调取任何订单接口。它不是不知道要调接口而是Prompt里虽然写了“你可以调用工具查询订单”但没人告诉它“在什么条件下调用、调完拿返回值做什么”它的能力从来就没有被结构化地组织过。第二类是“做到一半就断”。用户说“帮我取消订单并退款”理想流程是先查询订单状态再判断是否允许取消然后执行取消最后发起退款。模型往往做到第一步或者第二步就停了然后煞有介事地告诉用户“已经处理完毕”。这就是典型的没有流程编排能力模型自由发挥的边界太大了。第三类是“格式彻底崩坏”。有一段时间我们要求模型输出Json它确实输出了Json但字段有时叫“order_id”有时叫“oid”有时干脆在Json外面包一层Markdown代码块。下游解析程序直接炸掉。这些问题的根因不是模型不够聪明而是我们天真地把Agent当成一个“超级大脑”来用什么逻辑都靠它现场推理、现场发挥。人不是这么干活的——一个有经验的老员工手里会有一堆已经内化的SOP、工具、模板遇到什么场景就翻出对应的方法来执行而不是从零开始想“我该怎么处理这个问题”。技能Skill就是这层“内化的SOP工具模板”。它把那些可以提前确定的行为模式从模型身上剥离出来固化成模块让模型只在真正需要决策的地方做决策。1.2 技能化的本质把“熵”收进边界里从工程的角度看大模型的输出天然带有不确定性这是它强大的来源也是它难以直接进生产系统的死穴。技能化做的事情本质上不是抹掉这种不确定性而是把它压缩到尽量小的范围内。我把一个Agent系统的决策分成两类一类是“稳定决策”一类是“随机决策”。稳定决策指的是那些可以被规则、模板、工具封装的东西。比如订单状态的查询逻辑、数据清洗的固定处理流程、发送邮件的调用参数组装。这些事一旦确定下来就永远不应该变也不能让模型每次发挥。随机决策则是指那些真正需要理解语义、判断用户意图、生成自然语言的环节比如“用户这句话是不是在表达不满”“这个售后问题应该归到哪个类别”。技能体系的全部意义就是把稳定决策变成一个个黑盒技能模型只负责在技能之间做随机决策。这样即使模型某次“发挥失常”它最多是选错了技能而不是在执行过程中把流程搞得一塌糊涂。错误范围被收敛了系统才有可能被调试、被度量、被治理。围绕这个目标我们后来的架构从“一个巨大的Prompt”变成了三层结构能力层实际执行动作的代码、API调用、脚本也就是技能的“身体”描述层用自然语言把技能的能力范围、触发条件、参数要求写清楚这是给模型看的“说明书”编排层决定给定一个任务按什么顺序调用哪些技能以及单个技能失败后怎么处理。agent-skills这个词如果要做成一套真正可复用的方法论核心就是把这三层分别做好并且让它们之间的接口足够清晰。我接下来按这个顺序逐一展开。2. 技能的接口设计给模型看的说明书和给人看的文档完全是两回事2.1 一份技能元数据该长什么样先上一个我们实际在用的技能定义示例这是整套体系里最基础也最容易被做砸的一环。技能注册表里每个技能就是这样一个描述块{ name: query_order_status, description: 当用户询问订单物流、发货时间、订单状态、签收情况时使用本技能查询订单实时状态。不要用本技能处理退货退款退货退款请走 return_order 技能。若查询返回的状态为 canceled请直接告知用户订单已取消并询问是否需要重新下单。, input_schema: { type: object, properties: { order_id: { type: string, description: 用户提供的订单号格式通常为 10 位数字或字母组合。若用户未提供请先向用户索要订单号。 }, user_id: { type: string, description: 用户唯一标识用于权限校验从会话上下文中自动获取无需询问用户。, required: true } }, required: [order_id, user_id] }, output_schema: { type: object, properties: { order_status: { type: string, enum: [pending, processing, shipped, delivered, canceled] }, estimated_delivery: { type: string, format: date }, carrier: { type: string }, tracking_number: { type: string } } } }很多人看到这个描述块的第一反应是“这不就是个简化版的OpenAPI规范吗”。对形式上很像但有一个关键差别OpenAPI是给开发者看的技术契约而这份元数据的首要读者是模型。模型不会像人一样去“读文档”它是通过描述里的语义关联来判断“当前这句话跟哪个技能扯得上关系”。所以description字段的写法跟写API文档的逻辑完全不同。人看的文档会写“该接口用于查询订单状态参数order_id为订单编号”甚至默认调用方自己懂业务。给模型看的描述必须回答三个问题什么时候用、什么时候不用、用了之后返回值怎么处理。我在描述里写了“不要用本技能处理退货退款请走return_order技能”这就是在主动给模型画边界。实测下来这种“负向提示”对准确率的影响非常明显。没有这句之前大约15%的退货类问题会误调到查询订单状态加了这个边界说明之后这个比例降到了3%左右。模型本质上是在做语义匹配你给它说得越清楚的边界它误判的空间就越小。2.2 参数设计要压缩自由度接着说参数设计。很多人在设计技能输入参数时习惯照着内部系统接口的字段原样搬。但模型决定参数的过程本身就是一个随机过程参数越开放模型出错的机会越多。我们在input_schema里尽量做三件事。第一能用枚举约束的就用枚举。直接把受限的取值列表写进schema模型在大多数情况下会自动从枚举里选而不是自己编一个。比如订单状态就只有五种值告诉模型可能的状态值它就不会花式发挥。第二把能从上下文里拿的参数标记为自动注入不要让模型去问用户。user_id就是这么处理的会话一开始就通过登录态拿好描述里直接告诉模型“无需询问用户”。否则模型可能在处理到一半时突然停下来问一句“请提供您的用户ID”把整个交互体验搞得极其诡异。第三为每个参数写清楚“若用户未提供应该怎么做”。order_id这个参数我们写了“若用户未提供请先向用户索要”这意味着模型会把“索要订单号”当成一个合法的中间行为而不是卡在那里不知道下一步。这些细节看起来琐碎但本质上是在降低模型做决策的难度。模型不是神给它越清晰的路标它走偏的成本就越高。关于参数的类型我们统一用string和enum为主尽量不直接用自由格式的object嵌套。模型生成嵌套Json的能力虽然不错但只要一个层级错了解析端就会很狼狈。宁可技能内部多做一步解析也不要把复杂的嵌套结构交给模型来编。2.3 输出设计决定了你的Agent是“能用”还是“好用”输出这块踩的坑最多。最初我们的技能返回原始Json让模型自己挑选需要的信息并组织语言。结果就是模型经常“自由发挥”它会把Json里的字段名直接念给用户或者漏掉关键信息甚至凭空编一个不存在的状态。后来我们每个技能改了输出结构分为两段一段是结构化结果供编排逻辑和后续技能使用一段是自然语言摘要供模型直接转述给用户。{ result: { order_status: shipped, estimated_delivery: 2025-03-20, carrier: 顺丰速运, tracking_number: SF1234567890 }, summary: 订单已发货承运方为顺丰速运运单号 SF1234567890预计 3 月 20 日送达。 }summary字段是我们人为拼接的不让模型自己总结。为什么因为给用户的回复句子一旦交给模型自由发挥它就可能在中间插一句“需要我为您查询其他订单吗”或者把“预计送达3月20日”说成“预计3月20号左右”虽然无伤大雅但你永远不知道它下一句会冒出来什么。在To B和严肃业务场景里给用户的最终回复宁可生硬一点也要保证每次说的关键信息一致。模型可以做的事情是在summary基础上做轻度润色比如调整语气、补充礼貌用语但不能改动事实字段。也就是“事实由技能锁定风格由模型发挥”。还有一点要注意错误信息也要结构化。技能执行失败时返回的不是一个简单字符串而是错误码和错误上下文。这个在后面讲编排的时候会具体展开。3. 路由与编排Agent怎么知道什么时候用哪个技能3.1 从“全部塞进Prompt”到“按需加载”技能数量少的时候把所有的技能描述全都塞进系统Prompt里让模型自己挑问题不大。但一旦技能超过二十个这条路就走不通了。一方面Token消耗会变得很夸张几十个技能的完整描述加起来动辄上万Token每次请求都带着这些内容成本直接起飞。另一方面技能描述之间会产生互相干扰。模型看到一堆功能相近的描述选择准确率反而下降。这跟人在面对一份两百个选项的菜单时会犹豫一样选项越多决策质量越差。所以我们做了技能路由。核心逻辑很简单在把Prompt发给模型之前先根据用户输入从技能库里召回一小批最相关的技能描述只把这些描述注入系统Prompt。这个环节相当于给模型划了一个“今天的重点工作范围”。我们用的是一个非常朴素的实现但没有花哨模型也能跑得很好。核心思路是给每个技能维护一组“触发特征”再跟用户输入做相似度匹配from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity import numpy as np class SkillRouter: def __init__(self, skill_registry): self.skill_registry skill_registry self.trigger_texts [] self.skill_names [] for skill in skill_registry: self.skill_names.append(skill[name]) # 触发特征 技能描述 预先提取的典型问法 同义词集合 trigger_text f{skill[description]} { .join(skill[trigger_phrases])} self.trigger_texts.append(trigger_text) self.vectorizer TfidfVectorizer( ngram_range(1, 2), stop_wordszh, max_features5000 ) self.tfidf_matrix self.vectorizer.fit_transform(self.trigger_texts) def route(self, user_input, top_k5): input_vec self.vectorizer.transform([user_input]) scores cosine_similarity(input_vec, self.tfidf_matrix)[0] top_indices np.argsort(scores)[::-1][:top_k] candidates [] for idx in top_indices: if scores[idx] 0.25: # 低于阈值就不召回留给兜底流程 candidates.append({ skill: self.skill_registry[idx], score: float(scores[idx]) }) return candidates初学者可能会问为什么不用大模型直接做路由我们的经验是在技能数量几百以内这个量级用TF-IDF加余弦相似度的字典方法准确率足够延迟接近零成本可以忽略。大模型路由虽然语义理解更强但一次路由就要消耗几千Token而且它自己也有出错的可能。路由环节的错误会传导到后面的所有决策所以在这个点上我们用确定性最强的方式。召回之后这些候选技能描述会被拼进系统Prompt里。注意不是所有召回的技能都要展示只有得分超过阈值的才展示。我们体会下来top_k控制在4到6个比较合适——太少可能遗漏正确技能太多又会让模型重新陷入“选择困难”。3.2 编排像写主流程一样设计技能的调用顺序路由解决的是“第一步调用谁”编排解决的是“接下来调用谁”。这是两个完全不同的问题。我们早期把编排也交给模型自由发挥结果就是流程不可控。后来想明白一个道理绝大多数业务流是固定的没有理由让模型每次重新发明一遍。比如退货流程从发起申请到审核通过再到退款这条链路的每一步都是确定的唯一需要模型判断的是起点和几个条件分支。这种情况下编排逻辑应该写死在代码里模型只负责提供判断依据。我们采用了一种类似“状态机决策节点”的轻量编排模型。每一步是一个节点节点可以是技能调用也可以是条件判断。模型在条件判断节点上做决定但流程走向完全由代码控制。举例来说一个简单的退款流程编排class RefundWorkflow: def __init__(self, skill_runner): self.runner skill_runner def execute(self, session): # 节点1: 查询订单状态 order self.runner.run_skill(session, query_order_status, { order_id: session.context[order_id] }) if order[result][order_status] ! delivered: return {type: reject, reason: 订单未完成配送无法发起退款} # 节点2: 让模型判断是否需要人工审核 need_review self.runner.ask_llm( session, prompt根据订单信息和用户描述判断该退款申请是否需要人工介入。 只有当金额超过500元或商品已使用时才需要人工介入。, output_formatboolean ) if need_review: return {type: manual_review, reason: 系统判定需要人工审核} # 节点3: 执行退款 refund_result self.runner.run_skill(session, execute_refund, { order_id: session.context[order_id], amount: order[result][paid_amount] }) return {type: success, data: refund_result}这里最大的设计决策是**能让代码判断的就不让模型判断只有真正需要语义理解的地方才把模型插进去。**需要模型做判断的地方比如“是否需要人工介入”我们用明确的Prompt限定了判断依据模型只需要输出true或false不需要解释也不需要自己想往左拐还是往右拐。为什么跟模型交互时输出格式越简单越好因为自由文本输出会给下游流程带来不可预料的解析问题。布尔值判断加上枚举输出是我们在生产环境里踩过很多坑之后总结出来的最优解。3.3 失败处理给Agent一条“台阶”而不是一个“悬崖”技能调用一定会失败。网络超时、外部接口报错、参数不合法、权限不足每种失败都应该有对应的处理策略而很多人在设计Agent时根本没有这个环节。结果就是技能一报错Agent整个就卡在那里或者开始胡说八道。我们把技能失败设计成了三级处理机制。第一级是重试。对于超时类错误自动重试一到两次间隔时间递增。幂等性要求较高的操作比如创建订单、扣款这类不能重复执行的我们会在参数里额外注入一个幂等键保证重试不会造成重复操作。第二级是降级。如果某个技能不可用可以换成备选方案。比如主订单查询接口超时走只读缓存查询虽然延迟高一点但至少有数据。专门负责发送验证码的技能挂了就自动切换为一体化推送通道。这个思路和微服务里的熔断降级一模一样只是被应用在了Agent能力层。第三级是让模型带着错误信息做决策。当前的技能返回了业务错误码模型继续执行主流程可能会产生错误结果所以当重试和降级都用不上时我们会把错误码和错误上下文打包成一个结构化上下文让模型判断是切换另一个技能还是直接告诉用户暂时无法处理。这一级的设计非常关键。错误信息必须结构化。比如{ error_code: ORDER_NOT_FOUND, error_context: {order_id: SF1234567890}, suggestion: 请先向用户确认订单号是否正确或引导用户通过其他渠道人工查询 }模型拿到这个结构后能清楚地知道自己面临的状况和可选的处理路径而不是对着一个“系统错误”四个字发呆。这基本上就是把“错误处理”的常规划法平移到了Agent的场景里。4. 把技能当成团队资产来治理仓库结构、版本与回归4.1 技能仓库的目录结构设计当技能数量从几个增长到几十个、上百个时光有技术层设计不够还要有治理层的设计。技能应该像代码一样有仓库、有版本、有评审、有测试。我们目前使用的技能仓库目录结构如下skills/ query_order_status/ SKILL.md # 面向模型的自然语言描述 metadata.yaml # 版本、作者、依赖、权限等元信息 schema.json # input/output 的 JsonSchema 定义 executor.py # 技能的实际执行代码 test_cases.json # 回归测试用例 execute_refund/ SKILL.md metadata.yaml schema.json executor.py test_cases.json ...这里面三个文件值得单独说明。第一个是SKILL.md。它和schema.json里的description有什么区别我们的实践是schema.json里放的是精炼的、直接注入Prompt的描述面向模型SKILL.md是这份技能面向人的完整文档包括设计动机、使用限制、变更记录、典型调用链。SKILL.md是不会被直接喂给模型的它是给团队协作用的。第二个是metadata.yaml。它解决了技能的“户口”问题id: skill.query_order_status name: query_order_status version: 2.3.1 owner: fulfillment-team dependencies: - internal-api: order-service^3.4 permissions: - scope: order.read reason: 需要读取订单状态和物流信息 visibility: public enabled: true权限这块尤其重要。我们在第四版重构时把所有技能的权限单独拎出来管理一个技能能做什么、不能做什么固定写在metadata里由部署时统一注入凭证。这样既方便审计也避免技能代码里硬编码各种密钥。第三个是test_cases.json。这是技能回归测试的核心每个技能都配五到十组“样例输入期望行为”每次修改技能定义或者升级模型版本后都要过一遍回归。[ { id: case_001, input: 我的订单怎么还没发货, expected_skill: query_order_status, expected_params: {order_id: null, user_id: auto-injected}, expect_action: ask for order_id, note: 用户未提供订单号应先索要 }, { id: case_002, input: 查一下订单 SF1234567890 到哪了, expected_skill: query_order_status, expected_params: {order_id: SF1234567890, user_id: auto-injected}, expect_action: execute_skill, note: 订单号直接出现在问句中 } ]4.2 回归测试与“描述漂移”被大多数团队忽略的隐性风险技能治理里最容易忽略的是“描述漂移”问题。模型的版本升级以后对同样一段技能描述的语义理解会发生细微变化。原本在模型A上表现良好的技能描述换到模型B上触发准确率可能掉20个百分点。但我们很少会主动因为模型升级去重新看技能的触发表现这就像一个代码仓库里改了函数签名但因为你没有跑测试函数挂了都没人知道。所以每个技能仓库都配了回归测试。有一项测试专门测“路由准确性”放一组用户的真实表达看期望被召回的技能有没有出现在候选列表里期望参数有没有被正确提取。每次模型版本升级或者技能描述改动后都跑一遍全套回归。我们内部用了一个简单的持续集成脚本def evaluate_skills(skills, test_suite): results [] for skill in skills: for case in test_suite: expected case[expected_skill] routed route(case[input], top_k5) routed_names [r[skill][name] for r in routed] hit expected in routed_names results.append({ case_id: case[id], expected: expected, routed: routed_names, hit: hit, params: extract_params(case[input], routed) }) accuracy sum(r[hit] for r in results) / len(results) print(f路由准确率: {accuracy:.1%})这个脚本的价值在于把技能质量的度量变成一个可量化指标。只要路由准确率低于某个阈值就不允许把技能的改动合并到主干。这听起来很土但确实是最有效、最基本的质量门禁。4.3 一致性的价值为什么说“笨但稳定”胜过“聪明但飘忽”再说一个关于“技能描述需要持续迭代”的体会。我们在技能迭代上踩过一个比较深的坑有一次为了提高某个技能的触发率我们把描述写得特别具体把所有边角情况全部展开。结果加上去之后这个技能在核心场景的准确率反而下降了。原因是描述过于具体之后模型会把“细节匹配”当成“语义匹配”。真实用户表达千变万化描述里写越细模型越倾向于找跟描述字面对得上的表达而忽略语义上相关但表达方式不同的问法。后来我们把描述写成了一个金字塔结构最前面是技能的“核心职责”一句话概括然后是要覆盖的典型场景然后是必须避免的误用场景最后是返回值的处理说明。这个结构的好处是模型就算只理解了第一句也能大致知道技能是干什么的。就像给一个人贴一张标签“退货处理专家——只管退款退货不管物流查询——遇到拿不准的转人工。”模型调用的稳定性靠的是这种既简单又有明确边界的描述。5. 实测里最容易翻车的四个细节以及我的修法5.1 把技能描述写成代码注释模型根本不知道何时调用这是给自己看的思路和给模型看的思路的区别。很多工程师写技能描述时习惯性地写成“查询订单状态接口”这种话这本质上是给维护者看的代码注释不是给模型看的调用说明。模型判断是否调用技能靠的是用户意图和技能描述之间的语义匹配。如果描述里没有“当用户提到X时使用”这类意图触发词模型就只能靠猜。我后来形成的习惯是每个技能描述开头必须有“当用户……时使用本技能”的句式让模型在这个问题上少做思考。对比一下两种写法效果差异非常明显。下面这种写法从12%的误调率降到了4%差查询订单状态接口 好当用户询问订单物流、发货时间、订单状态、签收情况时使用本技能查询订单实时状态。不要用本技能处理退货退款退货退款请走 return_order 技能。若查询返回的状态为 canceled请直接告知用户订单已取消。5.2 强行要求模型输出Json遇到长文本就翻车我们在早期版本里严格要求模型在调用技能前必须输出完整Json美其名曰“结构化决策”。真实情况是一旦模型输出超过几百Token就有概率Json被截断或者在里面混入解释性文字。特别是在技能多了之后模型会把“思考过程”和“输出”混在一起打印出一大段“我要调用query_order_status因为用户看起来在问物流……”然后再跟一个残缺Json。一个非常行之有效的修法拆开思考和动作。模型只输出一个精简的动作表示包含技能名和参数。让模型在说出来之前先“憋住”。这在技术上主要是通过在Prompt里硬性规定输出格式且要求“只允许输出标准Json不要包含任何多余解释”并且用解析器做容错。这个方法当然不完美但对结构化稳定性有明确的提升。如果你依然觉得模型输出的Json不干净最保险的办法是在技能执行层再做一次参数校验和类型强制转换做不到就明确报错不要让脏数据流向下游。5.3 技能膨胀以后互相之间“抢活干”技能到了几十个量级之后会出现一个之前完全没预料到的问题技能之间“抢活”。用户说“我要退掉这个订单”同时命中了query_order_status、return_order、refund_status三个技能的相似度Top5模型在三个技能之间犹豫不定最终选择了影响面最大的那个结果把流程搞乱。这个问题要从两个方向治。第一是强化技能边界描述在返回相关的技能里明确写“不要用本技能处理退货退款”。第二是在路由时引入“排他规则”当return_order被召回并得分超过阈值时强制把query_order_status从候选列表里排掉。这类硬规则在关键时刻比模型判断可靠得多。这个“排他规则”的做法也可以理解成在技能库里定义了“技能间的协议”。技能不是孤岛它们之间互相竞争本身就意味着业务边界需要更清晰地划分。5.4 技能执行失败后Agent直接卡死无论技能写得再好外部依赖一定会出问题。我们曾经历过一次线上事故负责发送短信验证码的技能因为第三方服务商限流持续失败Agent在技能调用上不断重试导致用户等待时间超过30秒体验非常差。复盘后发现三层问题第一技能控制的重试次数没有上限失败后一次接一次地打第三方接口第二没有降级方案验证码发不了就一直卡着第三也没有报错给上层编排逻辑让Agent有机会用别的方式处理。事后我们给所有技能统一加上了外部调用包装器import time from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type retry( stopstop_after_attempt(3), waitwait_exponential(multiplier1, min1, max10), retryretry_if_exception_type(TimeoutError), before_sleeplambda retry_state: log(f技能调用超时第 {retry_state.attempt_number} 次重试) ) def call_external_with_retry(func, *args, request_idNone, **kwargs): return func(*args, **kwargs)除了重试次数限制另一个重要习惯是对可重复执行和不可重复执行的操作分开处理。查询类操作随便重试但涉及创建、退款、修改的操作必须带幂等键否则一次超时重试就可能产生两次扣款。幂等键我们统一用request_id加操作类型生成在调用外部接口时作为参数传进去这样即使网络超时重试外部系统也能识别出这是同一笔请求而不会重复执行。这些细节做出来后技能失败导致的线上事故几乎绝迹。为了更直观地说明改造前后的差距我整理了一份我们在客服助手项目上的实测对比数据基于约2000条真实用户请求抽样指标裸用大模型无技能体系有技能体系之后任务完整执行率38.2%86.4%技能调用准确率71.5%94.8%平均处理时长4.2秒2.1秒Json/结构化输出合法率68.0%99.1%因外部接口失败导致的卡死率12.0%0.3%这个改善不是来自模型升级而是来自把模型能做好的事和模型做不好的事分离开来。6. 从单个Agent到一群Agent技能体系的演进边界6.1 技能是Agent的“能力单元”Agent是技能的“执行岗位”当技能体系稳定之后再往上一层就是多Agent的编排。这时我们会发现当初把技能单独抽出来的设计是值得的。因为在一个多Agent系统里技能是复用的最小单元而Agent更像是技能的执行岗位一个Agent挂载一组技能扮演一种角色。做一个简单的比喻。技能相当于一个公司的“职能模块”财务能力、客服能力、运维能力。Agent相当于具体的“岗位”客服Agent、质检Agent、运营Agent。你也可以让一个Agent同时干好几个岗位的事但技能模块本身是可以被多个Agent复用的。比如我们的订单查询技能既被“客服Agent”用来回答用户物流咨询也被“运营Agent”用来做仓储物流分析。两个Agent看到的技能是同一个但是调用场景和历史上下文不同。如果技能不是作为一个独立模块存在而是直接塞进某个Agent的Prompt里这种复用根本不可能实现。所以在技能设计时我建议大家从一开始就要考虑未来复用的可能性。描述要尽量去掉对特定Agent的依赖不要写“你是客服助手你应该……”而是写“当用户询问物流信息时……”这样通用的触发条件。6.2 技能体系的演进方向从“能力模块化”到“标准化协议”随着技能库越来越大两个团队维护的技能可能互相需要调用共享技能库的治理问题就会浮出水面。我们目前正在做的事情是参考MCP这类工具协议把技能的“描述层”和“执行层”进一步解耦。描述层完全标准化成统一的数据结构执行层则通过进程内调用、本地HTTP服务、远程服务三种方式暴露给运行时。这个思路相当于给技能定义了一套通讯协议不同技能之间可以互相发现、协商、调用。到这一步技能体系就已经从一个“工程优化手段”变成了“Agent应用的操作系统”。未来内部任何一个新场景都可以像搭积木一样从能力仓库里挑选技能来组装。当然我并不是建议每个团队一上来就奔着这个目标去。技能体系最大的优点是渐进式引入哪怕你现在只是一个有两三个技能的Demo只要把“描述层、能力层、编排层”这三层想清楚了后续的扩展只是规模问题不是方向问题。我自己的经验是先把一个垂直场景做扎实把三五个技能打磨到调用准确率95%以上再横向复制到其他场景。技能描述每一次迭代投入的时间都会在后续复用中加倍赚回来。Agent这块的玩法变化很快但技能化的思路应该会持续很长时间。如果你也在折腾Agent建议从最小的一个业务动作开始先别急着上大而全的编排平台用一套目录结构把一两个技能管起来跑通路由和失败处理再慢慢把更多能力和流程装进去。做好这一层你会发现Agent真正开始像一个可靠交付工作的系统而不是一个彩排完美的Demo。