fix: switch all JSON-returning game generators to api/chat + robust parsing
Lint / Shell (shellcheck) (push) Successful in 10s
Lint / JS (eslint) (push) Successful in 6s
Lint / Python (ruff) (push) Successful in 5s
Lint / Python deps (pip-audit) (push) Successful in 1m0s
Lint / Secret scan (gitleaks) (push) Successful in 7s

hangman, scramble, riddle, and wyr all used api/generate which has no
system role. The model would wrap JSON in prose or markdown fences,
causing json.loads() to throw and the command to silently die after
the 'Generating...' message.

Fix for all four: switch to api/chat with a system message enforcing
raw JSON output, strip markdown fences, and use regex to extract the
JSON object even if surrounded by extra text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 00:55:45 -04:00
parent a47648435e
commit 82a3f24519
+54 -28
View File
@@ -1231,24 +1231,32 @@ def _hangman_display(game: dict) -> str:
async def _generate_hangman_word() -> dict | None:
prompt = (
"Generate a hangman word game. Pick a common English word between 5 and 8 letters. "
"Respond with ONLY valid JSON, no markdown: "
'{"word": "example", "hint": "a short category or hint about the word"}. '
"The word must be all lowercase letters only, no spaces or hyphens."
system_msg = (
"You are a hangman game generator. Always respond with ONLY a JSON object — no markdown, no explanation. "
'Format: {"word": "example", "hint": "short category or hint"}'
)
user_msg = "Pick a common English word between 5 and 8 letters (lowercase letters only, no hyphens or spaces) and give a short hint."
try:
timeout = aiohttp.ClientTimeout(total=20)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(
f"{OLLAMA_URL}/api/generate",
json={"model": ASK_MODEL, "prompt": prompt, "stream": False},
f"{OLLAMA_URL}/api/chat",
json={
"model": ASK_MODEL,
"stream": False,
"messages": [
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
},
) as response:
data = await response.json()
text = data.get("response", "").strip()
# Strip markdown fences
text = data.get("message", {}).get("content", "").strip()
if "```" in text:
text = text.split("```")[1].lstrip("json").strip()
text = re.sub(r"```[a-z]*\n?", "", text).strip()
m = re.search(r"\{[^{}]+\}", text, re.DOTALL)
if m:
text = m.group(0)
parsed = json.loads(text)
word = parsed.get("word", "").lower().strip()
hint = parsed.get("hint", "").strip()
@@ -1430,23 +1438,32 @@ _SCRAMBLE_GAMES: dict[str, dict] = {}
async def _generate_scramble_word() -> dict | None:
prompt = (
"Pick a common English word between 4 and 8 letters. "
"Respond with ONLY valid JSON, no markdown: "
'{"word": "example"}. '
"The word must be all lowercase letters only, no spaces or hyphens."
system_msg = (
"You are a word game generator. Always respond with ONLY a JSON object — no markdown, no explanation. "
'Format: {"word": "example"}'
)
user_msg = "Pick a common English word between 4 and 8 letters (lowercase letters only, no hyphens or spaces)."
try:
timeout = aiohttp.ClientTimeout(total=20)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(
f"{OLLAMA_URL}/api/generate",
json={"model": ASK_MODEL, "prompt": prompt, "stream": False},
f"{OLLAMA_URL}/api/chat",
json={
"model": ASK_MODEL,
"stream": False,
"messages": [
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
},
) as response:
data = await response.json()
text = data.get("response", "").strip()
text = data.get("message", {}).get("content", "").strip()
if "```" in text:
text = text.split("```")[1].lstrip("json").strip()
text = re.sub(r"```[a-z]*\n?", "", text).strip()
m = re.search(r"\{[^{}]+\}", text, re.DOTALL)
if m:
text = m.group(0)
parsed = json.loads(text)
word = parsed.get("word", "").lower().strip()
if word.isalpha() and 4 <= len(word) <= 8:
@@ -1641,23 +1658,32 @@ _RIDDLE_ACTIVE: dict[str, dict] = {}
async def _generate_riddle() -> dict | None:
prompt = (
"Generate a clever riddle and its answer. "
"Respond with ONLY valid JSON, no markdown: "
'{"riddle": "...", "answer": "..."}. '
"The answer should be a short word or phrase (1-4 words). Make it interesting!"
system_msg = (
"You are a riddle generator. Always respond with ONLY a JSON object — no markdown fences, no explanation. "
'Format: {"riddle": "the riddle text", "answer": "short answer"}'
)
user_msg = "Generate a clever riddle. The answer should be 1-4 words."
try:
timeout = aiohttp.ClientTimeout(total=20)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(
f"{OLLAMA_URL}/api/generate",
json={"model": ASK_MODEL, "prompt": prompt, "stream": False},
f"{OLLAMA_URL}/api/chat",
json={
"model": ASK_MODEL,
"stream": False,
"messages": [
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
},
) as response:
data = await response.json()
text = data.get("response", "").strip()
text = data.get("message", {}).get("content", "").strip()
if "```" in text:
text = text.split("```")[1].lstrip("json").strip()
text = re.sub(r"```[a-z]*\n?", "", text).strip()
m = re.search(r"\{[^{}]+\}", text, re.DOTALL)
if m:
text = m.group(0)
parsed = json.loads(text)
riddle = parsed.get("riddle", "").strip()
answer = parsed.get("answer", "").strip()