首页 / 资讯中心 / 文章详情

多平台 Agent Skills 实战:技能化改造与成本性能调优

多平台 Agent Skills 实战:技能化改造与成本性能调优 ★ FEATURED ARTICLE
做多平台Agent这一年多我踩过的坑比写过的代码还多。先说结论Agent Skills 这层抽象不是把工具函数换个名字而是把机器人会干什么这件事拆成可以独立开发、独立测试、独立上线的能力单元。如果你现在还在给每个平台各写一套机器人逻辑或者把所有提示词塞进一个超级System Prompt里那这篇文章值得看完。我这次把闪学it-Agent Skills的完整实战过程整理出来包括技能接口怎么定义、平台适配层怎么搭、客服助手同时跑在Web和IM和App上是怎么调度的以及跑起来之后成本性能怎么控。适合已经做过单平台对话机器人、想往多平台Agent方向深入的开发者也适合产品负责人判断技能化改造到底值不值得做。1. 先把概念对齐Agent Skills 到底在解决什么问题1.1 技能不是会调函数的机器人很多人第一次看到Skill这个词觉得这就是给Agent多加几个函数调用。我在实际项目里试过之后可以负责任地说这个理解会把你坑得很惨。普通的工具函数Tool解决的是一步动作查天气、算价格、发消息输入几个参数返回一个结果完事。但真实业务里的任务绝大多数是多步流程用户说要退货你需要先确认订单状态、再核对退货原因、然后判断是否符合政策、最后才生成退货单并通知仓库。这个流程里每一步都可能需要调不同系统、问用户不同问题、在不同环节做不同的兜底。Agent Skill 就是一个能承载这种多步流程的最小能力单元。它内部不仅包含调用哪些工具还包含这个技能自己的提示词、参数约定、状态流转、权限范围甚至它自己专属的记忆空间。你可以把它理解成一个由Agent执行的微应用——对外暴露清晰的接口对内做完整的业务闭环。我见过最典型的问题场景是这样的某团队做了一个客服机器人最初只在Web网页上用。后来业务要扩展到IM软件和App他们直接把原先的代码复制到新的服务里把消息接口改一改就上线。头两个月还行到第三个月需求变了——退货规则要调整于是Web端改了逻辑IM端忘了同步App端只改了一半。用户在不同的入口问同一个问题得到三个答案。这不是技术问题这是架构问题。1.2 我为什么从提示词堆叠切到技能化在我自己的项目里早期也是一个System Prompt 一堆Tool搞定一切。刚开始还觉得挺灵活但是当技能数量超过十个之后问题开始集中爆发一是提示词互相污染。每个技能的描述、few-shot示例全堆在一起模型经常把A技能的风格带到B技能里或者被冗长的System Prompt分散注意力该调工具的时候不调。二是排查问题像大海捞针。一次对话里模型到底走了哪条路径、用了哪个工具、在哪一步答错的没有任何边界可以隔离日志打出来全是黑盒。三是多平台改造寸步难行。每个平台的节奏、时限、消息格式都不一样但如果核心逻辑不拆出来任何适配改造都会影响全部平台。后来我给某公司的模拟项目X做改造把十来个功能全部重构成技能模块之后情况立刻变了。每个技能有独立的版本号、独立的测试用例、独立的上线开关模型只需要根据技能清单Skill Manifest决定什么时候调用哪个技能而不是在一个巨型Prompt里猜来猜去。这个改造带来的收益比我预期的还要大。1.3 技能化之后的三类直接收益第一是复用性。同一套技能Web端能用、IM机器人能用、App里的助手能用未来接语音助手也只是多写一个适配器的事。平台的差异被隔离在最外层的适配层业务逻辑不再为平台分裂。第二是可测试性。技能是粒度适中的模块我可以在Mock掉大模型的情况下对单个技能做单元测试给它构造好的参数验证它每一步状态流转是否符合预期。这在巨型提示词架构下几乎不可能做到因为你根本不知道模型会被哪句话带偏。第三是隔离性。一个技能崩溃了只影响它自己不会拖垮整个对话流程一个技能需要升级也不需要把整个机器人重新发布一遍。线上出问题时能快速定位到底哪个技能出了问题还能单独把它灰度掉。2. 技能模块的接口设计核心逻辑只写一次2.1 技能声明表让调度器一看就知道这技能能干嘛如果说技能是微应用那技能声明Skill Manifest就是它的产品说明书。这一步是整个技能化改造的地基设计不好后面全乱。我的做法是给每个技能定义一个JSON元数据包含四个核心部分这个技能是干什么的、需要什么参数、依赖哪些权限、返回什么结构。调度器通过这个清单来决定何时路由到这个技能而不是靠硬编码的匹配规则。{ name: order.status, description: 查询订单状态并处理用户的催发货、改地址等后续诉求, version: 1.3.0, parameters: { type: object, properties: { order_id: {type: string, description: 用户提供的订单编号}, action: { type: string, enum: [query, remind_ship, change_address], description: 用户本次想执行的动作 } }, required: [order_id] }, requires: [identity.profile, order.read, notify.send], returns: { type: object, properties: { status: {type: string}, estimated_ship_date: {type: string} } } }这里有几个细节我建议你抄作业description一定要写清楚什么情况下不该用这个技能。模型做路由时靠的就是这段描述做语义匹配。如果你只写处理订单它会把所有跟订单沾边的问题都丢进来导致技能被滥用。我踩过这个坑后来把负面说明加进去如果用户只是询问商品信息不应使用此技能应使用product.info路由准确率立刻上了一个台阶。requires字段是权限边界。每个技能声明自己需要哪些资源调度器执行前会检查当前上下文中是否已授权这些资源。这比在技能内部自行去查数据安全得多后面我会在踩坑部分再细说。2.2 技能的抽象基类代码层面的统一约束有了声明还得有代码层面的统一约束。我在Python项目里定义了一个抽象基类所有技能都继承它。核心是两件事一个方法是把技能参数Schema暴露出来另一个方法是执行主体逻辑。另外预留了两个钩子方法分别在技能进入和退出时触发用来做上下文初始化和清理。# skill_base.py from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Any, Dict dataclass class SkillContext: user_id: str session_id: str platform: str raw_message: Dict[str, Any] extra: Dict[str, Any] class BaseSkill(ABC): name: str description: str version: str 1.0 abstractmethod def parameters_schema(self) - Dict[str, Any]: 返回JSON Schema格式的参数声明 abstractmethod async def execute(self, params: Dict[str, Any], ctx: SkillContext) - Dict[str, Any]: 执行技能主逻辑返回结构化的结果 async def on_enter(self, ctx: SkillContext, params: Dict[str, Any]) - None: 技能启动钩子可以在这里加载技能专属状态 async def on_exit(self, ctx: SkillContext, result: Dict[str, Any]) - None: 技能退出钩子可以在这里做资源清理和审计日志为什么一定要强制execute返回结构化结果而不是自然语言文本因为我希望技能层完全不知道话术这回事。举个例子同一个查询订单状态的结果Web端可以渲染成带样式卡片IM端需要压成纯文本语音助手需要只提取关键短语再复述。如果技能层直接输出您的订单已发货预计明天到达这种话术不同平台就没法做差异化渲染了。正确的做法是技能返回{status: shipped, estimated_arrival: 2025-06-02}话术交给适配层去组织。2.3 多轮状态技能自己的状态与会话状态必须分开多轮对话状态是Agent项目里最容易出错的地方技能化之后尤其要分清楚两类状态会话级状态用户在这个会话里走到哪一步了、身份是谁和技能级状态这个技能内部流程跑到哪个环节了、已经收集了什么信息。我一开始犯的错就是把它们混在一起结果A技能执行到一半用户突然问了一个B技能的问题路由器切走之后A技能的中间状态就散架了等用户再切回来还得从头开始。现在的做法是给每个会话维护一个状态栈一个技能进入时压栈退出时弹栈。每个栈帧只保存该技能自己的参数和中间结果互相之间不共享可变状态。代码层面用KV存储来持久化key是session_id : stack_depthvalue是序列化的栈帧。async def push_skill_state(db, session_id, skill_name, state): key f{session_id}:{skill_name}:state await db.set(key, json.dumps(state), ttl3600) async def pop_skill_state(db, session_id, skill_name): key f{session_id}:{skill_name}:state raw await db.get(key) if raw: await db.delete(key) return json.loads(raw) if raw else {}这里有个经验状态TTL一定不能太长。我一开始设了24小时结果发现用户隔几个小时回来随口问一句上次那个退货呢系统还真能接上。表面看着很智能但代价是状态存储越积越多而且跨天恢复对话时用户往往已经忘了上下文强行续上反而显得诡异。后来统一改成1小时TTL超过时间就让用户重新描述体验反而更干净。2.4 一个完整技能的拆解示例拿订单查询与催发货这个技能举例。它的执行流程是这样的校验参数确认拿到的order_id格式合法。调用订单系统读取订单状态。如果订单已发货返回物流信息和预计到达时间。如果订单未发货根据action参数决定是直接返回承诺发货日期还是记录一条催发货工单并通知仓库。返回结构化结果由适配层决定怎么措辞。这个拆解的价值在于每一步都可以独立验证。第2步的接口超时了我知道是上游系统的问题第4步的工单没创建成功我知道是通知服务的问题。而如果不做技能化这些逻辑混在聊天机器人的回复生成过程里出了问题你只能靠猜。3. 多平台接入的正确姿势适配层只做翻译不做决策3.1 适配层的职责边界很多人在做多平台接入时习惯在平台回调里直接写业务逻辑收到消息、解析、调模型、组织回复、发回去。一开始跑得通但当技能数量多起来之后每个平台回调里都堆着一大坨差不多的代码改一处漏三处。正确的做法是引入一个适配层它只做三件事协议转换、身份映射、渲染输出。它不做意图判断、不做业务决策、不调用技能只负责把平台的消息翻译成内部统一的格式然后把内部的处理结果翻译回平台能识别的格式。整个链路是这样三层接入层平台适配器监听WebSocket、Webhook或推送通道负责处理平台的签名、消息格式差异。编排层核心调度维护技能清单、做意图路由、管理会话状态和状态栈。技能层业务执行每个技能独立运行只依赖编排层传入的标准参数和上下文。这个分层的核心原则是责任反向依赖单向。技能层永远不知道消息来自哪个平台编排层也尽量不感知平台特性只有接入层才关心平台细节。这样当你新增第五个平台时需要写的只是一个新的适配器技能全部原样复用。3.2 三类平台的差异到底有多大不同平台之间的差异往往比预想中大得多。我自己维护的Web、IM和App三种入口差异集中在这几个维度维度Web端IM机器人App内助手消息模型WebSocket双向长连接支持流式输出多为HTTP回调需在时限内回复多为长连接或推送形式较自由回复时限无严格限制通常3到5秒内需应答否则触发超时重试无严格限制富文本能力强支持卡片、Markdown、按钮弱通常纯文本或受限格式中支持卡片但样式受限会话标识浏览器生成的session_id平台的conversation/open_idApp端的会话ID身份信息需登录后绑定平台open_id需映射到内部用户已登录直接有用户token这张表的重点不是列差异而是告诉你适配层必须针对这些差异做专门处理而不是写一个通用代码然后祈祷能跑通。最典型的坑就是IM回复时限问题——如果你在IM入口要求模型完成一次完整的多轮推理后再回复大概率会超时。我们在IM上的解法是先应答、后干活收到消息立刻回一个正在处理把真实结果通过异步消息推送给用户或者让用户用一个任务ID去查询结果。这个策略后面踩坑章节还会细说。3.3 用户身份与会话的映射表设计多平台项目里身份映射是刚需。同一个用户在Web端是登录邮箱在IM里是一个open_id在App里又是另一个用户ID。如果不在适配层把身份统一技能层做权限判断时就只能拿到一堆碎片化身份根本没法判断这个人是不是订单的主人。我的做法是维护一张映射表以平台类型 平台用户ID为唯一键映射到内部唯一的用户IDCREATE TABLE user_binding ( platform VARCHAR(32) NOT NULL, platform_uid VARCHAR(128) NOT NULL, user_id VARCHAR(64) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT now(), updated_at TIMESTAMP NOT NULL DEFAULT now(), PRIMARY KEY (platform, platform_uid) );当IM平台来了一条消息适配层先用(im, open_id)查映射表拿到内部user_id后注入SkillContext技能层全程只认这个内部ID。这样无论用户从哪个入口进来看到的都是自己的订单、自己的会员等级、自己的历史会话。还有个细节容易被忽略匿名用户怎么办。不是所有入口都能登录。我们在Web端允许匿名提问这时候映射表里没有记录适配层就给这个浏览器会话生成一个临时的anonymous_xxx内部ID。技能层对匿名ID的权限做降级——可以查公开信息但不能查订单等个人数据。这个降级逻辑必须放在编排层统一做不能依赖某个技能自己去判断否则一定会漏。3.4 出站消息的差异化渲染前面提过技能层返回结构化结果话术由适配层组织。具体做法是每个技能在返回结果里带上一个内部的render_hint比如{type: order_card, data: {...}}然后每个平台的适配器注册自己的渲染器。Web端的渲染器把order_card渲染成HTML卡片IM端只有纯文本就压成单号/状态/预计时间几行文字App端则转成原生卡片组件。这整套机制的核心思想是内容决定结构平台决定样式。实际开发时我建议把渲染器也做成可配置的因为平台的富文本能力是会变的。IM平台今天只支持纯文本过半年可能开放了卡片接口那你只需在IM适配器里新增一个渲染器不需要动技能层一行代码。4. 实战上线一个客服助手同时在Web、IM、App上跑4.1 目标与技能清单这次实战的背景是给某电商业务搭一个客服助手要求覆盖三个入口Web在线客服、IM公众号机器人、App内的智能助手。业务方给的KPI很朴素能处理60%以上的常见咨询能完成退货流程引导能转人工时干脆利落。我们规划了六个技能彼此之间边界清晰greeting.entry开场白与路由引导判断用户想干嘛。product.info商品信息查询价格、参数、库存。order.status订单状态查询与催发货。order.return退货流程引导这是一个多步状态机技能。human.handoff转人工带上当前会话摘要。faq.answer常见问题兜底覆盖退换货政策、配送范围等高频问题。这里的关键点不是技能多而是技能边界划分合理。product.info和order.status很容易被模型混淆所以两个技能的description都写清楚了负面边界human.handoff是最高优先级兜底一旦用户明确表达不满或要求人工直接踢出路由竞争不让模型犹豫。4.2 意图路由先规则后模型多平台实战里意图路由我强烈建议先规则、后模型。具体来说给定一条入站消息路由流程分三步硬规则检查是否有敏感词、是否命中人工关键词、是否命中快捷菜单按钮的事件编码。这些不需要模型参与规则匹配最快最稳。轻量模型分类把消息文本和技能描述一起拿去做语义相似度匹配选出Top 2候选技能。路由仲裁如果Top 1置信度远高于Top 2直接采用如果两者接近向用户回一条您是想查订单还是查商品的消歧问题根据用户回答再路由。async def route(ctx: SkillContext) - RouteResult: text ctx.raw_message.get(text, ) rule_hit match_hard_rules(text) if rule_hit: return RouteResult(skillrule_hit.skill, confidence1.0) candidates await semantic_match_skills(text, top_k2) if candidates[0].score - candidates[1].score 0.15: return RouteResult(skillcandidates[0].skill, confidencecandidates[0].score) return RouteResult(skillrouting.disambiguate, candidatescandidates)为什么不全部交给大模型路由两个原因。第一是成本每个请求都调一次大模型做路由日活上来之后这笔费用很可观第二是稳定性规则是确定性的模型是非确定性的能用规则锁死的场景绝不让模型决策。我实测下来硬规则能拦住大约30%的流量剩下的交给语义匹配整体路由准确率能到92%以上。4.3 多轮流程的状态机设计退货引导是典型的多轮状态机技能。用户不可能一句话把订单号、退货原因、期望处理方式全说清所以技能内部分了几个状态每一轮根据当前状态决定问什么、收什么、下一步去哪。状态触发条件动作下一状态初始用户提出退货询问订单号收集订单号收集订单号收到合法订单号查询订单询问退货原因收集原因收集原因收到原因判断是否符合退货政策审核结果审核中政策判断完成生成退货单通知仓库完成完成生成成功返回退货单号和后续指引结束这个状态机的实现我建议用显式的状态表而不是让模型自由发挥。模型只负责从用户话里提取结构化信息并填充状态字段状态流转是确定性的代码逻辑。这样最稳——用户说我不想要了模型提取出退货原因七天无理由但下一跳去哪、要不要走人工审核由代码决定不给模型发挥空间。多平台上的状态机有个额外挑战用户在Web端走到收集原因一步中途切到IM继续问状态必须还能接上。所以状态机每一步产生的中间状态都会实时写入会话状态存储就是前面说的KV存栈帧并且绑定的key同时包含会话ID和用户ID。这样换平台也能续上——当然这有个前提用户的两个平台入口必须映射到同一个内部user_id。4.4 灰度开关与平台级熔断多平台同时上线最怕的就是新技能在某个平台翻车。我们给每个技能都加了平台级别的配置文件用配置中心统一管理{ skill: order.return, platforms: { web: {enabled: true, version: 2.1.0, rollout: 0.1}, im: {enabled: false}, app: {enabled: false} } }这个配置的含义是order.return这个技能只在Web端开启且只对10%的流量生效。等Web端观察几天错误率、完成率、转人工率都达标之后再把IM和App灰度打开。rollout字段是流量比例实现时在路由阶段取用户ID的哈希值落在范围内才放行新技能。平台级熔断是灰度之外的保命手段。我们在编排层监控每个技能在每个平台的错误率和P95延迟如果连续5分钟错误率超过10%自动把该技能在该平台降级为不可用路由直接跳到faq.answer或者转人工避免用户卡在一个坏掉的流程里。这个机制上线之后至少避免了两次事故级别的线上故障。5. 跑起来之后成本、队列、监控一个都不能少5.1 把每次会话的模型成本算清楚多平台Agent上线后第一个让你肉疼的一定是模型成本。我建议在项目初期就建立一个成本估算模型别等到月底账单出来再吃惊。以一次完整的退货引导会话为例大致需要调用模型4次路由消歧1次、信息提取1次、政策判断1次、结果组织1次。假设平均每次调用输入6000 token、输出800 token。按目前商用大模型API的中位价位估算输入约0.5元/万token输出约2元/万token那么单次会话成本大约是输入成本 4 × 6000 × 0.5 / 10000 1.2元 输出成本 4 × 800 × 2 / 10000 0.64元 单次会话总成本 ≈ 1.84元如果日活1000个完整会话一个月就是5.5万元。算完这笔账你就明白为什么路由阶段要先规则后模型为什么很多常见问题根本不需要调模型了。5.2 缓存与前置拦截是省钱主力我们的经验是至少三成流量可以通过缓存和规则直接消化根本不需要进技能编排高频FAQ把退换货政策配送范围这些答案做成规则映射命中关键词直接返回答案不走模型。会话级意图缓存同一个用户在同一个会话里连续问同类问题时首次用模型路由后续直接用缓存的路由结果跳过路由模型调用。常用技能参数缓存订单状态这种有一定时效性的查询可以在技能内部加一层短TTL缓存。短时间内重复查询同一订单直接返回缓存的订单信息减少一次上游系统调用。这些手段看着不起眼但叠加起来对成本的影响是数量级的。我实测过一个客服助手项目加了缓存和规则拦截之后模型调用量下降了35%而且回答延迟也降了——规则路径的P95延迟只有模型路径的十分之一。5.3 异步化与并发控制多平台的消息到达是突发的尤其是IM平台的营销活动期间同一秒可能涌进几百条消息。如果每条消息都同步触发技能编排和模型调用你的后端会瞬间被打爆。我们做了两层处理。第一层是消息先落队列平台适配器收到消息后先快速落库并返回已收到应答然后异步从队列里消费消息、执行路由和技能编排。这样做的好处是把不规则的流量削峰填谷避免瞬时高并发。第二层是技能执行带重试调用模型或者内部接口时如果返回限流或超时不能直接失败要按指数退避重试1秒、2秒、4秒……最多3次。同时给每个技能设置独立的并发信号量比如order.status最多同时20个并发超出排队的就等待。死等也不行等超过10秒就给用户回请稍后再试避免用户无休止地等一个卡死的任务。5.4 监控三件套日志、指标、告警多平台Agent的监控比普通Web服务要复杂因为一次用户请求会跨多个子系统平台回调、适配器、路由、技能、模型调用、内部接口。我们用的监控体系可以总结为三件套一是结构化日志。每次请求生成一个统一的request_id这个ID贯穿适配器、路由、技能、模型调用全程。日志里必须记录哪个平台、哪个技能、模型调用的输入输出token数、耗时、错误码。没有request_id贯穿的日志排查问题基本靠猜。二是业务指标。我们会统计每个技能在每个平台的调用量、成功率、平均耗时、转人工率。这四个指标里最有价值的是转人工率——一个技能如果频繁把用户转人工说明它没真正解决用户问题光看成功率是看不出来的。三是告警阈值。错误率连续5分钟超过10%要告警P95延迟超过5秒要告警单技能单小时调用量突增超过平均值3倍要告警。最后这个突增告警很重要很可能是恶意刷接口或者某个平台导流异常早发现能避免大问题。6. 踩坑实录五类问题排查链路6.1 消息格式的变形记第一次把客服助手从Web端扩展到IM端时我们踩了一个特别低级的坑IM平台会把消息里的Markdown符号直接当纯文本展示。Web端回复您的订单已发货在IM里用户看到的就是星号原样出现非常不专业。排查链路是这样的先确认不可能是技能层的锅因为技能层返回的是结构化数据话术是适配层组装的。再看日志IM适配器确实拿到了结构化结果问题出在渲染器——IM适配器复用了Web端的Markdown渲染逻辑。改法很简单给IM适配器写一个纯文本渲染器把粗体、链接、列表符号全部剥掉但保留换行和关键信息的强调顺序。这个坑的核心启示是每个平台的富文本能力不一样渲染器必须一平台一实现不能图省事复用。后来我们还发现不同IM平台之间对消息长度的限制也不同有的平台单条消息有字数上限长回复必须拆成多条依次发送顺序还不能乱。为了这个我特意给IM适配器加了个消息分片器超过长度的回复按语义断点切分成多条消息逐条推送。6.2 IM平台的三秒回调限制IM接入最经典的问题是回复时限。我们接第一个IM平台时按照Web端的思路走完整链路结果发现平台回调总是超时重试用户会收到重复消息而且体验很差。排查过程是这样的先看适配器日志确认消息确实收到了。再看编排层发现技能执行耗时2秒多加上模型调用又2秒多整个链路下来接近5秒远超平台的3秒回调时限。单纯优化代码能压到3秒以内但不稳定高峰期模型一慢就超时。最终方案是先应答、后干活适配器收到回调后第一时间向平台返回一个成功的空应答同时把消息投递到任务队列编排层处理完后通过平台的消息主动推送接口把结果发给用户如果平台不支持主动推送就在应答里返回一个任务ID并提供查询接口让用户主动拉取结果。这个方案把同步阻塞变成异步通知彻底绕开了回调时限限制。代价是实现复杂度上去了但这是多平台Agent绕不开的坎。6.3 多轮状态在语音入口的丢失第三个坑来自一次语音助手的接入实验。用户在Web端跟助手聊退货聊到一半转到语音助手继续结果语音助手完全接不上之前的上下文从零开始问订单号。定位后发现是身份映射的问题。Web端用户已登录内部user_id是稳定可查的但语音助手没有用户登录态每次进入都生成一个新的匿名ID。两个入口的会话状态存在不同的user_id下自然接不上。解决办法是给语音助手增加一个绑定码机制用户在Web端生成一个短期绑定码在语音助手端输入后适配层通过绑定码把语音入口的临时ID关联到Web端已登录的user_id上之后状态就能续上。这个机制做起来不复杂但很能体现适配层只做身份映射、不掺和业务逻辑的价值——身份统一了状态续接就是水到渠成的事。6.4 技能越权共享上下文惹的祸有一次线上事故让我记忆深刻用户问订单详情order.status技能正常返回了订单信息过了一会儿用户又问了商品推荐结果product.info技能的回复里居然带出了用户上一个订单的商品名。虽然不涉及支付信息但也足够让人后背发凉。根因在于我用了全局共享的上下文对象所有技能都能读到之前技能写入的中间结果。order.status把订单详情写进了共享上下文product.info在生成推荐时把这些残留数据当成了参考信息。修复分两步第一步严格隔离技能上下文——每个技能执行时拿到的是从会话状态栈里反序列化出来的、只属于自己栈帧的数据禁止读取其他技能的栈帧第二步做权限审计——product.info的权限声明里根本没有order.read编排层在路由到该技能时会再次确认该技能用到的所有数据都在其requires声明范围内越权的数据直接丢弃。这个事故让我确定了两个原则技能的数据访问必须遵循最小权限跨技能的上下文传递必须显式声明不允许隐式共享。6.5 技能升级时的平台兼容问题最后一个坑出现在版本升级上。我把order.return技能从v1.2升到v1.3新增了一个参数return_method退货方式寄回/上门取件。结果上线后IM端用户反映流程走到最后没有选择退货方式的步骤。排查后发现原因很清晰IM适配器的渲染器版本没有同步升级。技能v1.3返回的结构里多了一个字段Web端渲染器识别并展示了但IM渲染器还是老版本遇到不认识的字段就忽略了也没报错。所以我在版本管理上加了条铁律技能合同Skill Contract的变更必须显式管理。技能返回的JSON结构如果新增字段属于非破坏性变更渲染器可以忽略但如果删字段、改字段语义属于破坏性变更必须所有平台的渲染器同步发版后才能放量。每次技能发布前自动化测试会跑一遍所有平台的渲染器冒烟用例确保没有平台被落在旧版本上。多平台Agent做完之后回头看真正让我觉得这个方向值得投入的不是某个单点的效果优化而是整套体系开始进入可维护的节奏新技能像插U盘一样插进去就能用新平台加个适配器就能接线上出了故障能快速定位是某一层某一技能的问题。如果你也在做类似的事我建议别一上来就铺开全平台先在一个平台上把技能接口和状态机这套骨架跑稳再往外扩。骨架对了后面全是增量工作。另外有个小技巧可以分享技能清单文件一定要做成配置下发而不是硬编码这样才能在多个平台之间做灵活的灰度组合不然每加一个平台你就要重新发布一遍服务。
阅读完成 · 觉得有帮助?
咨询建站