"""配置加载:优先 YAML(若安装了 pyyaml),否则回退 JSON。 设计原则:router_system 核心零依赖,因此 pyyaml 是"可选"的。 默认 config/config.yaml 存在;若 pyyaml 不可用,可提供同名 .json。 """ from __future__ import annotations import json import os from pathlib import Path from typing import Any, Dict, Optional DEFAULT_CONFIG_PATH = Path(__file__).resolve().parent.parent / "config" / "config.yaml" _DEFAULTS: Dict[str, Any] = { "system": {"name": "multi-expert-router", "version": "0.1.0"}, "router": { "low_confidence_threshold": 0.60, # 分类置信度低于此值 -> 直接走最后处理者 "judge_fallback_threshold": 0.70, # Judge 质量分低于此值 -> 升级最后处理者 "default_temperature": 0.2, }, "execution": { "mode": "rule", # rule(L0 零参数)| hybrid "planner": "rule", # rule | hf "expert_backend": "rule", # rule(规则执行器)| hf | api "model_level": "L0", # L0 | L1 | L2 "max_plan_depth": 3, }, "classifier": {"type": "rule", "model": "Qwen/Qwen3-0.6B", "confidence_floor": 0.55}, "domains": ["code", "math", "legal", "medical", "finance", "life", "education", "general"], # 两级路由:大领域分组(用户接口指定 group → 组内路由模型 → 组内专业小模型) # 组内路由模型只识别本组领域,体积约为统一路由模型的 1/4 "domain_groups": { "tech": ["code", "math"], "professional": ["legal", "medical", "finance"], "lifestyle": ["life", "education"], "general": ["general"], }, "experts": { "code": {"type": "mock", "model": "Qwen/Qwen2.5-Coder-7B-Instruct"}, "math": {"type": "mock", "model": "Qwen/Qwen3-4B-Instruct"}, "legal": {"type": "mock", "model": "Qwen/Qwen3-4B-Instruct"}, "medical": {"type": "mock", "model": "Qwen/Qwen3-4B-Instruct"}, "finance": {"type": "mock", "model": "Qwen/Qwen3-4B-Instruct"}, "life": {"type": "mock", "model": "Qwen/Qwen3-1.7B-Instruct"}, "education": {"type": "mock", "model": "Qwen/Qwen3-1.7B-Instruct"}, "general": {"type": "mock", "model": "Qwen/Qwen3-1.7B-Instruct"}, }, "fallback": { "type": "mock", "model": "deepseek-v4-flash", "base_url": "https://api.deepseek.com", "api_key_env": "DEEPSEEK_API_KEY", }, "judge": {"type": "rule", "model": "Qwen/Qwen3-1.7B-Instruct"}, "cache": { "enabled": True, "semantic_enabled": True, "similarity_threshold": 0.88, "promote_frequency": 5, }, } def load_defaults() -> Dict[str, Any]: return _DEFAULTS def _try_load_yaml(path: Path) -> Optional[Dict[str, Any]]: try: import yaml # type: ignore except ImportError: return None try: with open(path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) return data if isinstance(data, dict) else None except Exception: return None def _try_load_json(path: Path) -> Optional[Dict[str, Any]]: json_path = path.with_suffix(".json") if not json_path.exists(): return None try: with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) return data if isinstance(data, dict) else None except Exception: return None def _merge_defaults(data: Dict[str, Any]) -> Dict[str, Any]: """将用户配置与内置默认配置做一层合并(用户优先)。""" merged = dict(_DEFAULTS) for k, v in data.items(): if isinstance(v, dict) and isinstance(merged.get(k), dict): merged[k] = {**merged[k], **v} else: merged[k] = v return merged def load_config(path: Optional[Path | str] = None) -> Dict[str, Any]: """加载配置,返回 dict。文件不存在或解析失败时返回内置默认配置。""" cfg_path = Path(path) if path else DEFAULT_CONFIG_PATH if cfg_path.exists(): data = _try_load_yaml(cfg_path) or _try_load_json(cfg_path) if data is not None: return _merge_defaults(data) return dict(_DEFAULTS) def get_api_key(cfg: Dict[str, Any]) -> Optional[str]: """从环境变量读取 API Key(用于 api 类型后端)。""" env_name = cfg.get("api_key_env") or "API_KEY" return os.environ.get(env_name) or None