DeepPragma / src /llm_client.py
jamalinu's picture
Update src/llm_client.py
ef89bb4 verified
Raw
History Blame Contribute Delete
4.08 kB
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",
}