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
+1 -1
View File
@@ -84,7 +84,7 @@ class TestOrchestratorWritesAgentWeights:
captured_values = {}
async def mock_specialists(h, *, version, before):
async def mock_specialists(h, *, version, before, model_override=None):
return reports
async def mock_provider(aid, **kw):