"""Bug-audit endpoint for runtime regression checks.""" from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from sqlalchemy import text from app.database import get_db from app.models import SiteSetting, AIModel from app.capability_registry import CAPABILITY_TYPES, get_capability_defaults, resolve_auto_model import logging logger = logging.getLogger(__name__) router = APIRouter() @router.get("/bug-audit") def bug_audit(db: Session = Depends(get_db)): """ Runtime self-check: 验证平台大模型路由/能力配置的关键不变量。 用于发现重复 bug(比如「已修复又出现」)。 """ issues = [] warnings = [] checks = [] # Check 1: 文本类能力(text/thinking) 必须指向启用的非图像/视频模型 TEXT_FAMILY = {"text", "thinking", "vision", "tool"} defaults = get_capability_defaults(db) for cap in ("text", "thinking"): mid = defaults.get(cap) if not mid: issues.append(f"{cap}: site_settings.model_capability_defaults 未设置") continue m = db.query(AIModel).filter(AIModel.id == mid, AIModel.is_enabled == True).first() if not m: issues.append(f"{cap}: 指向的模型 {mid} 不存在或已禁用") continue caps = [c.lower() for c in (m.capabilities or [])] if not ("text" in caps or "thinking" in caps): issues.append(f"{cap}: 指向的 {m.name} 没有 text/thinking 能力 (caps={caps})") if "image" in caps or "video" in caps: warnings.append(f"{cap}: 默认模型 {m.name} 含 image/video 能力 (普通多轮对话可能误判)") checks.append(f"{cap} -> {m.name} (caps={caps})") # Check 2: 必须有 is_default=true 的模型 default_model = db.query(AIModel).filter(AIModel.is_default == True, AIModel.is_enabled == True).first() if not default_model: issues.append("没有任何 is_default=true 且启用的模型") else: checks.append(f"is_default -> {default_model.name}") # Check 3: 所有 enabled 的 chat 模型都应该能正常 resolve for cap in ("text", "image", "video", "audio", "embedding", "rerank", "asr", "ocr"): m = resolve_auto_model(cap, db) if m: checks.append(f"resolve_auto_model({cap}) -> {m.name}") else: warnings.append(f"resolve_auto_model({cap}) 返回 None (该能力无默认模型)") # Check 4: detect_media_intent 单元自检(用经典样本) from app.api.chat_media import detect_media_intent test_cases = [ ("平台有哪些技能能", None), # 普通文字 ("你好", None), # 普通问候 ("画一只小猫", "image"), # 明确图像 ("生成一个视频介绍", "video"), # 明确视频 ("今天天气如何?", None), # 疑问句 ] for text_in, expected in test_cases: result = detect_media_intent(text_in) if result != expected: issues.append(f"detect_media_intent({text_in!r})={result!r} 期望 {expected!r}") else: checks.append(f"detect_media_intent({text_in!r})={result!r} ✓") # 汇总 healthy = len(issues) == 0 return { "healthy": healthy, "issues": issues, "warnings": warnings, "checks": checks, "summary": { "total_checks": len(checks), "issues": len(issues), "warnings": len(warnings), } }