config_manager.py 9.28 KB
# AIfeng/2025-07-11 13:36:00
"""
豆包ASR配置管理模块
提供配置文件加载、验证、合并和环境变量支持
"""

import json
import os
from pathlib import Path
from typing import Dict, Any, Optional


class ConfigManager:
    """配置管理器"""
    
    DEFAULT_CONFIG = {
        "asr_config": {
            "ws_url": "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel",
            "ws_url_nostream": "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_nostream",
            "resource_id": "volc.bigasr.sauc.duration",
            "resource_id_concurrent": "volc.bigasr.sauc.concurrent",
            "model_name": "bigmodel",
            "enable_punc": True,
            "streaming_mode": True,
            "seg_duration": 200,
            "mp3_seg_size": 1000
        },
        "auth_config": {
            "app_key": "",
            "access_key": ""
        },
        "audio_config": {
            "default_format": "wav",
            "default_rate": 16000,
            "default_bits": 16,
            "default_channel": 1,
            "default_codec": "raw",
            "supported_formats": ["wav", "mp3", "pcm"]
        },
        "connection_config": {
            "max_size": 1000000000,
            "timeout": 30,
            "retry_times": 3,
            "retry_delay": 1
        },
        "logging_config": {
            "enable_debug": False,
            "log_requests": True,
            "log_responses": True
        }
    }
    
    def __init__(self, config_path: Optional[str] = None):
        """
        初始化配置管理器
        
        Args:
            config_path: 配置文件路径
        """
        self.config_path = config_path
        self.config = self.DEFAULT_CONFIG.copy()
        
        if config_path:
            self.load_config(config_path)
        
        # 从环境变量加载配置
        self._load_from_env()
    
    def load_config(self, config_path: str) -> Dict[str, Any]:
        """
        加载配置文件
        
        Args:
            config_path: 配置文件路径
            
        Returns:
            Dict: 配置字典
        """
        try:
            config_file = Path(config_path)
            if not config_file.exists():
                raise FileNotFoundError(f"配置文件不存在: {config_path}")
            
            with open(config_file, 'r', encoding='utf-8') as f:
                file_config = json.load(f)
            
            # 合并配置
            self.config = self._merge_config(self.config, file_config)
            
            # 验证配置
            self._validate_config()
            
            return self.config
        
        except Exception as e:
            raise ValueError(f"加载配置文件失败: {e}")
    
    def _merge_config(self, base_config: Dict[str, Any], new_config: Dict[str, Any]) -> Dict[str, Any]:
        """
        合并配置字典
        
        Args:
            base_config: 基础配置
            new_config: 新配置
            
        Returns:
            Dict: 合并后的配置
        """
        merged = base_config.copy()
        
        for key, value in new_config.items():
            if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
                merged[key] = self._merge_config(merged[key], value)
            else:
                merged[key] = value
        
        return merged
    
    def _load_from_env(self):
        """从环境变量加载配置"""
        # ASR配置
        if os.getenv('DOUBAO_WS_URL'):
            self.config['asr_config']['ws_url'] = os.getenv('DOUBAO_WS_URL')
        
        if os.getenv('DOUBAO_MODEL_NAME'):
            self.config['asr_config']['model_name'] = os.getenv('DOUBAO_MODEL_NAME')
        
        if os.getenv('DOUBAO_SEG_DURATION'):
            try:
                self.config['asr_config']['seg_duration'] = int(os.getenv('DOUBAO_SEG_DURATION'))
            except ValueError:
                pass
        
        # 认证配置
        if os.getenv('DOUBAO_APP_KEY'):
            self.config['auth_config']['app_key'] = os.getenv('DOUBAO_APP_KEY')
        
        if os.getenv('DOUBAO_ACCESS_KEY'):
            self.config['auth_config']['access_key'] = os.getenv('DOUBAO_ACCESS_KEY')
        
        # 日志配置
        if os.getenv('DOUBAO_DEBUG'):
            self.config['logging_config']['enable_debug'] = os.getenv('DOUBAO_DEBUG').lower() == 'true'
    
    def _validate_config(self):
        """验证配置"""
        # 验证必需的认证信息
        auth_config = self.config.get('auth_config', {})
        if not auth_config.get('app_key'):
            raise ValueError("缺少必需的配置: auth_config.app_key")
        
        if not auth_config.get('access_key'):
            raise ValueError("缺少必需的配置: auth_config.access_key")
        
        # 验证ASR配置
        asr_config = self.config.get('asr_config', {})
        if not asr_config.get('ws_url'):
            raise ValueError("缺少必需的配置: asr_config.ws_url")
        
        # 验证音频配置
        audio_config = self.config.get('audio_config', {})
        supported_formats = audio_config.get('supported_formats', [])
        default_format = audio_config.get('default_format')
        
        if default_format and default_format not in supported_formats:
            raise ValueError(f"默认音频格式 {default_format} 不在支持的格式列表中: {supported_formats}")
        
        # 验证数值范围
        seg_duration = asr_config.get('seg_duration', 200)
        if not (50 <= seg_duration <= 1000):
            raise ValueError(f"分片时长必须在50-1000ms之间,当前值: {seg_duration}")
        
        sample_rate = audio_config.get('default_rate', 16000)
        if sample_rate not in [8000, 16000, 22050, 44100, 48000]:
            raise ValueError(f"不支持的采样率: {sample_rate}")
    
    def get_config(self) -> Dict[str, Any]:
        """
        获取完整配置
        
        Returns:
            Dict: 配置字典
        """
        return self.config.copy()
    
    def get_asr_config(self) -> Dict[str, Any]:
        """
        获取ASR配置
        
        Returns:
            Dict: ASR配置
        """
        return self.config.get('asr_config', {}).copy()
    
    def get_auth_config(self) -> Dict[str, Any]:
        """
        获取认证配置
        
        Returns:
            Dict: 认证配置
        """
        return self.config.get('auth_config', {}).copy()
    
    def get_audio_config(self) -> Dict[str, Any]:
        """
        获取音频配置
        
        Returns:
            Dict: 音频配置
        """
        return self.config.get('audio_config', {}).copy()
    
    def update_config(self, new_config: Dict[str, Any]):
        """
        更新配置
        
        Args:
            new_config: 新配置
        """
        self.config = self._merge_config(self.config, new_config)
        self._validate_config()
    
    def save_config(self, output_path: Optional[str] = None):
        """
        保存配置到文件
        
        Args:
            output_path: 输出文件路径,默认使用原配置文件路径
        """
        save_path = output_path or self.config_path
        if not save_path:
            raise ValueError("未指定保存路径")
        
        try:
            with open(save_path, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, indent=2, ensure_ascii=False)
        except Exception as e:
            raise ValueError(f"保存配置文件失败: {e}")
    
    def create_default_config(self, output_path: str) -> Dict[str, Any]:
        """
        创建默认配置文件
        
        Args:
            output_path: 输出文件路径
            
        Returns:
            Dict[str, Any]: 默认配置字典
        """
        try:
            with open(output_path, 'w', encoding='utf-8') as f:
                json.dump(self.DEFAULT_CONFIG, f, indent=2, ensure_ascii=False)
            return self.DEFAULT_CONFIG.copy()
        except Exception as e:
            raise ValueError(f"创建默认配置文件失败: {e}")
    
    def get_env_template(self) -> str:
        """
        获取环境变量模板
        
        Returns:
            str: 环境变量模板
        """
        template = """
# 豆包ASR环境变量配置模板

# ASR服务配置
DOUBAO_WS_URL=wss://openspeech.bytedance.com/api/v3/sauc/bigmodel
DOUBAO_MODEL_NAME=bigmodel
DOUBAO_SEG_DURATION=200

# 认证配置(必需)
DOUBAO_APP_KEY=your_app_key_here
DOUBAO_ACCESS_KEY=your_access_key_here

# 调试配置
DOUBAO_DEBUG=false
"""
        return template.strip()
    
    @classmethod
    def from_dict(cls, config_dict: Dict[str, Any]) -> 'ConfigManager':
        """
        从字典创建配置管理器
        
        Args:
            config_dict: 配置字典
            
        Returns:
            ConfigManager: 配置管理器实例
        """
        manager = cls()
        manager.config = manager._merge_config(manager.DEFAULT_CONFIG, config_dict)
        manager._validate_config()
        return manager
    
    @classmethod
    def from_env(cls) -> 'ConfigManager':
        """
        仅从环境变量创建配置管理器
        
        Returns:
            ConfigManager: 配置管理器实例
        """
        manager = cls()
        return manager