# -*- 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")