File size: 4,111 Bytes
19729e9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | """模型配置优先级测试:用户 > 管理员 > 环境变量(需求 5 / 15.5)。
验证 ``services.model_config.resolve_chain`` 的优先级、Fallback 拼接与去重,
以及 custom 提供商的可用性判定。导入路径由 tests/conftest.py 设置。
"""
from services.model_config import ( # noqa: E402
UserModelConfig,
resolve_chain,
build_llm_service,
)
def _admin(enabled=True, provider="kimi", api_key="admin-key", model="", base_url=""):
return {"enabled": enabled, "provider": provider, "api_key": api_key,
"model": model, "base_url": base_url}
def test_user_config_takes_priority():
"""用户自配可用时,链首为用户配置,source=user。"""
user = UserModelConfig(provider="deepseek", api_key="user-key", model="deepseek-v4-pro")
chain = resolve_chain(user_cfg=user, admin_cfg=_admin(), env={"OPENAI_API_KEY": "envk"})
assert chain.source == "user"
assert chain.active_provider == "deepseek"
assert chain.active_model == "deepseek-v4-pro"
# 管理员与环境档作为 Fallback 仍在链中。
providers = [s.provider for s in chain.specs]
assert "kimi" in providers and "openai" in providers
def test_admin_used_when_no_user_config():
"""用户未配置时,使用管理员配置,source=admin。"""
chain = resolve_chain(user_cfg=None, admin_cfg=_admin(provider="zhipu", api_key="ak"),
env={"OPENAI_API_KEY": "envk"})
assert chain.source == "admin"
assert chain.active_provider == "zhipu"
# 环境档兜底仍在。
assert "openai" in [s.provider for s in chain.specs]
def test_env_used_when_no_user_or_admin():
"""用户与管理员都没有时,回退环境变量,source=env。"""
chain = resolve_chain(user_cfg=None, admin_cfg=_admin(enabled=False),
env={"DEEPSEEK_API_KEY": "envk"})
assert chain.source == "env"
assert chain.active_provider == "deepseek"
def test_none_when_nothing_configured():
chain = resolve_chain(user_cfg=None, admin_cfg=None, env={})
assert chain.source == "none"
assert chain.specs == []
def test_disabled_admin_is_ignored():
chain = resolve_chain(user_cfg=None, admin_cfg=_admin(enabled=False), env={})
assert chain.source == "none"
def test_custom_user_requires_base_url_and_model():
"""custom 用户配置缺 base_url/model 时不可用,回退下一档。"""
bad = UserModelConfig(provider="custom", api_key="k") # 缺 base_url + model
chain = resolve_chain(user_cfg=bad, admin_cfg=_admin(), env={})
assert chain.source == "admin" # 用户档不可用 → 用管理员
good = UserModelConfig(provider="custom", api_key="k",
base_url="https://x/v1/chat/completions", model="m")
chain2 = resolve_chain(user_cfg=good, admin_cfg=_admin(), env={})
assert chain2.source == "user"
assert chain2.active_provider == "custom"
assert chain2.active_model == "m"
def test_admin_custom_requires_base_url():
"""管理员 custom 配置缺 base_url/model 时视为不可用。"""
chain = resolve_chain(
user_cfg=None,
admin_cfg=_admin(provider="custom", api_key="k"), # 缺 base_url+model
env={},
)
assert chain.source == "none"
def test_dedup_keeps_higher_priority():
"""用户与环境同为 deepseek 时去重,仅保留用户档(更高优先级)。"""
user = UserModelConfig(provider="deepseek", api_key="user-key")
chain = resolve_chain(user_cfg=user, admin_cfg=None, env={"DEEPSEEK_API_KEY": "envk"})
deepseek_specs = [s for s in chain.specs if s.provider == "deepseek"]
assert len(deepseek_specs) == 1
assert deepseek_specs[0].api_key == "user-key"
def test_build_llm_service_preserves_order():
user = UserModelConfig(provider="deepseek", api_key="uk", model="deepseek-v4-flash")
chain = resolve_chain(user_cfg=user, admin_cfg=_admin(provider="kimi", api_key="ak"), env={})
svc = build_llm_service(chain)
assert svc._specs[0].provider == "deepseek"
assert svc._specs[1].provider == "kimi"
|