yjernite HF Staff commited on
Commit
e562ce4
·
verified ·
1 Parent(s): e738d0a

Upload 5 files

Browse files
Files changed (5) hide show
  1. ui/__init__.py +2 -0
  2. ui/sidebar.py +35 -0
  3. ui/tab_config.py +106 -0
  4. ui/tab_policy.py +70 -0
  5. ui/tab_testing.py +179 -0
ui/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """UI package for moderation interface."""
2
+
ui/sidebar.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Sidebar UI component with app description and authentication."""
2
+
3
+ import os
4
+ import sys
5
+
6
+ import gradio as gr
7
+
8
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
+
10
+
11
+ def build_sidebar() -> dict:
12
+ """Build the sidebar UI with app description and login."""
13
+ with gr.Sidebar():
14
+ gr.Markdown("## About")
15
+ gr.Markdown(
16
+ """
17
+ This interface allows you to test moderation models with custom content policies.
18
+
19
+ **🧪 Testing Tab**: Enter content to test against your policy. View model predictions, categories, reasoning traces, and raw responses.
20
+
21
+ **📋 Policy Definition Tab**: Define your content policy by uploading a markdown file, entering it manually, or selecting from preset examples.
22
+
23
+ **⚙️ Configuration Tab**: Select models, adjust generation parameters, and customize system prompts and response formats.
24
+ """
25
+ )
26
+
27
+ gr.Markdown("---")
28
+ gr.Markdown("### Authentication")
29
+ login_button = gr.LoginButton(value="Log in to Hugging Face")
30
+ gr.Markdown("*Log in with your Hugging Face to be able to query models through Inference Providers.*")
31
+
32
+ return {
33
+ "login_button": login_button,
34
+ }
35
+
ui/tab_config.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration tab UI components."""
2
+
3
+ import os
4
+ import sys
5
+
6
+ import gradio as gr
7
+
8
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
+
10
+ from utils.constants import MODELS, REASONING_EFFORTS, RESPONSE_FORMAT
11
+ from utils.model_interface import extract_model_id, get_default_system_prompt
12
+
13
+
14
+ def build_config_tab() -> dict:
15
+ """Build the configuration tab UI."""
16
+ with gr.Tab("⚙️ Configuration"):
17
+ gr.Markdown("### Model Selection")
18
+
19
+ model_choices = [f"{m['name']} ({m['id']})" for m in MODELS]
20
+ model_dropdown = gr.Dropdown(label="Model", choices=model_choices, value=model_choices[0])
21
+ reasoning_effort = gr.Dropdown(label="Reasoning Effort (GPT-OSS only)", choices=REASONING_EFFORTS, value="Low", visible=True)
22
+
23
+ def update_reasoning_visibility(choice):
24
+ """Update reasoning effort visibility based on selected model."""
25
+ if not choice:
26
+ return gr.update(visible=False)
27
+ model_id = extract_model_id(choice)
28
+ return gr.update(visible=model_id.startswith("openai/gpt-oss") if model_id else False)
29
+
30
+ def update_system_prompt(model_choice, reasoning_effort_val):
31
+ """Update system prompt when model or reasoning effort changes."""
32
+ if not model_choice:
33
+ return ""
34
+ model_id = extract_model_id(model_choice)
35
+ return get_default_system_prompt(model_id, reasoning_effort_val)
36
+
37
+ # Initialize system prompt with default for first model
38
+ initial_model_id = extract_model_id(model_choices[0])
39
+ initial_system_prompt = get_default_system_prompt(initial_model_id, "Low")
40
+
41
+ gr.Markdown("---")
42
+ gr.Markdown("### System Prompt & Response Format")
43
+ gr.Markdown("*Edit the prompts below. System prompt varies by model type; response format is used for GPT-OSS developer channel and Qwen.*")
44
+
45
+ with gr.Row():
46
+ with gr.Column():
47
+ system_prompt_textbox = gr.Textbox(
48
+ label="System Prompt",
49
+ placeholder="System prompt will be auto-generated based on model...",
50
+ lines=10,
51
+ value=initial_system_prompt,
52
+ interactive=True,
53
+ )
54
+
55
+ with gr.Column():
56
+ response_format_textbox = gr.Textbox(
57
+ label="Response Format",
58
+ placeholder="Response format instructions...",
59
+ lines=10,
60
+ value=RESPONSE_FORMAT,
61
+ interactive=True,
62
+ )
63
+
64
+ gr.Markdown("*Edit the prompts above. Values are used directly when running tests.*")
65
+
66
+ def update_on_model_change(choice, reasoning_effort_val):
67
+ """Update both reasoning visibility and system prompt when model changes."""
68
+ visibility_update = update_reasoning_visibility(choice)
69
+ system_prompt_update = update_system_prompt(choice, reasoning_effort_val)
70
+ return visibility_update, system_prompt_update
71
+
72
+ # Update reasoning visibility and system prompt when model changes
73
+ model_dropdown.change(
74
+ update_on_model_change,
75
+ inputs=[model_dropdown, reasoning_effort],
76
+ outputs=[reasoning_effort, system_prompt_textbox],
77
+ )
78
+
79
+ # Update system prompt when reasoning effort changes (for GPT-OSS)
80
+ def update_on_reasoning_change(choice, effort):
81
+ """Update system prompt when reasoning effort changes."""
82
+ if not choice:
83
+ return ""
84
+ return update_system_prompt(choice, effort)
85
+
86
+ reasoning_effort.change(
87
+ update_on_reasoning_change,
88
+ inputs=[model_dropdown, reasoning_effort],
89
+ outputs=system_prompt_textbox,
90
+ )
91
+
92
+ gr.Markdown("---")
93
+ with gr.Accordion("Generation Parameters", open=False):
94
+ max_tokens = gr.Number(label="Max Tokens", value=4096, precision=0)
95
+ temperature = gr.Slider(label="Temperature", minimum=0.0, maximum=1.0, value=0.1, step=0.1)
96
+ top_p = gr.Slider(label="Top P", minimum=0.0, maximum=1.0, value=0.9, step=0.1)
97
+
98
+ return {
99
+ "model_dropdown": model_dropdown,
100
+ "reasoning_effort": reasoning_effort,
101
+ "system_prompt_textbox": system_prompt_textbox,
102
+ "response_format_textbox": response_format_textbox,
103
+ "max_tokens": max_tokens,
104
+ "temperature": temperature,
105
+ "top_p": top_p,
106
+ }
ui/tab_policy.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Policy definition tab UI components."""
2
+
3
+ import os
4
+ import sys
5
+
6
+ import gradio as gr
7
+
8
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
+
10
+ from utils.helpers import load_policy_from_file, load_preset_policy
11
+
12
+
13
+ def build_policy_tab(base_dir: str) -> dict:
14
+ """Build the policy definition tab UI."""
15
+ with gr.Tab("📋 Policy Definition"):
16
+ input_method = gr.Radio(label="Input Method", choices=["Upload Markdown", "Enter Manually", "Select Preset"], value="Select Preset")
17
+
18
+ upload_file = gr.File(label="Upload Markdown File", file_types=[".md"], visible=False)
19
+ upload_preview = gr.Textbox(label="File Preview", lines=10, interactive=False, visible=False)
20
+ load_upload_btn = gr.Button("Load Policy", visible=False)
21
+
22
+ manual_text = gr.Textbox(label="Policy Text", placeholder="Enter policy markdown...", lines=20, visible=False)
23
+ save_manual_btn = gr.Button("Save Policy", visible=False)
24
+
25
+ preset_dropdown = gr.Dropdown(
26
+ label="Select Preset", choices=["Hate Speech Policy", "Violence Policy", "Toxicity Policy"], value="Hate Speech Policy", visible=True
27
+ )
28
+ preset_preview = gr.Markdown(value="*Select a preset to preview*", visible=True)
29
+ load_preset_btn = gr.Button("Load Preset", visible=True)
30
+
31
+ gr.Markdown("---")
32
+ gr.Markdown("### Current Policy")
33
+ current_policy = gr.Markdown(value="*No policy loaded*")
34
+ clear_policy_btn = gr.Button("Clear Policy", variant="secondary")
35
+
36
+ current_policy_state = gr.State(value="")
37
+
38
+ def update_ui(method):
39
+ return (
40
+ gr.update(visible=(method == "Upload Markdown")),
41
+ gr.update(visible=(method == "Upload Markdown")),
42
+ gr.update(visible=(method == "Upload Markdown")),
43
+ gr.update(visible=(method == "Enter Manually")),
44
+ gr.update(visible=(method == "Enter Manually")),
45
+ gr.update(visible=(method == "Select Preset")),
46
+ gr.update(visible=(method == "Select Preset")),
47
+ gr.update(visible=(method == "Select Preset")),
48
+ )
49
+
50
+ input_method.change(update_ui, inputs=input_method, outputs=[upload_file, upload_preview, load_upload_btn, manual_text, save_manual_btn, preset_dropdown, preset_preview, load_preset_btn])
51
+
52
+ # Policy loading handlers
53
+ load_preset_btn.click(
54
+ lambda name: load_preset_policy(name, base_dir),
55
+ inputs=preset_dropdown,
56
+ outputs=[current_policy_state, current_policy],
57
+ )
58
+ load_upload_btn.click(
59
+ lambda f: load_policy_from_file(f.name) if f else ("", ""),
60
+ inputs=upload_file,
61
+ outputs=[current_policy_state, current_policy],
62
+ )
63
+ upload_file.change(lambda f: open(f.name).read() if f else "", inputs=upload_file, outputs=upload_preview)
64
+ save_manual_btn.click(lambda t: (t, t), inputs=manual_text, outputs=[current_policy_state, current_policy])
65
+ clear_policy_btn.click(lambda: ("", "*No policy loaded*"), outputs=[current_policy_state, current_policy])
66
+
67
+ return {
68
+ "current_policy_state": current_policy_state,
69
+ "current_policy": current_policy,
70
+ }
ui/tab_testing.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Testing tab UI components."""
2
+
3
+ import os
4
+ import sys
5
+
6
+ import gradio as gr
7
+
8
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
+
10
+ import json
11
+
12
+ from utils.constants import MODELS, TEST_EXAMPLES
13
+ from utils.model_interface import extract_model_id, get_model_info
14
+
15
+
16
+ def parse_json_response(response: str) -> dict:
17
+ """Parse JSON response, handling code blocks."""
18
+ response = response.strip()
19
+ try:
20
+ if "```json" in response:
21
+ response = response.split("```json")[1].split("```")[0]
22
+ elif "```" in response:
23
+ response = response.split("```")[1].split("```")[0]
24
+ return json.loads(response)
25
+ except json.JSONDecodeError:
26
+ return {"label": -1, "categories": []}
27
+
28
+
29
+ def format_model_info(model_choice, reasoning_effort) -> str:
30
+ """Format model information markdown."""
31
+ if not model_choice:
32
+ return "*Select a model in Configuration tab*"
33
+
34
+ model_id = extract_model_id(model_choice)
35
+ if not model_id:
36
+ return "*Select a model in Configuration tab*"
37
+
38
+ model_info = get_model_info(model_id)
39
+
40
+ if not model_info:
41
+ return f"*Model: {model_id}*"
42
+
43
+ model_name = model_info.get("name", model_id)
44
+ is_thinking = model_info.get("is_thinking", False)
45
+ supports_reasoning_level = model_info.get("supports_reasoning_level", False)
46
+
47
+ # Handle None or invalid reasoning_effort
48
+ reasoning_effort_val = reasoning_effort if reasoning_effort else "Low"
49
+
50
+ info_lines = [
51
+ f"**Model:** {model_name}",
52
+ f"- **Thinking Model:** {'Yes' if is_thinking else 'No'}",
53
+ f"- **Supports Reasoning Level:** {'Yes' if supports_reasoning_level else 'No'}",
54
+ ]
55
+
56
+ if supports_reasoning_level:
57
+ info_lines.append(f"- **Reasoning Effort:** {reasoning_effort_val}")
58
+
59
+ return "\n".join(info_lines)
60
+
61
+ def format_reasoning_info(model_choice, reasoning_text) -> tuple[str, bool]:
62
+ """Format reasoning info markdown and visibility."""
63
+ if not model_choice:
64
+ return "", False
65
+
66
+ model_id = extract_model_id(model_choice)
67
+ model_info = get_model_info(model_id)
68
+
69
+ if not model_info:
70
+ return "", False
71
+
72
+ is_thinking = model_info.get("is_thinking", False)
73
+
74
+ # For non-thinking models, always show the message
75
+ if not is_thinking:
76
+ return "*This model does not provide reasoning traces.*", True
77
+
78
+ # For thinking models, only show info if there's no reasoning text
79
+ if not reasoning_text or not reasoning_text.strip():
80
+ return "", False
81
+
82
+ return "", False
83
+
84
+
85
+ def format_test_result(result: dict) -> tuple[str, dict, str, str, str]:
86
+ """
87
+ Format test result for display.
88
+
89
+ Returns:
90
+ Tuple of (label_text, parsed_json, categories_text, reasoning_text, raw_response)
91
+ """
92
+ raw_content = result.get("content", "")
93
+ parsed = parse_json_response(raw_content)
94
+ label = parsed.get("label", -1)
95
+ categories = parsed.get("categories", [])
96
+
97
+ label_text = (
98
+ "## ❌ Policy Violation Detected" if label == 1
99
+ else "## ✅ No Policy Violation" if label == 0
100
+ else "## ⚠️ Unable to determine label"
101
+ )
102
+
103
+ if categories and len(categories) > 0:
104
+ cat_text = "### Categories:\n\n"
105
+ for cat in categories:
106
+ category_name = cat.get('category', 'Unknown')
107
+ reasoning_text = cat.get('reasoning', 'No reasoning provided')
108
+ policy_source = cat.get('policy_source', '')
109
+
110
+ cat_text += f"- **Category:** {category_name}\n"
111
+ cat_text += f"- **Explanation:** {reasoning_text}\n"
112
+ if policy_source:
113
+ cat_text += f"- **Policy Source:** {policy_source}\n"
114
+ cat_text += "\n\n"
115
+ else:
116
+ cat_text = "*No categories found in response*\n\n"
117
+ cat_text += "This output expects a valid JSON response, as specified for example in the default prompt.\n\n"
118
+ cat_text += "The raw response can be seen in the Model Response section below."
119
+
120
+ reasoning = result.get("reasoning", "")
121
+
122
+ # Format raw response for display
123
+ raw_response_text = f"```\n{raw_content}\n```"
124
+
125
+ return label_text, parsed, cat_text, reasoning or "", raw_response_text
126
+
127
+
128
+ def build_testing_tab() -> dict:
129
+ """Build the testing tab UI and set up simple handlers."""
130
+ with gr.Tab("🧪 Testing"):
131
+ with gr.Row():
132
+ with gr.Column(scale=1):
133
+ gr.Markdown("### Input")
134
+ with gr.Group():
135
+ test_input = gr.Textbox(label="Test Content", placeholder="Enter content to test...", lines=5)
136
+ example_dropdown = gr.Dropdown(label="Load Example", choices=list(TEST_EXAMPLES.keys()))
137
+ load_example_btn = gr.Button("Load Example", variant="secondary")
138
+ run_test_btn = gr.Button("Run Test", variant="primary")
139
+ # Initialize with default model info
140
+ initial_model = f"{MODELS[0]['name']} ({MODELS[0]['id']})"
141
+ initial_info_lines = [
142
+ f"**Model:** {MODELS[0]['name']}",
143
+ f"- **Thinking Model:** {'Yes' if MODELS[0]['is_thinking'] else 'No'}",
144
+ f"- **Supports Reasoning Level:** {'Yes' if MODELS[0]['supports_reasoning_level'] else 'No'}",
145
+ ]
146
+ if MODELS[0]['supports_reasoning_level']:
147
+ initial_info_lines.append("- **Reasoning Effort:** Low")
148
+ model_info_display = gr.Markdown(value="\n".join(initial_info_lines))
149
+
150
+ with gr.Column(scale=2):
151
+ gr.Markdown("### Results")
152
+ label_display = gr.Markdown(value="*Run a test to see results*")
153
+ with gr.Accordion("Categories & Reasoning", open=True):
154
+ categories_display = gr.Markdown(value="*No categories yet*")
155
+ with gr.Accordion("Model Response", open=False):
156
+ model_response_display = gr.Markdown(value="*No response yet*")
157
+ with gr.Accordion("Reasoning Trace", open=False):
158
+ reasoning_info = gr.Markdown(value="", visible=False)
159
+ reasoning_display = gr.Code(label="", language=None, value="", visible=False)
160
+
161
+ # Simple handlers that don't need cross-tab coordination
162
+ load_example_btn.click(
163
+ lambda name: TEST_EXAMPLES.get(name, ""),
164
+ inputs=example_dropdown,
165
+ outputs=test_input,
166
+ )
167
+
168
+ return {
169
+ "test_input": test_input,
170
+ "example_dropdown": example_dropdown,
171
+ "load_example_btn": load_example_btn,
172
+ "run_test_btn": run_test_btn,
173
+ "model_info_display": model_info_display,
174
+ "label_display": label_display,
175
+ "categories_display": categories_display,
176
+ "model_response_display": model_response_display,
177
+ "reasoning_info": reasoning_info,
178
+ "reasoning_display": reasoning_display,
179
+ }