feat: warn when the capability table and reality disagree

The M3 evidence sat at 08-02 for twenty-three days while nobody could
tell whether it still held. A declaration that goes stale in silence is
the failure this issue is really about, so the library now compares
what it declared against what it just observed and says so when the two
part ways.

Judgement is separated from logging: reconcile_thinking returns the
warning text, so tests assert on the text instead of parsing logs.
Two cases that look alike are kept apart — a model whose capability is
registered gets a drift warning quoting its evidence, an unregistered
one is never told the table said anything, because it never did.

False x UNKNOWN stays silent on purpose. UNKNOWN cannot falsify
anything, and warning on it would fire on every disabled call M3 makes
over the plain endpoint. A warning that always fires is not a warning.
This commit is contained in:
2026-08-26 00:23:57 -04:00
parent 3e869b9b39
commit 20a4a9ae47
4 changed files with 260 additions and 2 deletions
+50
View File
@@ -546,6 +546,56 @@ class TestThinkingObservationVerdict:
assert result.thinking_observation is ThinkingObservation.ABSENT
class TestThinkingReconciliation:
"""对账告警按 (model, direction) 节流(设计 §5)。
节流键必须含方向: 同一模型的开、关两档是两个独立的矛盾,合并键会让先出现
的那一档把另一档永久静音。
"""
def _handler(self, request):
payload = json.loads(request.content)
if payload.get("reasoning_effort") == "none":
# 关闭档却回了推理正文 → OBSERVED,与"要求关闭"矛盾
return _sse_stream(
_chunk(reasoning="偷偷想了"), _chunk(content="ok"), _chunk(usage=_USAGE)
)
# 开启档却零信号 → UNKNOWN,无法确认是否生效(M3 实测形态)
return _sse_stream(_chunk(content="ok"), _chunk(usage=_USAGE))
def _minimax(self, enable_thinking):
return _source(
name="mm", provider="minimax", model="MiniMax-M3", enable_thinking=enable_thinking
)
async def test_same_model_and_direction_warns_only_once(self):
transport = _transport_for(self._handler)
source = self._minimax(False)
messages: list[str] = []
sink_id = logger.add(messages.append, level="WARNING")
try:
await _complete(transport, source)
await _complete(transport, source)
finally:
logger.remove(sink_id)
hits = [m for m in messages if "MiniMax-M3" in m]
assert len(hits) == 1, f"同一 (model, direction) 应只告警一次,实得 {len(hits)}"
async def test_switching_direction_earns_a_second_warning(self):
transport = _transport_for(self._handler)
messages: list[str] = []
sink_id = logger.add(messages.append, level="WARNING")
try:
await _complete(transport, self._minimax(False))
await _complete(transport, self._minimax(False))
await _complete(transport, self._minimax(True))
await _complete(transport, self._minimax(True))
finally:
logger.remove(sink_id)
hits = [m for m in messages if "MiniMax-M3" in m]
assert len(hits) == 2, f"两个方向各应告警一次,实得 {len(hits)}"
class TestNonStreamFastPath:
async def test_non_stream_parses_message(self):
def handler(request):