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 _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.audio_scene == "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 _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_hot_word_vocabulary_id(cursor, request) -> str | None: vocabulary_id = _parse_json_object(request.extra_config).get("vocabulary_id") if request.hot_word_group_id: cursor.execute("SELECT vocabulary_id FROM hot_word_group WHERE id = %s", (request.hot_word_group_id,)) group_row = cursor.fetchone() if group_row and group_row.get("vocabulary_id"): vocabulary_id = group_row["vocabulary_id"] return vocabulary_id def list_parameters(category: str | None = None, keyword: str | None = None): try: 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 = 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 get_parameter(param_key: str): try: 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: 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: 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, llm_temperature, llm_top_p, llm_max_tokens, llm_system_prompt, description, is_active, is_default, created_at, updated_at FROM llm_model_config ORDER BY model_code ASC """ ) rows = 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: 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") 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 is_default = 1") cursor.execute( """ INSERT INTO llm_model_config (model_code, model_name, provider, endpoint_url, api_key, llm_model_name, llm_timeout, llm_temperature, llm_top_p, llm_max_tokens, llm_system_prompt, description, is_active, is_default) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """, ( request.model_code, request.model_name, request.provider, request.endpoint_url, request.api_key, request.llm_model_name, request.llm_timeout, request.llm_temperature, request.llm_top_p, request.llm_max_tokens, request.llm_system_prompt, 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: 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,)) existed = cursor.fetchone() if not existed: return create_api_response(code="404", 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_code <> %s AND is_default = 1", (model_code,), ) 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, llm_temperature = %s, llm_top_p = %s, llm_max_tokens = %s, llm_system_prompt = %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, request.llm_temperature, request.llm_top_p, request.llm_max_tokens, request.llm_system_prompt, 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.audio_scene, a.provider, a.endpoint_url, a.api_key, a.request_timeout_seconds, a.hot_word_group_id, a.extra_config, a.description, a.is_active, a.is_default, a.created_at, a.updated_at, g.name AS hot_word_group_name, g.vocabulary_id AS hot_word_group_vocab_id FROM audio_model_config a LEFT JOIN hot_word_group g ON g.id = a.hot_word_group_id """ params = [] if scene in ("asr", "voiceprint"): sql += " WHERE a.audio_scene = %s" params.append(scene) sql += " ORDER BY a.audio_scene 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.audio_scene not in ("asr", "voiceprint"): return create_api_response(code="400", message="audio_scene 仅支持 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 audio_scene = %s", (request.audio_scene,)) 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 audio_scene = %s AND is_default = 1", (request.audio_scene,), ) asr_vocabulary_id = _resolve_hot_word_vocabulary_id(cursor, request) extra_config = _merge_audio_extra_config(request, vocabulary_id=asr_vocabulary_id) cursor.execute( """ INSERT INTO audio_model_config (model_code, model_name, audio_scene, provider, endpoint_url, api_key, request_timeout_seconds, hot_word_group_id, 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.audio_scene, request.provider, request.endpoint_url, request.api_key, request.request_timeout_seconds, request.hot_word_group_id, 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.audio_scene not in ("asr", "voiceprint"): return create_api_response(code="400", message="audio_scene 仅支持 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", (model_code,)) existed = cursor.fetchone() if not existed: return create_api_response(code="404", 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 audio_scene = %s AND model_code <> %s AND is_default = 1", (request.audio_scene, model_code), ) asr_vocabulary_id = _resolve_hot_word_vocabulary_id(cursor, 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, audio_scene = %s, provider = %s, endpoint_url = %s, api_key = %s, request_timeout_seconds = %s, hot_word_group_id = %s, extra_config = %s, description = %s, is_active = %s, is_default = %s WHERE model_code = %s """, ( new_model_code, request.model_name, request.audio_scene, request.provider, request.endpoint_url, request.api_key, request.request_timeout_seconds, request.hot_word_group_id, 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.audio_scene != "asr": return create_api_response(code="400", message="当前仅支持音频识别(ASR)测试") with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) vocabulary_id = _resolve_hot_word_vocabulary_id(cursor, 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, "audio_scene": request.audio_scene, "hot_word_group_id": request.hot_word_group_id, "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)}") def get_system_config_compat(): try: with get_db_connection() as conn: cursor = conn.cursor(dictionary=True) cursor.execute( """ SELECT param_key, param_value FROM sys_system_parameters WHERE is_active = 1 """ ) rows = cursor.fetchall() data = {row["param_key"]: row["param_value"] for row in rows} if "max_audio_size" in data: try: data["MAX_FILE_SIZE"] = int(data["max_audio_size"]) * 1024 * 1024 except Exception: data["MAX_FILE_SIZE"] = 100 * 1024 * 1024 if "max_image_size" in data: try: data["MAX_IMAGE_SIZE"] = int(data["max_image_size"]) * 1024 * 1024 except Exception: data["MAX_IMAGE_SIZE"] = 10 * 1024 * 1024 else: data.setdefault("MAX_IMAGE_SIZE", 10 * 1024 * 1024) return create_api_response(code="200", message="获取系统配置成功", data=data) except Exception as e: return create_api_response(code="500", message=f"获取系统配置失败: {str(e)}")