尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

AI Agent技能工程化实践:目录规范与渐进式加载指南

AI Agent技能工程化实践:目录规范与渐进式加载指南 1. 项目概述为什么我们需要一份“Agent Skills完全指南”最近在折腾AI Agent项目时我遇到了一个非常具体且普遍的问题随着项目迭代团队里定义的“技能”Skills越来越多从简单的文件读写、网络请求到复杂的业务流程编排技能文件散落在各处。新加入的同事想了解现有能力得翻遍好几个目录想新增一个技能又不知道该怎么组织代码、怎么写文档、如何让它被系统自动发现和加载。更头疼的是在项目启动时如果一次性加载所有技能初始化时间长得让人无法忍受尤其是当一些技能依赖重型外部服务时。我相信这绝不是我们一个团队遇到的困境。这正是“Agent Skills完全指南”要解决的核心问题。它不是一个炫技的概念而是一套扎扎实实的工程实践方案目标是把Agent技能的开发、管理和使用从“手工作坊”模式升级到“标准化流水线”模式。这套方案的核心支柱有两个一是目录规范解决技能“从哪里来、到哪里去、长什么样”的问题确保项目的可维护性和团队协作效率二是渐进式加载解决技能“用的时候再请不用别占着内存”的问题提升系统的启动速度和运行时资源利用率。简单来说就是让Agent技能的开发像搭乐高一样有统一的接口和说明书并且可以按需取用。无论你是在构建一个内部助手、一个复杂的自动化流程引擎还是探索AI应用的新形态一套良好的技能工程体系都是项目能否持续演进的关键。接下来我将结合具体的实践拆解从目录结构设计到动态加载实现的完整链条分享我们踩过的坑和总结出的有效模式。2. 技能目录规范构建可维护的“技能仓库”一个混乱的目录是项目腐化的开始。规范的目录结构不仅是代码的容器更是团队共识的体现。我们的目标是让任何开发者都能在5分钟内找到所有技能并清楚如何新增一个。2.1 核心目录结构设计我们摒弃了按技术类型如utilshandlersservices划分的传统方式而是采用以技能为核心的领域驱动设计。最终的目录结构如下project-root/ ├── agents/ # Agent核心定义与配置 ├── skills/ # 【核心】技能仓库 │ ├── __init__.py │ ├── base.py # 技能基类与抽象 │ ├── registry.py # 技能注册中心 │ ├── core/ # 核心内置技能高可用、轻量 │ │ ├── __init__.py │ │ ├── file_io.py │ │ └── web_search.py │ ├── business/ # 业务领域技能 │ │ ├── __init__.py │ │ ├── order_processing.py │ │ └── data_analysis.py │ └── third_party/ # 第三方或实验性技能 │ ├── __init__.py │ └── experimental_llm_call.py ├── configs/ # 技能及Agent配置文件 ├── tests/ # 技能单元与集成测试 │ └── skills/ └── docs/ # 项目文档 └── skills/ # 技能详细文档设计理由与考量隔离与清晰core/business/third_party/的划分是基于技能的稳定性与归属。核心技能像操作系统内核必须稳定业务技能随需求变化第三方技能则独立管理避免污染核心代码。可发现性所有技能都位于skills/目录下开发者无需猜测技能在哪里。子目录的分类提供了第一层过滤。便于打包与分发整个skills目录可以相对独立地打包成一个技能包方便在不同项目间复用。2.2 SKILL.md 文件规范技能的“身份证”与“说明书”每个技能模块一个.py文件都必须伴随一个同名的SKILL.md文件。这个文件不是可有可无的注释而是技能元数据和用法的标准化描述。它主要服务于两个对象开发者了解如何维护和Agent系统自动发现与描述技能。一个完整的skills/business/order_processing.SKILL.md文件模板如下# Order Processing Skill **标识符**: skill_order_processing **分类**: business **版本**: 1.0.2 **依赖**: requests2.28, pydantic2.0 用于数据验证和API调用 **加载方式**: lazy 标记为惰性加载 **权限**: read_orders, write_orders **触发关键词**: [订单 下单 查询订单 process order] ## 功能描述 该技能用于处理电商平台的订单生命周期包括创建、查询、更新状态及取消订单。它封装了与后端订单服务API的所有交互细节。 ## 接口说明 技能提供一个主要方法 python async def execute(order_action: OrderAction, params: dict) - SkillResponse: - order_action: 枚举类型可选 CREATE, QUERY, UPDATE_STATUS, CANCEL。 - params: 参数字典根据action不同而不同。例如CREATE 需要 items, user_id; QUERY 需要 order_id。 ## 输入/输出示例 **自然语言指令**“帮我查询订单ID为12345的详情。” **Agent解析后调用** json { skill_id: skill_order_processing, action: QUERY, parameters: {order_id: 12345} } **预期输出** json { status: success, data: {order_id: 12345, status: shipped, ...}, message: 订单查询成功。 } ## 配置项 在configs/skills.yaml中可配置 yaml order_processing: api_base_url: https://api.yourcompany.com/v1 timeout_seconds: 30 retry_attempts: 3 ## 错误处理 - OrderNotFoundError: 当查询的订单ID不存在时抛出。 - APINetworkError: 网络异常时抛出会自动根据配置重试。 - 所有错误均会被技能捕获并封装为格式化的SkillResponse返回避免Agent崩溃。 ## 开发与测试说明 1. 本地测试请使用pytest tests/skills/test_order_processing.py。 2. 修改API交互逻辑后务必更新对应的集成测试用例。 3. 本技能依赖环境变量ORDER_API_KEY请在部署时设置。为什么需要如此详细的SKILL.md自动化集成Agent系统在启动时可以通过扫描SKILL.md文件自动获取技能的标识符、分类、依赖、加载方式并完成注册无需硬编码。文档即代码开发者修改技能行为后必须同步更新此文件保证了文档的时效性。触发关键词字段甚至可以直接用于训练Agent的意图识别模型。降低认知成本新成员通过阅读此文件能在几分钟内理解技能的全部上下文包括如何调用、如何配置、会出什么错。实操心得初期我们曾尝试用Python docstring或单独的meta.json来存储元数据但最终选择了SKILL.md。原因是它既能被机器解析通过提取特定标记如**标识符**:又能被人完美阅读实现了文档和配置的一体化。我们写了一个简单的解析器专门从这些Markdown文件中提取YAML Front Matter风格的信息。3. 技能基类与注册中心统一的“插座”标准有了规范的存放位置和说明书接下来要定义技能本身的长相。统一的接口是任何插件化系统的基石。3.1 抽象基类设计在skills/base.py中我们定义所有技能必须遵守的契约。from abc import ABC, abstractmethod from typing import Any, Dict, Optional from pydantic import BaseModel, Field class SkillResponse(BaseModel): 技能执行的标准化返回模型。 success: bool data: Optional[Any] None message: str error_code: Optional[str] None class SkillMetadata(BaseModel): 技能元数据模型对应SKILL.md的头部信息。 id: str name: str category: str general version: str 1.0.0 description: str dependencies: list[str] Field(default_factorylist) load_strategy: str eager # eager | lazy trigger_keywords: list[str] Field(default_factorylist) class BaseSkill(ABC): 技能抽象基类。所有具体技能必须继承于此。 metadata: SkillMetadata def __init__(self, config: Optional[Dict] None): 初始化技能可传入专属配置。 self.config config or {} self._initialized False async def initialize(self): 执行惰性加载的初始化工作如建立连接、加载大模型。 if not self._initialized: await self._setup() self._initialized True async def _setup(self): 供子类覆盖的内部初始化方法。默认空实现。 pass abstractmethod async def execute(self, **kwargs) - SkillResponse: 执行技能的核心方法。必须由子类实现。 pass async def cleanup(self): 清理技能占用的资源如关闭连接。 pass关键设计点解析标准化响应SkillResponse使用Pydantic模型确保所有技能返回的数据结构一致包含成功状态、数据、消息和错误码便于Agent统一处理。元数据绑定每个技能类都有一个metadata属性这个数据在类定义时就被固定下来通常由装饰器或元类注入与SKILL.md内容对应。显式生命周期initialize和cleanup方法提供了明确的资源管理入口。这对于数据库连接、GPU内存管理等场景至关重要。配置注入通过__init__传入配置使得技能的行为可以通过外部文件灵活调整符合十二要素应用原则。3.2 中央注册中心实现注册中心skills/registry.py是连接技能定义和Agent系统的桥梁。它负责发现、管理、提供技能。import importlib import inspect from pathlib import Path from typing import Dict, List, Type import yaml from .base import BaseSkill, SkillMetadata class SkillRegistry: 技能注册中心。单例模式管理所有技能的元数据和实例。 _instance None _skills_meta: Dict[str, SkillMetadata] {} # skill_id - metadata _skill_classes: Dict[str, Type[BaseSkill]] {} # skill_id - class _skill_instances: Dict[str, BaseSkill] {} # skill_id - instance (for eager load) def __new__(cls): if cls._instance is None: cls._instance super().__new__(cls) return cls._instance def discover_skills(self, skills_dir: Path): 自动发现指定目录下的所有技能。 for md_file in skills_dir.rglob(SKILL.md): # 1. 解析SKILL.md提取元数据 meta self._parse_skill_metadata(md_file) # 2. 找到对应的.py文件 py_file md_file.with_suffix(.py) if py_file.exists(): # 3. 动态导入模块 module_path self._file_path_to_module_path(py_file) try: module importlib.import_module(module_path) # 4. 在模块中查找BaseSkill的子类 for name, obj in inspect.getmembers(module): if (inspect.isclass(obj) and issubclass(obj, BaseSkill) and obj ! BaseSkill): self._skill_classes[meta.id] obj obj.metadata meta # 将元数据注入到类中 print(fDiscovered skill: {meta.id} - {obj.__name__}) break except ImportError as e: print(fFailed to import module for {md_file}: {e}) def _parse_skill_metadata(self, md_file: Path) - SkillMetadata: 简化版元数据解析器。实际项目可用更健壮的库。 content md_file.read_text(encodingutf-8) # 这里简单演示解析以**标识符**:开头的行 meta_dict {id: unknown, name: md_file.stem} for line in content.split(\n): if line.startswith(**标识符**:): meta_dict[id] line.split(:, 1)[1].strip().strip() elif line.startswith(**分类**:): meta_dict[category] line.split(:, 1)[1].strip() elif line.startswith(**加载方式**:): meta_dict[load_strategy] line.split(:, 1)[1].strip() return SkillMetadata(**meta_dict) def register_skill(self, skill_class: Type[BaseSkill], metadata: SkillMetadata): 手动注册一个技能类用于编程式注册。 skill_class.metadata metadata self._skill_classes[metadata.id] skill_class self._skills_meta[metadata.id] metadata def get_skill(self, skill_id: str) - BaseSkill: 根据技能ID获取技能实例。这是渐进式加载的核心入口。 # 1. 检查是否已有实例eager加载的 if skill_id in self._skill_instances: return self._skill_instances[skill_id] # 2. 检查是否有对应的类定义 if skill_id not in self._skill_classes: raise KeyError(fSkill {skill_id} not found in registry.) skill_class self._skill_classes[skill_id] metadata skill_class.metadata # 3. 加载配置 config self._load_skill_config(skill_id) # 4. 创建实例 instance skill_class(configconfig) # 5. 如果是惰性加载先不初始化否则立即初始化 if metadata.load_strategy eager: # 注意在异步环境中这里需要异步处理。简化示例用同步。 import asyncio asyncio.run(instance.initialize()) self._skill_instances[skill_id] instance return instance def _load_skill_config(self, skill_id: str) - Dict: 从配置文件如YAML中加载该技能的特定配置。 config_path Path(configs/skills.yaml) if config_path.exists(): with open(config_path, r) as f: all_configs yaml.safe_load(f) or {} # 支持按技能ID或分类查找配置 return all_configs.get(skill_id, {}) return {} # 全局注册中心实例 registry SkillRegistry()注册中心的核心职责自动发现discover_skills方法遍历目录通过SKILL.md和.py文件的配对关系自动将技能类及其元数据加载到内存的字典中。这是“约定优于配置”的体现。生命周期管理get_skill方法是关键。它根据load_strategy决定是返回已初始化的实例eager还是返回一个尚未初始化的实例lazy。对于lazy技能真正的初始化延迟到第一次execute调用时。配置管理集中管理技能的配置实现配置与代码分离。注意事项动态导入importlib在复杂的项目结构中可能会遇到路径问题。确保你的skills目录是一个Python包有__init__.py并且项目根目录在Python路径中。在生产环境中可以考虑在应用启动时一次性执行发现和注册而不是每次调用都动态导入。4. 渐进式加载策略实现“秒启”与资源优化一次性加载所有技能在技能数量多、依赖重的场景下是不可接受的。渐进式加载的核心思想是按需加载用时初始化。4.1 策略设计与实现我们在基类和注册中心已经为渐进式加载打下了基础。关键在于load_strategy元数据字段和get_skill方法的配合。让我们深入实现细节。首先修改BaseSkill的execute方法确保惰性加载的技能在使用前被正确初始化class BaseSkill(ABC): # ... 其他代码同上 ... async def execute(self, **kwargs) - SkillResponse: 执行技能确保惰性加载的技能已初始化。 if not self._initialized: await self.initialize() # 确保先初始化 # 调用子类具体的执行逻辑 return await self._execute(**kwargs) abstractmethod async def _execute(self, **kwargs) - SkillResponse: 子类需要实现的实际执行逻辑。 pass然后我们来看一个具体技能的例子它被标记为lazy加载因为它依赖一个启动缓慢的外部服务# skills/third_party/heavy_ai_service.py import asyncio from skills.base import BaseSkill, SkillMetadata, SkillResponse class HeavyAISkill(BaseSkill): 一个依赖重型AI模型的服务初始化很慢。 metadata SkillMetadata( idskill_heavy_ai, nameHeavy AI Analysis, categorythird_party, load_strategylazy, # 关键标记为惰性加载 description调用外部大语言模型进行深度分析。 ) async def _setup(self): 模拟耗时的初始化过程如加载模型、建立连接。 print(f[HeavyAISkill] 开始初始化模拟耗时操作...) await asyncio.sleep(5) # 模拟5秒的初始化延迟 self._model_connection 模拟的模型连接 print(f[HeavyAISkill] 初始化完成。) async def _execute(self, prompt: str) - SkillResponse: 执行AI分析。 # 假设这里调用真实的模型API analysis_result f分析结果: {prompt.upper()} return SkillResponse( successTrue, data{analysis: analysis_result}, messageAI分析完成 ) async def cleanup(self): 清理资源。 self._model_connection None print(f[HeavyAISkill] 资源已清理。)最后在Agent的核心调度逻辑中我们这样使用技能async def agent_execute_task(task_description: str): Agent执行任务的核心循环。 # 1. 意图识别与技能路由简化 # 假设通过NLU模块解析出需要调用的技能ID和参数 target_skill_id skill_heavy_ai params {prompt: 请分析这份报告的主要风险点} # 2. 从注册中心获取技能实例 # 对于lazy技能这里返回的是未初始化的实例 skill_instance registry.get_skill(target_skill_id) # 3. 执行技能 # 在execute()内部会检查并触发初始化 response await skill_instance.execute(**params) # 4. 处理结果 if response.success: print(f任务成功: {response.message}) print(f结果数据: {response.data}) else: print(f任务失败: {response.message} (错误码: {response.error_code})) # 模拟运行 import asyncio if __name__ __main__: # 启动时发现所有技能只注册元数据和类不初始化 registry.discover_skills(Path(./skills)) print(技能发现完成。系统已就绪耗时0秒。) # 执行一个需要重型AI技能的任务 print(\n--- 开始执行任务 ---) asyncio.run(agent_execute_task(分析报告))运行输出将会是技能发现完成。系统已就绪耗时0秒。 --- 开始执行任务 --- [HeavyAISkill] 开始初始化模拟耗时操作... [HeavyAISkill] 初始化完成。 任务成功: AI分析完成 结果数据: {analysis: 分析结果: 请分析这份报告的主要风险点}效果对比传统方式Eager Load系统启动时所有技能包括HeavyAISkill都进行初始化。假设有10个技能其中3个像这样需要5秒初始化那么用户需要等待至少15秒才能看到系统就绪。渐进式加载Lazy Load系统启动瞬间完成仅注册类信息。只有当用户的任务真正触发到skill_heavy_ai时才会触发那5秒的初始化。用户体验是“秒启”后续功能按需加载。4.2 高级优化预加载与缓存策略单纯的“用时加载”可能会在用户第一次使用关键技能时造成明显的延迟。我们可以引入更精细的策略预测性预加载根据用户历史行为或当前会话上下文预测接下来可能用到的技能在后台线程中提前初始化。async def preload_likely_skills(user_context): likely_skill_ids predict_skills_from_context(user_context) # 预测函数 for skill_id in likely_skill_ids: skill registry.get_skill(skill_id) if skill.metadata.load_strategy lazy: # 在后台任务中初始化不阻塞主线程 asyncio.create_task(skill.initialize())实例缓存与回收对于已经初始化但长时间未使用的技能可以将其cleanup并移出实例缓存释放资源如GPU内存、数据库连接池。class SkillRegistry: def __init__(self): self._lru_cache {} # 使用LRU缓存 self._max_cache_size 20 def get_skill(self, skill_id: str) - BaseSkill: # ... 获取实例逻辑 ... # 将实例放入LRU缓存 self._manage_cache(skill_id, instance) return instance def _manage_cache(self, skill_id, instance): if skill_id not in self._lru_cache: if len(self._lru_cache) self._max_cache_size: # 移除最久未使用的技能实例并清理资源 lru_id, lru_instance self._lru_cache.popitem(lastFalse) asyncio.create_task(lru_instance.cleanup()) self._lru_cache[skill_id] instance else: # 刷新访问时间 self._lru_cache.move_to_end(skill_id)踩坑实录我们曾将“模型文件”这类数百MB的资源放在技能的__init__里加载导致即使标记为lazy导入模块时也会触发加载失去了惰性意义。关键教训必须将重型资源的加载严格放在initialize或_setup方法中确保只有显式调用这些方法时才会触发。5. 工程实践中的集成、测试与部署将技能模块化并动态加载后如何保证整个系统的稳定性和可维护性这离不开严谨的工程实践。5.1 技能集成测试技能的独立性使得针对单个技能的单元测试变得简单。但更重要的是集成测试验证技能在Agent系统中能被正确发现、路由和执行。# tests/integration/test_skill_integration.py import pytest import asyncio from pathlib import Path from skills.registry import registry pytest.fixture(scopemodule, autouseTrue) def setup_registry(): 在整个测试模块开始前初始化注册中心。 registry.discover_skills(Path(./skills)) yield # 测试结束后可以清理 pytest.mark.asyncio async def test_skill_discovery_and_loading(): 测试技能发现与懒加载。 # 1. 确认技能已被发现 assert skill_heavy_ai in registry._skill_classes assert registry._skill_classes[skill_heavy_ai].metadata.load_strategy lazy # 2. 获取实例此时应未初始化 skill registry.get_skill(skill_heavy_ai) assert not skill._initialized # 3. 执行技能应触发初始化 import time start_time time.time() response await skill.execute(prompttest) elapsed time.time() - start_time # 4. 验证初始化发生且执行成功 assert skill._initialized assert elapsed 5 # 验证了初始化耗时 assert response.success is True assert 分析结果 in response.data[analysis] pytest.mark.asyncio async def test_skill_config_loading(): 测试技能配置注入。 # 假设configs/skills.yaml中有配置 skill registry.get_skill(skill_order_processing) # 验证配置被正确加载到实例的config属性中 assert skill.config.get(api_base_url) https://api.yourcompany.com/v15.2 部署与配置管理在Docker或Kubernetes环境中部署时技能工程化带来的好处更加明显。Docker镜像构建# Dockerfile FROM python:3.11-slim WORKDIR /app # 1. 复制依赖文件 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 2. 复制技能代码、配置文件、文档 COPY skills/ ./skills/ COPY configs/ ./configs/ COPY agents/ ./agents/ COPY main.py ./ # 3. 设置环境变量如API密钥 ENV ORDER_API_KEY${ORDER_API_KEY} CMD [python, main.py]配置分离所有技能的可变配置如API端点、密钥、超时时间必须放在configs/目录下并通过环境变量或配置中心注入。绝对不要在技能代码中硬编码。技能包分发可以将skills/core/目录打包成一个独立的Python包如company-agent-core-skills发布到私有PyPI。这样不同的Agent项目可以像安装普通库一样安装和更新核心技能集。5.3 监控与日志在技能基类中加入统一的日志和监控点对于问题排查和性能分析至关重要。class BaseSkill(ABC): async def execute(self, **kwargs) - SkillResponse: skill_id self.metadata.id start_time asyncio.get_event_loop().time() # 结构化日志 logger.info(fSkill [{skill_id}] execution started., extra{params: kwargs}) try: if not self._initialized: logger.debug(fSkill [{skill_id}] initializing lazily...) await self.initialize() # 执行实际逻辑 result await self._execute(**kwargs) execution_time asyncio.get_event_loop().time() - start_time # 记录性能指标 metrics.record_skill_execution_time(skill_id, execution_time) metrics.record_skill_success(skill_id) logger.info(fSkill [{skill_id}] execution succeeded in {execution_time:.2f}s.) return result except Exception as e: execution_time asyncio.get_event_loop().time() - start_time logger.error(fSkill [{skill_id}] execution failed after {execution_time:.2f}s., exc_infoe) metrics.record_skill_failure(skill_id, str(e)) return SkillResponse( successFalse, messagef技能执行失败: {str(e)}, error_codeSKILL_EXECUTION_ERROR )6. 常见问题排查与效能调优指南在实际开发和运维中你肯定会遇到各种问题。以下是我们总结的“避坑”清单和调优建议。6.1 问题排查速查表问题现象可能原因排查步骤与解决方案技能发现失败1.SKILL.md文件命名不规范或缺失。2. 技能类未继承BaseSkill。3. 技能目录不在Python模块搜索路径中。1. 检查skills/目录下每个技能是否都有配对的.py和SKILL.md文件。2. 确认技能类明确定义了metadata并实现了_execute方法。3. 在项目根目录运行Python或确保skills是一个可导入的包有__init__.py。惰性加载未生效1. 技能元数据中load_strategy未设置为lazy。2. 重型资源在__init__中加载而非_setup。3. Agent在启动时意外调用了skill.initialize()。1. 检查SKILL.md中的**加载方式**:字段。2. 将耗时的初始化代码移至_setup方法。3. 审查Agent启动流程避免提前初始化所有技能实例。技能执行报错KeyError1. 技能ID在注册中心不存在。2.SKILL.md中的**标识符**:与代码中metadata.id或注册时使用的ID不匹配。1. 调用registry.discover_skills后打印registry._skill_classes.keys()确认所有ID。2. 确保SKILL.md、技能类metadata、以及Agent路由逻辑中使用的技能ID三者完全一致。配置未注入1.configs/skills.yaml文件不存在或格式错误。2. 技能ID与YAML中的配置键名不匹配。3. 技能类未在__init__中接收config参数。1. 确认配置文件路径正确且是合法的YAML。2. 确保YAML中的键名与技能ID或技能类metadata.id匹配。3. 技能类的__init__方法必须包含config参数并赋值给self.config。性能问题首次调用慢惰性加载的技能首次初始化耗时过长。1. 考虑对关键路径上的技能采用预测性预加载。2. 优化技能的_setup方法如并行初始化、连接池复用。3. 将极度耗时的初始化过程如加载大模型改为独立的服务技能通过轻量级客户端调用。6.2 效能调优实践分级加载策略不要只用eager和lazy两种。可以引入background级别让技能在系统启动后在后台线程中低优先级初始化平衡启动速度和首次使用体验。技能依赖分析在SKILL.md中明确定义技能间的依赖关系。注册中心可以在加载技能A时自动将其依赖的技能B标记为eager或也进行预加载提前初始化避免链式延迟。健康检查与熔断对于依赖外部服务如数据库、API的技能在_setup或execute中加入健康检查。如果连续失败可以将该技能标记为“降级”状态Agent路由时暂时避开它并返回友好的降级响应。技能画像与热度统计记录每个技能的被调用频率和平均执行时间。对于高频技能即使标记为lazy也可以在系统启动后主动预热。对于长期不被使用的技能可以更激进地回收其资源实例。一个真实的调优案例我们有一个“文档向量化”技能依赖一个700MB的模型文件。最初它被标记为lazy但用户首次搜索文档时需等待30秒加载模型体验很差。我们将其调整为background并在系统启动后立即在后台线程开始加载。同时我们在技能元数据中增加了estimated_load_time: 30字段Agent在规划任务时如果知道即将调用此技能可以提前给用户一个“正在加载必要组件”的提示极大改善了感知体验。
返回列表