from proteus.providers import FakeProvider from proteus.game.agents import VanillaAgent, ActResult VALID = ["up", "down", "left", "right", "stay"] def test_act_parses_action_and_captures_reasoning_from_think_tags(): provider = FakeProvider( responses=["predator is east, go upACTION: up"], ) agent = VanillaAgent(provider) result = agent.act("grid here", VALID, "rules") assert isinstance(result, ActResult) assert result.action == "up" assert "predator is east" in result.reasoning assert result.raw_text # full text retained def test_act_falls_back_to_stay_when_unparseable(): provider = FakeProvider(responses=["I don't know what to do"]) agent = VanillaAgent(provider) result = agent.act("grid", VALID, "rules") assert result.action == "stay" # safe default when no valid action parsed def test_probe_returns_text_and_sends_question(): provider = FakeProvider(responses=["the predator is east"]) agent = VanillaAgent(provider) result = agent.probe("grid", "where is the predator?", "rules") assert result.answer == "the predator is east" # the probe question reached the provider assert any("where is the predator?" in m["content"] for m in provider.calls[-1]) def test_name_is_vanilla(): assert VanillaAgent(FakeProvider(responses=["x"])).name == "vanilla" def test_act_ignores_decoy_action_inside_think_block(): # A decoy "ACTION:" inside the think block must NOT win over the real # post-thinking ACTION line. (Regression for extracting from full text.) provider = FakeProvider(responses=["maybe ACTION: upACTION: down"]) agent = VanillaAgent(provider) result = agent.act("grid", VALID, "rules") assert result.action == "down" def test_act_uses_provider_native_thinking_when_no_inline_tags(): # Covers the middle branch of the reasoning fallback: no inline , # but the provider supplies a native thinking_text on CompletionResult. from proteus.providers.base import CompletionResult, LLMProvider class _NativeThink(LLMProvider): @property def model_name(self) -> str: return "native" def complete(self, messages, temperature=0.7, max_tokens=4096): return CompletionResult( text="ACTION: up", input_tokens=0, output_tokens=2, thinking_text="native reasoning here", ) result = VanillaAgent(_NativeThink()).act("grid", VALID, "rules") assert result.action == "up" assert result.reasoning == "native reasoning here" def test_act_appends_action_directive_with_available_actions(): provider = FakeProvider(responses=["ACTION: up"]) agent = VanillaAgent(provider) agent.act("grid here", VALID, "rules") user_msg = provider.calls[-1][-1]["content"] assert "grid here" in user_msg assert "ACTION:" in user_msg assert "up, down, left, right, stay" in user_msg # actions list formatted into directive def test_act_captures_token_accounting_from_completion_result(): # Inline count comes from the parser; output_tokens from the provider. provider = FakeProvider(responses=["go up nowACTION: up"]) result = VanillaAgent(provider).act("grid", VALID, "rules") assert result.thinking_tokens == 3 # "go up now" -> 3 words assert result.output_tokens > 0 assert result.input_tokens == 0 # FakeProvider always reports 0; documents the fake's constant, not a propagation check def test_act_prefers_provider_thinking_tokens_when_present(): # When the provider reports its own thinking_tokens (e.g. Ollama's structured # message.thinking), use that over the inline-tag parser count. from proteus.providers.base import CompletionResult, LLMProvider class _NativeTokens(LLMProvider): @property def model_name(self): return "native" def complete(self, messages, temperature=0.7, max_tokens=4096): return CompletionResult( text="ACTION: up", input_tokens=11, output_tokens=7, thinking_tokens=42, thinking_text="native reasoning", ) result = VanillaAgent(_NativeTokens()).act("grid", VALID, "rules") assert result.thinking_tokens == 42 assert result.input_tokens == 11 assert result.output_tokens == 7 def test_probe_returns_probe_result_with_reasoning_and_tokens(): from proteus.game.agents import ProbeResult provider = FakeProvider(responses=["predator is two cells eastgo up"]) result = VanillaAgent(provider).probe("grid", "where is the predator?", "rules") assert isinstance(result, ProbeResult) assert result.answer == "go up" # think-stripped answer assert "predator is two cells east" in result.reasoning assert result.raw_text == "predator is two cells eastgo up" assert result.thinking_tokens == 5 # 5-word think block (parser fallback) assert result.output_tokens > 0