refactor(aibox): make profile reasoning_enabled a constructor param (M3)
Profile-level reasoning_enabled was attached as an instance attribute after construction only when a profile matched; the base class never declared it and consumers probed with double getattr. Any code that accidentally set client.reasoning_enabled would silently override the global switch. The flag is now an explicit LLMClient constructor parameter with three-state semantics: True/False (profile-level explicit, or the global api.reasoning_enabled value concretized when the profile omits the field) and None (no profile -> consumer falls back to global). create_llm_client passes it at construction and no longer mutates the client afterwards; DeepSeekChat._reasoning_enabled simplifies to a single getattr probe that still tolerates clients without the attr. Tests: TestM3ReasoningConstructor (4 cases) covering profile True, profile-omitted -> global concretized, base default None, and the None -> global fallback at the consumer; one legacy assertion updated to the new contract (attribute exists, value None). Full suite 299 passed.
This commit is contained in:
1 parent
5f3ca9a246
commit
a583f44d5d
4 files changed
+71
-17
No files matched your search
@@ -161,11 +161,11 @@ class TestCreateClient:
|
||||
assert c.api_key == "sk-ark"
|
||||
assert c.base_url == "https://ark.example/v1/chat/completions"
|
||||
|
||||
def test_no_profile_no_reasoning_attr(self):
|
||||
"""无档案 → client 无 reasoning_enabled 属性(走全局配置)"""
|
||||
def test_no_profile_reasoning_is_none(self):
|
||||
"""无档案 → reasoning_enabled=None(构造默认,消费方回退全局配置)"""
|
||||
with mock.patch.dict(os.environ, {"DEEPSEEK_API_KEY": "sk-ds"}):
|
||||
c = create_llm_client(_cfg(models=None))
|
||||
assert not hasattr(c, "reasoning_enabled")
|
||||
assert c.reasoning_enabled is None
|
||||
|
||||
|
||||
# ==================== resolve_api_key 档案级解析 ====================
|
||||
@@ -479,6 +479,53 @@ class TestM1SetClient:
|
||||
fake_sub_chat.set_client.assert_called_once_with(fake_new_client)
|
||||
|
||||
|
||||
# ==================== M3:reasoning_enabled 构造参数契约 ====================
|
||||
|
||||
class TestM3ReasoningConstructor:
|
||||
"""M3:档案级 reasoning_enabled 从"事后挂实例属性"改为构造参数。
|
||||
|
||||
契约:LLMClient.reasoning_enabled 三态——True/False(档案级显式,
|
||||
含档案缺省时回退全局值的具体化)/ None(无档案,消费方回退全局)。
|
||||
"""
|
||||
|
||||
def test_profile_true_via_constructor(self):
|
||||
"""档案显式 reasoning_enabled=True → 构造参数传入 True"""
|
||||
with mock.patch.dict(os.environ, {"MOONSHOT_API_KEY": "sk-moon"}):
|
||||
c = create_llm_client(_cfg(), model="kimi")
|
||||
assert c.reasoning_enabled is True
|
||||
|
||||
def test_profile_missing_falls_back_to_global_concrete(self):
|
||||
"""档案未配置该字段 → 具体化为全局 api.reasoning_enabled 的值"""
|
||||
models = dict(MODELS)
|
||||
models["noreason"] = {
|
||||
"api_url": "https://ark.example/v1/chat/completions",
|
||||
"model": "doubao-pro",
|
||||
"api_key_env": "ARK_API_KEY",
|
||||
}
|
||||
with mock.patch.dict(os.environ, {"ARK_API_KEY": "sk-ark"}):
|
||||
c = create_llm_client(_cfg(models=models), model="noreason")
|
||||
assert c.reasoning_enabled is False # 全局 api.reasoning_enabled=False
|
||||
|
||||
def test_base_client_default_is_none(self):
|
||||
"""基类构造默认 → reasoning_enabled=None(未配置语义)"""
|
||||
from llm_client import DeepSeekClient
|
||||
with mock.patch.dict(os.environ, {"DEEPSEEK_API_KEY": "sk-ds"}):
|
||||
c = DeepSeekClient(api_key="k", model="m", base_url="https://x")
|
||||
assert c.reasoning_enabled is None
|
||||
|
||||
def test_chat_none_falls_back_to_global(self):
|
||||
"""DeepSeekChat 消费端:client.reasoning_enabled=None → 全局配置"""
|
||||
from test_once_mode import _make_ai
|
||||
ai = _make_ai()
|
||||
ai.client.reasoning_enabled = None # 显式置 None(模拟无档案契约)
|
||||
assert ai._reasoning_enabled() == constants_reasoning_default()
|
||||
|
||||
|
||||
def constants_reasoning_default():
|
||||
from common import constants
|
||||
return constants.REASONING_ENABLED
|
||||
|
||||
|
||||
# ==================== DeepSeekChat 档案级 reasoning 读取 ====================
|
||||
|
||||
class TestChatReasoningFlag:
|
||||
|
||||
Reference in new issue
Block a user