Spaces:
Paused
Paused
File size: 6,070 Bytes
d86bd2d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | # models/bias/bias_utils.py
"""
Bias Detection Utilities for Penny
Provides zero-shot classification for detecting potential bias in text responses.
Uses a classification model to identify neutral content vs. biased language patterns.
"""
import asyncio
import os
import httpx
from typing import Dict, Any, Optional, List
import logging
# --- Logging Setup ---
logger = logging.getLogger(__name__)
# --- Hugging Face API Configuration ---
HF_API_URL = "https://api-inference.huggingface.co/models/facebook/bart-large-mnli"
HF_TOKEN = os.getenv("HF_TOKEN")
AGENT_NAME = "penny-bias-checker"
# Define the labels for Zero-Shot Classification.
CANDIDATE_LABELS = [
"neutral and objective",
"contains political bias",
"uses emotional language",
"is factually biased",
]
def _is_bias_available() -> bool:
"""
Check if bias detection service is available.
Returns:
bool: True if HF_TOKEN is configured
"""
return HF_TOKEN is not None and len(HF_TOKEN) > 0
async def check_bias(text: str) -> Dict[str, Any]:
"""
Runs zero-shot classification to check for bias in the input text.
Uses a pre-loaded classification model to analyze text for:
- Neutral and objective language
- Political bias
- Emotional language
- Factual bias
Args:
text: The string of text to analyze for bias
Returns:
Dictionary containing:
- analysis: List of labels with confidence scores, sorted by score
- available: Whether the bias detection service is operational
- message: Optional error or status message
Example:
>>> result = await check_bias("This is neutral text.")
>>> result['analysis'][0]['label']
'neutral and objective'
"""
# Input validation
if not text or not isinstance(text, str):
logger.warning("check_bias called with invalid text input")
return {
"analysis": [],
"available": False,
"message": "Invalid input: text must be a non-empty string"
}
# Strip text to avoid processing whitespace
text = text.strip()
if not text:
logger.warning("check_bias called with empty text after stripping")
return {
"analysis": [],
"available": False,
"message": "Invalid input: text is empty"
}
# Check API availability
if not _is_bias_available():
logger.warning(f"{AGENT_NAME}: API not configured (missing HF_TOKEN)")
return {
"analysis": [],
"available": False,
"message": "Bias detection service is currently unavailable"
}
try:
# Prepare API request for zero-shot classification
headers = {"Authorization": f"Bearer {HF_TOKEN}"}
payload = {
"inputs": text,
"parameters": {
"candidate_labels": CANDIDATE_LABELS,
"multi_label": True
}
}
# Call Hugging Face Inference API
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(HF_API_URL, json=payload, headers=headers)
if response.status_code != 200:
logger.error(f"Bias detection API returned status {response.status_code}")
return {
"analysis": [],
"available": False,
"message": f"Bias detection API error: {response.status_code}"
}
results = response.json()
# Validate results structure
if not results or not isinstance(results, dict):
logger.error(f"Bias detection returned unexpected format: {type(results)}")
return {
"analysis": [],
"available": True,
"message": "Inference returned unexpected format"
}
labels = results.get('labels', [])
scores = results.get('scores', [])
if not labels or not scores:
logger.warning("Bias detection returned empty labels or scores")
return {
"analysis": [],
"available": True,
"message": "No classification results returned"
}
# Build analysis results
analysis = [
{"label": label, "score": float(score)}
for label, score in zip(labels, scores)
]
# Sort by confidence score (descending)
analysis.sort(key=lambda x: x['score'], reverse=True)
logger.debug(f"Bias check completed successfully, top result: {analysis[0]['label']} ({analysis[0]['score']:.3f})")
return {
"analysis": analysis,
"available": True
}
except httpx.TimeoutException:
logger.error("Bias detection request timed out")
return {
"analysis": [],
"available": False,
"message": "Bias detection request timed out"
}
except asyncio.CancelledError:
logger.warning("Bias detection task was cancelled")
raise
except Exception as e:
logger.error(f"Error during bias detection inference: {e}", exc_info=True)
return {
"analysis": [],
"available": False,
"message": f"Bias detection error: {str(e)}"
}
def get_bias_pipeline_status() -> Dict[str, Any]:
"""
Returns the current status of the bias detection pipeline.
Returns:
Dictionary with pipeline availability status
"""
return {
"agent_name": AGENT_NAME,
"available": _is_bias_available(),
"api_configured": HF_TOKEN is not None
} |