import json from typing import Any from app.core.database import get_db_connection from app.core.response import create_api_response from app.services.async_transcription_service import AsyncTranscriptionService from app.services.llm_service import LLMService from app.services.system_config_service import SystemConfigService llm_service = LLMService() transcription_service = AsyncTranscriptionService() def _validate_parameter_request(request): param_key = str(request.param_key or "").strip() if not param_key: return "参数键不能为空" if param_key in SystemConfigService.DEPRECATED_BRANDING_PARAMETER_KEYS: return f"{param_key} 已废弃,现已改为前端固定文案,不再支持配置" if param_key == SystemConfigService.TOKEN_EXPIRE_DAYS: if request.category != "system": return "token_expire_days 必须归类为 system" if request.value_type != "number": return "token_expire_days 的值类型必须为 number" try: expire_days = int(str(request.param_value).strip()) except (TypeError, ValueError): return "token_expire_days 必须为正整数" if expire_days <= 0: return "token_expire_days 必须大于 0" if expire_days > 365: return "token_expire_days 不能超过 365 天" return None def _parse_json_object(value: Any) -> dict[str, Any]: if value is None: return {} if isinstance(value, dict): return dict(value) if isinstance(value, str): value = value.strip() if not value: return {} try: parsed = json.loads(value) return parsed if isinstance(parsed, dict) else {} except json.JSONDecodeError: return {} return {} def _normalize_string_list(value: Any) -> list[str] | None: if value is None: return None if isinstance(value, list): values = [str(item).strip() for item in value if str(item).strip()] return values or None if isinstance(value, str): values = [item.strip() for item in value.split(",") if item.strip()] return values or None return None def _normalize_int_list(value: Any) -> list[int] | None: if value is None: return None if isinstance(value, list): items = value elif isinstance(value, str): items = [item.strip() for item in value.split(",") if item.strip()] else: return None normalized = [] for item in items: try: normalized.append(int(item)) except (TypeError, ValueError): continue return normalized or None def _clean_extra_config(config: dict[str, Any]) -> dict[str, Any]: cleaned: dict[str, Any] = {} for key, value in (config or {}).items(): if value is None: continue if isinstance(value, str): stripped = value.strip() if stripped: cleaned[key] = stripped continue if isinstance(value, list): normalized_list = [] for item in value: if item is None: continue if isinstance(item, str): stripped = item.strip() if stripped: normalized_list.append(stripped) else: normalized_list.append(item) if normalized_list: cleaned[key] = normalized_list continue cleaned[key] = value return cleaned def _merge_audio_extra_config(request, vocabulary_id: str | None = None) -> dict[str, Any]: extra_config = _parse_json_object(request.extra_config) if request.model_type == "asr": if vocabulary_id: extra_config["vocabulary_id"] = vocabulary_id else: extra_config.pop("vocabulary_id", None) merged = dict(extra_config) language_hints = _normalize_string_list(merged.get("language_hints")) if language_hints is not None: merged["language_hints"] = language_hints channel_id = _normalize_int_list(merged.get("channel_id")) if channel_id is not None: merged["channel_id"] = channel_id return _clean_extra_config(merged) def _merge_llm_extra_config(request) -> dict[str, Any]: extra_config = _parse_json_object(request.extra_config) if request.model_type == "vector": dimensions = extra_config.get("dimensions") try: if dimensions is not None: extra_config["dimensions"] = int(dimensions) except (TypeError, ValueError): extra_config.pop("dimensions", None) else: if "temperature" in extra_config: try: extra_config["temperature"] = float(extra_config["temperature"]) except (TypeError, ValueError): extra_config.pop("temperature", None) if "top_p" in extra_config: try: extra_config["top_p"] = float(extra_config["top_p"]) except (TypeError, ValueError): extra_config.pop("top_p", None) if "max_tokens" in extra_config: try: extra_config["max_tokens"] = int(extra_config["max_tokens"]) except (TypeError, ValueError): extra_config.pop("max_tokens", None) return _clean_extra_config(extra_config) def _normalize_llm_row(row: dict[str, Any]) -> dict[str, Any]: extra_config = _parse_json_object(row.get("extra_config")) row["model_type"] = row.get("model_type") or "text" row["extra_config"] = extra_config row["llm_temperature"] = extra_config.get("temperature") row["llm_top_p"] = extra_config.get("top_p") row["llm_max_tokens"] = extra_config.get("max_tokens") row["llm_system_prompt"] = extra_config.get("system_prompt") return row def _normalize_audio_row(row: dict[str, Any]) -> dict[str, Any]: extra_config = _parse_json_object(row.get("extra_config")) row["extra_config"] = extra_config row["service_model_name"] = extra_config.get("model") row["request_timeout_seconds"] = int(row.get("request_timeout_seconds") or 300) return row def _resolve_audio_vocabulary_id(request) -> str | None: vocabulary_id = _parse_json_object(request.extra_config).get("vocabulary_id") return str(vocabulary_id).strip() if vocabulary_id else None def list_parameters(category: str | None = None, keyword: str | None = None): try: SystemConfigService.ensure_builtin_parameters() with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) query = """ SELECT param_id, param_key, param_name, param_value, value_type, category, description, is_active, created_at, updated_at FROM sys_system_parameters WHERE 1=1 """ params = [] if category: query += " AND category = %s" params.append(category) if keyword: like_pattern = f"%{keyword}%" query += " AND (param_key LIKE %s OR param_name LIKE %s)" params.extend([like_pattern, like_pattern]) query += " ORDER BY category ASC, param_key ASC" cursor.execute(query, tuple(params)) rows = [ row for row in cursor.fetchall() if row["param_key"] not in SystemConfigService.DEPRECATED_BRANDING_PARAMETER_KEYS ] return create_api_response( code="200", message="获取参数列表成功", data={"items": rows, "total": len(rows)}, ) except Exception as e: return create_api_response(code="500", message=f"获取参数列表失败: {str(e)}") def get_parameter(param_key: str): try: if param_key in SystemConfigService.DEPRECATED_BRANDING_PARAMETER_KEYS: return create_api_response(code="404", message="参数不存在") SystemConfigService.ensure_builtin_parameters() with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute( """ SELECT param_id, param_key, param_name, param_value, value_type, category, description, is_active, created_at, updated_at FROM sys_system_parameters WHERE param_key = %s LIMIT 1 """, (param_key,), ) row = cursor.fetchone() if not row: return create_api_response(code="404", message="参数不存在") return create_api_response(code="200", message="获取参数成功", data=row) except Exception as e: return create_api_response(code="500", message=f"获取参数失败: {str(e)}") def create_parameter(request): try: validation_error = _validate_parameter_request(request) if validation_error: return create_api_response(code="400", message=validation_error) with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute("SELECT param_id FROM sys_system_parameters WHERE param_key = %s", (request.param_key,)) if cursor.fetchone(): return create_api_response(code="400", message="参数键已存在") cursor.execute( """ INSERT INTO sys_system_parameters (param_key, param_name, param_value, value_type, category, description, is_active) VALUES (%s, %s, %s, %s, %s, %s, %s) """, ( request.param_key, request.param_name, request.param_value, request.value_type, request.category, request.description, 1 if request.is_active else 0, ), ) conn.commit() SystemConfigService.invalidate_cache() return create_api_response(code="200", message="创建参数成功") except Exception as e: return create_api_response(code="500", message=f"创建参数失败: {str(e)}") def update_parameter(param_key: str, request): try: validation_error = _validate_parameter_request(request) if validation_error: return create_api_response(code="400", message=validation_error) with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute("SELECT param_id FROM sys_system_parameters WHERE param_key = %s", (param_key,)) if not cursor.fetchone(): return create_api_response(code="404", message="参数不存在") new_key = request.param_key or param_key if new_key != param_key: cursor.execute("SELECT param_id FROM sys_system_parameters WHERE param_key = %s", (new_key,)) if cursor.fetchone(): return create_api_response(code="400", message="新的参数键已存在") cursor.execute( """ UPDATE sys_system_parameters SET param_key = %s, param_name = %s, param_value = %s, value_type = %s, category = %s, description = %s, is_active = %s WHERE param_key = %s """, ( new_key, request.param_name, request.param_value, request.value_type, request.category, request.description, 1 if request.is_active else 0, param_key, ), ) conn.commit() SystemConfigService.invalidate_cache() return create_api_response(code="200", message="更新参数成功") except Exception as e: return create_api_response(code="500", message=f"更新参数失败: {str(e)}") def delete_parameter(param_key: str): try: with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute("SELECT param_id FROM sys_system_parameters WHERE param_key = %s", (param_key,)) if not cursor.fetchone(): return create_api_response(code="404", message="参数不存在") cursor.execute("DELETE FROM sys_system_parameters WHERE param_key = %s", (param_key,)) conn.commit() SystemConfigService.invalidate_cache() return create_api_response(code="200", message="删除参数成功") except Exception as e: return create_api_response(code="500", message=f"删除参数失败: {str(e)}") def list_llm_model_configs(): try: with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute( """ SELECT config_id, model_code, model_name, provider, endpoint_url, api_key, llm_model_name, llm_timeout, model_type, extra_config, description, is_active, is_default, created_at, updated_at FROM llm_model_config ORDER BY model_type ASC, model_code ASC """ ) rows = [_normalize_llm_row(row) for row in cursor.fetchall()] return create_api_response( code="200", message="获取LLM模型配置成功", data={"items": rows, "total": len(rows)}, ) except Exception as e: return create_api_response(code="500", message=f"获取LLM模型配置失败: {str(e)}") def create_llm_model_config(request): try: if request.model_type not in ("text", "vector"): return create_api_response(code="400", message="model_type 仅支持 text 或 vector") with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute("SELECT config_id FROM llm_model_config WHERE model_code = %s", (request.model_code,)) if cursor.fetchone(): return create_api_response(code="400", message="模型编码已存在") cursor.execute("SELECT COUNT(*) AS total FROM llm_model_config WHERE model_type = %s", (request.model_type,)) total_row = cursor.fetchone() or {"total": 0} is_default = bool(request.is_default) or total_row["total"] == 0 if is_default: cursor.execute("UPDATE llm_model_config SET is_default = 0 WHERE model_type = %s AND is_default = 1", (request.model_type,)) extra_config = _merge_llm_extra_config(request) cursor.execute( """ INSERT INTO llm_model_config (model_code, model_name, model_type, provider, endpoint_url, api_key, llm_model_name, llm_timeout, extra_config, description, is_active, is_default) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """, ( request.model_code, request.model_name, request.model_type, request.provider, request.endpoint_url, request.api_key, request.llm_model_name, request.llm_timeout, json.dumps(extra_config, ensure_ascii=False), request.description, 1 if request.is_active else 0, 1 if is_default else 0, ), ) conn.commit() return create_api_response(code="200", message="创建LLM模型配置成功") except Exception as e: return create_api_response(code="500", message=f"创建LLM模型配置失败: {str(e)}") def update_llm_model_config(model_code: str, request): try: if request.model_type not in ("text", "vector"): return create_api_response(code="400", message="model_type 仅支持 text 或 vector") with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute("SELECT config_id, model_type FROM llm_model_config WHERE model_code = %s", (model_code,)) existed = cursor.fetchone() if not existed: return create_api_response(code="404", message="模型配置不存在") if request.model_type != (existed.get("model_type") or "text"): return create_api_response(code="400", message="修改模型时不能变更模型类型") new_model_code = request.model_code or model_code if new_model_code != model_code: cursor.execute("SELECT config_id FROM llm_model_config WHERE model_code = %s", (new_model_code,)) duplicate_row = cursor.fetchone() if duplicate_row and duplicate_row["config_id"] != existed["config_id"]: return create_api_response(code="400", message="新的模型编码已存在") if request.is_default: cursor.execute( "UPDATE llm_model_config SET is_default = 0 WHERE model_type = %s AND model_code <> %s AND is_default = 1", (request.model_type, model_code), ) extra_config = _merge_llm_extra_config(request) cursor.execute( """ UPDATE llm_model_config SET model_code = %s, model_name = %s, provider = %s, endpoint_url = %s, api_key = %s, llm_model_name = %s, llm_timeout = %s, extra_config = %s, description = %s, is_active = %s, is_default = %s WHERE model_code = %s """, ( new_model_code, request.model_name, request.provider, request.endpoint_url, request.api_key, request.llm_model_name, request.llm_timeout, json.dumps(extra_config, ensure_ascii=False), request.description, 1 if request.is_active else 0, 1 if request.is_default else 0, model_code, ), ) conn.commit() return create_api_response(code="200", message="更新LLM模型配置成功") except Exception as e: return create_api_response(code="500", message=f"更新LLM模型配置失败: {str(e)}") def list_audio_model_configs(scene: str = "all"): try: with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) sql = """ SELECT a.config_id, a.model_code, a.model_name, a.model_type, a.provider, a.endpoint_url, a.api_key, a.request_timeout_seconds, a.extra_config, a.description, a.is_active, a.is_default, a.created_at, a.updated_at FROM audio_model_config a """ params = [] if scene in ("asr", "voiceprint"): sql += " WHERE a.model_type = %s" params.append(scene) sql += " ORDER BY a.model_type ASC, a.model_code ASC" cursor.execute(sql, tuple(params)) rows = [_normalize_audio_row(row) for row in cursor.fetchall()] return create_api_response(code="200", message="获取音频模型配置成功", data={"items": rows, "total": len(rows)}) except Exception as e: return create_api_response(code="500", message=f"获取音频模型配置失败: {str(e)}") def create_audio_model_config(request): try: if request.model_type not in ("asr", "voiceprint"): return create_api_response(code="400", message="model_type 仅支持 asr 或 voiceprint") with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute("SELECT config_id FROM audio_model_config WHERE model_code = %s", (request.model_code,)) if cursor.fetchone(): return create_api_response(code="400", message="模型编码已存在") cursor.execute("SELECT COUNT(*) AS total FROM audio_model_config WHERE model_type = %s", (request.model_type,)) total_row = cursor.fetchone() or {"total": 0} is_default = bool(request.is_default) or total_row["total"] == 0 if is_default: cursor.execute( "UPDATE audio_model_config SET is_default = 0 WHERE model_type = %s AND is_default = 1", (request.model_type,), ) asr_vocabulary_id = _resolve_audio_vocabulary_id(request) extra_config = _merge_audio_extra_config(request, vocabulary_id=asr_vocabulary_id) cursor.execute( """ INSERT INTO audio_model_config (model_code, model_name, model_type, provider, endpoint_url, api_key, request_timeout_seconds, extra_config, description, is_active, is_default) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """, ( request.model_code, request.model_name, request.model_type, request.provider, request.endpoint_url, request.api_key, request.request_timeout_seconds, json.dumps(extra_config, ensure_ascii=False), request.description, 1 if request.is_active else 0, 1 if is_default else 0, ), ) conn.commit() return create_api_response(code="200", message="创建音频模型配置成功") except Exception as e: return create_api_response(code="500", message=f"创建音频模型配置失败: {str(e)}") def update_audio_model_config(model_code: str, request): try: if request.model_type not in ("asr", "voiceprint"): return create_api_response(code="400", message="model_type 仅支持 asr 或 voiceprint") with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute("SELECT config_id, model_type FROM audio_model_config WHERE model_code = %s", (model_code,)) existed = cursor.fetchone() if not existed: return create_api_response(code="404", message="模型配置不存在") if request.model_type != existed["model_type"]: return create_api_response(code="400", message="修改音频模型时不能变更场景类型") new_model_code = request.model_code or model_code if new_model_code != model_code: cursor.execute("SELECT config_id FROM audio_model_config WHERE model_code = %s", (new_model_code,)) duplicate_row = cursor.fetchone() if duplicate_row and duplicate_row["config_id"] != existed["config_id"]: return create_api_response(code="400", message="新的模型编码已存在") if request.is_default: cursor.execute( "UPDATE audio_model_config SET is_default = 0 WHERE model_type = %s AND model_code <> %s AND is_default = 1", (request.model_type, model_code), ) asr_vocabulary_id = _resolve_audio_vocabulary_id(request) extra_config = _merge_audio_extra_config(request, vocabulary_id=asr_vocabulary_id) cursor.execute( """ UPDATE audio_model_config SET model_code = %s, model_name = %s, model_type = %s, provider = %s, endpoint_url = %s, api_key = %s, request_timeout_seconds = %s, extra_config = %s, description = %s, is_active = %s, is_default = %s WHERE model_code = %s """, ( new_model_code, request.model_name, request.model_type, request.provider, request.endpoint_url, request.api_key, request.request_timeout_seconds, json.dumps(extra_config, ensure_ascii=False), request.description, 1 if request.is_active else 0, 1 if request.is_default else 0, model_code, ), ) conn.commit() return create_api_response(code="200", message="更新音频模型配置成功") except Exception as e: return create_api_response(code="500", message=f"更新音频模型配置失败: {str(e)}") def delete_llm_model_config(model_code: str): try: with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute("SELECT config_id FROM llm_model_config WHERE model_code = %s", (model_code,)) if not cursor.fetchone(): return create_api_response(code="404", message="模型配置不存在") cursor.execute("DELETE FROM llm_model_config WHERE model_code = %s", (model_code,)) conn.commit() return create_api_response(code="200", message="删除LLM模型配置成功") except Exception as e: return create_api_response(code="500", message=f"删除LLM模型配置失败: {str(e)}") def delete_audio_model_config(model_code: str): try: with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute("SELECT config_id FROM audio_model_config WHERE model_code = %s", (model_code,)) if not cursor.fetchone(): return create_api_response(code="404", message="模型配置不存在") cursor.execute("DELETE FROM audio_model_config WHERE model_code = %s", (model_code,)) conn.commit() return create_api_response(code="200", message="删除音频模型配置成功") except Exception as e: return create_api_response(code="500", message=f"删除音频模型配置失败: {str(e)}") def test_llm_model_config(request): try: payload = request.model_dump() if hasattr(request, "model_dump") else request.dict() result = llm_service.test_model(payload, prompt=request.test_prompt) return create_api_response(code="200", message="LLM模型测试成功", data=result) except Exception as e: return create_api_response(code="500", message=f"LLM模型测试失败: {str(e)}") def test_audio_model_config(request): try: if request.model_type != "asr": return create_api_response(code="400", message="当前仅支持音频识别(ASR)测试") vocabulary_id = _resolve_audio_vocabulary_id(request) extra_config = _merge_audio_extra_config(request, vocabulary_id=vocabulary_id) runtime_config = { "provider": request.provider, "endpoint_url": request.endpoint_url, "api_key": request.api_key, "model_type": request.model_type, "request_timeout_seconds": request.request_timeout_seconds, **extra_config, } result = transcription_service.test_asr_model(runtime_config, test_file_url=request.test_file_url) return create_api_response(code="200", message="音频模型测试任务已提交", data=result) except Exception as e: return create_api_response(code="500", message=f"音频模型测试失败: {str(e)}") def get_public_system_config(): try: return create_api_response( code="200", message="获取公开配置成功", data=SystemConfigService.get_public_configs(), ) except Exception as e: return create_api_response(code="500", message=f"获取公开配置失败: {str(e)}")