feat: give zhipu, moonshot, anthropic and google a wire of their own

Eight segments now, and each one holds a ThinkingWire instead of two
fixed fragments: off, on_base, and the key a tier gets written to. The
two fragments could not say "on, at this depth", which is what every
current generation model wants.

Two deliberate behaviour changes fall out of it. The openai segment stops
reporting its shape as unknown — reasoning_effort is OpenAI's own field,
not a vendor dialect, so a compatible endpoint behind the gateway takes
it. And minimax's on-tier stops carrying a hardcoded medium: that was the
library picking a price for the caller, and medium is not even a tier GLM,
kimi or deepseek serve.

The issue #5 guards stay; their sample moves from "the openai segment" to
an explicitly registered unknown one, which is what they always meant to test.
This commit is contained in:
2026-09-05 00:42:58 -04:00
parent 2a50ddcf12
commit 7fabc792b2
7 changed files with 247 additions and 90 deletions
+19 -6
View File
@@ -16,6 +16,7 @@ from polygateway.errors import (
)
from polygateway.middleware.telemetry import TelemetryEmitter
from polygateway.pricing import ModelPrice, PricingTable
from polygateway.providers import ProviderProfile, ThinkingWire, register_provider
from polygateway.transports._http_errors import summarize_body
from polygateway.transports.openai_compat import (
OpenAICompatTransport,
@@ -60,10 +61,11 @@ def _sse_stream(*frames, done=True):
return httpx.Response(200, content=text.encode(), headers={"content-type": "text/event-stream"})
def _transport_for(handler):
def _transport_for(handler, *, registry=None):
mock = httpx.MockTransport(handler)
return OpenAICompatTransport(
client_factory=lambda src: httpx.AsyncClient(base_url=src.base_url, transport=mock)
client_factory=lambda src: httpx.AsyncClient(base_url=src.base_url, transport=mock),
registry=registry,
)
@@ -666,7 +668,9 @@ class TestRequestShaping:
@pytest.mark.parametrize(
("enable_thinking", "expected"),
[(True, "medium"), (False, "none")],
# 开档不再附 medium(2026-09-04): 那是替下游做的档位判断,且 medium 不在
# GLM/kimi/deepseek 的档位表里。MiniMax 开启档本就无需参数,要强度请配档位
[(True, None), (False, "none")],
)
async def test_minimax_injects_reasoning_effort(self, enable_thinking, expected):
"""issue #5: MiniMax 认的是 reasoning_effort,不是 enable_thinking。"""
@@ -680,7 +684,10 @@ class TestRequestShaping:
name="mm", provider="minimax", model="MiniMax-M3", enable_thinking=enable_thinking
)
await _complete(_transport_for(handler), source)
assert seen["reasoning_effort"] == expected
if expected is None:
assert "reasoning_effort" not in seen
else:
assert seen["reasoning_effort"] == expected
assert "enable_thinking" not in seen # 旧形态实测被静默丢弃,不再下发
async def test_extra_body_overrides_the_profile_slot(self):
@@ -758,9 +765,15 @@ class TestRequestShaping:
def handler(request): # pragma: no cover - 不该走到发请求
raise AssertionError("请求不该发出")
source = _source(name="k3", provider="openai", model="kimi-k3", enable_thinking=False)
# 2026-09-04 起默认表 8 段全部有形态,守卫样本改为显式注册的未知段
mystery = ProviderProfile(
name="mystery",
thinking=ThinkingWire(off=None, on_base=None, effort_key=None),
strip_think_tags=False,
)
source = _source(name="k3", provider="mystery", model="kimi-k3", enable_thinking=False)
with pytest.raises(RequestRejectedError, match="register_provider"):
await _complete(_transport_for(handler), source)
await _complete(_transport_for(handler, registry=register_provider(mystery)), source)
async def test_overlay_merged_into_payload(self):
seen = {}