first pull

This commit is contained in:
2026-02-03 01:20:00 +08:00
commit 0753da86a8
79 changed files with 7134 additions and 0 deletions
+452
View File
@@ -0,0 +1,452 @@
"""
零初始化 JSON 配置管理
用法:from src.config import cfg # 直接访问,自动初始化
"""
import json
import threading
from pathlib import Path
from typing import Any, Dict, Optional, TypeVar
from dataclasses import dataclass, field, asdict
def _project_root() -> Path:
"""自动查找项目根目录"""
markers = ['pyproject.toml', 'settings.json', '.gitignore', 'main.py', '.python-version']
current = Path(__file__).resolve().parent.parent # src的父目录
for path in [current, *current.parents]:
if any((path / m).exists() for m in markers):
return path
if path == path.parent:
break
return current
# 配置定义
@dataclass
class AIConfig:
api_key: Optional[str] = "sk-xxxxx"
base_url: str = "http://localhost:1234/v1"
model_name: str = "qwen/qwen3-4b-2507"
timeout: int = 30
temperature: float = 0.4
max_tokens: int = 8192
@dataclass
class TTSConfig:
enabled: bool = True
api_key: Optional[str] = None
gpt_model_name: str = "GPT_weights_v2Pro/Yosuga_Airi-e32.ckpt"
sovits_model_name: str = "SoVITS_weights_v2Pro/Yosuga_Airi_e16_s864.pth"
host: str = "localhost"
port: int = 20261
reference_audio: str = "./using/reference.wav"
streaming: bool = True
speed: float = 1.0
@dataclass
class ASRConfig:
enabled: bool = True
api_key: Optional[str] = None
model_name: str = "fast-whisper"
url: str = "http://localhost:20260/"
@dataclass
class AutoAgentConfig:
enabled: bool = True
api_key: Optional[str] = None
deployment_type: str = "lmstudio"
model_name: str = "ui-tars-1.5-7b@q4_k_m"
base_url: str = "http://localhost:1234/v1"
temperature: float = 0.1
max_tokens: int = 16384
@dataclass
class LLMConfig:
enabled: bool = True
role_character: str = "你是由Misakiotoha开发的助手稲葉愛理ちゃん,可以和用户一起玩游戏,聊天,做各种事情,性格抽象,没事爱整整活。"
max_context_tokens: int = 2048
enable_history: bool = True
language: str = "日本语"
@dataclass
class PathsConfig:
temp: str = "./tmp/"
log: str = "./log/"
using: str = "./using/"
class AppConfig:
"""
应用主配置
新增配置分组:1) 上方新建 dataclass 2) 下方 __init__ 添加字段 3) 完成
"""
def __init__(
self,
version: str = "1.0.0",
debug: bool = False,
ai: Optional[AIConfig] = None,
tts: Optional[TTSConfig] = None,
asr: Optional[ASRConfig] = None,
auto_agent: Optional[AutoAgentConfig] = None,
llm_core: Optional[LLMConfig] = None,
paths: Optional[PathsConfig] = None,
_config_path: Optional[Path] = None,
**kwargs
):
# 基础字段
self.version = version
self.debug = debug
self.ai = ai if ai is not None else AIConfig()
self.tts = tts if tts is not None else TTSConfig()
self.asr = asr if asr is not None else ASRConfig()
self.auto_agent = auto_agent if auto_agent is not None else AutoAgentConfig()
self.llm_core = llm_core if llm_core is not None else LLMConfig()
self.paths = paths if paths is not None else PathsConfig()
# 内部状态(非 dataclass 字段,不会被序列化)
self._config_path = _config_path
self._lock = threading.RLock() # 普通属性,非 dataclass 字段
# 应用其他字段(用于从 JSON 加载时)
for k, v in kwargs.items():
if hasattr(self, k):
setattr(self, k, v)
# 路径解析为绝对路径
if self._config_path:
root = self._config_path.parent
for field_name in ['temp', 'log', 'using']:
rel_path = getattr(self.paths, field_name)
if not Path(rel_path).is_absolute():
abs_path = (root / Path(rel_path)).resolve()
setattr(self.paths, field_name, str(abs_path) + '/')
# 便捷属性
@property
def temp_dir(self) -> Path:
return Path(self.paths.temp)
@property
def log_dir(self) -> Path:
return Path(self.paths.log)
@property
def using_dir(self) -> Path:
return Path(self.paths.using)
# 核心方法
def get(self, key: str, default: Any = None) -> Any:
"""
点号路径访问:cfg.get("ai.timeout") / cfg.get("tts.enabled")
"""
try:
keys = key.split('.')
value = self
for k in keys:
value = getattr(value, k) if not isinstance(value, dict) else value[k]
return value
except (AttributeError, KeyError):
return default
def set(self, key: str, value: Any, save: bool = True) -> 'AppConfig':
"""
点号路径设置,支持链式调用
cfg.set("ai.timeout", 60).set("debug", True)
"""
with self._lock:
keys = key.split('.')
target = self
for k in keys[:-1]:
target = getattr(target, k)
setattr(target, keys[-1], value)
if save:
self._save()
return self
def update(self, updates: Dict[str, Any], save: bool = True) -> 'AppConfig':
"""
批量更新:cfg.update({"ai": {"timeout": 60}, "debug": True})
"""
def deep_update(obj: Any, data: dict):
for k, v in data.items():
if hasattr(obj, k):
current = getattr(obj, k)
if isinstance(v, dict) and hasattr(current, '__dataclass_fields__'):
deep_update(current, v)
else:
setattr(obj, k, v)
with self._lock:
deep_update(self, updates)
if save:
self._save()
return self
def reload(self) -> 'AppConfig':
"""热重载配置"""
if self._config_path and self._config_path.exists():
with self._lock:
data = json.loads(self._config_path.read_text(encoding='utf-8'))
# 配置项名 -> dataclass 类的映射(和 _load 保持一致)
config_classes = {
'ai': AIConfig,
'tts': TTSConfig,
'asr': ASRConfig,
'auto_agent': AutoAgentConfig,
'llm_core': LLMConfig,
'paths': PathsConfig,
}
for k, v in data.items():
if hasattr(self, k) and not k.startswith('_'):
# 如果是配置项且是 dict,转换为 dataclass
if k in config_classes and isinstance(v, dict):
setattr(self, k, config_classes[k](**v))
else:
setattr(self, k, v)
print(f"配置重载: {self._config_path}")
return self
def to_dict(self) -> dict:
"""导出为字典(手动实现,排除内部属性)"""
result = {
'version': self.version,
'debug': self.debug,
'ai': asdict(self.ai) if hasattr(self.ai, '__dataclass_fields__') else self.ai,
'tts': asdict(self.tts) if hasattr(self.tts, '__dataclass_fields__') else self.tts,
'asr': asdict(self.asr) if hasattr(self.asr, '__dataclass_fields__') else self.asr,
'auto_agent': asdict(self.auto_agent) if hasattr(self.auto_agent, '__dataclass_fields__') else self.auto_agent,
'llm_core': asdict(self.llm_core) if hasattr(self.llm_core, '__dataclass_fields__') else self.llm_core,
'paths': asdict(self.paths) if hasattr(self.paths, '__dataclass_fields__') else self.paths,
}
return result
def _save(self) -> None:
"""保存到文件"""
if self._config_path:
with self._lock:
json_str = json.dumps(self.to_dict(), indent=2, ensure_ascii=False)
self._config_path.write_text(json_str, encoding='utf-8')
@classmethod
def _load(cls, path: Path) -> 'AppConfig':
"""从文件加载"""
data = json.loads(path.read_text(encoding='utf-8'))
# 配置项名 -> dataclass 类的映射
config_classes = {
'ai': AIConfig,
'tts': TTSConfig,
'asr': ASRConfig,
'auto_agent': AutoAgentConfig,
'llm_core': LLMConfig,
'paths': PathsConfig,
}
# 自动转换 dict 为对应 dataclass
for key, config_class in config_classes.items():
if key in data and isinstance(data[key], dict):
data[key] = config_class(**data[key])
return cls(_config_path=path, **data)
@classmethod
def _create_default(cls, path: Path) -> 'AppConfig':
"""创建默认配置"""
instance = cls(_config_path=path)
instance._save()
print(f"默认配置已创建: {path}")
return instance
def __repr__(self) -> str:
"""友好的打印格式"""
lines = ["AppConfig("]
for k, v in self.to_dict().items():
lines.append(f" {k}={v!r},")
lines.append(")")
return "\n".join(lines)
# 延迟初始化机制
_root: Path = _project_root()
_config_path: Path = _root / "settings.json"
_config_instance: Optional[AppConfig] = None
_init_lock: threading.Lock = threading.Lock()
def _ensure_initialized() -> AppConfig:
"""
确保配置已初始化(线程安全的延迟初始化)
"""
global _config_instance
if _config_instance is not None:
return _config_instance
with _init_lock:
if _config_instance is not None:
return _config_instance
# 自动加载或创建
if _config_path.exists():
_config_instance = AppConfig._load(_config_path)
print(f"配置加载: {_config_path}")
else:
_config_instance = AppConfig._create_default(_config_path)
# 确保目录存在
for d in [_config_instance.temp_dir,
_config_instance.log_dir,
_config_instance.using_dir]:
d.mkdir(parents=True, exist_ok=True)
return _config_instance
class _LazyConfig:
"""
配置代理类:拦截所有属性访问,第一次使用时自动初始化
"""
def __getattr__(self, name: str) -> Any:
"""拦截属性访问,延迟初始化"""
instance = _ensure_initialized()
return getattr(instance, name)
def __setattr__(self, name: str, value: Any) -> None:
"""拦截属性设置"""
# 特殊属性直接设置到代理对象本身
if name.startswith('_'):
super().__setattr__(name, value)
else:
instance = _ensure_initialized()
setattr(instance, name, value)
def __repr__(self) -> str:
instance = _ensure_initialized()
return repr(instance)
def __dir__(self) -> list:
"""支持 IDE 自动补全"""
instance = _ensure_initialized()
return dir(instance)
# 显式代理必要方法
def get(self, key: str, default: Any = None) -> Any:
return _ensure_initialized().get(key, default)
def set(self, key: str, value: Any, save: bool = True) -> AppConfig:
return _ensure_initialized().set(key, value, save)
def update(self, updates: Dict[str, Any], save: bool = True) -> AppConfig:
return _ensure_initialized().update(updates, save)
def reload(self) -> AppConfig:
return _ensure_initialized().reload()
def to_dict(self) -> dict:
return _ensure_initialized().to_dict()
def save(self) -> None:
_ensure_initialized()._save()
# 属性代理
@property
def ai(self) -> AIConfig:
return _ensure_initialized().ai
@property
def tts(self) -> TTSConfig:
return _ensure_initialized().tts
@property
def asr(self) -> ASRConfig:
return _ensure_initialized().asr
@property
def auto_agent(self) -> AutoAgentConfig:
return _ensure_initialized().auto_agent
@property
def llm_core(self) -> LLMConfig:
return _ensure_initialized().llm_core
@property
def paths(self) -> PathsConfig:
return _ensure_initialized().paths
@property
def temp_dir(self) -> Path:
return _ensure_initialized().temp_dir
@property
def log_dir(self) -> Path:
return _ensure_initialized().log_dir
@property
def using_dir(self) -> Path:
return _ensure_initialized().using_dir
# 全局配置对象:导入即用,自动初始化
cfg: AppConfig = _LazyConfig() # type: ignore
# 工具函数
def generate_example(path: Path = _root / "settings.example.json") -> None:
"""生成示例配置文件"""
example = {
"version": "1.0.0",
"debug": False,
"ai": {
"api_key": "sk-your-api-key",
"base_url": "https://api.deepseek.com",
"model": "deepseek-chat",
"timeout": 30
},
"tts": {
"enabled": True,
"model": "GPT_SoVITS",
"url": "http://localhost:12458/",
"reference_audio": "./using/reference.wav",
"streaming": True,
"speed": 1.0
},
"paths": {
"temp": "./tmp/",
"log": "./log/",
"data": "./data/"
}
}
path.write_text(json.dumps(example, indent=2, ensure_ascii=False), encoding='utf-8')
print(f"示例配置已生成: {path}")
# 测试代码
if __name__ == "__main__":
# 测试:直接访问,自动初始化
print("第一次访问 cfg.ai.model:")
print(f"{cfg.ai.model_name}")
print(f"\n配置详情:")
print(cfg)
print(f"\n测试修改:")
cfg.set("ai.timeout", 60)
print(f" ai.timeout = {cfg.ai.timeout}")
print(f"\n测试批量更新:")
cfg.update({"debug": True, "tts": {"speed": 1.5}})
print(f" debug = {cfg.debug}, tts.speed = {cfg.tts.speed}")
print(f"\n测试热重载:")
cfg.reload()