首页/新闻资讯/正文详情

Agent技能系统设计:从Function Calling到多技能编排的工程实践

发布时间:2026/9/26 14:45:35 来源:云帆数科 栏目:资讯中心
Agent技能系统设计:从Function Calling到多技能编排的工程实践
1. Agent 光有“会说话”不够还得“会干活”这两年做大模型应用一个很明显的感受是模型的能力边界已经不那么让人操心了真正拉开差距的是应用层怎么让模型稳定地“干成事”。你让 GPT 级别的模型写一首诗、总结一篇文章它几乎不会翻车但一旦让它去查库存、调接口、更新数据库、处理多步骤任务问题就全冒出来了——要么调用参数传错要么工具选得不合适要么执行到一半状态就乱了。我做的 agent-skills 这个方向本质就是想解决这件事把“让 Agent 调用工具”这件事从临时拼凑的 function calling升级成一套可注册、可描述、可编排、可回退的技能系统。所谓“技能”不是指模型会什么而是指我们的应用能给模型提供哪些可执行的操作。比如“查天气”“提交订单”“解析PDF”“发送消息”这些都是技能。一个成熟的 Agent 框架应该把这些技能管理起来技能叫什么、需要哪些参数、底层调哪个函数、返回什么格式、什么时候不能用、出错怎么办都应该有清晰定义。这篇东西我会从设计思路讲到落地实现最后把我在开发过程中踩过的坑也一并列出来。内容偏工程向适合已经在做 Agent 应用、或者正准备从零搭一套工具调用体系的同学参考。纯调用 API 的玩具项目能跑通但到了生产环境没有一个清晰的技能层是走不远的。2. 技能层的核心设计先做抽象再谈编排在动手写代码之前我先花了不少时间在设计上。因为我很清楚这类系统的坑通常不在单点功能而在结构设计——一旦抽象没做好后面每加一个技能都要改框架代码谁用谁骂。2.1 技能描述的结构化从一段话到一套元数据早期我见过很多粗糙的实现把工具说明写成一长段自然语言塞进 system prompt。比如“你是一个客服助手当用户需要查询订单时请使用查订单函数参数包括用户ID和订单号。当用户需要退款时使用退款函数……”这种写法的第一个问题是模型需要从大段文字里自己“找到”该用哪个函数既费 token 又容易误判。第二个问题是它没法校验参数模型生成了字段但字段是字符串还是整数必填还是选填完全靠模型猜这在生产环境就是事故源头。我的做法是给每个技能定义一套完整的元数据包含几个关键维度技能名称机器可读的唯一标识比如query_order_status功能描述给 LLM 看的自然语言说明说清楚这个技能在什么场景下用、不要和哪些技能混淆参数 Schema每个字段的名称、类型、是否必填、取值范围、字段说明这个直接用 JSON Schema 定义既能喂给模型做 function calling又能做运行时校验执行函数真正干活的代码入口接收参数字典返回结构化的执行结果错误码与错误处理策略技能返回什么样的错误信号Agent 该怎么响应元数据设计好了大模型调度技能的准确率会提升一大截。因为模型做工具选择本质上是拿用户输入和技能描述做语义匹配描述越精准、边界越清晰匹配的稳定性就越高。2.2 为什么参数声明必须和执行体分离这是我在做了三四个版本之后才想明白的事。早期版本我把参数声明写在使用它的 Agent 内部Agent 增多之后就出现了严重的耦合同一个“查订单”技能在客服 Agent 里定义了一份参数在运营 Agent 里又复制了一份两边字段还对不上。改一个字段所有地方都要同步漏一个就是事故。现在我把参数声明和执行体彻底分离。技能执行体是普通的 Python 函数它只关心“给定参数返回结果”完全不关心模型怎么调用它。参数声明则以 JSON Schema 的形式维护在注册中心作为技能元数据的一部分。这带来的好处很直接技能可以被任意多个 Agent 复用不会出现多份不一致的参数定义参数校验可以在进函数之前做非法参数根本不会污染业务逻辑如果要换底层模型只要模型的 function calling 能力支持 JSON Schema技能层完全不用动分离之后新增一个技能基本就是两步写一个普通函数写一份 JSON Schema然后注册。Agent 侧不用改一行代码。2.3 技能的注册、发现与热更新机制既然技能是独立于 Agent 存在的那这套系统里就必须有一个“技能注册中心”。所有技能启动时注册进来Agent 在需要的时候去“发现”它们。注册中心我建议用简单的字典存储key 是技能名value 是前面说的完整元数据对象。注意这里不要一开始就上 Redis 或者数据库单机内存足够应对绝大多数场景除非你有多个服务实例需要共享技能列表才需要外部存储。热更新是很容易忽略的点。开发过程中你会反复改技能参数、改描述如果每次都要重启服务体验非常痛苦。我的方案是给注册中心加一个文件监听机制技能定义写到独立的目录下每个技能一个文件文件变更后自动重新加载。这样在本地联调时改了技能描述或者执行逻辑刷新即生效不需要重启。生产环境则建议用版本化注册。每次修改技能定义就递增版本号Agent 在编排时可以选择使用指定版本这样即使技能上线后出了问题也能快速回退到旧版本而不需要回滚整个服务。3. 实操落地五步打造一个能用的技能系统光讲设计有点虚我把核心代码贴出来讲一下具体怎么落地。我用的 Python FastAPI 技术栈函数调用走的是 OpenAI 兼容接口但整个思路是通用的换成其他语言、其他模型也成立。3.1 定义技能数据结构干净的数据结构是系统的地基。我定义了一个Skill数据类它把技能的所有信息装在一起from dataclasses import dataclass, field from typing import Callable, Any, Optional dataclass class Skill: name: str description: str parameters_schema: dict execute: Callable[[dict], Any] version: str 1.0.0 tags: list[str] field(default_factorylist) timeout_seconds: Optional[float] None error_messages: dict field(default_factorydict)description是给模型看的所以要写清楚使用场景和边界parameters_schema是给模型和校验器一起用的必须严谨execute是给系统调用的入参是一个字典出参任意可序列化的对象。写到这里有个容易踩的坑execute函数如果直接定义为同步函数在高并发场景下会阻塞事件循环。我建议要么用异步函数要么在注册时用asyncio.to_thread包一层把耗时操作丢到线程池里去跑。3.2 用装饰器完成技能注册有了数据结构下一步是注册。我用装饰器的方式让技能开发变成“写一个函数加一个装饰器完事”。class SkillRegistry: def __init__(self): self._skills {} def register(self, nameNone, description, parameters_schemaNone, version1.0.0): def decorator(func): skill_name name or func.__name__ skill Skill( nameskill_name, descriptiondescription, parameters_schemaparameters_schema or {}, executefunc, versionversion, ) self._skills[skill_name] skill return func return decorator def get(self, skill_name: str) - Skill: return self._skills[skill_name] def list_skill_metadata(self) - list[dict]: return [ { name: s.name, description: s.description, parameters_schema: s.parameters_schema, } for s in self._skills.values() ]注册中心之外还需要一个全局实例registry SkillRegistry()实际定义一个技能时变成这样registry.register( nameweather_query, description查询指定城市当前天气当用户询问天气、温度、降雨情况时使用。 注意只能查询国内主要城市城市名需为标准名称。, parameters_schema{ type: object, properties: { city: {type: string, description: 城市名如北京、上海、广州}, unit: {type: string, enum: [celsius, fahrenheit], default: celsius} }, required: [city] } ) def weather_query(city: str, unit: str celsius): # 真实的查询逻辑 return {city: city, temperature: 26, unit: unit}这中间有个容易被忽略的好处装饰器注册是在模块导入时执行的。这意味着你只需把技能模块 import 一次技能就自动进入注册中心。我习惯在应用启动入口统一 import 技能模块目录保证所有技能都加载到位。3.3 与 LLM 函数调用机制对接技能注册好了怎么让模型知道这些技能、并且正确调用它们这一步就是目前主流大模型都支持的 function calling 机制。需要把注册中心里的技能元数据转换成模型 API 需要的 tools 格式。OpenAI 兼容接口的格式大致是这样的tools [] for meta in registry.list_skill_metadata(): tools.append({ type: function, function: { name: meta[name], description: meta[description], parameters: meta[parameters_schema], } })然后正常发起对话带上 tools 参数。模型返回的内容里如果有tool_calls字段就说明它决定调用某个技能。我负责解析这个字段从注册中心取出技能执行体传入参数执行再把结果作为新的消息返回给模型让模型基于工具的返回结果继续生成最终回复。有一个非常实用的细节模型生成工具参数时可能会多出一些不在 schema 里的字段。这通常是因为描述写得不够清楚或者模型“自由发挥”了。我在执行前会做一层严格校验只取 schema 里声明过的字段过滤掉多余字段再执行函数。这能避免大量因为模型幻觉字段导致的低级错误。3.4 执行流程的完整闭环把上面的内容拼起来就形成一次完整的“用户请求 → 模型决策 → 技能执行 → 结果回填”闭环。核心流程我用一个中间层来管理async def run_agent_with_skills(user_input: str, history: list[dict], registry: SkillRegistry): messages [ {role: system, content: SYSTEM_PROMPT}, *history, {role: user, content: user_input}, ] tools build_tools(registry) # 第一次调用模型可能返回工具调用指令也可能直接回复 response await llm.chat(messagesmessages, toolstools) message response.message messages.append(message) # 如果模型决定调用工具 if message.tool_calls: for tool_call in message.tool_calls: skill registry.get(tool_call.function.name) arguments json.loads(tool_call.function.arguments) result await run_skill(skill, arguments) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) # 第二次调用把工具结果交给模型进行总结 final_response await llm.chat(messagesmessages) return final_response.message.content return message.content这个闭环里我特意在“技能执行”环节加了异步包装和超时控制。真实环境里技能可能是网络请求、数据库操作可能很慢甚至卡死如果没有超时控制整个 Agent 就被卡住了。我的建议是给每个技能设置合理的超时默认 10 秒特别重的操作单独配置超时后返回一个“技能执行超时”的标准错误结果给模型让模型告诉用户稍后重试而不是把超时异常直接抛给用户3.5 给技能加上状态记忆和中间结果“技能”这个设计做到后面我发现它还能解决一个更大的问题多轮任务的状态管理。举个例子用户想订一张机票流程是查航班 → 选座位 → 填乘客信息 → 支付。如果每一次调用都是无状态的那 Agent 必须每次重新追问所有信息体验极其糟糕。我在技能系统里引入了“会话级状态缓存”。每个技能可以声明自己需要哪些状态字段执行完成后可以把结果写入会话缓存下一个技能执行时可以直接从缓存里取。比如查完航班之后航班号、价格、时间都存进缓存选座位的技能就不需要用户重新说一遍航班号。class SkillSession: def __init__(self, session_id: str): self.session_id session_id self.store {} def set(self, key: str, value): self.store[key] value def get(self, key: str, defaultNone): return self.store.get(key, default)这个设计想清楚之后Agent 的多轮对话体验会有质的提升。用户第一轮说“帮我查一下后天北京到上海的高铁”第二轮说“就买第二趟的”系统能自动关联到前一轮的查询结果上而不是把“第二趟”当成一个无法理解的残缺信息。4. 多技能协作时的编排策略单个技能再强也只是零件。Agent 真正发力的时候是多技能按顺序、按条件组合起来完成一个复杂任务。这一层我踩过的坑不少重点说说编排上要做的取舍。4.1 什么时候用顺序执行、什么时候用并行有些任务天然有先后依赖比如“查天气 → 根据天气推荐穿搭”这个必须串行。但有些任务之间毫无依赖比如同时查三个城市的天气或者同时拉取订单信息和用户信息这时就应该并行。在最初版本里我不管什么任务都串行调用结果响应时间慢得离谱。一个查三个城市天气的任务要等模型把三次工具调用串行执行完耗时翻三倍。后来改成并行的方案模型一次返回多个tool_calls时优先并行执行只有技能之间声明了依赖关系时才强制串行对于 OpenAI 兼容接口模型可以在一条回复里返回多个 tool_calls并且这些 tool_calls 之间默认是独立的。我把这些调用交给asyncio.gather并发执行再把结果一次性回填给模型整体耗时从串行的三倍降到了单次调用级别。但要注意并行执行的前提是技能之间有明确的数据隔离。如果两个技能同时操作同一个缓存键或者一个技能依赖另一个技能的输出那千万不要并行。4.2 条件路由让模型自己决定下一跳复杂的任务还有另一个特点下一步做什么取决于上一步的结果。比如用户问“这台笔记本打折吗”Agent 需要先查询商品信息发现是电子产品再查询该类目当前是否有优惠活动但如果用户问的是图书那走的是完全不同的流程。我的方案是引入“决策技能”decision skill。这本质上是一个特殊技能输入是当前任务状态和上下文输出是下一步应该调用哪个技能。用大白话说就是让模型当“路由”根据中间结果判断走向。registry.register( nameroute_to_next_step, description根据当前任务类型和已获取的信息判断下一步调用的技能名称。, parameters_schema{ type: object, properties: { next_skill: {type: string, description: 下一个要执行的技能名称}, reason: {type: string, description: 选择这个技能的原因}, }, required: [next_skill, reason] } ) def route_to_next_step(next_skill: str, reason: str): return {next_skill: next_skill, reason: reason}通过这种方式整个 Agent 的执行过程就变成了一棵动态生成的决策树。模型在每一步都会基于最新的上下文决定下一跳而不是靠预设好的静态 workflow 硬性绑定。这个方案的适应性更强但它对模型能力的要求也更高——如果底层模型比较弱建议还是写死流程图更稳。4.3 一个完整的工单编排示例为了上面这几个策略更好理解我拿“智能工单助手”的场景串一遍。用户提交一条工单“我的云服务器无法远程登录请帮我检查实例运行状态和防火墙配置。”这一步的实际执行过程是这样的模型识别出任务需要多个步骤先调用get_server_status技能传入实例 ID查看实例是否正常运行得到结果后模型根据技能返回的运行状态决定下一步如果实例停止调用start_server技能尝试开启实例如果实例运行中则调用check_firewall_rules技能检查防火墙配置防火墙技能执行后如果发现端口未开放调用add_firewall_rule添加规则最后模型把整个排查过程和结果整理成自然语言回复用户这个过程中每次技能返回的结果都会作为新的消息交给模型模型再决定是继续调用下一个技能还是已经有足够信息可以给出答复。如果中途任何一个技能返回了错误比如实例 ID 不存在模型会基于错误信息决定是重新调用还是向用户索要正确的信息。这正是 Agent 比传统规则引擎强的地方它能根据真实执行结果动态调整下一步动作而不是像流程编排软件那样严格按画好的线走。5. 常见问题与排查技巧实录这部分算是我在开发 agent-skills 过程中真实踩过的坑每个问题背后都有一段“排查两小时原因一句话”的经历。整理成速查表希望能帮你少走弯路。5.1 模型反复调用同一个技能陷入死循环这是 Agent 应用的高频问题。模型的执行逻辑有时候会“钻牛角尖”调了查天气的技能得到结果后它不回复用户而是又调了一次查天气结果还是一样再调一次……形成死循环。处理方法我建议三管齐下第一在 system prompt 里明确告诉模型“当你已经获得执行结果后请直接回答用户不要重复调用工具。”第二在代码里加工具调用次数上限比如单轮最多允许调用 8 次工具超出则强制终止让模型直接基于已有信息回答。第三对于同一个技能相同参数最多执行两次。第二次结果和第一次一致时生成“该操作的执行结果未发生变化请直接回复用户”的消息引导模型跳出循环。这三层机制叠上去之后死循环问题基本绝种了。5.2 技能调用成功了但模型说“我不知道”技能执行完结果明明很完整模型却回复用户“暂时无法获取信息”。这类问题通常在“结果回填”环节。原因多半是工具返回的结果太长、结构太复杂模型在有限的上下文里“看漏”了关键信息。比如返回了一个完整订单对象里面十几个字段模型只看了一眼就选择放弃。我的解法是工具返回给模型之前先做摘要压缩。查询订单状态的技能返回只需要三样东西——订单号、状态、预计送达时间。其他字段要么剔除要么放到一个“附加信息”字段里备用。让模型看到的一定是最精简、最直接的内容模型才知道怎么回答。5.3 参数 Schema 写得太松或太紧参数 Schema 是技能定义里最考验功力的部分。写太松比如字段只写了 type 没有写 description或者枚举值没列全模型就会自己编生成一些超出你预期的值。我有一次写了个枚举参数[low, medium, high]但忘了在 description 里说明“这是优先级”模型直接传了一个 “urgent” 进来校验没拦住执行函数直接崩了。写太紧则会扼杀模型的灵活性。比如城市名我一开始只枚举了北上广深结果用户问杭州模型想调技能也调不了。后来我把城市参数放开成自由文本同时在技能描述里写明“支持国内主要城市”反而效果更好。我的经验是枚举值和格式约束尽可能少用硬限制描述性引导尽可能写详细。留给模型合理的发挥空间同时用运行时校验兜底才是合适的状态。5.4 技能数量多了之后模型选错技能怎么办技能少的时候比如五六个模型准确率接近百分百。技能一旦上了二十个类似的技能描述多了模型就开始犯混了。比如“查天气”和“查空气质量”这两个技能语义极度相似模型经常选错。这一步我用了两种办法一种是给相似技能加上“区分说明”。注册中心支持给技能加别名和禁用条件。比如查空气质量的技能描述里加上这句“此技能只提供空气质量指数和污染等级不提供温度、降水等常规天气信息。查询天气请使用 weather_query。”模型在看到两个相似技能时就能更准确地区分。另一种是引入“技能分组”。在构造 tools 时不把所有技能一股脑全塞给模型而是先根据用户的意图做一次粗分类只把可能相关的几个技能放入候选集合。这样每次模型只需要在五六个候选技能里做选择准确率大幅提升。这个粗分类既可以用一个轻量模型做也可以用规则判断。5.5 每次构造 tools 时性能下降、Token 膨胀技能描述全量塞进 tools这个问题的副作用是 token 开销越来越大。你愿意交电费模型也不一定扛得住长上下文的注意力漂移——大量的和当前任务无关的技能描述会干扰模型对当前场景的判断。除了前面说的技能分组我还会做“技能迭代精简”。每隔一段时间翻一下历史请求看哪些技能从来没被模型选过要么删掉要么合并。坦白讲Agent 的技能和代码一样是有“技术债”的。初期加技能一时爽后期垃圾技能堆多了模型选择准确率和响应速度都会一起下降。技能精简的原则是保留高频使用的合并功能重叠的删除无人调用的。用数据说话不要凭感觉决定一个技能的生死。6. 回顾与个人体会从最初把工具描述堆在 prompt 里的莽撞版本到后来有了注册中心、参数 Schema、会话级状态、动态编排的完整技能层agent-skills 这个项目让我对大模型应用工程化有了很多新的理解。我最大的体会是Agent 的复杂度不在模型而在系统设计。模型的能力在那里能不能稳定发挥完全看应用层怎么引导、怎么约束、怎么编排。技能层就像是给模型搭了一个“受控的工具台”让它能安全、准确地干活。这也解释了为什么同样一个模型有人做出来像玩具有人做出来能上生产——差别不在模型在模型周边的这套机制。最后分享一个开发过程中的小习惯每次你给 Agent 加一个技能都顺手写一个只测这个技能的回归用例。agent-skills 这类系统最大的风险是“改一个技能坏一片编排”。有了回归用例以后加新技能时心里会踏实很多。毕竟上线后最尴尬的事不是模型不会回答问题而是它信心满满地调用了不知什么时候被改坏的技能然后一本正经地输出错误答案。

