feat: 将LLM配置主存储迁移到MySQL

变更项: 1) 新增 t_llm_config 数据访问层与建表逻辑。 2) Robot 启动时自动初始化并在空库时从 YAML 导入。 3) 后台 system LLM API 改为读写 MySQL。 4) LLMRegistry 改为优先 MySQL 读取并回退 YAML。 5) DashboardServer 挂载 llm_config_db 提供后台访问。
This commit is contained in:
liuwei
2026-04-20 14:51:43 +08:00
parent ef49588485
commit 1446bf5f39
5 changed files with 287 additions and 24 deletions

125
db/llm_config_db.py Normal file
View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
import json
from typing import Any, Dict
from loguru import logger
from db.base import BaseDBOperator
from db.connection import DBConnectionManager
class LLMConfigDBOperator(BaseDBOperator):
"""LLM 配置数据库操作器。
设计目标:
1. 把原先存放在 config.yaml 的 llm 配置迁移到 MySQL便于后台实时维护
2. 采用“单行配置”模型,降低维护复杂度:一行记录保存 default_backend/backends/scenes
3. 支持“首次启动自动导入 YAML 配置”,保证迁移过程对线上透明。
"""
def __init__(self, db_manager: DBConnectionManager):
super().__init__(db_manager)
def init_tables(self) -> bool:
"""初始化 LLM 配置表。
字段说明:
- id: 固定主键,当前仅使用 id=1 作为全局配置;
- default_backend: 全局默认后端;
- backends_json: 后端配置大对象JSON 字符串);
- scenes_json: 场景路由对象JSON 字符串);
- source: 记录当前配置来源,便于后续排障。
"""
try:
return self.execute_update(
"""
CREATE TABLE IF NOT EXISTS t_llm_config (
id TINYINT PRIMARY KEY,
default_backend VARCHAR(128) NOT NULL DEFAULT '',
backends_json JSON NOT NULL,
scenes_json JSON NOT NULL,
source VARCHAR(32) NOT NULL DEFAULT 'mysql',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
"""
)
except Exception as e:
logger.error(f"初始化 t_llm_config 失败: {e}")
return False
@staticmethod
def _loads_json(value: Any) -> Dict[str, Any]:
"""将数据库 JSON 字段统一解析为 dict。"""
if isinstance(value, dict):
return value
if isinstance(value, str):
try:
obj = json.loads(value)
return obj if isinstance(obj, dict) else {}
except json.JSONDecodeError:
return {}
return {}
def get_config(self) -> Dict[str, Any]:
"""读取数据库中的 LLM 配置。"""
row = self.execute_query(
"""
SELECT id, default_backend, backends_json, scenes_json, source, updated_at
FROM t_llm_config
WHERE id = 1
LIMIT 1
""",
fetch_one=True,
) or {}
if not row:
return {}
return {
"default_backend": str(row.get("default_backend") or "").strip(),
"backends": self._loads_json(row.get("backends_json")),
"scenes": self._loads_json(row.get("scenes_json")),
"source": str(row.get("source") or "mysql").strip(),
"updated_at": row.get("updated_at"),
}
def save_config(self, llm_config: Dict[str, Any], source: str = "mysql") -> bool:
"""保存覆盖LLM 配置到数据库。"""
data = llm_config or {}
default_backend = str(data.get("default_backend") or "").strip()
backends = data.get("backends", {}) or {}
scenes = data.get("scenes", {}) or {}
if not isinstance(backends, dict):
backends = {}
if not isinstance(scenes, dict):
scenes = {}
sql = """
INSERT INTO t_llm_config (id, default_backend, backends_json, scenes_json, source)
VALUES (1, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
default_backend = VALUES(default_backend),
backends_json = VALUES(backends_json),
scenes_json = VALUES(scenes_json),
source = VALUES(source)
"""
params = (
default_backend,
json.dumps(backends, ensure_ascii=False),
json.dumps(scenes, ensure_ascii=False),
str(source or "mysql"),
)
return self.execute_update(sql, params)
def bootstrap_from_yaml_if_empty(self, yaml_llm_config: Dict[str, Any]) -> bool:
"""当数据库为空时,把 YAML 里的 llm 配置导入到数据库。
迁移策略:
1. 只在“表中无 id=1 配置”时执行,避免覆盖后台已维护的数据;
2. 导入后标记 source=yaml_bootstrap便于识别初始数据来源
3. 返回 True 表示“已有配置或导入成功”False 表示导入失败。
"""
existed = self.get_config()
if existed:
return True
return self.save_config(yaml_llm_config or {}, source="yaml_bootstrap")