|
import os |
|
import time |
|
import requests |
|
import gradio as gr |
|
import pandas as pd |
|
import random |
|
import re |
|
from datetime import datetime |
|
from dotenv import load_dotenv |
|
from together import Together |
|
import openai |
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
def process_retrieval_text(retrieval_text, user_input): |
|
""" |
|
Process the retrieval text by identifying proper document boundaries |
|
and highlighting relevant keywords. |
|
""" |
|
if not retrieval_text or retrieval_text.strip() == "No retrieval text found.": |
|
return retrieval_text |
|
|
|
|
|
if retrieval_text.count("Doc:") > 0 and retrieval_text.count("Content:") > 0: |
|
|
|
chunks = [] |
|
|
|
|
|
doc_sections = re.split(r'\n\n(?=Doc:)', retrieval_text) |
|
|
|
for i, section in enumerate(doc_sections): |
|
if section.strip(): |
|
|
|
chunks.append(f"<strong>Evidence Document {i+1}</strong><br>{section.strip()}") |
|
else: |
|
|
|
|
|
raw_chunks = retrieval_text.strip().split("\n\n") |
|
chunks = [] |
|
current_chunk = "" |
|
|
|
for chunk in raw_chunks: |
|
|
|
|
|
if (len(chunk) < 50 and not re.search(r'doc|document|evidence', chunk.lower())) or \ |
|
not chunk.strip().startswith(("Doc", "Document", "Evidence", "Source", "Content")): |
|
if current_chunk: |
|
current_chunk += "\n\n" + chunk |
|
else: |
|
current_chunk = chunk |
|
else: |
|
|
|
if current_chunk: |
|
chunks.append(current_chunk) |
|
current_chunk = chunk |
|
|
|
|
|
if current_chunk: |
|
chunks.append(current_chunk) |
|
|
|
|
|
chunks = [f"<strong>Evidence Document {i+1}</strong><br>{chunk.strip()}" |
|
for i, chunk in enumerate(chunks)] |
|
|
|
|
|
keywords = re.findall(r'\b\w{4,}\b', user_input.lower()) |
|
keywords = [k for k in keywords if k not in ['what', 'when', 'where', 'which', 'would', 'could', |
|
'should', 'there', 'their', 'about', 'these', 'those', |
|
'them', 'from', 'have', 'this', 'that', 'will', 'with']] |
|
|
|
|
|
highlighted_chunks = [] |
|
for chunk in chunks: |
|
highlighted_chunk = chunk |
|
for keyword in keywords: |
|
|
|
pattern = r'\b(' + re.escape(keyword) + r')\b' |
|
highlighted_chunk = re.sub(pattern, r'<span class="highlight-match">\1</span>', highlighted_chunk, flags=re.IGNORECASE) |
|
|
|
highlighted_chunks.append(highlighted_chunk) |
|
|
|
return "<br><br>".join(highlighted_chunks) |
|
|
|
|
|
|
|
ORACLE_API_KEY = "key-HgVH3QX0GkyPKZhS3l3QrnLAqvjR2shrPPb_WK3lmrWHPzeKU" |
|
TOGETHER_API_KEY = "25e1acc0998143afee6b7cb3cb4a9447d39166be767a13a36a22da64234343de" |
|
OPENAI_API_KEY = "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" |
|
|
|
|
|
CUSTOM_CSS = """ |
|
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap'); |
|
|
|
body, .gradio-container { |
|
font-family: 'All Round Gothic Demi', 'Poppins', sans-serif !important; |
|
} |
|
|
|
.rating-box { |
|
border-radius: 8px; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
padding: 15px; |
|
margin-bottom: 10px; |
|
transition: all 0.3s ease; |
|
background-color: #ffffff; |
|
position: relative; |
|
overflow-y: auto; |
|
white-space: pre-line; |
|
font-family: 'All Round Gothic Demi', 'Poppins', sans-serif !important; |
|
} |
|
.rating-box:hover { |
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1); |
|
} |
|
.safe-rating { |
|
border-left: 5px solid #4CAF50; |
|
} |
|
.warning-rating { |
|
border-left: 5px solid #FCA539; |
|
} |
|
.unsafe-rating { |
|
border-left: 5px solid #F44336; |
|
} |
|
.empty-rating { |
|
border-left: 5px solid #FCA539; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-style: italic; |
|
color: #999; |
|
} |
|
|
|
/* Different heights for different rating boxes */ |
|
.contextual-box { |
|
min-height: 150px; |
|
} |
|
.secondary-box { |
|
min-height: 80px; |
|
} |
|
|
|
.result-header { |
|
font-size: 18px; |
|
font-weight: bold; |
|
margin-bottom: 10px; |
|
padding-bottom: 5px; |
|
border-bottom: 1px solid #eee; |
|
font-family: 'All Round Gothic Demi', 'Poppins', sans-serif !important; |
|
} |
|
.copy-button { |
|
position: absolute; |
|
top: 10px; |
|
right: 10px; |
|
padding: 5px 10px; |
|
background: #f0f0f0; |
|
border: none; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
font-size: 12px; |
|
font-family: 'All Round Gothic Demi', 'Poppins', sans-serif !important; |
|
} |
|
.copy-button:hover { |
|
background: #e0e0e0; |
|
} |
|
.orange-button { |
|
background: #FCA539 !important; |
|
color: #000000 !important; |
|
font-weight: bold; |
|
border-radius: 5px; |
|
padding: 10px 15px; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
transition: all 0.3s ease; |
|
font-family: 'All Round Gothic Demi', 'Poppins', sans-serif !important; |
|
} |
|
.orange-button:hover { |
|
box-shadow: 0 5px 15px rgba(0,0,0,0.2); |
|
transform: translateY(-2px); |
|
} |
|
|
|
/* Input box styling with orange border */ |
|
textarea.svelte-1pie7s6 { |
|
border-left: 5px solid #FCA539 !important; |
|
border-radius: 8px !important; |
|
} |
|
|
|
#loading-spinner { |
|
display: none; |
|
margin: 10px auto; |
|
width: 100%; |
|
height: 4px; |
|
position: relative; |
|
overflow: hidden; |
|
background-color: #ddd; |
|
} |
|
#loading-spinner:before { |
|
content: ''; |
|
display: block; |
|
position: absolute; |
|
left: -50%; |
|
width: 50%; |
|
height: 100%; |
|
background-color: #FCA539; |
|
animation: loading 1s linear infinite; |
|
} |
|
@keyframes loading { |
|
from {left: -50%;} |
|
to {left: 100%;} |
|
} |
|
.loading-active { |
|
display: block !important; |
|
} |
|
.empty-box-message { |
|
color: #999; |
|
font-style: italic; |
|
text-align: center; |
|
margin-top: 30px; |
|
font-family: 'All Round Gothic Demi', 'Poppins', sans-serif !important; |
|
} |
|
|
|
/* Knowledge Button Styling */ |
|
.knowledge-button { |
|
padding: 5px 10px; |
|
background-color: #222222; |
|
color: #ffffff !important; |
|
border: none; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
font-weight: 500; |
|
font-size: 12px; |
|
margin-bottom: 10px; |
|
display: inline-block; |
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1); |
|
transition: all 0.2s ease; |
|
text-decoration: none !important; |
|
} |
|
.knowledge-button:hover { |
|
background-color: #000000; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.15); |
|
} |
|
|
|
/* Knowledge popup styles - IMPROVED */ |
|
.knowledge-popup { |
|
display: block; |
|
padding: 20px; |
|
border: 2px solid #FCA539; |
|
background-color: white; |
|
border-radius: 8px; |
|
box-shadow: 0 5px 20px rgba(0,0,0,0.15); |
|
margin: 15px 0; |
|
position: relative; |
|
} |
|
|
|
.knowledge-popup-header { |
|
font-weight: bold; |
|
border-bottom: 1px solid #eee; |
|
padding-bottom: 10px; |
|
margin-bottom: 15px; |
|
color: #222; |
|
font-size: 16px; |
|
} |
|
|
|
.knowledge-popup-content { |
|
max-height: 400px; |
|
overflow-y: auto; |
|
line-height: 1.6; |
|
white-space: normal; |
|
} |
|
|
|
.knowledge-popup-content p { |
|
margin-bottom: 12px; |
|
} |
|
|
|
/* Document section formatting */ |
|
.doc-section { |
|
margin-bottom: 15px; |
|
padding-bottom: 15px; |
|
border-bottom: 1px solid #eee; |
|
} |
|
|
|
.doc-title { |
|
font-weight: bold; |
|
margin-bottom: 5px; |
|
color: #444; |
|
} |
|
|
|
.doc-content { |
|
padding-left: 10px; |
|
border-left: 3px solid #f0f0f0; |
|
} |
|
|
|
/* Matching text highlighting */ |
|
.highlight-match { |
|
background-color: #FCA539; |
|
color: black; |
|
font-weight: bold; |
|
padding: 0 2px; |
|
} |
|
|
|
/* Updated close button to match knowledge button */ |
|
.knowledge-popup-close { |
|
position: absolute; |
|
top: 15px; |
|
right: 15px; |
|
background-color: #222222; |
|
color: #ffffff !important; |
|
border: none; |
|
border-radius: 4px; |
|
padding: 5px 10px; |
|
cursor: pointer; |
|
font-size: 12px; |
|
font-weight: 500; |
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1); |
|
} |
|
.knowledge-popup-close:hover { |
|
background-color: #000000; |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.15); |
|
} |
|
|
|
h1, h2, h3, h4, h5, h6, p, span, div, button, input, textarea, label { |
|
font-family: 'All Round Gothic Demi', 'Poppins', sans-serif !important; |
|
} |
|
""" |
|
|
|
|
|
class ContextualAPIUtils: |
|
def __init__(self, api_key): |
|
self.api_key = api_key |
|
|
|
self.model_id = "92ab273b-378f-4b52-812b-7ec21506e49b" |
|
self.endpoint_url = f"https://api.contextual.ai/v1/agents/{self.model_id}/query" |
|
|
|
def chat(self, prompt): |
|
url = f"{self.endpoint_url}?retrievals_only=false&include_retrieval_content_text=true" |
|
headers = { |
|
"accept": "application/json", |
|
"content-type": "application/json", |
|
"authorization": f"Bearer {self.api_key}", |
|
} |
|
body = { |
|
"stream": False, |
|
"messages": [{"role": "user", "content": prompt}], |
|
} |
|
|
|
start_time = time.time() |
|
try: |
|
response = requests.post(url, headers=headers, json=body) |
|
response.raise_for_status() |
|
response_json = response.json() |
|
|
|
response_content = response_json.get("message", {}).get("content", "No content received.") |
|
retrieved_texts = [ |
|
f"Doc: {item.get('doc_name', 'Unknown')}, Page: {item.get('page', 'N/A')}\n" |
|
f"Content: {item.get('content_text', 'No Content')}" |
|
for item in response_json.get("retrieval_contents", []) |
|
] |
|
retrieval_text = "\n\n".join(retrieved_texts) if retrieved_texts else "No retrieval text found." |
|
error_message = "" |
|
|
|
except requests.exceptions.RequestException as e: |
|
response_content = "API Request Failed" |
|
retrieval_text = "" |
|
error_message = str(e) |
|
|
|
response_time = round(time.time() - start_time, 4) |
|
return response_content, response_time, retrieval_text, error_message |
|
|
|
|
|
def get_contextual_rating(contextual_api, user_input): |
|
rating_prompt = f"Content to rate: {user_input}" |
|
response_text, response_time, retrieval_text, error = contextual_api.chat(rating_prompt) |
|
if error: |
|
return f"Error: {error}", "", "unsafe" |
|
|
|
|
|
safety_level = "safe" |
|
if "out of policy" in response_text.lower() or "unsafe" in response_text.lower(): |
|
safety_level = "unsafe" |
|
elif "caution" in response_text.lower() or "warning" in response_text.lower(): |
|
safety_level = "warning" |
|
|
|
return response_text, retrieval_text, safety_level |
|
|
|
|
|
LLAMA_HARM_CATEGORIES = { |
|
"S1": "Hate Speech", "S2": "Harassment", "S3": "Violence & Threats", |
|
"S4": "Sexual Content", "S5": "Self-Harm & Suicide", "S6": "Child Sexual Exploitation", |
|
"S7": "Dangerous & Illegal Activities", "S8": "Misinformation", "S9": "Political Content", |
|
"S10": "Spam & Scams", "S11": "Privacy Violations", "S12": "Defamation", "S13": "Other Unsafe Content" |
|
} |
|
|
|
def get_llama_guard_rating(together_client, user_input): |
|
chat_input = [{"role": "user", "content": user_input}] |
|
try: |
|
response = together_client.chat.completions.create( |
|
model="meta-llama/Meta-Llama-Guard-3-8B", messages=chat_input |
|
) |
|
raw_output = response.choices[0].message.content.strip() |
|
for code, full_name in LLAMA_HARM_CATEGORIES.items(): |
|
raw_output = raw_output.replace(code, full_name) |
|
|
|
|
|
safety_level = "safe" |
|
if "unsafe" in raw_output.lower(): |
|
safety_level = "unsafe" |
|
elif "caution" in raw_output.lower() or "warning" in raw_output.lower(): |
|
safety_level = "warning" |
|
|
|
return raw_output, safety_level |
|
except Exception as e: |
|
return f"Error: {str(e)}", "unsafe" |
|
|
|
|
|
def get_openai_moderation(openai_client, user_input): |
|
try: |
|
start_time = time.time() |
|
response = openai_client.moderations.create(input=user_input, model="omni-moderation-latest") |
|
end_time = time.time() |
|
|
|
moderation_result = response.results[0] |
|
flagged = moderation_result.flagged |
|
safety_status = "Unsafe" if flagged else "Safe" |
|
safety_level = "unsafe" if flagged else "safe" |
|
|
|
categories = moderation_result.categories |
|
high_risk_categories = { |
|
category: score |
|
for category, score in vars(categories).items() |
|
if isinstance(score, (int, float)) and score > 0.5 |
|
} |
|
|
|
if not flagged and any(score > 0.3 for score in vars(categories).values() if isinstance(score, (int, float))): |
|
safety_level = "warning" |
|
|
|
formatted_result = f"Safety Status: {safety_status}\n" |
|
if high_risk_categories: |
|
formatted_result += "Flagged Categories (Confidence > 0.5):\n" |
|
for category, score in high_risk_categories.items(): |
|
formatted_result += f" - {category}: {score:.2f}\n" |
|
else: |
|
formatted_result += "Flagged Categories: None\n" |
|
|
|
return formatted_result, safety_level |
|
except Exception as e: |
|
return f"Safety Status: Error\nError: {str(e)}", "unsafe" |
|
|
|
|
|
def rate_user_input(user_input): |
|
|
|
contextual_api = ContextualAPIUtils(api_key=ORACLE_API_KEY) |
|
together_client = Together(api_key=TOGETHER_API_KEY) |
|
openai_client = openai.OpenAI(api_key=OPENAI_API_KEY) |
|
|
|
|
|
llama_rating, llama_safety = get_llama_guard_rating(together_client, user_input) |
|
contextual_rating, contextual_retrieval, contextual_safety = get_contextual_rating(contextual_api, user_input) |
|
openai_rating, openai_safety = get_openai_moderation(openai_client, user_input) |
|
|
|
|
|
llama_rating = re.sub(r'\.(?=\s+[A-Z])', '.\n', llama_rating) |
|
contextual_rating = re.sub(r'\.(?=\s+[A-Z])', '.\n', contextual_rating) |
|
|
|
|
|
processed_retrieval = process_retrieval_text(contextual_retrieval, user_input) |
|
|
|
|
|
llama_html = f"""<div class="rating-box secondary-box {llama_safety}-rating">{llama_rating}</div>""" |
|
openai_html = f"""<div class="rating-box secondary-box {openai_safety}-rating">{openai_rating}</div>""" |
|
|
|
|
|
knowledge_html = "" |
|
knowledge_button = "" |
|
|
|
if processed_retrieval and processed_retrieval != "No retrieval text found.": |
|
|
|
import uuid |
|
popup_id = f"knowledge-popup-{uuid.uuid4().hex[:8]}" |
|
|
|
|
|
knowledge_html = f""" |
|
<div id="{popup_id}" class="knowledge-popup" style="display: none;"> |
|
<div class="knowledge-popup-header">Retrieved Knowledge</div> |
|
<button class="knowledge-popup-close" |
|
onclick="this.parentElement.style.display='none'; |
|
document.getElementById('btn-{popup_id}').style.display='inline-block'; |
|
return false;"> |
|
Close |
|
</button> |
|
<div class="knowledge-popup-content"> |
|
{processed_retrieval} |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
knowledge_button = f""" |
|
<div style="margin-top: 10px; margin-bottom: 5px;"> |
|
<a href="#" id="btn-{popup_id}" class="knowledge-button" |
|
onclick="document.getElementById('{popup_id}').style.display='block'; this.style.display='none'; return false;"> |
|
Show supporting evidence |
|
</a> |
|
</div> |
|
""" |
|
|
|
|
|
contextual_html = f""" |
|
<div class="rating-box contextual-box {contextual_safety}-rating"> |
|
<button class="copy-button" onclick="navigator.clipboard.writeText(this.parentElement.innerText.replace('Copy', ''))">Copy</button> |
|
{contextual_rating} |
|
</div> |
|
{knowledge_button} |
|
{knowledge_html} |
|
""" |
|
|
|
return contextual_html, llama_html, openai_html, "" |
|
|
|
def random_test_case(): |
|
try: |
|
df = pd.read_csv("hate_speech_test_cases.csv") |
|
sample = df.sample(1).iloc[0]["user input"] |
|
return sample |
|
except Exception as e: |
|
return f"Error: {e}" |
|
|
|
|
|
def create_gradio_app(): |
|
|
|
theme = gr.themes.Default().set( |
|
body_text_size="16px", |
|
body_text_color="#333333", |
|
button_primary_background_fill="#FCA539", |
|
button_primary_text_color="#000000", |
|
button_secondary_background_fill="#FCA539", |
|
button_secondary_text_color="#000000", |
|
background_fill_primary="#FFFFFF", |
|
background_fill_secondary="#F8F9FA", |
|
block_title_text_weight="600", |
|
block_border_width="1px", |
|
block_shadow="0 1px 3px rgba(0,0,0,0.1)", |
|
border_color_primary="#E0E0E0" |
|
) |
|
|
|
|
|
custom_css = CUSTOM_CSS + """ |
|
/* Policy preview popup styles */ |
|
.policy-popup { |
|
display: none; |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-color: rgba(0,0,0,0.7); |
|
z-index: 1000; |
|
justify-content: center; |
|
align-items: center; |
|
} |
|
|
|
.policy-popup-content { |
|
background-color: white; |
|
width: 80%; |
|
height: 80%; |
|
border-radius: 8px; |
|
padding: 20px; |
|
position: relative; |
|
box-shadow: 0 5px 20px rgba(0,0,0,0.3); |
|
display: flex; |
|
flex-direction: column; |
|
} |
|
|
|
.policy-popup-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 15px; |
|
border-bottom: 1px solid #eee; |
|
padding-bottom: 10px; |
|
} |
|
|
|
.policy-popup-title { |
|
font-weight: bold; |
|
font-size: 18px; |
|
} |
|
|
|
.policy-popup-close { |
|
background-color: #222222; |
|
color: white; |
|
border: none; |
|
border-radius: 4px; |
|
padding: 5px 10px; |
|
cursor: pointer; |
|
} |
|
|
|
.policy-popup-close:hover { |
|
background-color: #000000; |
|
} |
|
|
|
.policy-iframe-container { |
|
flex: 1; |
|
overflow: hidden; |
|
} |
|
|
|
.policy-iframe { |
|
width: 100%; |
|
height: 100%; |
|
border: 1px solid #eee; |
|
} |
|
|
|
/* Fallback for when PDF can't be displayed in iframe */ |
|
.policy-fallback { |
|
padding: 20px; |
|
text-align: center; |
|
} |
|
|
|
.policy-fallback a { |
|
display: inline-block; |
|
margin-top: 15px; |
|
padding: 10px 15px; |
|
background-color: #FCA539; |
|
color: #000000; |
|
text-decoration: none; |
|
border-radius: 4px; |
|
font-weight: bold; |
|
} |
|
|
|
/* Custom gray button style */ |
|
.gray-button { |
|
background-color: #c4c4c3 !important; |
|
color: #000000 !important; |
|
} |
|
""" |
|
|
|
with gr.Blocks(title="Hate Speech Policy Rating Oracle", theme=theme, css=custom_css) as app: |
|
|
|
loading_spinner = gr.HTML('<div id="loading-spinner"></div>') |
|
|
|
|
|
pdf_file = gr.File("Hate Speech Policy.pdf", visible=False, label="Policy PDF") |
|
|
|
|
|
policy_popup_html = """ |
|
<div id="policy-popup" class="policy-popup"> |
|
<div class="policy-popup-content"> |
|
<div class="policy-popup-header"> |
|
<div class="policy-popup-title">Hate Speech Policy</div> |
|
<button class="policy-popup-close" onclick="document.getElementById('policy-popup').style.display='none';">Close</button> |
|
</div> |
|
<div class="policy-iframe-container"> |
|
<!-- Primary method: Try Google PDF Viewer --> |
|
<iframe class="policy-iframe" id="policy-iframe"></iframe> |
|
|
|
<!-- Fallback content if iframe fails --> |
|
<div class="policy-fallback" id="policy-fallback" style="display:none;"> |
|
<p>The policy document couldn't be displayed in the preview.</p> |
|
<a href="#" id="policy-download-link" target="_blank">Download Policy PDF</a> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
// Function to handle opening the policy popup |
|
function openPolicyPopup() { |
|
// Set PDF URL - this approach is more reliable with Gradio |
|
const pdfFileName = "Hate Speech Policy.pdf"; |
|
|
|
// Try multiple approaches to display the PDF |
|
// 1. Google PDF viewer (works in most cases) |
|
const googleViewerUrl = "https://docs.google.com/viewer?embedded=true&url="; |
|
|
|
// 2. Direct link as fallback |
|
let directPdfUrl = ""; |
|
|
|
// Find the PDF link by looking for file links in the DOM |
|
const links = document.querySelectorAll("a"); |
|
for (const link of links) { |
|
if (link.href && link.href.includes(encodeURIComponent(pdfFileName))) { |
|
directPdfUrl = link.href; |
|
break; |
|
} |
|
} |
|
|
|
// Set the iframe source if we found a link |
|
const iframe = document.getElementById("policy-iframe"); |
|
const fallback = document.getElementById("policy-fallback"); |
|
const downloadLink = document.getElementById("policy-download-link"); |
|
|
|
if (directPdfUrl) { |
|
// Try Google Viewer first |
|
iframe.src = googleViewerUrl + encodeURIComponent(directPdfUrl); |
|
iframe.style.display = "block"; |
|
fallback.style.display = "none"; |
|
|
|
// Set the download link |
|
downloadLink.href = directPdfUrl; |
|
|
|
// Provide fallback in case Google Viewer fails |
|
iframe.onerror = function() { |
|
iframe.style.display = "none"; |
|
fallback.style.display = "block"; |
|
}; |
|
} else { |
|
// No direct URL found, show fallback |
|
iframe.style.display = "none"; |
|
fallback.style.display = "block"; |
|
downloadLink.href = "#"; |
|
downloadLink.textContent = "PDF not available"; |
|
} |
|
|
|
// Display the popup |
|
document.getElementById('policy-popup').style.display = 'flex'; |
|
} |
|
</script> |
|
""" |
|
|
|
gr.HTML(policy_popup_html) |
|
|
|
gr.Markdown("# Hate Speech Policy Rating Oracle") |
|
gr.Markdown( |
|
"Assess whether user-generated social content contains hate speech using Contextual AI's State-of-the-Art Agentic RAG system. Classifications are steerable and explainable as they are based on a policy document rather than parametric knowledge! This app also returns ratings from LlamaGuard 3.0 and the OpenAI Moderation API for you to compare. This is a demo from Contextual AI researchers. Feedback is welcome as we work with design partners to bring this to production. \n" |
|
"## Instructions \n" |
|
"Enter user-generated content to receive an assessment from all three models. Or use our random test case generator to have it pre-filled. \n" |
|
"## How it works\n" |
|
"The Hate Speech Policy Rating Oracle leverages Contextual's groundbreaking agentic RAG (Retrieval-Augmented Generation) technology to provide accurate, consistent, and explainable content evaluations. Unlike traditional content moderation systems that rely solely on language models, our solution is firmly grounded in policy documents, ensuring evaluations are based on specific guidelines rather than abstract interpretations.\n\n" |
|
|
|
"### Key Benefits:\n\n" |
|
|
|
"- **Document-Grounded Evaluations**: Every rating is directly tied to our <a href='#' onclick='openPolicyPopup(); return false;'>hate speech policy document</a>, which makes our system far superior to other solutions that lack transparent decision criteria.\n\n" |
|
|
|
"- **Adaptable Policies**: The policy document serves as a starting point and can be easily adjusted to meet your specific requirements. As policies evolve, the system immediately adapts without requiring retraining.\n\n" |
|
|
|
"- **Clear Rationales**: Each evaluation includes a detailed explanation referencing specific policy sections, allowing users to understand exactly why content was flagged or approved.\n\n" |
|
|
|
"- **Continuous Improvement**: The system learns from feedback, addressing any misclassifications by improving retrieval accuracy over time.\n\n" |
|
|
|
"Our approach combines Contextual's state-of-the-art <a href='https://contextual.ai/blog/introducing-instruction-following-reranker/' target='_blank'>steerable reranker</a>, <a href='https://contextual.ai/blog/introducing-grounded-language-model/' target='_blank'>world's most grounded language model</a>, and <a href='https://contextual.ai/blog/combining-rag-and-specialization/' target='_blank'>tuning for agent specialization</a> to achieve superhuman performance in content evaluation tasks. This technology enables consistent, fine-grained assessments across any content type and format.\n\n" |
|
|
|
"<span style='color:red; font-weight:bold;'>SAFETY WARNING</span><br>" |
|
"Some of the randomly generated test cases contain hateful language that you might find offensive or upsetting." |
|
) |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
|
|
random_test_btn = gr.Button("π² Random Test Case", elem_classes=["orange-button"]) |
|
|
|
|
|
rate_btn = gr.Button("Rate Content", variant="primary", size="lg", elem_classes=["gray-button"]) |
|
|
|
|
|
user_input = gr.Textbox(label="Input content to rate:", placeholder="Type content to evaluate here...", lines=6) |
|
|
|
with gr.Column(scale=2): |
|
|
|
gr.HTML(""" |
|
<div> |
|
<h3 class="result-header">π Contextual Safety Oracle</h3> |
|
<div style="margin-top: -10px; margin-bottom: 10px;"> |
|
<a href="#" class="knowledge-button" onclick="openPolicyPopup(); return false;">View policy</a> |
|
</div> |
|
</div> |
|
""") |
|
contextual_results = gr.HTML('<div class="rating-box contextual-box empty-rating">Rating will appear here</div>') |
|
|
|
|
|
retrieved_knowledge = gr.HTML('', visible=False) |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
|
|
gr.HTML(""" |
|
<div> |
|
<h3 class="result-header">π¦ LlamaGuard Rating</h3> |
|
<div style="margin-top: -10px; margin-bottom: 10px;"> |
|
<a href="https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Guard3/8B/MODEL_CARD.md" |
|
target="_blank" class="knowledge-button">View model card</a> |
|
</div> |
|
</div> |
|
""") |
|
llama_results = gr.HTML('<div class="rating-box secondary-box empty-rating">Rating will appear here</div>') |
|
with gr.Column(): |
|
|
|
gr.HTML(""" |
|
<div> |
|
<h3 class="result-header">π§· OpenAI Moderation</h3> |
|
<div style="margin-top: -10px; margin-bottom: 10px;"> |
|
<a href="https://platform.openai.com/docs/guides/moderation" |
|
target="_blank" class="knowledge-button">View model card</a> |
|
</div> |
|
</div> |
|
""") |
|
openai_results = gr.HTML('<div class="rating-box secondary-box empty-rating">Rating will appear here</div>') |
|
|
|
|
|
def show_loading(): |
|
return """<script> |
|
const spinner = document.getElementById('loading-spinner'); |
|
if (spinner) spinner.style.display = 'block'; |
|
</script>""" |
|
|
|
def hide_loading(): |
|
return """<script> |
|
const spinner = document.getElementById('loading-spinner'); |
|
if (spinner) spinner.style.display = 'none'; |
|
</script>""" |
|
|
|
|
|
random_test_btn.click( |
|
show_loading, |
|
inputs=None, |
|
outputs=loading_spinner |
|
).then( |
|
random_test_case, |
|
inputs=[], |
|
outputs=[user_input] |
|
).then( |
|
hide_loading, |
|
inputs=None, |
|
outputs=loading_spinner |
|
) |
|
|
|
|
|
rate_btn.click( |
|
show_loading, |
|
inputs=None, |
|
outputs=loading_spinner |
|
).then( |
|
rate_user_input, |
|
inputs=[user_input], |
|
outputs=[contextual_results, llama_results, openai_results, retrieved_knowledge] |
|
).then( |
|
hide_loading, |
|
inputs=None, |
|
outputs=loading_spinner |
|
) |
|
|
|
return app |
|
|
|
|
|
if __name__ == "__main__": |
|
app = create_gradio_app() |
|
app.launch(share=True) |