diff --git a/src/brain/gemini_client.py b/src/brain/gemini_client.py index dd9674b..c664eb2 100644 --- a/src/brain/gemini_client.py +++ b/src/brain/gemini_client.py @@ -445,7 +445,10 @@ class GeminiClient: # not a parsed TradeDecision. Skip parse_response to avoid spurious # "Missing fields" warnings and return the raw response directly. (#247) if "prompt_override" in market_data: - self._total_decisions += 1 + logger.info( + "Gemini raw response received (prompt_override, tokens=%d)", token_count + ) + # Not a trade decision — don't inflate _total_decisions metrics return TradeDecision( action="HOLD", confidence=0, rationale=raw, token_count=token_count ) diff --git a/tests/test_brain.py b/tests/test_brain.py index 8d2711c..c857720 100644 --- a/tests/test_brain.py +++ b/tests/test_brain.py @@ -360,6 +360,36 @@ class TestPromptOverride: # Raw playbook JSON preserved in rationale assert decision.rationale == playbook_json + @pytest.mark.asyncio + async def test_prompt_override_takes_priority_over_optimization(self, settings): + """prompt_override must win over enable_optimization=True.""" + client = GeminiClient(settings) + client._enable_optimization = True + + custom_prompt = "Explicit playbook prompt" + + mock_response = MagicMock() + mock_response.text = '{"market_outlook": "neutral", "stocks": []}' + + with patch.object( + client._client.aio.models, + "generate_content", + new_callable=AsyncMock, + return_value=mock_response, + ) as mock_generate: + market_data = { + "stock_code": "PLANNER", + "current_price": 0, + "prompt_override": custom_prompt, + } + await client.decide(market_data) + + actual_prompt = mock_generate.call_args[1].get( + "contents", mock_generate.call_args[0][1] if len(mock_generate.call_args[0]) > 1 else None + ) + # The custom prompt must be used, not the compressed prompt + assert actual_prompt == custom_prompt + @pytest.mark.asyncio async def test_without_prompt_override_uses_build_prompt(self, settings): """Without prompt_override, decide() should use build_prompt as before."""