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": "