鸿蒙智能体开发实战:27.Skill 测试、发布与管理
·
前言
开发完一个自定义 Skill 后,并不意味着工作就结束了。如何确保 Skill 的质量?如何高效地进行测试?如何灰度发布并持续迭代?这些都是 Skill 开发中不可或缺的环节。
测试是质量的保障,发布是价值的传递,管理是持续运营的基础。一个成熟的 Skill 开发流程,应该覆盖从测试到运维的全生命周期。
本文将详细介绍 Skill 的测试方法、发布流程、版本管理策略以及运维监控最佳实践。
一、Skill 测试体系
1.1 测试分层
Skill 的测试可以划分为四个层次:
| 测试层次 | 测试对象 | 测试工具 | 执行频率 |
|---|---|---|---|
| 单元测试 | 单个函数/方法 | pytest, unittest | 每次提交 |
| 集成测试 | Skill + Plugin 协作 | pytest + httpx | 每次提交 |
| 端到端测试 | 完整用户流程 | 模拟对话 | 每次发布 |
| 性能测试 | 响应时间/并发 | locust, wrk | 版本迭代 |

上图展示了从单元测试到灰度发布再到全量上线的完整 Skill 发布流水线
1.2 单元测试
测试配置加载
import pytest
from unittest.mock import Mock, patch, AsyncMock
import json
from typing import Dict, Any
class TestSkillConfig:
"""测试 Skill 配置加载"""
def test_skill_config_loading(self):
"""测试 Skill 配置能正确加载"""
config = {
"skillName": "test_skill",
"version": "1.0.0",
"trigger": {
"type": "keyword",
"keywords": ["测试", "test"]
}
}
assert config["skillName"] == "test_skill"
assert config["version"] == "1.0.0"
assert "测试" in config["trigger"]["keywords"]
def test_skill_config_validation(self):
"""测试配置验证"""
invalid_config = {
"skillName": "", # 名称为空
"version": "1.0.0"
}
with pytest.raises(ValueError, match="skillName is required"):
validate_skill_config(invalid_config)
def test_trigger_condition_matching(self):
"""测试触发条件匹配"""
trigger = KeywordTrigger(["壁纸", "生成壁纸"])
assert trigger.match("帮我生成壁纸") == True
assert trigger.match("今天的天气") == False
assert trigger.match("壁纸") == True
assert trigger.match("") == False
def validate_skill_config(config: Dict[str, Any]) -> bool:
"""验证 Skill 配置"""
if not config.get("skillName"):
raise ValueError("skillName is required")
if not config.get("version"):
raise ValueError("version is required")
if "trigger" not in config:
raise ValueError("trigger configuration is required")
return True
class KeywordTrigger:
"""关键词触发器"""
def __init__(self, keywords: list):
self.keywords = keywords
def match(self, user_input: str) -> bool:
"""检查用户输入是否匹配关键词"""
if not user_input:
return False
return any(kw in user_input for kw in self.keywords)
测试交互流程
class TestSkillInteraction:
"""测试 Skill 交互流程"""
@pytest.mark.asyncio
async def test_wallpaper_skill_conversation(self):
"""测试壁纸 Skill 的多轮对话"""
skill = WallpaperSkill()
# 第一轮:询问风格
response = await skill.handle_conversation("帮我生成壁纸")
assert "风格" in response.get("reply", "")
assert "options" in response
assert len(response["options"]) > 0
# 第二轮:选择风格,询问主题
response = await skill.handle_conversation("极简主义")
assert "主题" in response.get("reply", "")
# 第三轮:选择主题,询问场景
response = await skill.handle_conversation("远山轮廓")
assert "场景" in response.get("reply", "") or "尺寸" in response.get("reply", "")
# 第四轮:确认并执行
response = await skill.handle_conversation("竖屏锁屏")
assert response.get("execute") == True or "生成" in response.get("reply", "")
class WallpaperSkill:
"""测试用壁纸 Skill"""
def __init__(self):
self.state = "initial"
self.collected_params = {}
async def handle_conversation(self, user_input: str) -> Dict[str, Any]:
"""处理对话"""
if self.state == "initial":
self.state = "asking_style"
return {
"reply": "你好!想要什么风格的壁纸呢?",
"options": ["极简主义", "水墨国风", "梦幻星空"]
}
elif self.state == "asking_style":
self.collected_params["style"] = user_input
self.state = "asking_theme"
return {
"reply": f"好的,{user_input}风格!想要什么主题呢?",
"options": ["远山轮廓", "星辰大海", "花鸟鱼虫"]
}
elif self.state == "asking_theme":
self.collected_params["theme"] = user_input
self.state = "asking_scene"
return {
"reply": "最后,请选择使用场景:",
"options": ["竖屏锁屏", "竖屏主屏", "横屏锁屏"]
}
elif self.state == "asking_scene":
self.collected_params["scene"] = user_input
self.state = "completed"
return {
"reply": "正在生成壁纸...",
"execute": True,
"params": self.collected_params
}
return {"reply": "请告诉我你的需求"}
1.3 集成测试
测试 Plugin 调用
class TestPluginIntegration:
"""测试 Skill 与 Plugin 的集成"""
@pytest.mark.asyncio
async def test_image_generation_plugin_call(self):
"""测试图片生成 Plugin 调用"""
# 使用 Mock 模拟 Plugin
mock_plugin = AsyncMock()
mock_plugin.execute.return_value = {
"data": [{"url": "https://example.com/image.jpg"}]
}
plugin_manager = PluginManager()
plugin_manager.register_plugin("image_gen", mock_plugin)
# 调用 Plugin
result = await plugin_manager.call_plugin(
"image_gen", "generate_image",
{"prompt": "星空壁纸", "size": "1080x2400"}
)
# 验证结果
assert result["data"][0]["url"] == "https://example.com/image.jpg"
mock_plugin.execute.assert_called_once_with(
"generate_image",
{"prompt": "星空壁纸", "size": "1080x2400"}
)
@pytest.mark.asyncio
async def test_plugin_timeout_handling(self):
"""测试 Plugin 超时处理"""
mock_plugin = AsyncMock()
mock_plugin.execute.side_effect = asyncio.TimeoutError()
plugin_manager = PluginManager()
plugin_manager.register_plugin("slow_plugin", mock_plugin)
import asyncio
try:
result = await asyncio.wait_for(
plugin_manager.call_plugin("slow_plugin", "slow_tool", {}),
timeout=1.0
)
except asyncio.TimeoutError:
result = {"error": "timeout", "message": "调用超时"}
assert "error" in result
assert result["error"] == "timeout"
测试参数映射
class TestParameterMapping:
"""测试参数映射"""
def test_parameter_mapping_direct(self):
"""测试直接映射"""
result = ParameterMapper.map_parameters(
{"style": "极简主义"},
[{"source": "style", "target": "params.style", "transform": "direct"}]
)
assert result == {"params.style": "极简主义"}
def test_parameter_mapping_stringify(self):
"""测试字符串转换"""
result = ParameterMapper.map_parameters(
{"count": 5},
[{"source": "count", "target": "params.count", "transform": "stringify"}]
)
assert result == {"params.count": "5"}
assert isinstance(result["params.count"], str)
def test_parameter_mapping_nested(self):
"""测试嵌套路径获取"""
result = ParameterMapper.map_parameters(
{"user": {"preferences": {"style": "水墨"}}},
[{"source": "user.preferences.style", "target": "style", "transform": "direct"}]
)
assert result == {"style": "水墨"}
def test_parameter_mapping_missing(self):
"""测试缺失参数的处理"""
result = ParameterMapper.map_parameters(
{},
[{"source": "nonexistent", "target": "target", "transform": "direct"}]
)
assert result == {} # 缺失的参数不会出现在结果中
1.4 端到端测试
class TestEndToEnd:
"""
端到端测试
模拟完整用户对话流程
"""
@pytest.mark.asyncio
async def test_full_wallpaper_generation_flow(self):
"""
测试完整壁纸生成流程
流程:欢迎 → 选择风格 → 选择主题 → 选择场景 → 生成完成
"""
skill = WallpaperSkill()
# 模拟对话
dialogue = [
("你好", "welcome"),
("极简主义", "style_selected"),
("远山轮廓", "theme_selected"),
("竖屏锁屏", "scene_selected"),
]
for user_input, expected_state in dialogue:
response = await skill.handle_conversation(user_input)
assert response is not None
print(f"用户: {user_input} -> 智能体: {response.get('reply', '')[:50]}...")
print("端到端测试通过!")
@pytest.mark.asyncio
async def test_edge_cases(self):
"""测试边界情况"""
skill = WallpaperSkill()
test_cases = [
# (输入, 预期行为)
("", "友好提示"), # 空输入
("?!@#$", "引导重新输入"), # 无效输入
"你好你好你好你好" * 100, # 超长输入(测试用)
("帮我生成壁纸\n生成壁纸", "去重处理"), # 重复输入
]
for user_input, expected in test_cases[:3]:
response = await skill.handle_conversation(user_input)
assert response is not None
print(f"边界测试: {str(user_input)[:20]}... -> {response.get('reply', '')[:30]}...")
1.5 性能测试
# 性能测试脚本
"""
性能测试脚本
使用 locust 进行负载测试
运行方式:
locust -f performance_test.py --host=http://localhost:8080
"""
from locust import HttpUser, task, between
import json
class SkillUser(HttpUser):
"""模拟 Skill 用户"""
wait_time = between(1, 5) # 用户等待时间
def on_start(self):
"""用户启动时的初始化"""
self.session_id = None
self.conversation_history = []
@task(3)
def generate_wallpaper(self):
"""生成壁纸(高频操作)"""
payload = {
"jsonrpc": "2.0",
"method": "message/stream",
"params": {
"message": {
"role": "user",
"parts": [{
"kind": "text",
"text": "帮我生成一张星空壁纸"
}]
}
}
}
with self.client.post(
"/agent/message",
json=payload,
headers={"Content-Type": "application/json"},
catch_response=True,
name="generate_wallpaper"
) as response:
if response.status_code == 200:
response.success()
else:
response.failure(f"Status: {response.status_code}")
@task(1)
def health_check(self):
"""健康检查"""
self.client.get("/health", name="health_check")
# 性能测试指标
PERFORMANCE_TARGETS = {
"p50_response_time": 2000, # 中位数响应时间 < 2s
"p95_response_time": 5000, # 95分位响应时间 < 5s
"error_rate": 0.01, # 错误率 < 1%
"throughput": 10, # 吞吐量 > 10 req/s
}
二、Skill 发布流程
2.1 发布检查清单
在发布 Skill 前,需要逐项确认以下内容。下图展示了小艺开放平台中云插件的测试与发布界面:

上图展示了云插件的测试发布页面,开发者可以在发布前进行模拟集测试,确认插件功能正常后点击发布

上图展示了 MCP 插件的发布界面,发布后插件将在平台上生效,可供智能体调用
提示:发布前请务必完成模拟集测试,确保插件工具的输入输出符合预期,避免发布后影响智能体的正常运行。
## Skill 发布检查清单
### □ 功能完整性
- [ ] 所有交互路径都已测试通过
- [ ] 边界情况已覆盖(空输入、无效输入等)
- [ ] 异常处理逻辑完善
- [ ] 多轮对话状态流转正确
### □ 集成测试
- [ ] 所有绑定的 Plugin 调用正常
- [ ] 参数映射正确
- [ ] 超时和降级策略生效
- [ ] Plugin 返回结果处理正确
### □ 配置检查
- [ ] 触发条件配置合理(不误触、不漏触)
- [ ] 参数定义完整(类型、描述、是否必填)
- [ ] 输出格式正确(文本/卡片)
- [ ] 版本号已更新
### □ 性能检查
- [ ] 响应时间在预期范围内
- [ ] 并发用户数达标
- [ ] 无内存泄漏
- [ ] API 调用次数在预算内
### □ 安全合规
- [ ] 不包含敏感信息(API Key 等)
- [ ] 内容符合平台规范
- [ ] AI 标识已配置
- [ ] 用户数据处理合规
2.2 版本号规范
class SkillVersionManager:
"""
Skill 版本管理器
遵循语义化版本规范 (Semantic Versioning)
MAJOR.MINOR.PATCH
"""
def __init__(self, version: str = "1.0.0"):
self.major, self.minor, self.patch = map(int, version.split("."))
def bump_major(self):
"""主版本号递增(不兼容的 API 修改)"""
self.major += 1
self.minor = 0
self.patch = 0
return str(self)
def bump_minor(self):
"""次版本号递增(向下兼容的功能新增)"""
self.minor += 1
self.patch = 0
return str(self)
def bump_patch(self):
"""修订号递增(向下兼容的问题修正)"""
self.patch += 1
return str(self)
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}"
def get_release_notes(self, version: str) -> str:
"""获取版本发布说明模板"""
notes = {
"major": "重大更新,包含不兼容的 API 变更",
"minor": "新增功能,向下兼容",
"patch": "问题修复和性能优化",
}
parts = version.split(".")
if len(parts) == 3:
old_version = SkillVersionManager(version)
if old_version.major != self.major:
return notes["major"]
elif old_version.minor != self.minor:
return notes["minor"]
else:
return notes["patch"]
return "版本更新"
# 使用示例
version_mgr = SkillVersionManager("1.0.0")
print(f"当前版本: {version_mgr}") # 1.0.0
print(f"补丁版本: {version_mgr.bump_patch()}") # 1.0.1
print(f"功能版本: {version_mgr.bump_minor()}") # 1.1.0
print(f"主版本: {version_mgr.bump_major()}") # 2.0.0
2.3 灰度发布策略
import random
from typing import Dict, Any, List
class CanaryReleaseManager:
"""
灰度发布管理器
支持灰度比例控制、用户分组、A/B 测试
"""
def __init__(self, skill_name: str):
self.skill_name = skill_name
self.release_configs: Dict[str, Dict[str, Any]] = {}
def configure_canary(
self,
new_version: str,
canary_percentage: float = 0.1,
target_users: List[str] = None,
target_conditions: Dict[str, Any] = None
):
"""
配置灰度发布
Args:
new_version: 新版本号
canary_percentage: 灰度比例 (0-1)
target_users: 指定的测试用户列表
target_conditions: 条件匹配规则
"""
self.release_configs[new_version] = {
"percentage": canary_percentage,
"target_users": target_users or [],
"target_conditions": target_conditions or {},
"metrics": {
"total_requests": 0,
"error_count": 0,
"avg_response_time": 0.0
}
}
print(f"灰度发布配置完成: {new_version} @ {canary_percentage*100:.0f}%")
def should_use_canary(self, user_id: str, version: str) -> bool:
"""
判断用户是否应使用灰度版本
Args:
user_id: 用户 ID
version: 版本号
Returns:
是否使用灰度版本
"""
config = self.release_configs.get(version)
if not config:
return False
# 优先使用白名单用户
if user_id in config["target_users"]:
return True
# 按比例灰度
user_hash = hash(f"{user_id}:{self.skill_name}")
return (user_hash % 100) / 100.0 < config["percentage"]
def record_metrics(
self,
version: str,
response_time: float,
is_error: bool
):
"""记录灰度版本指标"""
config = self.release_configs.get(version)
if config:
config["metrics"]["total_requests"] += 1
if is_error:
config["metrics"]["error_count"] += 1
# 更新平均响应时间
n = config["metrics"]["total_requests"]
old_avg = config["metrics"]["avg_response_time"]
config["metrics"]["avg_response_time"] = old_avg + (
response_time - old_avg
) / n
def get_canary_report(self, version: str) -> Dict[str, Any]:
"""获取灰度报告"""
config = self.release_configs.get(version)
if not config:
return {"error": f"Version {version} not found"}
metrics = config["metrics"]
total = metrics["total_requests"]
error_rate = metrics["error_count"] / total if total > 0 else 0
return {
"version": version,
"total_requests": total,
"error_rate": f"{error_rate:.2%}",
"avg_response_time": f"{metrics['avg_response_time']:.2f}s",
"can_promote": total > 100 and error_rate < 0.01,
"recommendation": "建议全量发布" if total > 100 and error_rate < 0.01
else "继续观察"
}
# 使用示例
canary_mgr = CanaryReleaseManager("wallpaper_generator")
canary_mgr.configure_canary("2.0.0", canary_percentage=0.2)
# 判断用户是否使用灰度版本
user_id = "user_12345"
if canary_mgr.should_use_canary(user_id, "2.0.0"):
print(f"用户 {user_id} 使用 V2.0.0(灰度版本)")
else:
print(f"用户 {user_id} 使用 V1.0.0(稳定版本)")
三、Skill 运维监控
3.1 日志收集
import logging
import json
from datetime import datetime
from typing import Dict, Any, Optional
class SkillMonitor:
"""
Skill 监控器
实时监控 Skill 的运行状态和性能指标
"""
def __init__(self, skill_name: str):
self.skill_name = skill_name
self.logger = logging.getLogger(f"skill_monitor.{skill_name}")
self.metrics = {
"activation_count": 0,
"completion_count": 0,
"failure_count": 0,
"total_response_time": 0.0,
"plugin_call_count": 0,
}
def record_activation(self, trigger_type: str):
"""记录 Skill 激活"""
self.metrics["activation_count"] += 1
self.logger.info(
json.dumps({
"event": "skill_activated",
"skill": self.skill_name,
"trigger_type": trigger_type,
"timestamp": datetime.now().isoformat()
}, ensure_ascii=False)
)
def record_completion(
self,
response_time: float,
params: Optional[Dict] = None
):
"""记录 Skill 执行完成"""
self.metrics["completion_count"] += 1
self.metrics["total_response_time"] += response_time
self.logger.info(
json.dumps({
"event": "skill_completed",
"skill": self.skill_name,
"response_time": f"{response_time:.3f}s",
"params": params,
"timestamp": datetime.now().isoformat()
}, ensure_ascii=False)
)
def record_failure(self, error: str, context: Optional[Dict] = None):
"""记录 Skill 执行失败"""
self.metrics["failure_count"] += 1
self.logger.error(
json.dumps({
"event": "skill_failed",
"skill": self.skill_name,
"error": error,
"context": context,
"timestamp": datetime.now().isoformat()
}, ensure_ascii=False)
)
def get_health_report(self) -> Dict[str, Any]:
"""获取健康报告"""
total = self.metrics["activation_count"]
failures = self.metrics["failure_count"]
return {
"skill_name": self.skill_name,
"total_activations": total,
"success_rate": f"{(1 - failures/max(total, 1)):.2%}",
"avg_response_time": (
f"{self.metrics['total_response_time'] / max(self.metrics['completion_count'], 1):.3f}s"
),
"plugin_calls": self.metrics["plugin_call_count"],
"status": "healthy" if failures / max(total, 1) < 0.05 else "degraded"
}
3.2 调用量分析
class SkillAnalytics:
"""
Skill 数据分析
分析 Skill 的使用情况和用户行为
"""
def __init__(self):
self.usage_records: list = []
def record_usage(
self,
skill_name: str,
user_id: str,
action: str,
duration: float,
success: bool
):
"""记录使用日志"""
self.usage_records.append({
"skill": skill_name,
"user": user_id,
"action": action,
"duration": duration,
"success": success,
"timestamp": datetime.now().isoformat()
})
def get_skill_stats(self, skill_name: str) -> Dict[str, Any]:
"""获取指定 Skill 的统计信息"""
records = [r for r in self.usage_records if r["skill"] == skill_name]
if not records:
return {"skill": skill_name, "total_uses": 0}
total = len(records)
success_count = sum(1 for r in records if r["success"])
avg_duration = sum(r["duration"] for r in records) / total
return {
"skill": skill_name,
"total_uses": total,
"success_rate": f"{success_count/total:.2%}",
"avg_duration": f"{avg_duration:.3f}s",
"unique_users": len(set(r["user"] for r in records)),
"top_actions": self._get_top_actions(records)
}
def _get_top_actions(
self,
records: list,
top_n: int = 5
) -> list:
"""获取最常用的操作"""
action_counts = {}
for r in records:
action = r["action"]
action_counts[action] = action_counts.get(action, 0) + 1
sorted_actions = sorted(
action_counts.items(),
key=lambda x: x[1],
reverse=True
)
return [
{"action": action, "count": count}
for action, count in sorted_actions[:top_n]
]
3.3 告警配置
class SkillAlertManager:
"""
Skill 告警管理器
当监控指标超过阈值时发送告警
"""
def __init__(self):
self.alert_rules = {
"error_rate_high": {
"metric": "error_rate",
"threshold": 0.05,
"description": "错误率超过 5%"
},
"response_time_slow": {
"metric": "avg_response_time",
"threshold": 5.0,
"description": "平均响应时间超过 5 秒"
},
"activation_drop": {
"metric": "activation_count",
"threshold": 0.5,
"description": "激活量下降 50%"
}
}
self.alert_history: list = []
def check_alerts(self, metrics: Dict[str, Any]) -> list:
"""检查是否需要触发告警"""
triggered = []
for rule_name, rule in self.alert_rules.items():
current_value = metrics.get(rule["metric"], 0)
if current_value > rule["threshold"]:
alert = {
"rule": rule_name,
"description": rule["description"],
"current_value": current_value,
"threshold": rule["threshold"],
"timestamp": datetime.now().isoformat()
}
triggered.append(alert)
self.alert_history.append(alert)
print(f"[告警] {rule['description']} (当前: {current_value})")
return triggered
def get_alert_summary(self) -> Dict[str, Any]:
"""获取告警汇总"""
recent_alerts = [
a for a in self.alert_history[-100:]
]
return {
"total_alerts": len(self.alert_history),
"recent_alerts": len(recent_alerts),
"top_rules": self._get_top_alert_rules()
}
def _get_top_alert_rules(self) -> list:
"""获取最频繁的告警规则"""
rule_counts = {}
for alert in self.alert_history:
rule = alert["rule"]
rule_counts[rule] = rule_counts.get(rule, 0) + 1
return sorted(rule_counts.items(), key=lambda x: x[1], reverse=True)
3.4 AB 测试框架
class ABTestManager:
"""
AB 测试管理器
对 Skill 的不同版本进行对比测试
"""
def __init__(self, experiment_name: str):
self.experiment_name = experiment_name
self.variants: Dict[str, Dict[str, Any]] = {}
self.results: Dict[str, list] = {}
def add_variant(self, name: str, config: Dict[str, Any], traffic: float = 0.5):
"""
添加测试变体
Args:
name: 变体名称
config: 变体配置
traffic: 流量分配比例
"""
self.variants[name] = {
"config": config,
"traffic": traffic
}
self.results[name] = []
def assign_variant(self, user_id: str) -> str:
"""为用户分配测试变体"""
user_hash = hash(f"{user_id}:{self.experiment_name}")
# 根据流量比例分配
total_traffic = sum(v["traffic"] for v in self.variants.values())
normalized_hash = (user_hash % 10000) / 10000.0
cumulative = 0
for name, variant in self.variants.items():
cumulative += variant["traffic"] / total_traffic
if normalized_hash <= cumulative:
return name
return list(self.variants.keys())[-1]
def record_result(self, variant: str, metric: str, value: float):
"""记录测试结果"""
if variant in self.results:
self.results[variant].append({
"metric": metric,
"value": value,
"timestamp": datetime.now().isoformat()
})
def get_analysis(self) -> Dict[str, Any]:
"""获取测试分析报告"""
analysis = {}
for variant, records in self.results.items():
if not records:
continue
metrics = {}
for r in records:
metric = r["metric"]
if metric not in metrics:
metrics[metric] = []
metrics[metric].append(r["value"])
analysis[variant] = {
"record_count": len(records),
"metrics": {
name: {
"avg": sum(values) / len(values),
"min": min(values),
"max": max(values),
"count": len(values)
}
for name, values in metrics.items()
}
}
return analysis
def get_winner(self, primary_metric: str = "conversion") -> str:
"""获取胜出变体"""
best_variant = None
best_value = float("-inf")
for variant, records in self.results.items():
values = [
r["value"] for r in records
if r["metric"] == primary_metric
]
if values:
avg_value = sum(values) / len(values)
if avg_value > best_value:
best_value = avg_value
best_variant = variant
return best_variant
四、版本回滚策略
4.1 自动回滚
class RollbackManager:
"""
发布回滚管理器
当灰度版本出现异常时自动回滚
"""
def __init__(self):
self.version_history: list = []
self.rollback_thresholds = {
"error_rate": 0.05,
"response_time": 10.0,
"activation_drop": 0.3
}
def record_deployment(self, version: str, timestamp: str = None):
"""记录部署"""
self.version_history.append({
"version": version,
"deployed_at": timestamp or datetime.now().isoformat(),
"status": "active"
})
def should_rollback(self, metrics: Dict[str, Any]) -> bool:
"""判断是否需要回滚"""
checks = []
# 检查错误率
error_rate = metrics.get("error_rate", 0)
if error_rate > self.rollback_thresholds["error_rate"]:
checks.append(f"错误率 {error_rate:.2%} 超过阈值 {self.rollback_thresholds['error_rate']:.2%}")
# 检查响应时间
avg_response = metrics.get("avg_response_time", 0)
if avg_response > self.rollback_thresholds["response_time"]:
checks.append(f"响应时间 {avg_response:.2f}s 超过阈值 {self.rollback_thresholds['response_time']}s")
if checks:
print("触发回滚条件:")
for check in checks:
print(f" - {check}")
return True
return False
def get_previous_stable_version(self) -> str:
"""获取上一个稳定版本"""
for record in reversed(self.version_history):
if record.get("status") == "stable":
return record["version"]
return None
五、Skill 上架审核
5.1 审核流程概述
Skill 发布后需要通过平台审核才能正式上架:
# Skill 上架审核流程
1. 提交审核申请 → 填写审核信息
2. 平台初审 → 检查基本配置和合规性
3. 功能测试 → 验证 Skill 交互和 Plugin 调用
4. 安全审计 → 检查数据安全和权限使用
5. 审核结果 → 通过/驳回/需修改
5.2 审核要点
| 审核维度 | 检查内容 | 常见问题 |
|---|---|---|
| 功能完整性 | 所有交互路径可用 | 部分场景未处理 |
| 内容合规 | 无违规内容 | 生成内容未过滤 |
| 权限合理 | 权限申请最小化 | 申请过多敏感权限 |
| 隐私保护 | 用户数据处理规范 | 未明确隐私政策 |
| 性能达标 | 响应时间符合要求 | 超时未处理 |
注意:审核驳回后需根据驳回原因修改并重新提交,建议在提交前进行自查。
六、持续集成与自动化部署
6.1 CI/CD 流程
通过 CI/CD 实现 Skill 的自动化测试和部署:
# .gitlab-ci.yml
stages:
- test
- build
- deploy
skill_test:
stage: test
script:
- pytest tests/ --cov=src --cov-report=html
- python -m pytest tests/integration/ -v
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
skill_deploy:
stage: deploy
script:
- python scripts/deploy_skill.py --env production
only:
- main
when: manual
6.2 自动化测试策略
| 测试类型 | 执行时机 | 工具 | 通过标准 |
|---|---|---|---|
| 单元测试 | 每次 Push | pytest | 覆盖率 >= 80% |
| 集成测试 | PR 合并前 | pytest + httpx | 全部通过 |
| 端到端测试 | 版本发布前 | 自研脚本 | 核心流程通过 |
| 性能测试 | 版本发布前 | locust | P95 < 3s |
总结
本文详细介绍了 Skill 的测试、发布与管理全流程:
- 测试体系:单元测试、集成测试、端到端测试、性能测试的四层测试架构
- 测试代码:配置测试、交互流程测试、Plugin 集成测试、参数映射测试
- 发布流程:检查清单、语义化版本规范、灰度发布策略
- 运维监控:日志收集、调用量分析、告警配置、AB 测试框架
- 版本管理:版本号规范、回滚策略
掌握这些测试和运维方法,能够确保 Skill 的质量和稳定性,让开发者的 Skill 持续可靠地为用户提供服务。
一个好的 Skill,不仅要"能工作",更要"持续稳定地工作"。测试和运维不是开发完成后的附加项,而是 Skill 全生命周期的重要组成部分。
如果这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐ 和关注 🎯!你的支持是我持续创作的动力!
相关资源
更多推荐



所有评论(0)