Spaces:
Running
Running
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,370 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import torch
|
3 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
4 |
+
import numpy as np
|
5 |
+
|
6 |
+
class FallacyFinder:
|
7 |
+
def __init__(self):
|
8 |
+
# Fallacy labels mapping
|
9 |
+
self.fallacy_labels = {
|
10 |
+
'ad_hominem': 'Ad Hominem (Personal Attack)',
|
11 |
+
'strawman': 'Strawman (Misrepresenting Argument)',
|
12 |
+
'whataboutism': 'Whataboutism (Deflecting with Counter-Accusations)',
|
13 |
+
'gaslighting': 'Gaslighting (Making Someone Question Reality)',
|
14 |
+
'false_dichotomy': 'False Dichotomy (Only Two Options When More Exist)',
|
15 |
+
'appeal_to_emotion': 'Appeal to Emotion (Using Emotions Instead of Logic)',
|
16 |
+
'darvo': 'DARVO (Deny, Attack, Reverse Victim/Offender)',
|
17 |
+
'moving_goalposts': 'Moving Goalposts (Changing Criteria When Challenged)',
|
18 |
+
'cherry_picking': 'Cherry Picking (Selecting Only Supporting Evidence)',
|
19 |
+
'appeal_to_authority': 'Appeal to Authority (Inappropriate Use of Authority)',
|
20 |
+
'slippery_slope': 'Slippery Slope (One Thing Leads to Extreme Consequences)',
|
21 |
+
'motte_and_bailey': 'Motte and Bailey (Defending Weak Position with Stronger One)',
|
22 |
+
'gish_gallop': 'Gish Gallop (Overwhelming with Many Weak Arguments)',
|
23 |
+
'kafkatrapping': 'Kafkatrapping (Denial Proves Guilt)',
|
24 |
+
'sealioning': 'Sealioning (Persistent Bad-Faith Requests for Evidence)',
|
25 |
+
'no_fallacy': 'No Fallacy (Clean, Logical Statement)'
|
26 |
+
}
|
27 |
+
|
28 |
+
# Fallacy descriptions
|
29 |
+
self.fallacy_descriptions = {
|
30 |
+
'ad_hominem': "Attacking the person making the argument rather than addressing the argument itself.",
|
31 |
+
'strawman': "Misrepresenting someone's argument to make it easier to attack.",
|
32 |
+
'whataboutism': "Deflecting criticism by pointing out similar issues elsewhere instead of addressing the original concern.",
|
33 |
+
'gaslighting': "Making someone question their own memory, perception, or sanity.",
|
34 |
+
'false_dichotomy': "Presenting only two options when more alternatives exist.",
|
35 |
+
'appeal_to_emotion': "Using emotional manipulation instead of logical reasoning to persuade.",
|
36 |
+
'darvo': "A manipulation tactic: Deny the abuse, Attack the victim, Reverse Victim and Offender roles.",
|
37 |
+
'moving_goalposts': "Changing the criteria for evidence or success when the original criteria are met.",
|
38 |
+
'cherry_picking': "Selecting only data or examples that support your position while ignoring contradictory evidence.",
|
39 |
+
'appeal_to_authority': "Citing an authority figure inappropriately to support an argument.",
|
40 |
+
'slippery_slope': "Arguing that one small step will lead to a chain of negative consequences.",
|
41 |
+
'motte_and_bailey': "Switching between a defensible position (motte) and a controversial one (bailey).",
|
42 |
+
'gish_gallop': "Overwhelming an opponent with many rapid-fire arguments, regardless of their quality.",
|
43 |
+
'kafkatrapping': "A logical trap where denial of guilt is taken as evidence of guilt.",
|
44 |
+
'sealioning': "Persistently requesting evidence or debate in a way that appears reasonable but is actually harassment.",
|
45 |
+
'no_fallacy': "The statement appears to be logically sound and free of common fallacies."
|
46 |
+
}
|
47 |
+
|
48 |
+
# Try to load model, fallback to rule-based if not available
|
49 |
+
self.model = None
|
50 |
+
self.tokenizer = None
|
51 |
+
self.use_model = False
|
52 |
+
|
53 |
+
try:
|
54 |
+
# This would be your trained model path
|
55 |
+
# For now, we'll use a rule-based approach
|
56 |
+
# self.tokenizer = AutoTokenizer.from_pretrained("your-model-path")
|
57 |
+
# self.model = AutoModelForSequenceClassification.from_pretrained("your-model-path")
|
58 |
+
# self.use_model = True
|
59 |
+
pass
|
60 |
+
except:
|
61 |
+
print("Model not found, using rule-based approach")
|
62 |
+
|
63 |
+
def predict_with_rules(self, text):
|
64 |
+
"""Rule-based fallacy detection for when model isn't available"""
|
65 |
+
text_lower = text.lower().strip()
|
66 |
+
|
67 |
+
# Ad Hominem patterns
|
68 |
+
ad_hominem_patterns = [
|
69 |
+
'you\'re', 'you are', 'stupid', 'idiot', 'moron', 'dumb', 'ignorant',
|
70 |
+
'loser', 'pathetic', 'crazy', 'insane', 'nuts'
|
71 |
+
]
|
72 |
+
if any(pattern in text_lower for pattern in ad_hominem_patterns) and ('you' in text_lower):
|
73 |
+
return 'ad_hominem', 0.85
|
74 |
+
|
75 |
+
# Strawman patterns
|
76 |
+
strawman_patterns = [
|
77 |
+
'so you\'re saying', 'so you think', 'so you believe', 'so you want',
|
78 |
+
'according to you', 'you\'re advocating', 'you want to destroy'
|
79 |
+
]
|
80 |
+
if any(pattern in text_lower for pattern in strawman_patterns):
|
81 |
+
return 'strawman', 0.82
|
82 |
+
|
83 |
+
# Whataboutism patterns
|
84 |
+
whataboutism_patterns = [
|
85 |
+
'what about', 'but what about', 'how about when', 'what about when you',
|
86 |
+
'but you', 'but when you'
|
87 |
+
]
|
88 |
+
if any(pattern in text_lower for pattern in whataboutism_patterns):
|
89 |
+
return 'whataboutism', 0.88
|
90 |
+
|
91 |
+
# Gaslighting patterns
|
92 |
+
gaslighting_patterns = [
|
93 |
+
'that never happened', 'you\'re imagining', 'you\'re being too sensitive',
|
94 |
+
'you\'re overreacting', 'that\'s not what i said', 'you\'re remembering wrong',
|
95 |
+
'you\'re being paranoid', 'you\'re being dramatic'
|
96 |
+
]
|
97 |
+
if any(pattern in text_lower for pattern in gaslighting_patterns):
|
98 |
+
return 'gaslighting', 0.80
|
99 |
+
|
100 |
+
# False Dichotomy patterns
|
101 |
+
false_dichotomy_patterns = [
|
102 |
+
'either you\'re', 'either we', 'you\'re either', 'it\'s either',
|
103 |
+
'with us or against us', 'love it or leave it'
|
104 |
+
]
|
105 |
+
if any(pattern in text_lower for pattern in false_dichotomy_patterns):
|
106 |
+
return 'false_dichotomy', 0.78
|
107 |
+
|
108 |
+
# Appeal to Emotion patterns
|
109 |
+
emotion_patterns = [
|
110 |
+
'think of the children', 'innocent people will', 'if you have any heart',
|
111 |
+
'how can you sleep', 'break the hearts', 'suffer needlessly'
|
112 |
+
]
|
113 |
+
if any(pattern in text_lower for pattern in emotion_patterns):
|
114 |
+
return 'appeal_to_emotion', 0.83
|
115 |
+
|
116 |
+
# DARVO patterns
|
117 |
+
darvo_patterns = [
|
118 |
+
'i never did that', 'you\'re the one who', 'i\'m the victim here',
|
119 |
+
'you\'re attacking me', 'i\'m innocent', 'you started this'
|
120 |
+
]
|
121 |
+
if any(pattern in text_lower for pattern in darvo_patterns) and len([p for p in darvo_patterns if p in text_lower]) >= 2:
|
122 |
+
return 'darvo', 0.75
|
123 |
+
|
124 |
+
# Moving Goalposts patterns
|
125 |
+
goalpost_patterns = [
|
126 |
+
'that doesn\'t count because', 'you need better sources', 'that\'s not enough evidence',
|
127 |
+
'those statistics don\'t count', 'that\'s different because'
|
128 |
+
]
|
129 |
+
if any(pattern in text_lower for pattern in goalpost_patterns):
|
130 |
+
return 'moving_goalposts', 0.77
|
131 |
+
|
132 |
+
# Appeal to Authority patterns
|
133 |
+
authority_patterns = [
|
134 |
+
'my doctor said', 'the expert said', 'the ceo thinks', 'einstein believed',
|
135 |
+
'my professor told me', 'the government says'
|
136 |
+
]
|
137 |
+
if any(pattern in text_lower for pattern in authority_patterns):
|
138 |
+
return 'appeal_to_authority', 0.72
|
139 |
+
|
140 |
+
# Slippery Slope patterns
|
141 |
+
slope_patterns = [
|
142 |
+
'if we allow this', 'this will lead to', 'give them an inch',
|
143 |
+
'this is just the beginning', 'where will it end', 'slippery slope'
|
144 |
+
]
|
145 |
+
if any(pattern in text_lower for pattern in slope_patterns):
|
146 |
+
return 'slippery_slope', 0.74
|
147 |
+
|
148 |
+
# Check for healthy communication (No Fallacy)
|
149 |
+
healthy_patterns = [
|
150 |
+
'i understand your perspective', 'i disagree because', 'based on the evidence',
|
151 |
+
'i appreciate your input', 'let\'s examine', 'i think we need more information',
|
152 |
+
'i see the merit', 'thank you for sharing'
|
153 |
+
]
|
154 |
+
if any(pattern in text_lower for pattern in healthy_patterns):
|
155 |
+
return 'no_fallacy', 0.90
|
156 |
+
|
157 |
+
# Default to no fallacy if nothing detected
|
158 |
+
return 'no_fallacy', 0.60
|
159 |
+
|
160 |
+
def predict_fallacy(self, text):
|
161 |
+
"""Main prediction function"""
|
162 |
+
if not text.strip():
|
163 |
+
return None, 0, "Please enter a message to analyze."
|
164 |
+
|
165 |
+
if self.use_model and self.model is not None:
|
166 |
+
# Use trained model
|
167 |
+
inputs = self.tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=512)
|
168 |
+
|
169 |
+
with torch.no_grad():
|
170 |
+
outputs = self.model(**inputs)
|
171 |
+
predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
|
172 |
+
predicted_class_id = predictions.argmax().item()
|
173 |
+
confidence = predictions.max().item()
|
174 |
+
|
175 |
+
# Map to fallacy label
|
176 |
+
label_keys = list(self.fallacy_labels.keys())
|
177 |
+
predicted_label = label_keys[predicted_class_id]
|
178 |
+
|
179 |
+
else:
|
180 |
+
# Use rule-based approach
|
181 |
+
predicted_label, confidence = self.predict_with_rules(text)
|
182 |
+
|
183 |
+
fallacy_name = self.fallacy_labels[predicted_label]
|
184 |
+
description = self.fallacy_descriptions[predicted_label]
|
185 |
+
|
186 |
+
return predicted_label, confidence, fallacy_name, description
|
187 |
+
|
188 |
+
def analyze_message(self, message):
|
189 |
+
"""Analyze a message and return formatted results"""
|
190 |
+
if not message.strip():
|
191 |
+
return "Please enter a message to analyze.", "", ""
|
192 |
+
|
193 |
+
predicted_label, confidence, fallacy_name, description = self.predict_fallacy(message)
|
194 |
+
|
195 |
+
# Format confidence as percentage
|
196 |
+
confidence_percent = f"{confidence * 100:.1f}%"
|
197 |
+
|
198 |
+
# Determine confidence level
|
199 |
+
if confidence >= 0.8:
|
200 |
+
confidence_level = "π΄ High Confidence"
|
201 |
+
confidence_color = "high"
|
202 |
+
elif confidence >= 0.6:
|
203 |
+
confidence_level = "π‘ Medium Confidence"
|
204 |
+
confidence_color = "medium"
|
205 |
+
else:
|
206 |
+
confidence_level = "π’ Low Confidence"
|
207 |
+
confidence_color = "low"
|
208 |
+
|
209 |
+
# Format the main result
|
210 |
+
if predicted_label == 'no_fallacy':
|
211 |
+
result = f"β
**No Fallacy Detected**\n\n{confidence_level} ({confidence_percent})"
|
212 |
+
icon = "β
"
|
213 |
+
else:
|
214 |
+
result = f"β οΈ **{fallacy_name}**\n\n{confidence_level} ({confidence_percent})"
|
215 |
+
icon = "β οΈ"
|
216 |
+
|
217 |
+
# Format the explanation
|
218 |
+
explanation = f"**What this means:**\n{description}"
|
219 |
+
|
220 |
+
# Add examples or tips based on fallacy type
|
221 |
+
if predicted_label == 'ad_hominem':
|
222 |
+
explanation += "\n\n**Better approach:** Focus on the argument itself rather than the person making it."
|
223 |
+
elif predicted_label == 'strawman':
|
224 |
+
explanation += "\n\n**Better approach:** Address the actual argument being made, not a distorted version of it."
|
225 |
+
elif predicted_label == 'whataboutism':
|
226 |
+
explanation += "\n\n**Better approach:** Address the original concern directly before discussing other issues."
|
227 |
+
elif predicted_label == 'gaslighting':
|
228 |
+
explanation += "\n\n**Better approach:** Acknowledge the other person's experience and work toward understanding."
|
229 |
+
elif predicted_label == 'no_fallacy':
|
230 |
+
explanation = "**Great communication!** This message appears to use logical reasoning and respectful language."
|
231 |
+
|
232 |
+
return result, explanation, confidence_color
|
233 |
+
|
234 |
+
# Initialize the fallacy finder
|
235 |
+
finder = FallacyFinder()
|
236 |
+
|
237 |
+
def analyze_fallacy(message):
|
238 |
+
return finder.analyze_message(message)
|
239 |
+
|
240 |
+
# Create the Gradio interface
|
241 |
+
with gr.Blocks(
|
242 |
+
theme=gr.themes.Soft(primary_hue="red", secondary_hue="orange"),
|
243 |
+
title="Fallacy Finder",
|
244 |
+
css="""
|
245 |
+
.gradio-container {
|
246 |
+
max-width: 900px !important;
|
247 |
+
}
|
248 |
+
.high-confidence {
|
249 |
+
border-left: 4px solid #ef4444;
|
250 |
+
}
|
251 |
+
.medium-confidence {
|
252 |
+
border-left: 4px solid #eab308;
|
253 |
+
}
|
254 |
+
.low-confidence {
|
255 |
+
border-left: 4px solid #22c55e;
|
256 |
+
}
|
257 |
+
"""
|
258 |
+
) as demo:
|
259 |
+
|
260 |
+
gr.Markdown(
|
261 |
+
"""
|
262 |
+
# π Fallacy Finder
|
263 |
+
|
264 |
+
Analyze messages for logical fallacies and argumentative patterns. Enter any statement, argument, or message to identify potential fallacies and get suggestions for better communication.
|
265 |
+
|
266 |
+
**Detects 15+ types of fallacies** including ad hominem, strawman, whataboutism, gaslighting, and more.
|
267 |
+
"""
|
268 |
+
)
|
269 |
+
|
270 |
+
with gr.Row():
|
271 |
+
with gr.Column(scale=2):
|
272 |
+
message_input = gr.Textbox(
|
273 |
+
label="Enter a message to analyze",
|
274 |
+
placeholder="e.g., 'You're just saying that because you're too young to understand politics'",
|
275 |
+
lines=4,
|
276 |
+
info="Paste any statement, argument, or message you want to check for logical fallacies"
|
277 |
+
)
|
278 |
+
|
279 |
+
analyze_btn = gr.Button("π Analyze for Fallacies", variant="primary", size="lg")
|
280 |
+
|
281 |
+
with gr.Column(scale=1):
|
282 |
+
gr.Markdown(
|
283 |
+
"""
|
284 |
+
### Quick Examples:
|
285 |
+
|
286 |
+
**Ad Hominem:**
|
287 |
+
*"You're too stupid to understand"*
|
288 |
+
|
289 |
+
**Strawman:**
|
290 |
+
*"So you want to destroy the economy?"*
|
291 |
+
|
292 |
+
**Whataboutism:**
|
293 |
+
*"What about when you did the same thing?"*
|
294 |
+
|
295 |
+
**No Fallacy:**
|
296 |
+
*"I disagree because the evidence shows..."*
|
297 |
+
"""
|
298 |
+
)
|
299 |
+
|
300 |
+
with gr.Row():
|
301 |
+
with gr.Column():
|
302 |
+
result_output = gr.Textbox(
|
303 |
+
label="Analysis Result",
|
304 |
+
lines=4,
|
305 |
+
interactive=False
|
306 |
+
)
|
307 |
+
|
308 |
+
explanation_output = gr.Textbox(
|
309 |
+
label="Explanation & Suggestions",
|
310 |
+
lines=6,
|
311 |
+
interactive=False
|
312 |
+
)
|
313 |
+
|
314 |
+
# Examples section
|
315 |
+
gr.Markdown("## π Try These Examples")
|
316 |
+
|
317 |
+
example_messages = [
|
318 |
+
"You're clearly too young and inexperienced to have a valid opinion on this topic",
|
319 |
+
"So you're saying we should just let criminals run free and destroy society?",
|
320 |
+
"What about when you lied to me last year? Why should I trust you now?",
|
321 |
+
"That never happened, you're just being overly emotional and dramatic about nothing",
|
322 |
+
"Either you support the troops or you hate America - there's no middle ground",
|
323 |
+
"I understand your concerns, but based on the research I've seen, I think we should consider other options"
|
324 |
+
]
|
325 |
+
|
326 |
+
examples = gr.Examples(
|
327 |
+
examples=[[msg] for msg in example_messages],
|
328 |
+
inputs=[message_input],
|
329 |
+
outputs=[result_output, explanation_output],
|
330 |
+
fn=analyze_fallacy,
|
331 |
+
cache_examples=False
|
332 |
+
)
|
333 |
+
|
334 |
+
# Information section
|
335 |
+
gr.Markdown(
|
336 |
+
"""
|
337 |
+
## π― How It Works
|
338 |
+
|
339 |
+
This tool analyzes text for common logical fallacies and argumentative patterns that can harm productive communication. It looks for:
|
340 |
+
|
341 |
+
- **Personal attacks** instead of addressing arguments
|
342 |
+
- **Misrepresentations** of opposing viewpoints
|
343 |
+
- **Emotional manipulation** instead of logical reasoning
|
344 |
+
- **Deflection tactics** that avoid the main issue
|
345 |
+
- **False choices** that ignore other options
|
346 |
+
|
347 |
+
### π‘ Tips for Better Arguments
|
348 |
+
|
349 |
+
β
**Focus on the argument, not the person**
|
350 |
+
β
**Address the actual position being argued**
|
351 |
+
β
**Use evidence and logical reasoning**
|
352 |
+
β
**Acknowledge valid points from the other side**
|
353 |
+
β
**Stay on topic and address concerns directly**
|
354 |
+
"""
|
355 |
+
)
|
356 |
+
|
357 |
+
# Connect the function
|
358 |
+
analyze_btn.click(
|
359 |
+
fn=analyze_fallacy,
|
360 |
+
inputs=[message_input],
|
361 |
+
outputs=[result_output, explanation_output]
|
362 |
+
)
|
363 |
+
|
364 |
+
# Launch the app
|
365 |
+
if __name__ == "__main__":
|
366 |
+
demo.launch(
|
367 |
+
share=True,
|
368 |
+
server_name="0.0.0.0",
|
369 |
+
server_port=7860
|
370 |
+
)
|