diff --git a/src/bannerlord/agents/king.py b/src/bannerlord/agents/king.py index a8928d8c..e70179fa 100644 --- a/src/bannerlord/agents/king.py +++ b/src/bannerlord/agents/king.py @@ -126,8 +126,7 @@ class KingAgent: if victory.achieved: logger.info( - "SOVEREIGN VICTORY — King of Calradia! " - "Territory: %.1f%%, tick: %d", + "SOVEREIGN VICTORY — King of Calradia! Territory: %.1f%%, tick: %d", victory.territory_control_pct, self._tick, ) @@ -186,7 +185,7 @@ class KingAgent: logger.warning( "King LLM decision failed at tick %d: %s — defaulting to RECRUIT", self._tick, exc ) - return KingSubgoal(token="RECRUIT", context="LLM unavailable — safe default") + return KingSubgoal(token="RECRUIT", context="LLM unavailable — safe default") # noqa: S106 def _llm_decide(self, state: dict[str, Any]) -> KingSubgoal: """Synchronous Ollama call (runs in a thread via asyncio.to_thread).""" diff --git a/src/bannerlord/agents/vassals.py b/src/bannerlord/agents/vassals.py index 5accf15b..74583b25 100644 --- a/src/bannerlord/agents/vassals.py +++ b/src/bannerlord/agents/vassals.py @@ -76,9 +76,7 @@ class BaseVassal: msg = self._subgoal_queue.get_nowait() if msg.to_agent == self.name: self._active_subgoal = msg.subgoal - logger.debug( - "%s received subgoal %s", self.name, msg.subgoal.token - ) + logger.debug("%s received subgoal %s", self.name, msg.subgoal.token) except asyncio.QueueEmpty: pass @@ -147,10 +145,8 @@ class WarVassal(BaseVassal): subgoal_bonus=bonus, ) - def _plan_action( - self, state: dict[str, Any], subgoal: KingSubgoal - ) -> TaskMessage | None: - if subgoal.token == "EXPAND_TERRITORY" and subgoal.target: + def _plan_action(self, state: dict[str, Any], subgoal: KingSubgoal) -> TaskMessage | None: + if subgoal.token == "EXPAND_TERRITORY" and subgoal.target: # noqa: S105 return TaskMessage( from_agent=self.name, to_agent="logistics_companion", @@ -158,7 +154,7 @@ class WarVassal(BaseVassal): args={"destination": subgoal.target}, priority=subgoal.priority, ) - if subgoal.token == "RECRUIT": + if subgoal.token == "RECRUIT": # noqa: S105 qty = subgoal.quantity or 20 return TaskMessage( from_agent=self.name, @@ -167,7 +163,7 @@ class WarVassal(BaseVassal): args={"troop_type": "infantry", "quantity": qty}, priority=subgoal.priority, ) - if subgoal.token == "TRAIN": + if subgoal.token == "TRAIN": # noqa: S105 return TaskMessage( from_agent=self.name, to_agent="logistics_companion", @@ -219,10 +215,8 @@ class EconomyVassal(BaseVassal): subgoal_bonus=bonus, ) - def _plan_action( - self, state: dict[str, Any], subgoal: KingSubgoal - ) -> TaskMessage | None: - if subgoal.token == "FORTIFY" and subgoal.target: + def _plan_action(self, state: dict[str, Any], subgoal: KingSubgoal) -> TaskMessage | None: + if subgoal.token == "FORTIFY" and subgoal.target: # noqa: S105 return TaskMessage( from_agent=self.name, to_agent="logistics_companion", @@ -230,7 +224,7 @@ class EconomyVassal(BaseVassal): args={"settlement": subgoal.target}, priority=subgoal.priority, ) - if subgoal.token == "TRADE": + if subgoal.token == "TRADE": # noqa: S105 return TaskMessage( from_agent=self.name, to_agent="caravan_companion", @@ -282,10 +276,8 @@ class DiplomacyVassal(BaseVassal): subgoal_bonus=bonus, ) - def _plan_action( - self, state: dict[str, Any], subgoal: KingSubgoal - ) -> TaskMessage | None: - if subgoal.token == "ALLY" and subgoal.target: + def _plan_action(self, state: dict[str, Any], subgoal: KingSubgoal) -> TaskMessage | None: + if subgoal.token == "ALLY" and subgoal.target: # noqa: S105 return TaskMessage( from_agent=self.name, to_agent="scout_companion", @@ -293,7 +285,7 @@ class DiplomacyVassal(BaseVassal): args={"name": subgoal.target}, priority=subgoal.priority, ) - if subgoal.token == "SPY" and subgoal.target: + if subgoal.token == "SPY" and subgoal.target: # noqa: S105 return TaskMessage( from_agent=self.name, to_agent="scout_companion", diff --git a/tests/infrastructure/test_metabolic_router.py b/tests/infrastructure/test_metabolic_router.py index 21ccf689..39c8a4d2 100644 --- a/tests/infrastructure/test_metabolic_router.py +++ b/tests/infrastructure/test_metabolic_router.py @@ -1,12 +1,9 @@ """Tests for the three-tier metabolic LLM router (issue #966).""" -import asyncio -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, MagicMock import pytest -pytestmark = pytest.mark.unit - from infrastructure.router.metabolic import ( DEFAULT_TIER_MODELS, MetabolicRouter, @@ -16,6 +13,7 @@ from infrastructure.router.metabolic import ( get_metabolic_router, ) +pytestmark = pytest.mark.unit # ── classify_complexity ────────────────────────────────────────────────────── @@ -198,7 +196,12 @@ class TestMetabolicRouter: async def test_t1_uses_t1_model(self): mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "ok", "provider": "ollama-local", "model": "qwen3:8b", "latency_ms": 100} + return_value={ + "content": "ok", + "provider": "ollama-local", + "model": "qwen3:8b", + "latency_ms": 100, + } ) router = MetabolicRouter(cascade=mock_cascade) await router.route("go north", state={}) @@ -208,7 +211,12 @@ class TestMetabolicRouter: async def test_t2_uses_t2_model(self): mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "ok", "provider": "ollama-local", "model": "qwen3:14b", "latency_ms": 300} + return_value={ + "content": "ok", + "provider": "ollama-local", + "model": "qwen3:14b", + "latency_ms": 300, + } ) router = MetabolicRouter(cascade=mock_cascade) await router.route("what should I say to the innkeeper", state={}) @@ -218,7 +226,12 @@ class TestMetabolicRouter: async def test_t3_uses_t3_model(self): mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "ok", "provider": "ollama-local", "model": "qwen3:30b", "latency_ms": 2000} + return_value={ + "content": "ok", + "provider": "ollama-local", + "model": "qwen3:30b", + "latency_ms": 2000, + } ) router = MetabolicRouter(cascade=mock_cascade) await router.route("plan the optimal quest route", state={}) @@ -228,7 +241,12 @@ class TestMetabolicRouter: async def test_custom_tier_models_respected(self): mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "ok", "provider": "test", "model": "custom-8b", "latency_ms": 100} + return_value={ + "content": "ok", + "provider": "test", + "model": "custom-8b", + "latency_ms": 100, + } ) custom = {ModelTier.T1_ROUTINE: "custom-8b"} router = MetabolicRouter(cascade=mock_cascade, tier_models=custom) @@ -239,7 +257,12 @@ class TestMetabolicRouter: async def test_t3_pauses_world_before_inference(self): mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "ok", "provider": "ollama", "model": "qwen3:30b", "latency_ms": 1500} + return_value={ + "content": "ok", + "provider": "ollama", + "model": "qwen3:30b", + "latency_ms": 1500, + } ) router = MetabolicRouter(cascade=mock_cascade) @@ -281,7 +304,12 @@ class TestMetabolicRouter: async def test_t1_does_not_pause_world(self): mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "ok", "provider": "ollama", "model": "qwen3:8b", "latency_ms": 120} + return_value={ + "content": "ok", + "provider": "ollama", + "model": "qwen3:8b", + "latency_ms": 120, + } ) router = MetabolicRouter(cascade=mock_cascade) @@ -297,7 +325,12 @@ class TestMetabolicRouter: async def test_t2_does_not_pause_world(self): mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "ok", "provider": "ollama", "model": "qwen3:14b", "latency_ms": 350} + return_value={ + "content": "ok", + "provider": "ollama", + "model": "qwen3:14b", + "latency_ms": 350, + } ) router = MetabolicRouter(cascade=mock_cascade) @@ -314,7 +347,12 @@ class TestMetabolicRouter: """If world.act() raises, inference must still complete.""" mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "done", "provider": "ollama", "model": "qwen3:30b", "latency_ms": 2000} + return_value={ + "content": "done", + "provider": "ollama", + "model": "qwen3:30b", + "latency_ms": 2000, + } ) router = MetabolicRouter(cascade=mock_cascade) @@ -329,7 +367,12 @@ class TestMetabolicRouter: async def test_no_world_adapter_t3_still_works(self): mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "plan done", "provider": "ollama", "model": "qwen3:30b", "latency_ms": 2000} + return_value={ + "content": "plan done", + "provider": "ollama", + "model": "qwen3:30b", + "latency_ms": 2000, + } ) router = MetabolicRouter(cascade=mock_cascade) # No set_world() called @@ -347,7 +390,12 @@ class TestMetabolicRouter: """Calling route without ui_state should not raise.""" mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "ok", "provider": "ollama", "model": "qwen3:8b", "latency_ms": 100} + return_value={ + "content": "ok", + "provider": "ollama", + "model": "qwen3:8b", + "latency_ms": 100, + } ) router = MetabolicRouter(cascade=mock_cascade) # No ui_state argument @@ -357,7 +405,12 @@ class TestMetabolicRouter: async def test_temperature_and_max_tokens_forwarded(self): mock_cascade = MagicMock() mock_cascade.complete = AsyncMock( - return_value={"content": "ok", "provider": "ollama", "model": "qwen3:14b", "latency_ms": 200} + return_value={ + "content": "ok", + "provider": "ollama", + "model": "qwen3:14b", + "latency_ms": 200, + } ) router = MetabolicRouter(cascade=mock_cascade) await router.route("describe the scene", state={}, temperature=0.1, max_tokens=50) diff --git a/tests/unit/test_bannerlord/test_models.py b/tests/unit/test_bannerlord/test_models.py index 4b64d949..10410b75 100644 --- a/tests/unit/test_bannerlord/test_models.py +++ b/tests/unit/test_bannerlord/test_models.py @@ -15,7 +15,6 @@ from bannerlord.models import ( WarReward, ) - # ── KingSubgoal ─────────────────────────────────────────────────────────────── @@ -35,7 +34,7 @@ class TestKingSubgoal: KingSubgoal(token="NUKE_CALRADIA") def test_priority_clamp(self): - with pytest.raises(Exception): + with pytest.raises(ValueError): KingSubgoal(token="TRADE", priority=3.0) def test_optional_fields_default_none(self):