File size: 3,759 Bytes
8ab53f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
"""
多维度加权决策模型路由器与质量降级漏斗
Author: XiaoZhe (Commercial Contact: janejulius119@gmail.com / WeChat: julius119)
"""

import logging
from dataclasses import dataclass
from typing import Dict, List, Tuple, Optional

logger = logging.getLogger("VerseFlow.Router")

@dataclass
class ModelProfile:
    model_id: str
    display_name: str
    param_count: float
    max_resolution: Tuple[int, int]
    max_duration: float
    fp8_vram_gb: float
    has_native_audio: bool
    identity_consistency: float
    motion_quality: float
    face_quality: float
    text_compliance: float
    audio_sync: float
    speed_score: float

@dataclass
class RoutingRequest:
    scene_type: str
    duration: float
    resolution: Tuple[int, int]
    requires_audio: bool
    requires_text: bool
    identity_critical: bool
    deadline_s: float

class ModelRouter:
    """6 维度加权决策模型路由器"""
    SCENE_WEIGHTS = {
        "product_demo": {"identity_consistency": 0.25, "text_compliance": 0.25, "motion_quality": 0.20, "speed_score": 0.30},
        "talking_head": {"face_quality": 0.40, "identity_consistency": 0.30, "audio_sync": 0.20, "speed_score": 0.10},
        "b_roll": {"speed_score": 0.40, "motion_quality": 0.30, "identity_consistency": 0.30},
        "brand_ad": {"text_compliance": 0.30, "identity_consistency": 0.30, "audio_sync": 0.20, "motion_quality": 0.20}
    }

    def __init__(self, models: Optional[List[ModelProfile]] = None):
        self.models = models or self._get_default_profiles()

    def _get_default_profiles(self) -> List[ModelProfile]:
        return [
            ModelProfile("minimax_h3", "MiniMax H3", 33.0, (2560, 1440), 15.0, 38.0, True, 9.0, 8.5, 9.0, 9.5, 9.5, 3.0),[cite: 1]
            ModelProfile("wan2.2_14b", "Wan2.2 14B", 14.0, (1280, 720), 10.0, 28.0, False, 8.5, 9.0, 8.5, 7.5, 0.0, 4.0),[cite: 1]
            ModelProfile("hunyuan_video_1_5", "Hunyuan 1.5", 8.3, (1280, 720), 12.0, 24.0, False, 8.0, 8.0, 9.5, 7.0, 0.0, 5.0),[cite: 1]
            ModelProfile("ltx_2_3", "LTX 2.3", 22.0, (1920, 1080), 10.0, 16.0, True, 7.0, 8.0, 7.5, 6.0, 8.0, 9.0),[cite: 1]
            ModelProfile("wan2.2_5b", "Wan2.2 5B", 5.0, (854, 480), 5.0, 8.0, False, 6.0, 7.0, 6.5, 6.0, 0.0, 9.5),[cite: 1]
        ]

    def route(self, request: RoutingRequest) -> List[Tuple[str, float, str]]:
        candidates = []
        weights = self.SCENE_WEIGHTS.get(request.scene_type, self.SCENE_WEIGHTS["b_roll"])

        for model in self.models:
            if request.duration > model.max_duration:
                continue
            if request.resolution[0] > model.max_resolution[0] or request.resolution[1] > model.max_resolution[1]:
                continue

            score = 0.0
            for attr, weight in weights.items():
                val = getattr(model, attr, 5.0)
                score += val * weight

            if request.requires_audio and not model.has_native_audio:
                score *= 0.6

            reason = f"场景匹配得分: {score:.2f} | Model={model.display_name}"
            candidates.append((model.model_id, score, reason))

        candidates.sort(key=lambda x: x[1], reverse=True)
        return candidates

class QualityFunnelController:
    """三级质量控制漏斗"""
    @staticmethod
    def get_stage_config(quality_tier: str) -> Dict[str, Any]:
        if quality_tier == "draft":
            return {"model_id": "wan2.2_5b", "resolution": (854, 480), "max_attempts": 1}[cite: 1]
        elif quality_tier == "refine":
            return {"model_id": "auto_route", "resolution": (1280, 720), "max_attempts": 3}[cite: 1]
        else:
            return {"model_id": "minimax_h3", "resolution": (1920, 1080), "max_attempts": 5}[cite: 1]