fix: 20q now answers any question, not just yes/no
Lint / Shell (shellcheck) (push) Successful in 12s
Lint / JS (eslint) (push) Successful in 6s
Lint / Python (ruff) (push) Failing after 5s
Lint / Python deps (pip-audit) (push) Successful in 41s
Lint / Secret scan (gitleaks) (push) Successful in 5s

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 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 19:47:11 -04:00
parent d095c34276
commit ad09286e27
+12 -11
View File
@@ -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 <question> (20 total) or guess with !answer <guess>",
f"🤔 I've got something in mind! Hint: {thing_data['hint']}\nAsk any question with !q <question> (20 total) or guess with !answer <guess>",
f'<font color="#a855f7"><strong>🤔 20 Questions!</strong></font><br>'
f'I\'m thinking of something — hint: <em>{thing_data["hint"]}</em><br>'
f'Ask with <code>!q &lt;question&gt;</code> (20 total) or guess with <code>!answer &lt;guess&gt;</code>',
f'Ask anything with <code>!q &lt;question&gt;</code> (20 total) or guess with <code>!answer &lt;guess&gt;</code>',
)
@command("q", "Ask a yes/no question in 20 Questions (!q <question>)")
@command("q", "Ask a question in 20 Questions (!q <question>)")
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.")