fix(im_llm): 代理绕过Docker内部hostname
This commit is contained in:
1 parent
96aabc31f6
commit
9a43ee244d
24 files changed
+898
-129
No files matched your search
@@ -0,0 +1,143 @@
|
||||
"""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"
|
||||
Reference in new issue
Block a user