nikita200 commited on
Commit
3584ae6
·
1 Parent(s): c7b31ba

Add Gradio demo UI with interactive charts for HF Spaces

Browse files

- app.py: Gradio dashboard with 6 Plotly charts (traffic, actions, CPU, latency, queue, reward)
- Dockerfile: updated to run Gradio app with HF Spaces uid 1000 user
- requirements.txt: add gradio and plotly

Files changed (3) hide show
  1. Dockerfile +9 -4
  2. app.py +325 -0
  3. requirements.txt +2 -0
Dockerfile CHANGED
@@ -12,8 +12,13 @@ COPY . .
12
  # HuggingFace Spaces requires port 7860
13
  EXPOSE 7860
14
 
15
- # Healthcheck so orchestrators know when the app is ready
16
- HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=3 \
17
- CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:7860/health')"
18
 
19
- CMD ["uvicorn", "environment:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
12
  # HuggingFace Spaces requires port 7860
13
  EXPOSE 7860
14
 
15
+ # HF Spaces runs as user with uid 1000
16
+ RUN useradd -m -u 1000 user
17
+ USER user
18
 
19
+ ENV HOME=/home/user \
20
+ PATH=/home/user/.local/bin:$PATH \
21
+ GRADIO_SERVER_NAME=0.0.0.0 \
22
+ GRADIO_SERVER_PORT=7860
23
+
24
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio demo for the Adaptive Traffic Controller.
3
+
4
+ Runs the simulation with a rule-based agent and visualises every step
5
+ through interactive charts. Deploy on HF Spaces as-is.
6
+
7
+ pip install gradio plotly
8
+ python app.py
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import gradio as gr
14
+ import plotly.graph_objects as go
15
+ from plotly.subplots import make_subplots
16
+
17
+ from models import Action, ACTION_ACCEPT_RATE, ServerState
18
+ from simulator import compute_next_state, initial_state
19
+ from tasks import TRAFFIC_PATTERNS, EPISODE_LENGTHS
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Rule-based agent (mirrors the LLM system prompt heuristics)
23
+ # ---------------------------------------------------------------------------
24
+
25
+ ACTION_LABELS = {
26
+ Action.allow_all: "allow_all (100%)",
27
+ Action.throttle_70: "throttle_70 (70%)",
28
+ Action.throttle_40: "throttle_40 (40%)",
29
+ Action.drop_aggressive: "drop_aggressive (20%)",
30
+ }
31
+
32
+ ACTION_COLORS = {
33
+ Action.allow_all: "#2ecc71", # green
34
+ Action.throttle_70: "#f1c40f", # yellow
35
+ Action.throttle_40: "#e67e22", # orange
36
+ Action.drop_aggressive: "#e74c3c", # red
37
+ }
38
+
39
+
40
+ def rule_based_agent(state: ServerState) -> Action:
41
+ """Heuristic agent — uses both current metrics AND upcoming request_rate."""
42
+ cpu = state.cpu_usage
43
+ latency = state.avg_latency
44
+ queue = state.queue_length
45
+ rate = state.request_rate # upcoming traffic the env exposes
46
+
47
+ # Proactive: if upcoming traffic would exceed capacity, throttle early
48
+ if rate > 130:
49
+ return Action.drop_aggressive
50
+ if rate > 100:
51
+ return Action.throttle_40
52
+ if rate > 70:
53
+ return Action.throttle_70
54
+
55
+ # Reactive: use current server health
56
+ if cpu < 0.6 and latency < 200 and queue < 50:
57
+ return Action.allow_all
58
+ if cpu < 0.75 and latency < 300:
59
+ return Action.throttle_70
60
+ if cpu < 0.9 and latency < 500 and queue < 150:
61
+ return Action.throttle_40
62
+ return Action.drop_aggressive
63
+
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # Random / naive baselines for comparison
67
+ # ---------------------------------------------------------------------------
68
+
69
+ def always_allow_agent(_state: ServerState) -> Action:
70
+ return Action.allow_all
71
+
72
+
73
+ def always_throttle_agent(_state: ServerState) -> Action:
74
+ return Action.throttle_40
75
+
76
+
77
+ AGENTS = {
78
+ "Smart Agent (rule-based)": rule_based_agent,
79
+ "Baseline: Always Allow": always_allow_agent,
80
+ "Baseline: Always Throttle 40%": always_throttle_agent,
81
+ }
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Simulation runner
85
+ # ---------------------------------------------------------------------------
86
+
87
+ def run_episode(task_id: str, agent_fn):
88
+ traffic_fn = TRAFFIC_PATTERNS[task_id]
89
+ max_steps = EPISODE_LENGTHS[task_id]
90
+
91
+ state = initial_state(traffic_fn(0))
92
+ steps, cpus, mems, latencies, queues = [], [], [], [], []
93
+ incoming_rates, allowed_rates, rewards, actions = [], [], [], []
94
+ cumulative_reward = []
95
+ total_reward = 0.0
96
+
97
+ for step in range(max_steps):
98
+ action = agent_fn(state)
99
+ incoming = traffic_fn(step)
100
+ accept_rate = ACTION_ACCEPT_RATE[action]
101
+ allowed = incoming * accept_rate
102
+
103
+ next_state, crashed = compute_next_state(state, allowed, incoming)
104
+ next_state.step = step + 1
105
+
106
+ # Reward (same formula as environment.py)
107
+ throughput_reward = allowed / max(incoming, 1.0)
108
+ latency_penalty = max(0.0, (next_state.avg_latency - 200.0) / 800.0)
109
+ queue_penalty = min(1.0, next_state.queue_length / 500.0)
110
+ reward = throughput_reward - latency_penalty * 0.5 - queue_penalty * 0.3
111
+ if crashed:
112
+ reward = -10.0
113
+ reward = round(reward, 4)
114
+ total_reward += reward
115
+
116
+ steps.append(step)
117
+ cpus.append(next_state.cpu_usage)
118
+ mems.append(next_state.memory_usage)
119
+ latencies.append(next_state.avg_latency)
120
+ queues.append(next_state.queue_length)
121
+ incoming_rates.append(incoming)
122
+ allowed_rates.append(allowed)
123
+ rewards.append(reward)
124
+ actions.append(action)
125
+ cumulative_reward.append(total_reward)
126
+
127
+ if crashed:
128
+ break
129
+
130
+ # Update state for next step
131
+ if step + 1 < max_steps:
132
+ upcoming = traffic_fn(step + 1)
133
+ next_state.request_rate = round(upcoming, 2)
134
+ state = next_state
135
+
136
+ return {
137
+ "steps": steps,
138
+ "cpu": cpus,
139
+ "memory": mems,
140
+ "latency": latencies,
141
+ "queue": queues,
142
+ "incoming": incoming_rates,
143
+ "allowed": allowed_rates,
144
+ "reward": rewards,
145
+ "cumulative_reward": cumulative_reward,
146
+ "actions": actions,
147
+ "crashed": crashed,
148
+ "total_reward": total_reward,
149
+ "final_step": len(steps),
150
+ "max_steps": max_steps,
151
+ }
152
+
153
+
154
+ # ---------------------------------------------------------------------------
155
+ # Plotly charts
156
+ # ---------------------------------------------------------------------------
157
+
158
+ def build_dashboard(task_id: str, agent_name: str):
159
+ agent_fn = AGENTS[agent_name]
160
+ data = run_episode(task_id, agent_fn)
161
+
162
+ steps = data["steps"]
163
+ fig = make_subplots(
164
+ rows=3, cols=2,
165
+ subplot_titles=(
166
+ "Traffic: Incoming vs Allowed (req/s)",
167
+ "Agent Actions Over Time",
168
+ "CPU & Memory Usage",
169
+ "Avg Latency (ms)",
170
+ "Queue Length",
171
+ "Cumulative Reward",
172
+ ),
173
+ vertical_spacing=0.08,
174
+ horizontal_spacing=0.08,
175
+ )
176
+
177
+ # 1) Traffic: incoming vs allowed
178
+ fig.add_trace(go.Scatter(
179
+ x=steps, y=data["incoming"], name="Incoming",
180
+ line=dict(color="#e74c3c", width=2),
181
+ fill="tozeroy", fillcolor="rgba(231,76,60,0.1)",
182
+ ), row=1, col=1)
183
+ fig.add_trace(go.Scatter(
184
+ x=steps, y=data["allowed"], name="Allowed",
185
+ line=dict(color="#2ecc71", width=2),
186
+ fill="tozeroy", fillcolor="rgba(46,204,113,0.1)",
187
+ ), row=1, col=1)
188
+ # Capacity line
189
+ fig.add_hline(y=100, line_dash="dash", line_color="gray",
190
+ annotation_text="Server Capacity", row=1, col=1)
191
+
192
+ # 2) Actions as colored bar chart
193
+ action_colors = [ACTION_COLORS[a] for a in data["actions"]]
194
+ action_labels = [ACTION_LABELS[a] for a in data["actions"]]
195
+ accept_pcts = [ACTION_ACCEPT_RATE[a] * 100 for a in data["actions"]]
196
+ fig.add_trace(go.Bar(
197
+ x=steps, y=accept_pcts, name="Accept %",
198
+ marker_color=action_colors,
199
+ text=action_labels, textposition="none",
200
+ hovertemplate="Step %{x}<br>Accept: %{y}%<br>%{text}<extra></extra>",
201
+ ), row=1, col=2)
202
+
203
+ # 3) CPU & Memory
204
+ fig.add_trace(go.Scatter(
205
+ x=steps, y=data["cpu"], name="CPU",
206
+ line=dict(color="#3498db", width=2),
207
+ ), row=2, col=1)
208
+ fig.add_trace(go.Scatter(
209
+ x=steps, y=data["memory"], name="Memory",
210
+ line=dict(color="#9b59b6", width=2),
211
+ ), row=2, col=1)
212
+ fig.add_hline(y=0.8, line_dash="dash", line_color="#e74c3c",
213
+ annotation_text="Danger", row=2, col=1)
214
+
215
+ # 4) Latency
216
+ fig.add_trace(go.Scatter(
217
+ x=steps, y=data["latency"], name="Latency",
218
+ line=dict(color="#e67e22", width=2),
219
+ fill="tozeroy", fillcolor="rgba(230,126,34,0.1)",
220
+ ), row=2, col=2)
221
+ fig.add_hline(y=400, line_dash="dash", line_color="#e74c3c",
222
+ annotation_text="Danger (400ms)", row=2, col=2)
223
+
224
+ # 5) Queue
225
+ fig.add_trace(go.Scatter(
226
+ x=steps, y=data["queue"], name="Queue",
227
+ line=dict(color="#1abc9c", width=2),
228
+ fill="tozeroy", fillcolor="rgba(26,188,156,0.1)",
229
+ ), row=3, col=1)
230
+ fig.add_hline(y=200, line_dash="dash", line_color="#e74c3c",
231
+ annotation_text="Danger (200)", row=3, col=1)
232
+
233
+ # 6) Cumulative Reward
234
+ fig.add_trace(go.Scatter(
235
+ x=steps, y=data["cumulative_reward"], name="Cum. Reward",
236
+ line=dict(color="#2c3e50", width=2.5),
237
+ fill="tozeroy", fillcolor="rgba(44,62,80,0.08)",
238
+ ), row=3, col=2)
239
+
240
+ # Layout
241
+ fig.update_layout(
242
+ height=900,
243
+ showlegend=False,
244
+ template="plotly_white",
245
+ title_text=f"Adaptive Traffic Controller — {task_id} | {agent_name}",
246
+ title_x=0.5,
247
+ font=dict(size=12),
248
+ margin=dict(t=80, b=40),
249
+ )
250
+
251
+ # Summary
252
+ status = "CRASHED" if data["crashed"] else "Survived"
253
+ summary = (
254
+ f"### Results\n"
255
+ f"- **Status:** {status}\n"
256
+ f"- **Steps completed:** {data['final_step']} / {data['max_steps']}\n"
257
+ f"- **Total reward:** {data['total_reward']:.3f}\n"
258
+ f"- **Avg reward/step:** {data['total_reward'] / max(data['final_step'], 1):.3f}\n"
259
+ )
260
+
261
+ return fig, summary
262
+
263
+
264
+ # ---------------------------------------------------------------------------
265
+ # Gradio UI
266
+ # ---------------------------------------------------------------------------
267
+
268
+ DESCRIPTION = """
269
+ # Adaptive Traffic Controller
270
+
271
+ An LLM agent that dynamically throttles backend traffic to **prevent server crashes
272
+ while maximising throughput**. Watch how it reacts to traffic spikes in real time!
273
+
274
+ **How it works:**
275
+ 1. Pick a traffic scenario (easy/medium/hard) and an agent strategy
276
+ 2. The simulation runs step-by-step — each step the agent observes server metrics
277
+ (CPU, memory, latency, queue) and decides how much traffic to allow
278
+ 3. Charts show every metric and the agent's decisions over time
279
+
280
+ **Actions available to the agent:**
281
+ | Action | Traffic Allowed |
282
+ |---|---|
283
+ | `allow_all` | 100% |
284
+ | `throttle_70` | 70% |
285
+ | `throttle_40` | 40% |
286
+ | `drop_aggressive` | 20% |
287
+ """
288
+
289
+ with gr.Blocks(
290
+ title="Adaptive Traffic Controller",
291
+ theme=gr.themes.Soft(),
292
+ ) as demo:
293
+ gr.Markdown(DESCRIPTION)
294
+
295
+ with gr.Row():
296
+ task_dd = gr.Dropdown(
297
+ choices=["task_easy", "task_medium", "task_hard"],
298
+ value="task_easy",
299
+ label="Traffic Scenario",
300
+ )
301
+ agent_dd = gr.Dropdown(
302
+ choices=list(AGENTS.keys()),
303
+ value="Smart Agent (rule-based)",
304
+ label="Agent Strategy",
305
+ )
306
+ run_btn = gr.Button("Run Simulation", variant="primary", scale=0)
307
+
308
+ plot_out = gr.Plot(label="Dashboard")
309
+ summary_out = gr.Markdown()
310
+
311
+ run_btn.click(
312
+ fn=build_dashboard,
313
+ inputs=[task_dd, agent_dd],
314
+ outputs=[plot_out, summary_out],
315
+ )
316
+
317
+ # Run on load so the page isn't empty
318
+ demo.load(
319
+ fn=build_dashboard,
320
+ inputs=[task_dd, agent_dd],
321
+ outputs=[plot_out, summary_out],
322
+ )
323
+
324
+ if __name__ == "__main__":
325
+ demo.launch()
requirements.txt CHANGED
@@ -5,3 +5,5 @@ openai>=1.30.0
5
  httpx>=0.27.0
6
  numpy>=1.26.0
7
  pyyaml>=6.0.1
 
 
 
5
  httpx>=0.27.0
6
  numpy>=1.26.0
7
  pyyaml>=6.0.1
8
+ gradio>=4.30.0
9
+ plotly>=5.22.0