feat: 多专业小模型+路由模型系统 MVP(mock 全链路 + FastAPI 网关 + 论文调研)

This commit is contained in:
tzt
2026-08-12 10:40:04 +08:00
commit 1e51167ea5
50 changed files with 49382 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
"""配置加载:优先 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,
},
"classifier": {"type": "rule", "model": "Qwen/Qwen3-0.6B", "confidence_floor": 0.55},
"domains": ["code", "math", "legal", "medical", "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"},
"general": {"type": "mock", "model": "Qwen/Qwen3-1.7B-Instruct"},
},
"fallback": {
"type": "mock",
"model": "deepseek-chat",
"base_url": "https://api.deepseek.com/v1",
"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