Files
abot/utils/ai/llm_registry.py
liuwei ef49588485 refactor: 移除LLM旧兼容入口并统一scene单路由
变更项:

1. LLMRegistry 仅保留 scene 入口,删除 backend_name/backend_ref/scene_ref 等兼容解析分支,未声明 scene 时仅保留直连配置。

2. Dify/GlobalNews/GameTask 插件初始化改为仅传 scene,不再拼接 backend/provider/url 等旧兼容字段。

3. 清理插件配置冗余:dify/global_news/game_task/douyu 的 config.toml 删除 backend 字段,统一由 scene 映射后端。

4. 后台 system API 调整为严格模式:插件依赖扫描仅采集 scene;scene 保存时必须绑定有效 backend。

5. 后台页面去除拓扑中的配置Backend冗余列,并新增前端校验,禁止提交空场景或未绑定后端。
2026-04-20 14:45:03 +08:00

112 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, Optional
import yaml
class LLMRegistry:
"""从项目根 config.yaml 读取集中式 LLM 后端配置。"""
_cache: Dict[str, Any] = {"mtime": None, "data": {}}
@classmethod
def get_root_config_path(cls) -> Path:
return Path(__file__).resolve().parents[2] / "config.yaml"
@classmethod
def load_root_config(cls) -> Dict[str, Any]:
path = cls.get_root_config_path()
if not path.exists():
return {}
stat = path.stat()
if cls._cache["mtime"] == stat.st_mtime and cls._cache["data"]:
return cls._cache["data"]
with open(path, "r", encoding="utf-8") as fp:
data = yaml.safe_load(fp) or {}
cls._cache = {"mtime": stat.st_mtime, "data": data}
return data
@classmethod
def get_llm_config(cls) -> Dict[str, Any]:
config = cls.load_root_config()
llm_config = config.get("llm", {}) or {}
return llm_config if isinstance(llm_config, dict) else {}
@classmethod
def get_default_backend(cls) -> str:
"""读取全局默认后端名称。"""
llm_config = cls.get_llm_config()
return str(llm_config.get("default_backend", "") or "").strip()
@classmethod
def get_backend(cls, backend_name: str) -> Dict[str, Any]:
if not backend_name:
return {}
llm_config = cls.get_llm_config()
backends = llm_config.get("backends", {}) or {}
backend = backends.get(backend_name, {}) or {}
return dict(backend) if isinstance(backend, dict) else {}
@classmethod
def get_scenes(cls) -> Dict[str, str]:
"""读取 llm.scenes 场景路由配置,返回 scene->backend 的映射。"""
llm_config = cls.get_llm_config()
raw_scenes = llm_config.get("scenes", {}) or {}
if not isinstance(raw_scenes, dict):
return {}
normalized: Dict[str, str] = {}
for raw_scene, raw_backend in raw_scenes.items():
scene_name = str(raw_scene or "").strip()
backend_name = str(raw_backend or "").strip()
if not scene_name or not backend_name:
continue
normalized[scene_name] = backend_name
return normalized
@classmethod
def get_scene_backend_name(cls, scene_name: str) -> str:
"""根据场景名解析后端名;若场景不存在则自动回退 default_backend。"""
name = str(scene_name or "").strip()
if not name:
return cls.get_default_backend()
scenes = cls.get_scenes()
backend_name = str(scenes.get(name, "") or "").strip()
if backend_name:
return backend_name
return cls.get_default_backend()
@classmethod
def resolve_by_scene(cls, scene_name: str) -> Dict[str, Any]:
"""按场景解析最终后端配置,并附带 scene 字段用于链路追踪。"""
backend_name = cls.get_scene_backend_name(scene_name)
backend = cls.get_backend(backend_name)
if backend_name:
backend["backend"] = backend_name
if scene_name:
backend["scene"] = str(scene_name).strip()
return backend
@classmethod
def resolve(cls, local_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
local = dict(local_config or {})
# 严格模式说明:
# 1. 统一只认 scene 作为路由入口,避免 backend/backend_ref 等多入口并存;
# 2. 若未声明 scene则视为“调用方直接给出完整连接参数”原样返回 local。
scene_name = local.get("scene") or ""
scene_name = str(scene_name).strip()
if scene_name:
merged = cls.resolve_by_scene(scene_name)
merged.update(local)
# 约定:只要声明了 scene就以 scene 路由结果为准。
# 这样后台切换 scene 绑定时,无需改插件配置即可全局生效。
merged["backend"] = cls.get_scene_backend_name(scene_name)
merged["scene"] = scene_name
return merged
return local