Team project, HackAI
Bifocal
Flags abusive and manipulative messages with a multi-task classifier, and offers guidance through hybrid RAG with Gemini.
artifacts/metrics.json
# held_out accuracy, rounded
{
"incident_type": 0.741,
"emotional_state": 0.685,
"severity_score": 0.481,
"potential_crime": 0.824
}
Problem
Manipulation and coercive control can be hard to see from inside a conversation, especially one message at a time. Our team wanted a tool that could flag those patterns and suggest a useful next step.
What I built
Paste a conversation and Bifocal classifies each message, gives the conversation a 0–100 risk score, recommends next steps for flagged messages and answers safety questions from a grounded knowledge base.
The model is served through Flask to a React and TypeScript app.
How
- React and TypeScript appPaste a conversation
- Flask APIanalyze, recommend, ask
- MiniLM encoder and task headsIncident, emotion, severity, potential crimeHybrid RAGDense and BM25 retrieval
- Risk scoringMessage and conversation, 0–100Gemini guidanceExtractive fallback without a key
A pretrained sentence encoder feeds four task heads, trained with a class-weighted multi-task loss on 540 labeled messages. Temperature scaling calibrates the probabilities the API returns.
Guidance comes from hybrid retrieval over a digital-safety knowledge base, with Gemini generating the answer and an extractive fallback when no API key is set.
Results
- 74% held-out accuracy across 9 incident types, and 93% held-out on the simpler concerning-versus-safe split.
- Local CPU serving: single-message p95 of about 12 ms, and about 69 ms for a full recommendation with RAG.
- A 33-test pytest suite and GitHub Actions CI cover the service.
What I learned
- On a small, sensitive dataset, calibrated probabilities and honest held-out numbers matter more than a headline score.
- Mapping a model output to a useful next step is a product problem as much as a modelling one.