From d31a61cd0bdaefb329b183796686af60228c7a8f Mon Sep 17 00:00:00 2001 From: agentson Date: Wed, 25 Feb 2026 01:54:55 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20prompt=5Foverride=20=EA=B2=BD=EB=A1=9C?= =?UTF-8?q?=20=5Ftotal=5Fdecisions=20=EB=AF=B8=EC=B9=B4=EC=9A=B4=ED=8A=B8,?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?,=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=B4=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 리뷰 지적 사항 반영: - _total_decisions 카운트 제거 (플레이북 생성은 거래 결정이 아님 → 메트릭 왜곡 방지) - "Gemini raw response received" INFO 로그 추가 (완료 추적 가능) - test_prompt_override_takes_priority_over_optimization 신규 추가 (enable_optimization=True 상태에서도 prompt_override 우선됨을 검증) Co-Authored-By: Claude Sonnet 4.6 --- src/brain/gemini_client.py | 5 ++++- tests/test_brain.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) 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."""