fix: 20q answer dedup cache — prevent repeated answers
Add a rolling cache of the last 30 answers (persisted to twentyq_cache.json) and pass the recent list to the model as an explicit avoid clause. Also prompt the model to vary categories each round. If the model still returns a cached answer it is rejected and one retry is attempted automatically. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+35
-1
@@ -2730,13 +2730,39 @@ async def cmd_ac(client: AsyncClient, room_id: str, sender: str, args: str):
|
|||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
|
|
||||||
_TWENTYQ_GAMES: dict[str, dict] = {}
|
_TWENTYQ_GAMES: dict[str, dict] = {}
|
||||||
|
_TWENTYQ_RECENT_MAX = 30
|
||||||
|
_TWENTYQ_CACHE_FILE = Path("twentyq_cache.json")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_20q_cache() -> list[str]:
|
||||||
|
try:
|
||||||
|
data = json.loads(_TWENTYQ_CACHE_FILE.read_text())
|
||||||
|
return data.get("things", [])
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _save_20q_cache(things: list[str]) -> None:
|
||||||
|
try:
|
||||||
|
_TWENTYQ_CACHE_FILE.write_text(json.dumps({"things": things}, indent=2))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Failed to save 20q cache: %s", e)
|
||||||
|
|
||||||
|
|
||||||
|
_20q_recent: list[str] = _load_20q_cache()
|
||||||
|
|
||||||
|
|
||||||
async def _generate_20q_thing() -> dict | None:
|
async def _generate_20q_thing() -> dict | None:
|
||||||
|
avoid_clause = (
|
||||||
|
f" Do NOT use any of these recently used answers: {', '.join(_20q_recent[-20:])}."
|
||||||
|
if _20q_recent else ""
|
||||||
|
)
|
||||||
system_msg = (
|
system_msg = (
|
||||||
"You are generating a subject for a game of 20 questions. "
|
"You are generating a subject for a game of 20 questions. "
|
||||||
"Pick a specific, well-known, concrete thing. Avoid overly obscure topics. "
|
"Pick a specific, well-known, concrete thing. Avoid overly obscure topics. "
|
||||||
"Good categories: animal, famous person, place, everyday object, food, movie/show, fictional character. "
|
"Good categories: animal, famous person, place, everyday object, food, movie/show, fictional character. "
|
||||||
|
"Choose a DIFFERENT category each time — vary between animals, people, objects, places, food, etc."
|
||||||
|
+ avoid_clause + " "
|
||||||
"Respond with ONLY a JSON object — no markdown, no explanation. "
|
"Respond with ONLY a JSON object — no markdown, no explanation. "
|
||||||
'{"thing": "elephant", "category": "animal", "hint": "it\'s a living creature"}'
|
'{"thing": "elephant", "category": "animal", "hint": "it\'s a living creature"}'
|
||||||
)
|
)
|
||||||
@@ -2763,6 +2789,14 @@ async def _generate_20q_thing() -> dict | None:
|
|||||||
category = parsed.get("category", "thing").strip()
|
category = parsed.get("category", "thing").strip()
|
||||||
hint = parsed.get("hint", f"it's a {category}").strip()
|
hint = parsed.get("hint", f"it's a {category}").strip()
|
||||||
if thing and len(thing) > 1:
|
if thing and len(thing) > 1:
|
||||||
|
# Check it's not a repeat (case-insensitive)
|
||||||
|
if thing.lower() in [r.lower() for r in _20q_recent]:
|
||||||
|
logger.warning("20q generated a cached answer '%s', regenerating", thing)
|
||||||
|
return None
|
||||||
|
_20q_recent.append(thing)
|
||||||
|
if len(_20q_recent) > _TWENTYQ_RECENT_MAX:
|
||||||
|
_20q_recent.pop(0)
|
||||||
|
_save_20q_cache(_20q_recent)
|
||||||
return {"thing": thing, "category": category, "hint": hint}
|
return {"thing": thing, "category": category, "hint": hint}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("20q generation error: %s", e, exc_info=True)
|
logger.error("20q generation error: %s", e, exc_info=True)
|
||||||
@@ -2807,7 +2841,7 @@ async def cmd_20q(client: AsyncClient, room_id: str, sender: str, args: str):
|
|||||||
return
|
return
|
||||||
|
|
||||||
await send_text(client, room_id, "🤔 I'm thinking of something...")
|
await send_text(client, room_id, "🤔 I'm thinking of something...")
|
||||||
thing_data = await _generate_20q_thing()
|
thing_data = await _generate_20q_thing() or await _generate_20q_thing()
|
||||||
if not thing_data:
|
if not thing_data:
|
||||||
await send_text(client, room_id, "Failed to think of something. Try again!")
|
await send_text(client, room_id, "Failed to think of something. Try again!")
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user