1331 lines
43 KiB
Python
1331 lines
43 KiB
Python
import logging
|
|
|
|
import pytest
|
|
|
|
from opensquilla.engine.pipeline import TurnContext
|
|
from opensquilla.engine.steps import squilla_router as squilla_router_step
|
|
from opensquilla.engine.steps.squilla_router import apply_squilla_router
|
|
from opensquilla.gateway.config import GatewayConfig
|
|
from opensquilla.squilla_router.v4_phase3 import V4Phase3Strategy
|
|
|
|
|
|
class FakeStrategy:
|
|
def __init__(self, tier: str, confidence: float, extra: dict) -> None:
|
|
self.tier = tier
|
|
self.confidence = confidence
|
|
self.extra = extra
|
|
self.calls = 0
|
|
self.messages: list[str] = []
|
|
|
|
async def classify(
|
|
self,
|
|
message: str,
|
|
valid_tiers: list[str],
|
|
routing_history: list[dict] | None = None,
|
|
) -> tuple[str, float, str, dict]:
|
|
self.calls += 1
|
|
self.messages.append(message)
|
|
assert self.tier in valid_tiers
|
|
return self.tier, self.confidence, "v4_phase3", dict(self.extra)
|
|
|
|
|
|
class ContextAwareFakeStrategy(FakeStrategy):
|
|
def __init__(self, tier: str, confidence: float, extra: dict) -> None:
|
|
super().__init__(tier, confidence, extra)
|
|
self.contexts: list[dict] = []
|
|
|
|
async def classify(
|
|
self,
|
|
message: str,
|
|
valid_tiers: list[str],
|
|
routing_history: list[dict] | None = None,
|
|
prev_assistant_text: str | None = None,
|
|
prev_assistant_usage: dict | None = None,
|
|
history_user_texts: list[str] | None = None,
|
|
flags_text_override: str | None = None,
|
|
) -> tuple[str, float, str, dict]:
|
|
self.calls += 1
|
|
self.messages.append(message)
|
|
self.contexts.append(
|
|
{
|
|
"routing_history": [dict(entry) for entry in routing_history or []],
|
|
"prev_assistant_text": prev_assistant_text,
|
|
"prev_assistant_usage": dict(prev_assistant_usage or {}),
|
|
"history_user_texts": list(history_user_texts or []),
|
|
"flags_text_override": flags_text_override,
|
|
}
|
|
)
|
|
assert self.tier in valid_tiers
|
|
return self.tier, self.confidence, "v4_phase3", dict(self.extra)
|
|
|
|
|
|
class ExplodingV4Strategy:
|
|
def __init__(self, *args, **kwargs) -> None:
|
|
raise RuntimeError(
|
|
"failed to initialize V4 Phase 3 router: DLL load failed while importing "
|
|
"onnxruntime_pybind11_state"
|
|
)
|
|
|
|
|
|
class MacLibompExplodingV4Strategy:
|
|
def __init__(self, *args, **kwargs) -> None:
|
|
raise RuntimeError(
|
|
"failed to initialize V4 Phase 3 router: dlopen("
|
|
".../lightgbm/lib/lib_lightgbm.dylib, 0x0006): Library not loaded: "
|
|
"@rpath/libomp.dylib Referenced from: .../lib_lightgbm.dylib"
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_squilla_router_state(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
squilla_router_step._history_store.clear()
|
|
squilla_router_step._strategy = None
|
|
squilla_router_step._strategy_key = None
|
|
squilla_router_step._router_runtime_warning_emitted = False
|
|
yield
|
|
squilla_router_step._history_store.clear()
|
|
squilla_router_step._strategy = None
|
|
squilla_router_step._strategy_key = None
|
|
squilla_router_step._router_runtime_warning_emitted = False
|
|
monkeypatch.undo()
|
|
|
|
|
|
def make_context(
|
|
message: str,
|
|
*,
|
|
rollout_phase: str = "full",
|
|
session_key: str = "test-session",
|
|
raw_message: str | None = None,
|
|
attachments: list[dict] | None = None,
|
|
) -> TurnContext:
|
|
# Pin the packaged openrouter ladder: these tests assert tier moves by
|
|
# their distinct per-tier models, which the tokenrhythm default's uniform
|
|
# synthesized ladder cannot express.
|
|
config = GatewayConfig(llm={"provider": "openrouter"})
|
|
config.squilla_router.rollout_phase = rollout_phase
|
|
return TurnContext(
|
|
message=message,
|
|
session_key=session_key,
|
|
config=config,
|
|
provider=None,
|
|
model=config.llm.model,
|
|
tool_defs=[],
|
|
system_prompt="system",
|
|
raw_message=raw_message,
|
|
attachments=attachments or [],
|
|
)
|
|
|
|
|
|
def require_runtime_router() -> None:
|
|
try:
|
|
V4Phase3Strategy(require_router_runtime=True)
|
|
except Exception as exc:
|
|
pytest.skip(f"V4 model router runtime unavailable: {exc}")
|
|
|
|
|
|
def fake_strategy(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tier: str,
|
|
confidence: float,
|
|
extra: dict,
|
|
) -> FakeStrategy:
|
|
strategy = FakeStrategy(tier, confidence, extra)
|
|
monkeypatch.setattr(squilla_router_step, "_get_strategy", lambda _config: strategy)
|
|
return strategy
|
|
|
|
|
|
def context_aware_fake_strategy(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tier: str,
|
|
confidence: float,
|
|
extra: dict,
|
|
) -> ContextAwareFakeStrategy:
|
|
strategy = ContextAwareFakeStrategy(tier, confidence, extra)
|
|
monkeypatch.setattr(squilla_router_step, "_get_strategy", lambda _config: strategy)
|
|
return strategy
|
|
|
|
|
|
def test_vision_followup_gate_default_timeout_allows_real_provider_latency() -> None:
|
|
config = GatewayConfig()
|
|
|
|
assert config.squilla_router.vision_followup_gate_timeout_seconds >= 10.0
|
|
|
|
|
|
def test_vision_followup_gate_default_output_budget_handles_reasoning_models() -> None:
|
|
config = GatewayConfig()
|
|
|
|
assert config.squilla_router.vision_followup_gate_max_output_tokens >= 512
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_full_rollout_applies_routed_model_thinking_and_p0_prompt(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.91,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
ctx = make_context("Summarize this short note.")
|
|
baseline_model = ctx.model
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.model == "deepseek/deepseek-v4-pro"
|
|
assert routed.metadata["routed_tier"] == "c1"
|
|
assert routed.metadata["routed_model"] == "deepseek/deepseek-v4-pro"
|
|
assert routed.metadata["routing_applied"] is True
|
|
assert routed.metadata["applied_model"] == "deepseek/deepseek-v4-pro"
|
|
assert routed.metadata["baseline_model"] == baseline_model
|
|
assert routed.metadata["routing_confidence"] == 0.91
|
|
assert routed.metadata["routing_source"] == "v4_phase3"
|
|
assert "savings_pct" in routed.metadata
|
|
assert "savings_max_price_per_m" in routed.metadata
|
|
assert "savings_routed_price_per_m" in routed.metadata
|
|
assert routed.metadata["thinking_mode"] == "T1"
|
|
assert routed.metadata["thinking_requested"] is True
|
|
assert routed.metadata["thinking_level"] == "low"
|
|
assert routed.metadata["prompt_policy"] == "P0"
|
|
assert "[RESPONSE_POLICY: Answer directly" in routed.message
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_router_records_lower_text_tier_fallback_chain(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c3",
|
|
0.91,
|
|
{
|
|
"route_class": "R3",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
|
|
routed = await apply_squilla_router(make_context("Solve a difficult architecture problem."))
|
|
|
|
assert routed.metadata["routed_tier"] == "c3"
|
|
assert [item["tier"] for item in routed.metadata["router_fallback_chain"]] == [
|
|
"c2",
|
|
"c1",
|
|
"c0",
|
|
]
|
|
assert [item["model"] for item in routed.metadata["router_fallback_chain"]] == [
|
|
"z-ai/glm-5.2",
|
|
"deepseek/deepseek-v4-pro",
|
|
"deepseek/deepseek-v4-flash",
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_router_reports_provider_state_loss_without_changing_route(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.91,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
ctx = make_context("Continue the long task.")
|
|
ctx.metadata["session_context_states"] = [
|
|
{
|
|
"provider": "anthropic",
|
|
"model": "claude-sonnet-4-6",
|
|
"state_kind": "anthropic_compaction_block",
|
|
"valid": True,
|
|
"portable": False,
|
|
},
|
|
{
|
|
"provider": "portable",
|
|
"model": "",
|
|
"state_kind": "structured_summary_v1",
|
|
"valid": True,
|
|
"portable": True,
|
|
},
|
|
]
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.model == "deepseek/deepseek-v4-pro"
|
|
diagnostic = routed.metadata["provider_state_continuity"]
|
|
assert diagnostic["decision"] == "use_portable_fallback"
|
|
assert diagnostic["provider_state_loss_risk"] is True
|
|
assert diagnostic["candidate_provider"] == "openrouter"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_router_continuity_diagnostic_ignores_expired_provider_state(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.91,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
ctx = make_context("Continue the long task.")
|
|
ctx.metadata["session_context_states"] = [
|
|
{
|
|
"provider": "anthropic",
|
|
"model": "claude-sonnet-4-6",
|
|
"state_kind": "anthropic_compaction_block",
|
|
"created_at": 100,
|
|
"expires_at": 150,
|
|
"valid": True,
|
|
"portable": False,
|
|
},
|
|
{
|
|
"provider": "portable",
|
|
"model": "",
|
|
"state_kind": "structured_summary_v1",
|
|
"created_at": 90,
|
|
"valid": True,
|
|
"portable": True,
|
|
},
|
|
]
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
diagnostic = routed.metadata["provider_state_continuity"]
|
|
assert diagnostic["decision"] == "use_portable_fallback"
|
|
assert diagnostic["provider_state_loss_risk"] is False
|
|
assert diagnostic["active_state_provider"] is None
|
|
assert diagnostic["portable_fallback_available"] is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_p2_prompt_hint_is_recorded_but_not_injected(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c3",
|
|
0.97,
|
|
{
|
|
"route_class": "R3",
|
|
"thinking_mode": "T3",
|
|
"prompt_policy": "P2",
|
|
"prompt_hint": "Use a careful plan before answering.",
|
|
},
|
|
)
|
|
ctx = make_context("Plan a risky multi-step migration.")
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.model == "anthropic/claude-opus-4.8"
|
|
assert routed.metadata["routed_tier"] == "c3"
|
|
assert routed.metadata["thinking_level"] == "high"
|
|
assert routed.metadata["prompt_policy"] == "P2"
|
|
assert routed.metadata["routing_extra"]["prompt_hint"] == "Use a careful plan before answering."
|
|
assert "[RESPONSE_POLICY:" not in routed.message
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_v4_thinking_mode_overrides_explicit_tier_thinking_level(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c2",
|
|
0.92,
|
|
{
|
|
"route_class": "R2",
|
|
"thinking_mode": "T2",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
ctx = make_context("Analyze this implementation path.")
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routed_tier"] == "c2"
|
|
assert routed.metadata["thinking_mode"] == "T2"
|
|
assert routed.metadata["thinking_requested"] is True
|
|
assert routed.metadata["thinking_level"] == "medium"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_confidence_gate_promotes_low_confidence_t0_to_default_t1_and_reconciles_thinking(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c0",
|
|
0.1,
|
|
{
|
|
"route_class": "R0",
|
|
"thinking_mode": "T0",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
ctx = make_context("Maybe simple, but classifier is uncertain.")
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
extra = routed.metadata["routing_extra"]
|
|
|
|
assert routed.metadata["routed_tier"] == "c1"
|
|
assert routed.model == "deepseek/deepseek-v4-pro"
|
|
assert extra["confidence_gate_applied"] is True
|
|
assert extra["base_tier"] == "c0"
|
|
assert extra["final_tier"] == "c1"
|
|
assert routed.metadata["thinking_mode"] == "T1"
|
|
assert routed.metadata["thinking_level"] == "low"
|
|
assert "[RESPONSE_POLICY: Answer directly" in routed.message
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_confidence_gate_falls_back_low_confidence_non_default_text_tier(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c2",
|
|
0.1,
|
|
{
|
|
"route_class": "R2",
|
|
"thinking_mode": "T2",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
ctx = make_context("Classifier is uncertain but picked an expensive tier.")
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
extra = routed.metadata["routing_extra"]
|
|
|
|
assert routed.metadata["routed_tier"] == "c1"
|
|
assert routed.model == "deepseek/deepseek-v4-pro"
|
|
assert extra["confidence_gate_applied"] is True
|
|
assert extra["pre_confidence_tier"] == "c2"
|
|
assert extra["final_tier"] == "c1"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_confidence_gate_keeps_near_threshold_high_tier(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c3",
|
|
0.49,
|
|
{
|
|
"route_class": "R3",
|
|
"thinking_mode": "T3",
|
|
"prompt_policy": "P2",
|
|
},
|
|
)
|
|
ctx = make_context("Classifier is near threshold but chose a high-risk tier.")
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
extra = routed.metadata["routing_extra"]
|
|
|
|
assert routed.metadata["routed_tier"] == "c3"
|
|
assert extra["confidence_gate_applied"] is False
|
|
assert extra["pre_confidence_tier"] == "c3"
|
|
assert extra["final_tier"] == "c3"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_large_material_estimate_floors_low_router_tier(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(monkeypatch, "c1", 0.91, {"route_class": "R1"})
|
|
ctx = make_context("Please process the attached pasted text.")
|
|
ctx.metadata["input_normalization"] = {
|
|
"guard_action": "generated_text_attachment",
|
|
"material_estimated_tokens": 45_000,
|
|
}
|
|
ctx.metadata["material_estimated_tokens"] = 45_000
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routed_tier"] == "c2"
|
|
assert routed.metadata["routing_source"] == "large_context_floor"
|
|
assert routed.metadata["large_context_floor_from_tier"] == "c1"
|
|
assert routed.metadata["large_context_material_tokens"] == 45_000
|
|
assert routed.metadata["routing_extra"]["final_tier"] == "c2"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_large_material_ratio_floors_low_router_tier_to_t3(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(monkeypatch, "c1", 0.91, {"route_class": "R1"})
|
|
ctx = make_context("Please process the attached pasted text.")
|
|
object.__setattr__(ctx.config.squilla_router, "context_window_tokens", 100_000)
|
|
ctx.metadata["input_normalization"] = {
|
|
"guard_action": "generated_text_attachment",
|
|
"material_estimated_tokens": 40_000,
|
|
}
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routed_tier"] == "c3"
|
|
assert routed.metadata["routing_source"] == "large_context_floor"
|
|
assert routed.metadata["large_context_floor_from_tier"] == "c1"
|
|
assert routed.metadata["large_context_material_tokens"] == 40_000
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_anti_downgrade_keeps_recent_higher_tier_despite_confidence_gate(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
ctx1 = make_context("Hard first turn.", session_key="test-confidence-history")
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c2",
|
|
0.9,
|
|
{
|
|
"route_class": "R2",
|
|
"thinking_mode": "T2",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
routed1 = await apply_squilla_router(ctx1)
|
|
assert routed1.metadata["routed_tier"] == "c2"
|
|
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c0",
|
|
0.1,
|
|
{
|
|
"route_class": "R0",
|
|
"thinking_mode": "T0",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
ctx2 = make_context("Uncertain follow-up.", session_key="test-confidence-history")
|
|
|
|
routed2 = await apply_squilla_router(ctx2)
|
|
extra = routed2.metadata["routing_extra"]
|
|
|
|
assert routed2.metadata["routed_tier"] == "c2"
|
|
assert routed2.model == "z-ai/glm-5.2"
|
|
assert extra["confidence_gate_applied"] is True
|
|
assert extra["pre_confidence_tier"] == "c0"
|
|
assert extra["final_tier"] == "c2"
|
|
assert extra["anti_downgrade_applied"] is True
|
|
assert extra["previous_tier"] == "c2"
|
|
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.9,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
ctx3 = make_context("Normal follow-up.", session_key="test-confidence-history")
|
|
|
|
routed3 = await apply_squilla_router(ctx3)
|
|
extra3 = routed3.metadata["routing_extra"]
|
|
|
|
assert routed3.metadata["routed_tier"] == "c2"
|
|
assert routed3.model == "z-ai/glm-5.2"
|
|
assert extra3["confidence_gate_applied"] is False
|
|
assert extra3["anti_downgrade_applied"] is True
|
|
assert extra3["previous_tier"] == "c2"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_anti_downgrade_uses_previous_turn_not_window_highest(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
session_key = "test-previous-not-highest"
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c3",
|
|
0.9,
|
|
{
|
|
"route_class": "R3",
|
|
"thinking_mode": "T3",
|
|
"prompt_policy": "P2",
|
|
},
|
|
)
|
|
routed1 = await apply_squilla_router(make_context("Very hard turn.", session_key=session_key))
|
|
assert routed1.metadata["routed_tier"] == "c3"
|
|
|
|
ctx2 = make_context("Less hard turn.", session_key=session_key)
|
|
ctx2.config.squilla_router.kv_cache_anti_downgrade_enabled = False
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c2",
|
|
0.9,
|
|
{
|
|
"route_class": "R2",
|
|
"thinking_mode": "T2",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
routed2 = await apply_squilla_router(ctx2)
|
|
assert routed2.metadata["routed_tier"] == "c2"
|
|
|
|
ctx3 = make_context("Easy follow-up.", session_key=session_key)
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.9,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
routed3 = await apply_squilla_router(ctx3)
|
|
extra3 = routed3.metadata["routing_extra"]
|
|
|
|
assert routed3.metadata["routed_tier"] == "c2"
|
|
assert routed3.model == "z-ai/glm-5.2"
|
|
assert extra3["anti_downgrade_applied"] is True
|
|
assert extra3["previous_tier"] == "c2"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_anti_downgrade_keeps_previous_high_tier_without_margin_gate(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
session_key = "test-anti-downgrade-ignore-margin"
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c3",
|
|
0.95,
|
|
{
|
|
"route_class": "R3",
|
|
"thinking_mode": "T3",
|
|
"prompt_policy": "P2",
|
|
"margin": 0.99,
|
|
},
|
|
)
|
|
routed1 = await apply_squilla_router(
|
|
make_context("Architecture review.", session_key=session_key)
|
|
)
|
|
assert routed1.metadata["routed_tier"] == "c3"
|
|
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.99,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P1",
|
|
"margin": 0.99,
|
|
},
|
|
)
|
|
routed2 = await apply_squilla_router(make_context("Follow-up.", session_key=session_key))
|
|
extra = routed2.metadata["routing_extra"]
|
|
|
|
assert routed2.metadata["routed_tier"] == "c3"
|
|
assert routed2.model == "anthropic/claude-opus-4.8"
|
|
assert extra["anti_downgrade_applied"] is True
|
|
assert extra["previous_tier"] == "c3"
|
|
assert extra["kv_cache_window_seconds"] == 600
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_complaint_upgrade_promotes_tier_thinking_and_blocks_compressed_prompt(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.9,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
ctx = make_context("不对,重新回答")
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
extra = routed.metadata["routing_extra"]
|
|
|
|
assert routed.metadata["routed_tier"] == "c2"
|
|
assert routed.model == "z-ai/glm-5.2"
|
|
assert extra["complaint_detected"] is True
|
|
assert extra["complaint_upgrade_applied"] is True
|
|
assert routed.metadata["thinking_mode"] == "T2"
|
|
assert routed.metadata["thinking_level"] == "medium"
|
|
assert routed.metadata["prompt_policy"] == "P1"
|
|
assert "[RESPONSE_POLICY:" not in routed.message
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_complaint_upgrade_starts_from_previous_experienced_tier(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
session_key = "test-complaint-upgrade-previous-tier"
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c2",
|
|
0.9,
|
|
{
|
|
"route_class": "R2",
|
|
"thinking_mode": "T2",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
routed1 = await apply_squilla_router(
|
|
make_context("Analyze this tricky failure.", session_key=session_key)
|
|
)
|
|
assert routed1.metadata["routed_tier"] == "c2"
|
|
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.9,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
routed2 = await apply_squilla_router(make_context("答非所问", session_key=session_key))
|
|
extra = routed2.metadata["routing_extra"]
|
|
|
|
assert routed2.metadata["routed_tier"] == "c3"
|
|
assert routed2.model == "anthropic/claude-opus-4.8"
|
|
assert extra["previous_tier"] == "c2"
|
|
assert extra["complaint_detected"] is True
|
|
assert extra["complaint_upgrade_applied"] is True
|
|
assert extra["anti_downgrade_applied"] is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_complaint_upgrade_uses_pre_confidence_high_tier(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
session_key = "test-complaint-upgrade-pre-confidence-tier"
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.9,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
await apply_squilla_router(
|
|
make_context("Compare PostgreSQL and MySQL.", session_key=session_key)
|
|
)
|
|
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c2",
|
|
0.01,
|
|
{
|
|
"route_class": "R2",
|
|
"thinking_mode": "T2",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
routed = await apply_squilla_router(
|
|
make_context("不对,太泛了,重新写。", session_key=session_key)
|
|
)
|
|
extra = routed.metadata["routing_extra"]
|
|
|
|
assert extra["confidence_gate_applied"] is True
|
|
assert extra["pre_confidence_tier"] == "c2"
|
|
assert routed.metadata["routed_tier"] == "c3"
|
|
assert extra["complaint_upgrade_applied"] is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_router_classifies_raw_semantic_input_but_injects_prompt_into_display_message(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
strategy = fake_strategy(
|
|
monkeypatch,
|
|
"c0",
|
|
0.92,
|
|
{
|
|
"route_class": "R0",
|
|
"thinking_mode": "T0",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
ctx = make_context(
|
|
"Displayed prompt wrapper",
|
|
raw_message="Summarize the underlying user input.",
|
|
)
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert strategy.messages == ["Summarize the underlying user input."]
|
|
assert routed.metadata["routed_tier"] == "c0"
|
|
assert routed.metadata["prompt_policy"] == "P0"
|
|
assert routed.message.startswith("Displayed prompt wrapper")
|
|
assert "Summarize the underlying user input." not in routed.message
|
|
assert "[RESPONSE_POLICY: Answer directly" in routed.message
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_router_passes_transcript_context_into_strategy(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
strategy = context_aware_fake_strategy(
|
|
monkeypatch,
|
|
"c2",
|
|
0.88,
|
|
{
|
|
"route_class": "R2",
|
|
"thinking_mode": "T2",
|
|
"prompt_policy": "P1",
|
|
},
|
|
)
|
|
ctx = make_context("Continue from the previous answer.")
|
|
ctx.metadata.update(
|
|
{
|
|
"router_prev_assistant_text": "Previous assistant answer.",
|
|
"router_prev_assistant_usage": {"output_tokens": 321},
|
|
"router_history_user_texts": ["First user question.", "Second user question."],
|
|
"router_flags_text_override": "Continue from the previous answer.",
|
|
"routing_history": [
|
|
{
|
|
"text": "First user question.",
|
|
"route_class": "R1",
|
|
"final_route_class": "R1",
|
|
"difficulty": 1.0,
|
|
"margin": 0.5,
|
|
}
|
|
],
|
|
}
|
|
)
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routed_tier"] == "c2"
|
|
assert strategy.messages == ["Continue from the previous answer."]
|
|
assert strategy.contexts == [
|
|
{
|
|
"routing_history": [
|
|
{
|
|
"text": "First user question.",
|
|
"route_class": "R1",
|
|
"final_route_class": "R1",
|
|
"difficulty": 1.0,
|
|
"margin": 0.5,
|
|
"_ts": pytest.approx(strategy.contexts[0]["routing_history"][0]["_ts"]),
|
|
}
|
|
],
|
|
"prev_assistant_text": "Previous assistant answer.",
|
|
"prev_assistant_usage": {"output_tokens": 321},
|
|
"history_user_texts": ["First user question.", "Second user question."],
|
|
"flags_text_override": "Continue from the previous answer.",
|
|
}
|
|
]
|
|
|
|
|
|
def test_v4_request_contains_current_history_assistant_and_route_context() -> None:
|
|
class FakeInferenceRequest:
|
|
def __init__(self, **kwargs) -> None:
|
|
self.__dict__.update(kwargs)
|
|
|
|
strategy = V4Phase3Strategy(require_router_runtime=False)
|
|
strategy._request_type = FakeInferenceRequest
|
|
|
|
request = strategy._build_request(
|
|
"Current user question.",
|
|
[
|
|
{
|
|
"text": "Previous user question.",
|
|
"final_route_class": "R2",
|
|
"difficulty_score": 2.0,
|
|
"margin": 0.7,
|
|
}
|
|
],
|
|
prev_assistant_text="Previous assistant answer.",
|
|
prev_assistant_usage={"output_tokens": 456},
|
|
history_user_texts=["Earlier user question.", "Previous user question."],
|
|
flags_text_override="Current user question.",
|
|
)
|
|
|
|
assert request.current_user_text == "Current user question."
|
|
assert request.history_user_texts == ["Earlier user question.", "Previous user question."]
|
|
assert request.prev_assistant_text == "Previous assistant answer."
|
|
assert request.prev_assistant_usage == {"output_tokens": 456}
|
|
assert request.prev_route_decisions[0].route_class == "R2"
|
|
assert request.prev_route_decisions[0].difficulty == 2.0
|
|
assert request.prev_route_decisions[0].margin == 0.7
|
|
assert request.flags_text_override == "Current user question."
|
|
assert request.context_metadata["history_user_turn_count"] == 2
|
|
assert request.context_metadata["has_prev_assistant"] is True
|
|
assert request.context_metadata["context_tokens_est"] > 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_image_input_routes_directly_to_vision_model_without_prompt_injection(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
squilla_router_step,
|
|
"_get_strategy",
|
|
lambda _config: pytest.fail("image routing should not invoke text strategy"),
|
|
)
|
|
ctx = make_context(
|
|
"What is in this screenshot?",
|
|
attachments=[{"type": "image", "mime_type": "image/png"}],
|
|
)
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.model == "moonshotai/kimi-k2.6"
|
|
assert routed.metadata["routed_tier"] == "image_model"
|
|
assert routed.metadata["routed_model"] == "moonshotai/kimi-k2.6"
|
|
assert routed.metadata["routing_applied"] is True
|
|
assert routed.metadata["routing_confidence"] == 1.0
|
|
assert routed.metadata["routing_source"] == "image_route"
|
|
assert routed.metadata["route_max_history_turns"] == 1
|
|
assert routed.metadata["thinking_requested"] is True
|
|
assert routed.metadata["thinking_level"] == "medium"
|
|
assert "[RESPONSE_POLICY:" not in routed.message
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_image_route_uses_first_configured_image_tier_without_random_choice(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
squilla_router_step,
|
|
"_get_strategy",
|
|
lambda _config: pytest.fail("image routing should not invoke text strategy"),
|
|
)
|
|
monkeypatch.setattr(
|
|
"random.choice",
|
|
lambda _items: pytest.fail("image route should be deterministic"),
|
|
)
|
|
ctx = make_context(
|
|
"Describe this screenshot.",
|
|
attachments=[{"type": "image/png", "data": "abc"}],
|
|
)
|
|
ctx.config.squilla_router.tiers = {
|
|
"vision_primary": {
|
|
"model": "vision/primary",
|
|
"supports_image": True,
|
|
"thinking_level": "low",
|
|
},
|
|
"vision_backup": {
|
|
"model": "vision/backup",
|
|
"supports_image": True,
|
|
"thinking_level": "high",
|
|
},
|
|
"c1": {"model": "text/model"},
|
|
}
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routed_tier"] == "vision_primary"
|
|
assert routed.metadata["routed_model"] == "vision/primary"
|
|
assert routed.model == "vision/primary"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_image_route_records_tier_provider_for_cross_provider_execution(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
squilla_router_step,
|
|
"_get_strategy",
|
|
lambda _config: pytest.fail("image routing should not invoke text strategy"),
|
|
)
|
|
ctx = make_context(
|
|
"What is in this screenshot?",
|
|
attachments=[{"type": "image", "mime_type": "image/png"}],
|
|
)
|
|
ctx.config.llm.provider = "anthropic"
|
|
ctx.config.squilla_router.cross_provider_tiers = True
|
|
ctx.config.squilla_router.tiers = {
|
|
"image_model": {
|
|
"model": "vision/model-1",
|
|
"provider": "openai",
|
|
"supports_image": True,
|
|
"image_only": True,
|
|
},
|
|
"c1": {"model": "text/model-1"},
|
|
}
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routing_source"] == "image_route"
|
|
assert routed.metadata["routing_applied"] is True
|
|
assert routed.metadata.get("routed_provider") == "openai"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_image_route_flags_tier_provider_mismatch_when_cross_provider_disabled(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
squilla_router_step,
|
|
"_get_strategy",
|
|
lambda _config: pytest.fail("image routing should not invoke text strategy"),
|
|
)
|
|
ctx = make_context(
|
|
"Describe this screenshot.",
|
|
attachments=[{"type": "image", "mime_type": "image/png"}],
|
|
)
|
|
ctx.config.llm.provider = "anthropic"
|
|
ctx.config.squilla_router.cross_provider_tiers = False
|
|
ctx.config.squilla_router.tiers = {
|
|
"image_model": {
|
|
"model": "vision/model-1",
|
|
"provider": "openai",
|
|
"supports_image": True,
|
|
"image_only": True,
|
|
},
|
|
"c1": {"model": "text/model-1"},
|
|
}
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routing_source"] == "image_route"
|
|
assert routed.metadata.get("router_tier_provider_mismatch") == "openai"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_gate_needs_image_routes_followup_to_vision_model(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
squilla_router_step,
|
|
"_get_strategy",
|
|
lambda _config: pytest.fail("gate image routing should not invoke text strategy"),
|
|
)
|
|
ctx = make_context("Continue from that image.")
|
|
ctx.metadata["router_history_has_recent_image"] = True
|
|
ctx.metadata["router_history_image_turn_count"] = 2
|
|
ctx.metadata["router_turns_since_last_image"] = 1
|
|
ctx.metadata["router_vision_followup_gate_decision"] = "needs_image"
|
|
ctx.metadata["router_vision_followup_needs_image"] = True
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.model == "moonshotai/kimi-k2.6"
|
|
assert routed.metadata["routed_tier"] == "image_model"
|
|
assert routed.metadata["routing_source"] == "image_route"
|
|
assert routed.metadata["image_route_reason"] == "gate_history"
|
|
assert routed.metadata["route_max_history_turns"] == 8
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sticky_without_gate_no_longer_routes_to_vision_model(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
strategy = fake_strategy(
|
|
monkeypatch,
|
|
"c2",
|
|
0.77,
|
|
{"route_class": "R2", "thinking_mode": "T1", "prompt_policy": "P1"},
|
|
)
|
|
ctx = make_context("Write a Python script.")
|
|
ctx.metadata["router_history_has_recent_image"] = True
|
|
ctx.metadata["router_history_image_turn_count"] = 1
|
|
ctx.metadata["router_vision_sticky_remaining"] = 3
|
|
ctx.metadata["router_vision_followup_gate_decision"] = "text_only"
|
|
ctx.metadata["router_vision_followup_needs_image"] = False
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert strategy.calls == 1
|
|
assert routed.metadata["routing_source"] != "image_route"
|
|
assert routed.metadata.get("image_route_reason") is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_recent_historical_image_without_sticky_uses_text_router(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
strategy = fake_strategy(
|
|
monkeypatch,
|
|
"c0",
|
|
0.88,
|
|
{"route_class": "R0", "thinking_mode": "T1", "prompt_policy": "P0"},
|
|
)
|
|
ctx = make_context("Help me write a Python script.")
|
|
ctx.metadata["router_history_has_recent_image"] = True
|
|
ctx.metadata["router_history_image_turn_count"] = 1
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert strategy.calls == 1
|
|
assert routed.metadata["routing_source"] == "v4_phase3"
|
|
assert routed.metadata["routed_tier"] == "c0"
|
|
assert "image_route_reason" not in routed.metadata
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_image_attachment_without_image_tier_fails_locally(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
squilla_router_step,
|
|
"_get_strategy",
|
|
lambda _config: pytest.fail(
|
|
"image routing without image tier should not invoke text strategy"
|
|
),
|
|
)
|
|
ctx = make_context(
|
|
"What is in this screenshot?",
|
|
attachments=[{"type": "image", "mime_type": "image/png"}],
|
|
)
|
|
ctx.config.squilla_router.tiers["image_model"]["supports_image"] = False
|
|
|
|
with pytest.raises(
|
|
RuntimeError,
|
|
match="No image-capable SquillaRouter tier is configured",
|
|
):
|
|
await apply_squilla_router(ctx)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_caption_less_image_attachment_still_routes_to_vision_tier(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
squilla_router_step,
|
|
"_get_strategy",
|
|
lambda _config: pytest.fail("image routing should not invoke text strategy"),
|
|
)
|
|
for caption in ("", " \n\t "):
|
|
ctx = make_context(
|
|
caption,
|
|
attachments=[{"type": "image", "mime_type": "image/png"}],
|
|
)
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routing_source"] == "image_route"
|
|
assert routed.metadata["routed_tier"] == "image_model"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_caption_less_image_attachment_without_image_tier_fails_locally(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
squilla_router_step,
|
|
"_get_strategy",
|
|
lambda _config: pytest.fail(
|
|
"image routing without image tier should not invoke text strategy"
|
|
),
|
|
)
|
|
ctx = make_context(
|
|
"",
|
|
attachments=[{"type": "image", "mime_type": "image/png"}],
|
|
)
|
|
ctx.config.squilla_router.tiers["image_model"]["supports_image"] = False
|
|
|
|
with pytest.raises(
|
|
RuntimeError,
|
|
match="No image-capable SquillaRouter tier is configured",
|
|
):
|
|
await apply_squilla_router(ctx)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_non_image_attachment_does_not_force_vision_route(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
strategy = fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.91,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
ctx = make_context(
|
|
"Summarize the attached PDF text.",
|
|
attachments=[{"type": "application/pdf", "mime_type": "application/pdf"}],
|
|
)
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert strategy.calls == 1
|
|
assert routed.metadata["routing_source"] == "v4_phase3"
|
|
assert routed.metadata["routed_tier"] == "c1"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_observe_rollout_records_decisions_without_applying_model_or_prompt(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
fake_strategy(
|
|
monkeypatch,
|
|
"c2",
|
|
0.93,
|
|
{
|
|
"route_class": "R2",
|
|
"thinking_mode": "T2",
|
|
"prompt_policy": "P2",
|
|
"prompt_hint": "Use extra care.",
|
|
},
|
|
)
|
|
ctx = make_context("Analyze this code path.", rollout_phase="observe")
|
|
baseline_model = ctx.model
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.model == baseline_model
|
|
assert routed.metadata["routed_tier"] == "c2"
|
|
assert routed.metadata["routed_model"] == "z-ai/glm-5.2"
|
|
assert routed.metadata["routing_applied"] is False
|
|
assert routed.metadata["thinking_mode"] == "T2"
|
|
assert routed.metadata["thinking_level"] == "medium"
|
|
assert "[RESPONSE_POLICY:" not in routed.message
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_repeated_message_across_sessions_is_classified_each_time(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
strategy = fake_strategy(
|
|
monkeypatch,
|
|
"c1",
|
|
0.91,
|
|
{
|
|
"route_class": "R1",
|
|
"thinking_mode": "T1",
|
|
"prompt_policy": "P0",
|
|
},
|
|
)
|
|
|
|
first = await apply_squilla_router(make_context("Repeat this.", session_key="session-a"))
|
|
second = await apply_squilla_router(make_context("Repeat this.", session_key="session-b"))
|
|
|
|
assert first.metadata["routing_source"] == "v4_phase3"
|
|
assert second.metadata["routing_source"] == "v4_phase3"
|
|
assert strategy.calls == 2
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_required_router_runtime_failure_falls_back_to_heuristic_tiering(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
import opensquilla.squilla_router.v4_phase3 as v4_phase3
|
|
|
|
monkeypatch.setattr(v4_phase3, "V4Phase3Strategy", ExplodingV4Strategy)
|
|
ctx = make_context("Explain the setup steps.")
|
|
ctx.config.squilla_router.require_router_runtime = True
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
# Short plain text lands in the heuristic c0 band; the decision is
|
|
# honestly attributed to the fallback classifier, not the ML runtime.
|
|
assert routed.metadata["routing_source"] == "heuristic"
|
|
assert routed.metadata["routed_tier"] == "c0"
|
|
assert routed.metadata["routing_confidence"] == pytest.approx(0.55)
|
|
assert routed.metadata["routing_extra"]["heuristic_band"] == "short_plain"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_router_runtime_failure_emits_one_operator_warning(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
import opensquilla.squilla_router.v4_phase3 as v4_phase3
|
|
|
|
monkeypatch.setattr(v4_phase3, "V4Phase3Strategy", ExplodingV4Strategy)
|
|
caplog.set_level(logging.WARNING)
|
|
|
|
first = make_context("Explain the setup steps.")
|
|
first.config.squilla_router.require_router_runtime = True
|
|
await apply_squilla_router(first)
|
|
squilla_router_step._strategy = None
|
|
squilla_router_step._strategy_key = None
|
|
second = make_context("Explain the setup steps again.")
|
|
second.config.squilla_router.require_router_runtime = True
|
|
await apply_squilla_router(second)
|
|
|
|
messages = [
|
|
record.getMessage()
|
|
for record in caplog.records
|
|
if "Microsoft Visual C++ Redistributable 2015-2022 x64" in record.getMessage()
|
|
]
|
|
assert len(messages) == 1
|
|
assert "safe router fallback" in messages[0]
|
|
assert "https://aka.ms/vs/17/release/vc_redist.x64.exe" in messages[0]
|
|
assert "After installing, reopen PowerShell and restart OpenSquilla" in messages[0]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_router_runtime_failure_emits_macos_libomp_guidance(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
import opensquilla.squilla_router.v4_phase3 as v4_phase3
|
|
|
|
monkeypatch.setattr(v4_phase3, "V4Phase3Strategy", MacLibompExplodingV4Strategy)
|
|
caplog.set_level(logging.WARNING)
|
|
|
|
ctx = make_context("Explain the setup steps.")
|
|
ctx.config.squilla_router.require_router_runtime = True
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routing_source"] == "heuristic"
|
|
messages = [
|
|
record.getMessage()
|
|
for record in caplog.records
|
|
if "brew install libomp" in record.getMessage()
|
|
]
|
|
assert len(messages) == 1
|
|
assert "default-tier routing" in messages[0]
|
|
assert "opensquilla gateway restart" in messages[0]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_runtime_router_short_chinese_prompt_injects_localized_p0_hint() -> None:
|
|
require_runtime_router()
|
|
ctx = make_context("直接总结这句话。")
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routing_source"] == "v4_phase3"
|
|
assert routed.metadata["routed_tier"] == "c0"
|
|
assert routed.model == "deepseek/deepseek-v4-flash"
|
|
assert routed.metadata["thinking_mode"] == "T0"
|
|
assert routed.metadata.get("thinking_requested") is None
|
|
assert routed.metadata["prompt_policy"] == "P0"
|
|
assert "[RESPONSE_POLICY: 直接作答,缩短思考长度,避免无关展开。]" in routed.message
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_runtime_router_complex_request_applies_deep_thinking_without_p2_prompt() -> None:
|
|
require_runtime_router()
|
|
ctx = make_context("Plan a risky multi-step database migration with rollback and verification.")
|
|
|
|
routed = await apply_squilla_router(ctx)
|
|
|
|
assert routed.metadata["routing_source"] == "v4_phase3"
|
|
assert routed.metadata["routed_tier"] == "c3"
|
|
assert routed.model == "anthropic/claude-opus-4.8"
|
|
assert routed.metadata["thinking_mode"] == "T3"
|
|
assert routed.metadata["thinking_requested"] is True
|
|
assert routed.metadata["thinking_level"] == "high"
|
|
assert routed.metadata["prompt_policy"] == "P2"
|
|
assert routed.metadata["routing_extra"]["prompt_hint"] == (
|
|
"Analyze thoroughly, cover key constraints, avoid omissions."
|
|
)
|
|
assert "[RESPONSE_POLICY:" not in routed.message
|