Add auto-polls to settle disagreements between users

LLM analysis now detects when two users are in a genuine
disagreement. When detected, the bot creates a native Discord
poll with each user's position as an option.

- Disagreement detection added to LLM analysis tool schema
- Polls last 4 hours with 1 hour per-channel cooldown
- LLM extracts topic, both positions, and usernames
- Configurable via polls section in config.yaml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 09:22:32 -05:00
parent 13a2030021
commit 622f0a325b
4 changed files with 113 additions and 0 deletions

View File

@@ -90,6 +90,36 @@ ANALYSIS_TOOL = {
"type": ["string", "null"],
"description": "The game channel name this message is about (e.g. 'gta-online', 'warzone'), or null if not game-specific.",
},
"disagreement_detected": {
"type": "boolean",
"description": "True if the target message is part of a clear disagreement between two users in the recent context. Only flag genuine back-and-forth debates, not one-off opinions.",
},
"disagreement_summary": {
"type": ["object", "null"],
"description": "If disagreement_detected is true, summarize the disagreement. Null otherwise.",
"properties": {
"topic": {
"type": "string",
"description": "Short topic of the disagreement (max 60 chars, e.g. 'Are snipers OP in Warzone?').",
},
"side_a": {
"type": "string",
"description": "First user's position (max 50 chars, e.g. 'Snipers are overpowered').",
},
"side_b": {
"type": "string",
"description": "Second user's position (max 50 chars, e.g. 'Snipers are balanced').",
},
"user_a": {
"type": "string",
"description": "Display name of the first user.",
},
"user_b": {
"type": "string",
"description": "Display name of the second user.",
},
},
},
},
"required": ["toxicity_score", "categories", "reasoning", "off_topic", "topic_category", "topic_reasoning", "coherence_score", "coherence_flag"],
},
@@ -213,6 +243,19 @@ class LLMClient:
result.setdefault("note_update", None)
result.setdefault("detected_game", None)
result["disagreement_detected"] = bool(result.get("disagreement_detected", False))
summary = result.get("disagreement_summary")
if result["disagreement_detected"] and isinstance(summary, dict):
# Truncate fields to Discord poll limits
summary["topic"] = str(summary.get("topic", ""))[:60]
summary["side_a"] = str(summary.get("side_a", ""))[:50]
summary["side_b"] = str(summary.get("side_b", ""))[:50]
summary.setdefault("user_a", "")
summary.setdefault("user_b", "")
result["disagreement_summary"] = summary
else:
result["disagreement_summary"] = None
return result
def _parse_content_fallback(self, text: str) -> dict | None: