前言

开发完一个自定义 Skill 后,并不意味着工作就结束了。如何确保 Skill 的质量?如何高效地进行测试?如何灰度发布并持续迭代?这些都是 Skill 开发中不可或缺的环节。

测试是质量的保障,发布是价值的传递,管理是持续运营的基础。一个成熟的 Skill 开发流程,应该覆盖从测试到运维的全生命周期。

本文将详细介绍 Skill 的测试方法、发布流程、版本管理策略以及运维监控最佳实践。

一、Skill 测试体系

1.1 测试分层

Skill 的测试可以划分为四个层次:

测试层次 测试对象 测试工具 执行频率
单元测试 单个函数/方法 pytest, unittest 每次提交
集成测试 Skill + Plugin 协作 pytest + httpx 每次提交
端到端测试 完整用户流程 模拟对话 每次发布
性能测试 响应时间/并发 locust, wrk 版本迭代

Skill 测试发布流程示意图

上图展示了从单元测试到灰度发布再到全量上线的完整 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 插件发布界面

上图展示了 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 的测试、发布与管理全流程:

  1. 测试体系:单元测试、集成测试、端到端测试、性能测试的四层测试架构
  2. 测试代码:配置测试、交互流程测试、Plugin 集成测试、参数映射测试
  3. 发布流程:检查清单、语义化版本规范、灰度发布策略
  4. 运维监控:日志收集、调用量分析、告警配置、AB 测试框架
  5. 版本管理:版本号规范、回滚策略

掌握这些测试和运维方法,能够确保 Skill 的质量和稳定性,让开发者的 Skill 持续可靠地为用户提供服务。

一个好的 Skill,不仅要"能工作",更要"持续稳定地工作"。测试和运维不是开发完成后的附加项,而是 Skill 全生命周期的重要组成部分。


如果这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐ 和关注 🎯!你的支持是我持续创作的动力!

相关资源

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