Files
abot/db/llm_config_db.py
liuwei 1446bf5f39 feat: 将LLM配置主存储迁移到MySQL
变更项: 1) 新增 t_llm_config 数据访问层与建表逻辑。 2) Robot 启动时自动初始化并在空库时从 YAML 导入。 3) 后台 system LLM API 改为读写 MySQL。 4) LLMRegistry 改为优先 MySQL 读取并回退 YAML。 5) DashboardServer 挂载 llm_config_db 提供后台访问。
2026-04-20 14:51:43 +08:00

126 lines
4.7 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.
# -*- 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")