From ad09286e277ef8f7f5e6924670310feb738a62ea Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sun, 26 Apr 2026 19:47:11 -0400 Subject: [PATCH] fix: 20q now answers any question, not just yes/no Allow open-ended questions like "What color is it?" or "How big is it?" The model now gives brief descriptive answers (capped at 12 words) for open questions while still answering Yes/No/Sometimes/Partly for binary ones. Updated command descriptions and in-game prompts accordingly. Co-Authored-By: Claude Sonnet 4.6 --- matrixbot/commands.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/matrixbot/commands.py b/matrixbot/commands.py index bd14303..2086a6a 100644 --- a/matrixbot/commands.py +++ b/matrixbot/commands.py @@ -2772,9 +2772,10 @@ async def _generate_20q_thing() -> dict | None: async def _answer_20q(thing: str, category: str, question: str) -> str: system_msg = ( f'You are playing 20 questions. You are thinking of: "{thing}" ({category}). ' - "Answer the player's question with ONLY: Yes, No, Sometimes, or Partly. " - "Do NOT reveal what you are thinking of. Do NOT give hints beyond the single word answer. " - "One word answer only." + "Answer the player's question honestly and helpfully. " + "For yes/no questions: answer Yes, No, Sometimes, or Partly. " + "For open questions (color, size, shape, etc.): give a brief, accurate answer in 1-8 words. " + "CRITICAL: Do NOT say the name of the thing. Do NOT give it away. Keep answers short." ) try: timeout = aiohttp.ClientTimeout(total=20) @@ -2787,15 +2788,15 @@ async def _answer_20q(thing: str, category: str, question: str) -> str: ) as response: data = await response.json() raw = data.get("message", {}).get("content", "").strip() - # Only keep the first word to prevent leakage - first_word = raw.split()[0].rstrip(".,!?") if raw.split() else "..." - return first_word + # Cap at 12 words to prevent the model rambling and leaking the answer + words = raw.split() + return " ".join(words[:12]) if words else "..." except Exception as e: logger.error("20q answer error: %s", e, exc_info=True) return "..." -@command("20q", "AI thinks of something — ask up to 20 yes/no questions with !q, guess with !answer") +@command("20q", "AI thinks of something — ask up to 20 questions with !q, guess with !answer") async def cmd_20q(client: AsyncClient, room_id: str, sender: str, args: str): if room_id in _TWENTYQ_GAMES: g = _TWENTYQ_GAMES[room_id] @@ -2819,14 +2820,14 @@ async def cmd_20q(client: AsyncClient, room_id: str, sender: str, args: str): "asked": [], } await send_html(client, room_id, - f"🤔 I've got something in mind! Hint: {thing_data['hint']}\nAsk yes/no questions with !q (20 total) or guess with !answer ", + f"🤔 I've got something in mind! Hint: {thing_data['hint']}\nAsk any question with !q (20 total) or guess with !answer ", f'🤔 20 Questions!
' f'I\'m thinking of something — hint: {thing_data["hint"]}
' - f'Ask with !q <question> (20 total) or guess with !answer <guess>', + f'Ask anything with !q <question> (20 total) or guess with !answer <guess>', ) -@command("q", "Ask a yes/no question in 20 Questions (!q )") +@command("q", "Ask a question in 20 Questions (!q )") async def cmd_q(client: AsyncClient, room_id: str, sender: str, args: str): if room_id not in _TWENTYQ_GAMES: await send_text(client, room_id, "No 20Q game active. Start one with !20q") @@ -2834,7 +2835,7 @@ async def cmd_q(client: AsyncClient, room_id: str, sender: str, args: str): g = _TWENTYQ_GAMES[room_id] question = sanitize_input(args.strip()) if not question: - await send_text(client, room_id, "Ask a question, e.g. !q Is it alive?") + await send_text(client, room_id, "Ask a question, e.g. !q Is it alive? or !q What color is it?") return if g["questions_left"] <= 0: await send_text(client, room_id, "No questions left! Use !answer to make your final guess.")