Files

144 lines
6.1 KiB
Python

"""Regression R4: 防止能力路由误判回归。
R4.1: detector_media_intent 对纯文本输入返回 None(不误判为 image/video)。
R4.2: resolve_auto_model("text") 返回有 text 能力的模型。
R4.3: 普通多轮对话必须走 text 模型,不可走 image/video。
R4.4: 所有已注册能力可由 resolve_auto_model 解析到启用模型或 None。
R4.5: is_default 模型必须存在且启用。
"""
import json
import pytest
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from app.api.chat_media import detect_media_intent, detect_ambiguous_creative_intent
from app.capability_registry import resolve_auto_model, get_capability_defaults, CAPABILITY_TYPES, TEXT_FAMILY
from app.database import SessionLocal
from app.models import AIModel
@pytest.fixture(scope="module")
def db():
"""获得独立测试数据库会话"""
db = SessionLocal()
yield db
db.close()
# ─── R4.1: detect_media_intent regression suite ───
MEDIA_INTENT_CASES = [
# (input, expected_intent)
("你好", None),
("今天天气怎么样", None),
("帮我写一篇文章", None),
("平台有哪些技能能", None), # 历史 bug:曾被误判为 image
("新对话", None), # 新建对话入口
("画一只小猫", "image"),
("生成一张海报", "image"),
("给我画个头像", "image"),
("生成一个视频介绍", "video"),
("做一段短片", "video"),
("来段视频", "video"),
("朗读这段文字", "audio"),
("语音合成一段播报", "audio"),
("你能生成视频吗?", None), # 疑问句不触发
("怎么画一只猫?", None),
("如何生成图片呢?", None),
("画一只小猫,要可爱的", "image"), # 有明确指令
("Generate a cat image", "image"), # 英文
("help me create a video", "video"),
]
class TestR4_1_MediaIntent:
@pytest.mark.parametrize("text,expected", MEDIA_INTENT_CASES)
def test_detect_media_intent(self, text, expected):
result = detect_media_intent(text)
assert result == expected, f"detect_media_intent({text!r}) = {result!r}, expected {expected!r}"
# ─── R4.2: resolve_auto_model 回归 ───
class TestR4_2_AutoModel:
def test_text_model_has_text_capability(self, db):
"""文本对话默认模型必须有 text 能力"""
m = resolve_auto_model("text", db)
assert m is not None, "resolve_auto_model('text') returned None"
caps = [c.lower() for c in (m.capabilities or [])]
assert "text" in caps, f"Text model {m.name} has no 'text' capability: {caps}"
def test_text_model_not_image_video(self, db):
"""文本默认模型不应触发 image/video 误判"""
m = resolve_auto_model("text", db)
caps = [c.lower() for c in (m.capabilities or [])]
if "image" in caps or "video" in caps:
# If text model has image caps, detect_media_intent must catch it
assert detect_media_intent("你好") is None, "Normal text detected as media despite model having image caps"
assert detect_media_intent("新对话") is None
def test_thinking_model_exists(self, db):
m = resolve_auto_model("thinking", db)
assert m is not None, "resolve_auto_model('thinking') returned None"
def test_all_11_capabilities_resolvable(self, db):
"""所有 11 个能力至少有一个模型或 None"""
for cap_def in CAPABILITY_TYPES:
cap = cap_def["id"]
m = resolve_auto_model(cap, db)
is_text_family = cap in TEXT_FAMILY
if is_text_family:
assert m is not None, f"{cap} is TEXT_FAMILY but resolve_auto_model returned None"
# media/embedding/rerank/asr/ocr can legitimately be None if no model
# ─── R4.3: is_default model exists ───
class TestR4_3_DefaultModel:
def test_default_model_exists(self, db):
m = db.query(AIModel).filter(AIModel.is_default == True, AIModel.is_enabled == True).first()
assert m is not None, "No is_default=true and enabled model"
caps = [c.lower() for c in (m.capabilities or [])]
assert "text" in caps, f"Default model {m.name} must have 'text' capability: {caps}"
def test_default_model_resolvable(self, db):
m = db.query(AIModel).filter(AIModel.is_default == True, AIModel.is_enabled == True).first()
assert m.id == resolve_auto_model("text", db).id, "is_default model != resolve_auto_model('text')"
# ─── R4.4: capability defaults consistency ───
class TestR4_4_CapabilityDefaults:
def test_text_defaults_in_sync_with_auto(self, db):
"""site_settings text capability defaults must match resolve_auto_model"""
defaults = get_capability_defaults(db)
for cap in ("text", "thinking"):
mid = defaults.get(cap)
if mid:
m = resolve_auto_model(cap, db)
assert m is not None, f"SiteSetting {cap}->{mid} not resolvable"
assert m.id == mid, f"Mismatch: site_setting {cap}={mid} but resolve_auto_model returns {m.id}"
def test_no_disabled_models_in_defaults(self, db):
defaults = get_capability_defaults(db)
for cap, mid in defaults.items():
if mid:
m = db.query(AIModel).filter(AIModel.id == mid, AIModel.is_enabled == True).first()
assert m is not None, f"Capability {cap} points to disabled/nonexistent model {mid}"
# ─── R4.5: detect_ambiguous_creative_intent smoke ───
class TestR4_5_AmbiguousIntent:
def test_ambiguous_detected(self):
r = detect_ambiguous_creative_intent("帮我做一个东西")
assert r is not None, "Ambiguous creative intent not detected"
def test_explicit_not_ambiguous(self):
r = detect_ambiguous_creative_intent("帮我写一篇文章")
assert r is None, "Article request should not be ambiguous"
def test_media_not_ambiguous(self):
r = detect_ambiguous_creative_intent("画一只小猫")
assert r is None, "Image intent should bypass ambiguous detection"