291 lines
8.9 KiB
Python
291 lines
8.9 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import AsyncIterator
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from opensquilla.engine.runtime import TurnRunner
|
|
from opensquilla.gateway.config import GatewayConfig, SquillaRouterConfig
|
|
from opensquilla.provider import ChatConfig, EnsembleProvider, Message
|
|
from opensquilla.provider.selector import ProviderConfig
|
|
|
|
|
|
class _Provider:
|
|
provider_name = "fake"
|
|
|
|
def chat(
|
|
self,
|
|
messages: list[Message],
|
|
tools: list[Any] | None = None,
|
|
config: ChatConfig | None = None,
|
|
) -> AsyncIterator[Any]:
|
|
raise AssertionError("credential-guard tests must not start provider chat")
|
|
|
|
async def list_models(self) -> list[Any]:
|
|
return []
|
|
|
|
|
|
class _FakeSelector:
|
|
def __init__(self, *, provider: str, api_key: str) -> None:
|
|
self._cfg = ProviderConfig(
|
|
provider=provider,
|
|
model="base-model",
|
|
api_key=api_key,
|
|
base_url="https://example.invalid/api",
|
|
)
|
|
|
|
@property
|
|
def current_config(self) -> ProviderConfig:
|
|
return self._cfg
|
|
|
|
def override_model(self, model: str) -> None:
|
|
self._cfg = ProviderConfig(
|
|
provider=self._cfg.provider,
|
|
model=model,
|
|
api_key=self._cfg.api_key,
|
|
base_url=self._cfg.base_url,
|
|
proxy=self._cfg.proxy,
|
|
provider_routing=self._cfg.provider_routing,
|
|
)
|
|
|
|
def resolve(self) -> _Provider:
|
|
return _Provider()
|
|
|
|
|
|
def _static_b5_config(**ensemble_overrides: Any) -> GatewayConfig:
|
|
return GatewayConfig(
|
|
squilla_router=SquillaRouterConfig(enabled=False),
|
|
llm_ensemble={"enabled": True, **ensemble_overrides},
|
|
)
|
|
|
|
|
|
async def test_static_b5_wrap_skipped_without_openrouter_credential(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
runner = TurnRunner(provider_selector=None, config=_static_b5_config())
|
|
selector = _FakeSelector(provider="groq", api_key="sk-groq-synthetic")
|
|
single_provider = _Provider()
|
|
|
|
turn, provider = await runner._run_pipeline(
|
|
"hello",
|
|
"agent:main:test",
|
|
single_provider,
|
|
selector,
|
|
[],
|
|
"system prompt",
|
|
[],
|
|
)
|
|
|
|
# The model-override step may re-resolve a fresh single-model provider from
|
|
# the selector; the guard's contract is that no ensemble wrap happens.
|
|
assert not isinstance(provider, EnsembleProvider)
|
|
assert isinstance(provider, _Provider)
|
|
assert turn.metadata["ensemble_wrap_skipped_reason"] == (
|
|
"static_openrouter_b5_no_credential"
|
|
)
|
|
assert "ensemble_enabled" not in turn.metadata
|
|
|
|
|
|
async def test_static_b5_wraps_when_openrouter_env_key_present(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-synthetic")
|
|
runner = TurnRunner(provider_selector=None, config=_static_b5_config())
|
|
selector = _FakeSelector(provider="groq", api_key="sk-groq-synthetic")
|
|
|
|
turn, provider = await runner._run_pipeline(
|
|
"hello",
|
|
"agent:main:test",
|
|
_Provider(),
|
|
selector,
|
|
[],
|
|
"system prompt",
|
|
[],
|
|
)
|
|
|
|
assert isinstance(provider, EnsembleProvider)
|
|
assert turn.metadata["ensemble_enabled"] is True
|
|
assert "ensemble_wrap_skipped_reason" not in turn.metadata
|
|
|
|
|
|
async def test_static_b5_wraps_when_active_provider_is_keyed_openrouter(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
runner = TurnRunner(provider_selector=None, config=_static_b5_config())
|
|
selector = _FakeSelector(provider="openrouter", api_key="sk-or-synthetic")
|
|
|
|
turn, provider = await runner._run_pipeline(
|
|
"hello",
|
|
"agent:main:test",
|
|
_Provider(),
|
|
selector,
|
|
[],
|
|
"system prompt",
|
|
[],
|
|
)
|
|
|
|
assert isinstance(provider, EnsembleProvider)
|
|
assert turn.metadata["ensemble_enabled"] is True
|
|
assert "ensemble_wrap_skipped_reason" not in turn.metadata
|
|
|
|
|
|
async def test_static_tokenrhythm_b5_wrap_skipped_without_tokenrhythm_credential(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.delenv("TOKENRHYTHM_API_KEY", raising=False)
|
|
# An OpenRouter key must not unlock the tokenrhythm profile.
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-synthetic")
|
|
runner = TurnRunner(
|
|
provider_selector=None,
|
|
config=_static_b5_config(selection_mode="static_tokenrhythm_b5"),
|
|
)
|
|
selector = _FakeSelector(provider="groq", api_key="sk-groq-synthetic")
|
|
|
|
turn, provider = await runner._run_pipeline(
|
|
"hello",
|
|
"agent:main:test",
|
|
_Provider(),
|
|
selector,
|
|
[],
|
|
"system prompt",
|
|
[],
|
|
)
|
|
|
|
assert not isinstance(provider, EnsembleProvider)
|
|
assert turn.metadata["ensemble_wrap_skipped_reason"] == (
|
|
"static_tokenrhythm_b5_no_credential"
|
|
)
|
|
assert "ensemble_enabled" not in turn.metadata
|
|
|
|
|
|
async def test_static_tokenrhythm_b5_wraps_when_active_provider_is_keyed(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.delenv("TOKENRHYTHM_API_KEY", raising=False)
|
|
runner = TurnRunner(
|
|
provider_selector=None,
|
|
config=_static_b5_config(selection_mode="static_tokenrhythm_b5"),
|
|
)
|
|
selector = _FakeSelector(provider="tokenrhythm", api_key="sk-tr-synthetic")
|
|
|
|
turn, provider = await runner._run_pipeline(
|
|
"hello",
|
|
"agent:main:test",
|
|
_Provider(),
|
|
selector,
|
|
[],
|
|
"system prompt",
|
|
[],
|
|
)
|
|
|
|
assert isinstance(provider, EnsembleProvider)
|
|
assert turn.metadata["ensemble_enabled"] is True
|
|
assert "ensemble_wrap_skipped_reason" not in turn.metadata
|
|
|
|
|
|
async def test_router_dynamic_wrap_is_not_credential_gated(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
runner = TurnRunner(
|
|
provider_selector=None,
|
|
config=_static_b5_config(selection_mode="router_dynamic"),
|
|
)
|
|
selector = _FakeSelector(provider="groq", api_key="sk-groq-synthetic")
|
|
|
|
turn, provider = await runner._run_pipeline(
|
|
"hello",
|
|
"agent:main:test",
|
|
_Provider(),
|
|
selector,
|
|
[],
|
|
"system prompt",
|
|
[],
|
|
)
|
|
|
|
assert isinstance(provider, EnsembleProvider)
|
|
assert turn.metadata["ensemble_enabled"] is True
|
|
assert "ensemble_wrap_skipped_reason" not in turn.metadata
|
|
|
|
|
|
def _custom_b5_guard_config(candidates: list[dict[str, Any]]) -> GatewayConfig:
|
|
return GatewayConfig(
|
|
squilla_router=SquillaRouterConfig(enabled=False),
|
|
llm={
|
|
"provider": "groq",
|
|
"model": "base-model",
|
|
"api_key": "sk-groq-synthetic",
|
|
"base_url": "https://example.invalid/api",
|
|
},
|
|
llm_ensemble={
|
|
"enabled": True,
|
|
"selection_mode": "custom_b5",
|
|
"candidates": candidates,
|
|
},
|
|
)
|
|
|
|
|
|
async def test_custom_b5_wrap_skipped_when_a_member_credential_is_missing(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
cfg = _custom_b5_guard_config(
|
|
[
|
|
{"provider": "groq", "model": "candidate-a"},
|
|
{"provider": "openrouter", "model": "z-ai/glm-5.2"},
|
|
]
|
|
)
|
|
runner = TurnRunner(provider_selector=None, config=cfg)
|
|
selector = _FakeSelector(provider="groq", api_key="sk-groq-synthetic")
|
|
|
|
turn, provider = await runner._run_pipeline(
|
|
"hello",
|
|
"agent:main:test",
|
|
_Provider(),
|
|
selector,
|
|
[],
|
|
"system prompt",
|
|
[],
|
|
)
|
|
|
|
assert not isinstance(provider, EnsembleProvider)
|
|
assert turn.metadata["ensemble_wrap_skipped_reason"] == (
|
|
"custom_b5_not_ready:missing_credential:openrouter"
|
|
)
|
|
assert "ensemble_enabled" not in turn.metadata
|
|
|
|
|
|
async def test_custom_b5_wraps_when_every_member_resolves_a_key(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-synthetic")
|
|
cfg = _custom_b5_guard_config(
|
|
[
|
|
{"provider": "groq", "model": "candidate-a", "role": "primary"},
|
|
{"provider": "openrouter", "model": "z-ai/glm-5.2", "role": "contrast"},
|
|
{"provider": "groq", "model": "fuser", "role": "aggregator"},
|
|
]
|
|
)
|
|
runner = TurnRunner(provider_selector=None, config=cfg)
|
|
selector = _FakeSelector(provider="groq", api_key="sk-groq-synthetic")
|
|
|
|
turn, provider = await runner._run_pipeline(
|
|
"hello",
|
|
"agent:main:test",
|
|
_Provider(),
|
|
selector,
|
|
[],
|
|
"system prompt",
|
|
[],
|
|
)
|
|
|
|
assert isinstance(provider, EnsembleProvider)
|
|
assert provider.profile_name == "custom_b5"
|
|
assert [member.label for member in provider.proposers] == ["primary", "contrast"]
|
|
assert provider.aggregator.provider_config.model == "fuser"
|
|
assert turn.metadata["ensemble_enabled"] is True
|
|
assert "ensemble_wrap_skipped_reason" not in turn.metadata
|