Spaces:
Sleeping
Sleeping
| from groq import Groq | |
| import os | |
| import json | |
| class GroqClient: | |
| """Wrapper around the Groq API for pragmatic hate-speech classification.""" | |
| LABELS = [ | |
| "Direct Hate Speech", | |
| "Sarcastic Hate Speech", | |
| "Coded/Dogwhistle Hate — Needs Review", | |
| "Sarcastic/Ironic (Non-Hateful)", | |
| "Neutral", | |
| ] | |
| def __init__(self, model: str = "llama-3.3-70b-versatile"): | |
| self.client = Groq(api_key=os.environ.get("GROQ_API_KEY")) | |
| self.model = model | |
| def classify(self, text: str, kb_context: list = None) -> dict: | |
| """ | |
| Classify text for hate speech, including sarcastic and coded forms. | |
| kb_context: optional list of retrieved knowledge-base entries | |
| (each with 'surface_pattern', 'category', 'note') to ground | |
| the model's reasoning about potential coded references. | |
| """ | |
| context_block = "" | |
| if kb_context: | |
| context_block = "\n\nKnown documented coded patterns that may be relevant:\n" | |
| for entry in kb_context: | |
| context_block += ( | |
| f"- Pattern type: {entry['category']}. " | |
| f"Note: {entry['note']}\n" | |
| ) | |
| system_prompt = ( | |
| "You are an expert in pragmatics and hate speech detection, " | |
| "specializing in speech that evades detection through irony, " | |
| "sarcasm, or culturally coded references (dogwhistles).\n\n" | |
| f"Classify the text into exactly one of: {self.LABELS}.\n\n" | |
| "- 'Direct Hate Speech': explicit insults/slurs, no irony.\n" | |
| "- 'Sarcastic Hate Speech': literal words seem neutral/positive, " | |
| "but tone/context reveals a hateful or discriminatory attack.\n" | |
| "- 'Coded/Dogwhistle Hate — Needs Review': the phrase is " | |
| "semantically neutral on its surface, but may function as an " | |
| "indirect reference to a group or historical figure understood " | |
| "within a specific community, used to express hate while " | |
| "maintaining deniability. Use this label when you suspect " | |
| "coding but cannot be fully certain from text alone — this is " | |
| "a flag for human review, not a final verdict.\n" | |
| "- 'Sarcastic/Ironic (Non-Hateful)': irony present, no hate target.\n" | |
| "- 'Neutral': no hate, no irony, no coding.\n" | |
| f"{context_block}\n" | |
| "Be conservative with 'Coded/Dogwhistle Hate' — many neutral " | |
| "statements about religion, professions, or nationality are " | |
| "genuinely neutral. Only flag when structure or context " | |
| "suggests deliberate substitution (e.g. an odd, unnecessarily " | |
| "roundabout way to refer to a person/group that a direct " | |
| "term would describe more naturally).\n\n" | |
| 'Respond ONLY with JSON: {"label": "<label>", "reasoning": ' | |
| '"<one short sentence>", "confidence": "<low|medium|high>"}' | |
| ) | |
| completion = self.client.chat.completions.create( | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": text}, | |
| ], | |
| model=self.model, | |
| temperature=0, | |
| response_format={"type": "json_object"}, | |
| ) | |
| raw = completion.choices[0].message.content | |
| try: | |
| data = json.loads(raw) | |
| label = data.get("label", "Neutral") | |
| if label not in self.LABELS: | |
| label = "Neutral" | |
| return { | |
| "label": label, | |
| "reasoning": data.get("reasoning", ""), | |
| "confidence": data.get("confidence", "medium"), | |
| } | |
| except (json.JSONDecodeError, AttributeError): | |
| return { | |
| "label": "Neutral", | |
| "reasoning": "Could not parse model response.", | |
| "confidence": "low", | |
| } |