diff --git a/src/polygateway/thinking.py b/src/polygateway/thinking.py index da8af1f..55f3837 100644 --- a/src/polygateway/thinking.py +++ b/src/polygateway/thinking.py @@ -696,6 +696,13 @@ def _tier_unsupported( if fallback == "nearest" or effort is Effort.AUTO else ";若希望自动落到最近的档,请配 EFFORT_FALLBACK=nearest" ) + if effort is Effort.AUTO: + example = capability.cheapest_effort or Effort.NONE + hint = ( + ";请显式选择清单中的档位,例如 " + f"{{SCOPE}}__{{PROVIDER}}__{{N}}__REASONING_EFFORT={example.value}," + f"或调用时传 reasoning_effort=Effort.{example.name};库不会自动应用此选择" + ) return f"{head}{body}{hint}" diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index af51c6b..e08e8b8 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1316,3 +1316,27 @@ class TestManagedReasoningAdmission: assert (await client._limiter_backend.source_stats(source.name)).inflight == 0 finally: await client._transport.aclose() + + +async def test_managed_conflict_returns_half_open_probe_and_permit(): + """本地拒绝没有上游响应,不计故障且必须归还半开探针。""" + from tests.contracts.conftest import FakeClock + + clock = FakeClock() + source = _source( + provider="openai", reasoning_effort="high", extra_body={"reasoning_effort": "high"} + ) + gate = InMemoryGate(config=BreakerConfig(1, 1, 10), now=clock) + entry = await gate.try_enter(source.name, "setup") + await gate.record_failure(entry, "source_dead", True) + clock.advance(2) + client = _client(sources=[source], breaker=gate) + try: + with pytest.raises(RequestRejectedError, match="冲突"): + await client.chat([]) + assert (await client._limiter_backend.source_stats(source.name)).inflight == 0 + next_entry = await gate.try_enter(source.name, "next") + assert next_entry.allowed and next_entry.is_probe + await gate.release_probe(next_entry) + finally: + await client._transport.aclose() diff --git a/tests/unit/test_thinking.py b/tests/unit/test_thinking.py index e3bb6bc..00c00cc 100644 --- a/tests/unit/test_thinking.py +++ b/tests/unit/test_thinking.py @@ -911,3 +911,14 @@ class TestThinkingRawOwnership: wire = ThinkingWire(off=None, on_base={"output_config": {"enabled": True}}, effort_key=None) with pytest.raises(ThinkingUnsupportedError): validate_thinking_raw(raw, effort=Effort.AUTO, wire=wire, origin="test") + + +def test_auto_rejection_explains_how_to_choose_explicitly(): + """可执行配置是指路,不由库自动应用其建议。""" + with pytest.raises(ThinkingUnsupportedError) as error: + resolve_thinking( + get_provider("minimax"), get_capability("MiniMax-M3"), Effort.AUTO, model="MiniMax-M3" + ) + assert "REASONING_EFFORT=" in str(error.value) + assert "reasoning_effort=Effort." in str(error.value) + assert "EFFORT_FALLBACK=nearest" not in str(error.value)