fix: enforce JSON-only Gemini responses and add graceful plain-text fallback to prevent agent crash
Browse files
backend/app/services/gemini.py
CHANGED
|
@@ -600,8 +600,11 @@ async def run_agent_chat(db: AsyncSession, user_id: uuid.UUID, prompt: str) -> d
|
|
| 600 |
"Examine the requested stock and gather all necessary details using your tools. "
|
| 601 |
"You must refer to your machine learning predictions tool as the 'QuantIQ ML Signal Engine' "
|
| 602 |
"and your technical indicators tool as 'QuantIQ Technical Indicators' in your reasoning. "
|
| 603 |
-
"You
|
| 604 |
-
|
|
|
|
|
|
|
|
|
|
| 605 |
"Ensure 'bullish_probability' is between 0 and 100 representing the upward price probability. "
|
| 606 |
"The 'reason' should be a detailed, structured, and comprehensive quantitative analysis. "
|
| 607 |
"Explain the technical metrics, crossover directions, and the ML Signal Engine predictions clearly. "
|
|
@@ -649,6 +652,18 @@ async def run_agent_chat(db: AsyncSession, user_id: uuid.UUID, prompt: str) -> d
|
|
| 649 |
"bullish_probability": data.get("bullish_probability", 50),
|
| 650 |
"reason": data.get("reason", "No analysis summary could be generated.")
|
| 651 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
except Exception as e:
|
| 653 |
duration = asyncio.get_event_loop().time() - start_time
|
| 654 |
agent_latency_seconds.observe(duration)
|
|
|
|
| 600 |
"Examine the requested stock and gather all necessary details using your tools. "
|
| 601 |
"You must refer to your machine learning predictions tool as the 'QuantIQ ML Signal Engine' "
|
| 602 |
"and your technical indicators tool as 'QuantIQ Technical Indicators' in your reasoning. "
|
| 603 |
+
"ABSOLUTE RULE: You MUST ALWAYS return your final response as a raw JSON object — "
|
| 604 |
+
"no markdown fences, no prose, no preamble. ONLY valid JSON. "
|
| 605 |
+
"Even if tools fail or data is limited, you MUST still return a JSON object. "
|
| 606 |
+
'The schema is: {"bullish_probability": int, "reason": "string"}. '
|
| 607 |
+
"If tools failed, estimate bullish_probability as 50 and explain the limitation in the reason field. "
|
| 608 |
"Ensure 'bullish_probability' is between 0 and 100 representing the upward price probability. "
|
| 609 |
"The 'reason' should be a detailed, structured, and comprehensive quantitative analysis. "
|
| 610 |
"Explain the technical metrics, crossover directions, and the ML Signal Engine predictions clearly. "
|
|
|
|
| 652 |
"bullish_probability": data.get("bullish_probability", 50),
|
| 653 |
"reason": data.get("reason", "No analysis summary could be generated.")
|
| 654 |
}
|
| 655 |
+
except ValueError as json_err:
|
| 656 |
+
# The model returned plain text instead of JSON (can happen when tools fail)
|
| 657 |
+
# Gracefully capture the model's text and wrap it into a valid response dict
|
| 658 |
+
duration = asyncio.get_event_loop().time() - start_time
|
| 659 |
+
agent_latency_seconds.observe(duration)
|
| 660 |
+
agent_steps_total.labels(status="failed").inc(1)
|
| 661 |
+
raw_text = str(json_err).replace("Could not extract JSON from: ", "", 1)
|
| 662 |
+
print(f"Gemini returned plain text instead of JSON — wrapping gracefully. Preview: {raw_text[:120]}")
|
| 663 |
+
return {
|
| 664 |
+
"bullish_probability": 50,
|
| 665 |
+
"reason": raw_text
|
| 666 |
+
}
|
| 667 |
except Exception as e:
|
| 668 |
duration = asyncio.get_event_loop().time() - start_time
|
| 669 |
agent_latency_seconds.observe(duration)
|