Merge pull request 'fix: prompt_override 시 parse_response 건너뛰어 Missing fields 경고 제거 (#247)' (#248) from feature/issue-247-skip-parse-response-on-prompt-override into main
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
Reviewed-on: #248
This commit was merged in pull request #248.
This commit is contained in:
@@ -441,6 +441,18 @@ class GeminiClient:
|
|||||||
action="HOLD", confidence=0, rationale=f"API error: {exc}", token_count=token_count
|
action="HOLD", confidence=0, rationale=f"API error: {exc}", token_count=token_count
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# prompt_override callers (e.g. pre_market_planner) expect raw text back,
|
||||||
|
# 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:
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
decision = self.parse_response(raw)
|
decision = self.parse_response(raw)
|
||||||
self._total_decisions += 1
|
self._total_decisions += 1
|
||||||
|
|
||||||
|
|||||||
@@ -302,9 +302,10 @@ class TestPromptOverride:
|
|||||||
client = GeminiClient(settings)
|
client = GeminiClient(settings)
|
||||||
|
|
||||||
custom_prompt = "You are a playbook generator. Return JSON with scenarios."
|
custom_prompt = "You are a playbook generator. Return JSON with scenarios."
|
||||||
|
playbook_json = '{"market_outlook": "neutral", "stocks": []}'
|
||||||
|
|
||||||
mock_response = MagicMock()
|
mock_response = MagicMock()
|
||||||
mock_response.text = '{"action": "HOLD", "confidence": 50, "rationale": "test"}'
|
mock_response.text = playbook_json
|
||||||
|
|
||||||
with patch.object(
|
with patch.object(
|
||||||
client._client.aio.models,
|
client._client.aio.models,
|
||||||
@@ -317,7 +318,7 @@ class TestPromptOverride:
|
|||||||
"current_price": 0,
|
"current_price": 0,
|
||||||
"prompt_override": custom_prompt,
|
"prompt_override": custom_prompt,
|
||||||
}
|
}
|
||||||
await client.decide(market_data)
|
decision = await client.decide(market_data)
|
||||||
|
|
||||||
# Verify the custom prompt was sent, not a built prompt
|
# Verify the custom prompt was sent, not a built prompt
|
||||||
mock_generate.assert_called_once()
|
mock_generate.assert_called_once()
|
||||||
@@ -325,17 +326,50 @@ class TestPromptOverride:
|
|||||||
"contents", mock_generate.call_args[0][1] if len(mock_generate.call_args[0]) > 1 else None
|
"contents", mock_generate.call_args[0][1] if len(mock_generate.call_args[0]) > 1 else None
|
||||||
)
|
)
|
||||||
assert actual_prompt == custom_prompt
|
assert actual_prompt == custom_prompt
|
||||||
|
# Raw response preserved in rationale without parse_response (#247)
|
||||||
|
assert decision.rationale == playbook_json
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_prompt_override_skips_optimization(self, settings):
|
async def test_prompt_override_skips_parse_response(self, settings):
|
||||||
"""prompt_override should bypass prompt optimization."""
|
"""prompt_override bypasses parse_response — no Missing fields warning, raw preserved."""
|
||||||
client = GeminiClient(settings)
|
client = GeminiClient(settings)
|
||||||
client._enable_optimization = True
|
client._enable_optimization = True
|
||||||
|
|
||||||
custom_prompt = "Custom playbook prompt"
|
custom_prompt = "Custom playbook prompt"
|
||||||
|
playbook_json = '{"market_outlook": "bullish", "stocks": [{"stock_code": "AAPL"}]}'
|
||||||
|
|
||||||
mock_response = MagicMock()
|
mock_response = MagicMock()
|
||||||
mock_response.text = '{"action": "HOLD", "confidence": 50, "rationale": "ok"}'
|
mock_response.text = playbook_json
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
client._client.aio.models,
|
||||||
|
"generate_content",
|
||||||
|
new_callable=AsyncMock,
|
||||||
|
return_value=mock_response,
|
||||||
|
):
|
||||||
|
with patch.object(client, "parse_response") as mock_parse:
|
||||||
|
market_data = {
|
||||||
|
"stock_code": "PLANNER",
|
||||||
|
"current_price": 0,
|
||||||
|
"prompt_override": custom_prompt,
|
||||||
|
}
|
||||||
|
decision = await client.decide(market_data)
|
||||||
|
|
||||||
|
# parse_response must NOT be called for prompt_override
|
||||||
|
mock_parse.assert_not_called()
|
||||||
|
# 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(
|
with patch.object(
|
||||||
client._client.aio.models,
|
client._client.aio.models,
|
||||||
@@ -353,6 +387,7 @@ class TestPromptOverride:
|
|||||||
actual_prompt = mock_generate.call_args[1].get(
|
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
|
"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
|
assert actual_prompt == custom_prompt
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
Reference in New Issue
Block a user