From 0b20251de0f5f1f2b85aa7ccfc5d15de0e32f222 Mon Sep 17 00:00:00 2001 From: agentson Date: Wed, 25 Feb 2026 01:31:54 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20parse=5Fresponse=EC=97=90=EC=84=9C=20mis?= =?UTF-8?q?sing=20fields=20=EC=8B=9C=20raw=20=ED=85=8D=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=B3=B4=EC=A1=B4=20(#245)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pre_market_planner는 prompt_override로 Gemini에 플레이북 JSON을 요청한다. Gemini가 플레이북 JSON을 반환해도 parse_response가 action/confidence/rationale 키가 없다는 이유로 rationale="Missing required fields"를 반환해 실제 응답이 버려졌다. 이로 인해 플레이북 생성이 항상 실패하고 RSI 기반 기본 폴백이 사용됐으며, RSI가 없는 해외 시장 데이터와 매칭되지 않아 모든 결정이 HOLD(confidence=0)였다. 수정: missing fields 시 rationale=raw로 설정해 실제 Gemini 응답을 보존한다. pre_market_planner가 decision.rationale에서 플레이북 JSON을 추출하여 정상 파싱 가능. Co-Authored-By: Claude Sonnet 4.6 --- src/brain/gemini_client.py | 4 +++- tests/test_brain.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/brain/gemini_client.py b/src/brain/gemini_client.py index 2a11803..d6a3f2b 100644 --- a/src/brain/gemini_client.py +++ b/src/brain/gemini_client.py @@ -346,8 +346,10 @@ class GeminiClient: # Validate required fields if not all(k in data for k in ("action", "confidence", "rationale")): logger.warning("Missing fields in Gemini response — defaulting to HOLD") + # Preserve raw text in rationale so prompt_override callers (e.g. pre_market_planner) + # can extract their own JSON format from decision.rationale (#245) return TradeDecision( - action="HOLD", confidence=0, rationale="Missing required fields" + action="HOLD", confidence=0, rationale=raw ) action = str(data["action"]).upper() diff --git a/tests/test_brain.py b/tests/test_brain.py index 42eb49a..1103d6e 100644 --- a/tests/test_brain.py +++ b/tests/test_brain.py @@ -93,9 +93,21 @@ class TestMalformedJsonHandling: def test_json_with_missing_fields_returns_hold(self, settings): client = GeminiClient(settings) - decision = client.parse_response('{"action": "BUY"}') + raw = '{"action": "BUY"}' + decision = client.parse_response(raw) assert decision.action == "HOLD" assert decision.confidence == 0 + # rationale preserves raw so prompt_override callers (e.g. pre_market_planner) + # can extract non-TradeDecision JSON from decision.rationale (#245) + assert decision.rationale == raw + + def test_non_trade_decision_json_preserves_raw_in_rationale(self, settings): + """Playbook JSON (no action/confidence/rationale) must be preserved for planner.""" + client = GeminiClient(settings) + playbook_json = '{"market_outlook": "neutral", "stocks": []}' + decision = client.parse_response(playbook_json) + assert decision.action == "HOLD" + assert decision.rationale == playbook_json def test_json_with_invalid_action_returns_hold(self, settings): client = GeminiClient(settings)