相关推荐

AI Agent自动剪辑视频实测:OpenMontage本地部署全流程解析
AI Agent自动剪辑视频实测:OpenMontage本地部署全流程解析

1. 先说结论:AI Agent 和你想的可能不太一样 我直接用这句话开头吧: AI Agent 确实能独立做完一条视频,但它不是你想的那种“全自动一键出片” 。 把标题里那个问号拆开看,很多人对 AI Agent 的第一印象是——丢给它几个素材&a… · 2026/9/26 14:45:27

智慧工地源码:物联网+BIM+数字孪生软硬一体实现方案
智慧工地源码:物联网+BIM+数字孪生软硬一体实现方案

1. 这套“智慧工地源码”到底在解决什么真问题?我第一次看到“智慧工地整套源码|物联网 BIM 数字孪生,软硬一体整体解决方案”这个标题时,心里咯噔一下——不是因为技术多高深,而是因为太熟悉了。过去三年&#xff0c… · 2026/9/26 14:45:27

Python校园消费行为分析实战:从一卡通数据到聚类与可视化
Python校园消费行为分析实战:从一卡通数据到聚类与可视化

简介:面向Python毕业设计学生,此项目以校园智能卡消费数据为切入点,完整展示了从数据处理、特征构建到消费行为分析、经济状况评估的路径。压缩包共17个文件,包含6个Python源码(覆盖数据加载、预处理、模型训练、可视化… · 2026/9/26 14:45:27

Claude Code 终端 AI 编程助手全指南:TaoToken 统一 Key 接入与指令全讲解
Claude Code 终端 AI 编程助手全指南:TaoToken 统一 Key 接入与指令全讲解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 17:49:22

RK3568开发笔记:Qt程序报错Failed to move cursor on screen的配置排查与修复
RK3568开发笔记:Qt程序报错Failed to move cursor on screen的配置排查与修复

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 17:49:22

Win7安装UHD630核显驱动的INF修改实战指南
Win7安装UHD630核显驱动的INF修改实战指南

1. 这不是“兼容性问题”,而是Windows 7对九代酷睿核显的系统级封印你手头那台刚装上i5-9400F或i7-9700K的旧主机,显示器黑着,设备管理器里UHD 630显示为“Microsoft基本显示适配器”,右键更新驱动却提示“该硬件没有与之兼容的驱… · 2026/9/26 17:49:22

Docker一键安装包避坑指南:从安装到可用的完整配置与验证
Docker一键安装包避坑指南:从安装到可用的完整配置与验证

简介:这份资源是面向运维工程师、后端开发及需要快速搭建容器环境的用户准备的 Docker 一键安装包,主要解决在 Linux 服务器上手动配置 Docker 依赖繁琐、版本不统一的问题,适合具备基础 Linux 操作能力、希望离线或批量部署 Docker 的技术人… · 2026/9/26 17:49:22

Docker容器AI-CLI配置完整指南:TaoToken统一Key接入与settings.json骨架
Docker容器AI-CLI配置完整指南:TaoToken统一Key接入与settings.json骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 17:49:04

Linux下安装方正小标宋与仿宋_GB2312字体:跨平台兼容与冲突排查指南
Linux下安装方正小标宋与仿宋_GB2312字体:跨平台兼容与冲突排查指南

1. 方正小标宋与仿宋_GB2312到底是两款什么字体先把一个容易混淆的概念说清楚:方正小标宋和仿宋_GB2312不是同一类东西,虽然它们经常在同一个场景里被一起提到。方正小标宋是一款标题用字。它的字形特点是横细竖粗、起笔收笔带有明显的装饰角&#xff0c… · 2026/9/26 17:49:04

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码