File size: 5,827 Bytes
8a03d2c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"""模型配置构建器"""

import json
import time
from typing import Any, cast

from src.core import MODELS_CONFIG_FILE
from ..utils.logger import get_logger
from ..utils.string_utils import snake_to_camel

# 初始化日志
logger = get_logger(__name__)


class ModelConfigBuilder:
    """解析模型名称、处理后缀、构建生成配置"""
    
    _cached_map: dict[str, str] | None = None
    _last_load_time: float = 0
    
    def __init__(self) -> None:
        # 只有在启动阶段打印
        from src.utils.logger import get_request_id
        if not get_request_id():
            logger.info("模型配置构建器初始化完成", extra={
                "model_count": len(self._get_model_map())
            })
    
    def _get_model_map(self) -> dict[str, str]:
        # 简单缓存机制,每 60 秒检查一次文件更新
        current_time = time.time()
        if ModelConfigBuilder._cached_map is not None and current_time - ModelConfigBuilder._last_load_time < 60:
            return ModelConfigBuilder._cached_map
            
        try:
            with open(MODELS_CONFIG_FILE, 'r', encoding='utf-8') as f:
                config = json.load(f)
                ModelConfigBuilder._cached_map = cast(dict[str, str], config.get('alias_map', {}))
                ModelConfigBuilder._last_load_time = current_time
                logger.debug("模型配置文件加载成功", extra={
                    "config_file": MODELS_CONFIG_FILE,
                    "alias_count": len(ModelConfigBuilder._cached_map)
                })
        except Exception as e:
            logger.warning(f"模型配置文件加载失败,使用空配置", extra={
                "config_file": MODELS_CONFIG_FILE,
                "error": str(e)
            })
            if ModelConfigBuilder._cached_map is None:
                ModelConfigBuilder._cached_map = {}
        
        return ModelConfigBuilder._cached_map or {}
    
    def parse_model_name(self, model: str) -> str:
        """
        解析模型名称,返回 backend_model
        """
        return self._get_model_map().get(model, model)
    
    def build_generation_config(
        self,
        gen_config: dict[str, Any],
        gemini_payload: dict[str, Any] | None = None,
        **kwargs: Any
    ) -> dict[str, Any]:
        """构建生成配置"""
        # 防止修改原始配置对象
        final_config = gen_config.copy()
        
        # 1. 直接合并用户提供的配置
        if gemini_payload:
            user_gen_config_raw = gemini_payload.get('generationConfig', {}) or gemini_payload.get('generation_config', {})
            if user_gen_config_raw:
                user_gen_config: dict[str, Any] = {}
                # 显式转换为 Dict (如果它是 Pydantic model)
                if hasattr(user_gen_config_raw, 'model_dump'):
                     user_gen_config = user_gen_config_raw.model_dump(exclude_none=True)
                elif isinstance(user_gen_config_raw, dict):
                     user_gen_config = cast(dict[str, Any], user_gen_config_raw)
                
                if user_gen_config:
                    final_config.update(user_gen_config)

        # 1.5 合并 kwargs 中的生成配置参数
        for k, v in kwargs.items():
            # 直接添加所有 kwargs 参数,让转换函数处理驼峰转换
            final_config[k] = v

        # 2. 统一转换为 camelCase (适配 Vcore AI API)
        return self._convert_to_gemini_format(final_config)

    def _convert_to_gemini_format(self, config: dict[str, Any]) -> dict[str, Any]:
        """将 snake_case 配置转换为 camelCase"""
        converted: dict[str, Any] = {}
        for k, v in config.items():
            camel_key = snake_to_camel(k)
            
            # 特殊处理 thinkingConfig 中的 thinkingLevel 值
            if camel_key == "thinkingConfig" and isinstance(v, dict):
                thinking_config: dict[str, Any] = self._camelize_nested(cast(dict[str, Any], v))
                if "thinkingLevel" in thinking_config:
                    # 将小写的 thinking level 转换为大写
                    level = thinking_config["thinkingLevel"]
                    if isinstance(level, str):
                        thinking_config["thinkingLevel"] = level.upper()
                converted[camel_key] = thinking_config
            elif camel_key in {"imageConfig", "speechConfig", "audioTimestamp", "routingConfig"} and isinstance(v, dict):
                converted[camel_key] = self._camelize_nested(cast(dict[str, Any], v))
            elif camel_key == "topK" and isinstance(v, (int, float)):
                # topK 最大值为 63,防止 API 报错
                converted[camel_key] = min(63, int(v))
            else:
                converted[camel_key] = v
                
        return converted

    def _camelize_nested(self, value: Any) -> Any:
        """递归把 Gemini 配置中的 snake_case 转为 camelCase。"""
        if isinstance(value, dict):
            return {snake_to_camel(str(k)): self._camelize_nested(v) for k, v in value.items()}
        if isinstance(value, list):
            return [self._camelize_nested(item) for item in value]
        return value
    
    def build_safety_settings(self) -> list[dict[str, str]]:
        """构建安全设置"""
        return [
            {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
            {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
            {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
            {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
            {"category": "HARM_CATEGORY_CIVIC_INTEGRITY", "threshold": "BLOCK_NONE"}
        ]