fix(concurrency): 消除 model 覆盖的模块级变量中转(并发串味)

R4 首版实现用模块级 _ACTIVE_MODEL_OVERRIDE 中转 model override,
理由是不想改动 run_specialists 的签名(既有测试会 mock 它)。

但 backtest.py:225 会 asyncio.gather 并发 8 场预测(Semaphore(8)),
每场都调用 predict_match_multi —— 模块级变量会被并发调用互相覆盖,
导致 A 场的预测用上 B 场的模型。这是静默的正确性缺陷。

改为: run_specialists 新增 model_override 形参,一路显式下传;
删除模块级变量。同步更新 6 处 mock 签名。

新增 3 个守卫并做变异验证:
  - test_r4_no_module_level_model_override_global (源码级,可判别)
  - test_r4_run_specialists_accepts_model_override_parameter
  - test_r4_dispatch_passes_override_to_specialists
变异测试: 重新引入全局变量方案 → 两个守卫变红;还原 → 全绿。

注: 曾尝试写并发行为测试,但真实 run_specialists 会访问数据库,
测试环境下不稳定(ConnectionRefusedError),会是 flaky 的假证据,
故改用清晰的源码级判别守卫,并在注释中说明原因。

240 passed / 1 skipped; tsc 0 error; vite build 成功。
This commit is contained in:
shangfangjian
2026-09-21 17:52:45 +08:00
parent 50a6753ff8
commit 06e973d34d
5 changed files with 80 additions and 33 deletions
+4 -4
View File
@@ -66,7 +66,7 @@ class TestAllExpertsFailed:
header = _make_header()
# Mock run_specialists 返回全 error
async def mock_run_specialists(header, *, version, before):
async def mock_run_specialists(header, *, version, before, model_override=None):
return _all_error_reports()
# Mock _agent_provider
@@ -131,7 +131,7 @@ class TestAllExpertsFailed:
"""5 个专家全 no_data → status=degraded,不调终裁。"""
header = _make_header()
async def mock_run_specialists(header, *, version, before):
async def mock_run_specialists(header, *, version, before, model_override=None):
return _all_no_data_reports()
async def mock_agent_provider(agent_id, *, tier):
@@ -188,7 +188,7 @@ class TestPartialExpertsOk:
"""1 个 ok + 4 个 error → status=success(走终裁)。"""
header = _make_header()
async def mock_run_specialists(header, *, version, before):
async def mock_run_specialists(header, *, version, before, model_override=None):
return _mixed_reports()
async def mock_agent_provider(agent_id, *, tier):
@@ -255,7 +255,7 @@ class TestNoAggregatorCallOnDegraded:
header = _make_header()
aggregator_called = []
async def mock_run_specialists(header, *, version, before):
async def mock_run_specialists(header, *, version, before, model_override=None):
return _all_error_reports()
async def mock_agent_provider(agent_id, *, tier):