roanbrasil commited on
Commit
d90e920
Β·
verified Β·
1 Parent(s): e264ed4

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +296 -0
app.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio Space: K8s Multi-Agent Debate Demo
3
+ HuggingFace Space: roanbrasil/k8s-multi-agent
4
+ """
5
+ import json
6
+ import gradio as gr
7
+
8
+ HARD8_RESULTS = [
9
+ {"resource": "Deployment", "rounds": 4, "time": 9.05,
10
+ "manifest": """apiVersion: apps/v1
11
+ kind: Deployment
12
+ metadata:
13
+ name: java-app
14
+ spec:
15
+ replicas: 2
16
+ selector:
17
+ matchLabels:
18
+ app: java-app
19
+ template:
20
+ metadata:
21
+ labels:
22
+ app: java-app
23
+ spec:
24
+ containers:
25
+ - name: java-app
26
+ image: openjdk:17
27
+ resources:
28
+ limits:
29
+ memory: "512Mi\""""},
30
+ {"resource": "HorizontalPodAutoscaler", "rounds": 4, "time": 10.27,
31
+ "manifest": """apiVersion: autoscaling/v2
32
+ kind: HorizontalPodAutoscaler
33
+ metadata:
34
+ name: api-hpa
35
+ spec:
36
+ scaleTargetRef:
37
+ apiVersion: apps/v1
38
+ kind: Deployment
39
+ name: api-server
40
+ minReplicas: 2
41
+ maxReplicas: 10
42
+ metrics:
43
+ - type: Resource
44
+ resource:
45
+ name: cpu
46
+ target:
47
+ type: Utilization
48
+ averageUtilization: 50"""},
49
+ {"resource": "StatefulSet", "rounds": 4, "time": 7.63,
50
+ "manifest": """apiVersion: apps/v1
51
+ kind: StatefulSet
52
+ metadata:
53
+ name: kafka
54
+ spec:
55
+ serviceName: kafka
56
+ replicas: 3
57
+ selector:
58
+ matchLabels:
59
+ app: kafka
60
+ template:
61
+ metadata:
62
+ labels:
63
+ app: kafka
64
+ spec:
65
+ containers:
66
+ - name: kafka
67
+ image: confluentinc/cp-kafka:7.4.0
68
+ volumeMounts:
69
+ - name: kafka-data
70
+ mountPath: /var/lib/kafka/data
71
+ volumeClaimTemplates:
72
+ - metadata:
73
+ name: kafka-data
74
+ spec:
75
+ accessModes: ["ReadWriteOnce"]
76
+ resources:
77
+ requests:
78
+ storage: 1Gi"""},
79
+ {"resource": "Ingress (TLS)", "rounds": 4, "time": 8.33,
80
+ "manifest": """apiVersion: networking.k8s.io/v1
81
+ kind: Ingress
82
+ metadata:
83
+ name: secure-ingress
84
+ spec:
85
+ tls:
86
+ - hosts:
87
+ - secure.example.com
88
+ secretName: tls-secret
89
+ rules:
90
+ - host: secure.example.com
91
+ http:
92
+ paths:
93
+ - path: /
94
+ pathType: Prefix
95
+ backend:
96
+ service:
97
+ name: example-service
98
+ port:
99
+ number: 80"""},
100
+ {"resource": "PersistentVolumeClaim", "rounds": 4, "time": 6.81,
101
+ "manifest": """apiVersion: v1
102
+ kind: PersistentVolumeClaim
103
+ metadata:
104
+ name: postgres-pvc
105
+ namespace: database
106
+ spec:
107
+ accessModes:
108
+ - ReadWriteOnce
109
+ resources:
110
+ requests:
111
+ storage: 1Gi"""},
112
+ {"resource": "CronJob", "rounds": 4, "time": 9.26,
113
+ "manifest": """apiVersion: batch/v1
114
+ kind: CronJob
115
+ metadata:
116
+ name: db-backup
117
+ spec:
118
+ schedule: "0 2 * * *"
119
+ jobTemplate:
120
+ spec:
121
+ template:
122
+ spec:
123
+ containers:
124
+ - name: backup
125
+ image: postgres:15
126
+ command: ["/bin/sh", "-c", "pg_dump $DATABASE_URL > /backup/dump.sql"]
127
+ restartPolicy: OnFailure"""},
128
+ {"resource": "NetworkPolicy", "rounds": 4, "time": 8.29,
129
+ "manifest": """apiVersion: networking.k8s.io/v1
130
+ kind: NetworkPolicy
131
+ metadata:
132
+ name: api-netpol
133
+ namespace: production
134
+ spec:
135
+ podSelector:
136
+ matchLabels:
137
+ app: api
138
+ policyTypes:
139
+ - Ingress
140
+ - Egress
141
+ ingress:
142
+ - from:
143
+ - podSelector:
144
+ matchLabels:
145
+ role: frontend
146
+ ports:
147
+ - protocol: TCP
148
+ port: 80"""},
149
+ {"resource": "ClusterRole", "rounds": 4, "time": 8.67,
150
+ "manifest": """apiVersion: rbac.authorization.k8s.io/v1
151
+ kind: ClusterRole
152
+ metadata:
153
+ name: pod-reader
154
+ rules:
155
+ - apiGroups: [""]
156
+ resources: ["pods"]
157
+ verbs: ["get", "list", "watch"]"""},
158
+ ]
159
+
160
+ SINGLE_MODEL_RESULTS = {
161
+ "Baseline GPT (46M)": {"yaml": 30.0, "k8s": 36.7, "sem": 96.9, "lat": 0.35},
162
+ "AttnRes GPT (48M)": {"yaml": 26.7, "k8s": 36.7, "sem": 97.8, "lat": 0.75},
163
+ "Qwen2.5-Coder (7B)": {"yaml": 40.0, "k8s": 33.3, "sem": 98.1, "lat": 1.27},
164
+ "DeepSeek-Coder (6.7B)":{"yaml": 16.7, "k8s": 33.3, "sem": 95.0, "lat": 1.61},
165
+ }
166
+
167
+ ARCHITECTURE_MD = """
168
+ ## System Architecture
169
+
170
+ ```
171
+ Problem β†’ BM25 RAG (4,794 K8s docs)
172
+ ↓
173
+ Agent 1 (AttnRes GPT 48M)
174
+ Fast domain specialist
175
+ ↓ draft
176
+ kubeconform --strict
177
+ ↓ error report
178
+ Agent 2 (Qwen2.5-Coder 7B)
179
+ Reasoning critic
180
+ ↓ critique + instruction
181
+ Agent 1 retries (max 3 rounds)
182
+ ↓ if not solved
183
+ Agent 2 generates directly (fallback)
184
+ ```
185
+
186
+ **Key design choices:**
187
+ - **Asymmetric roles**: small model drafts fast, large model reasons deeply
188
+ - **External validator**: kubeconform provides ground-truth schema signal (no hallucinated validation)
189
+ - **BM25 RAG**: top-3 K8s-specific documents grounded to each problem
190
+ - **Max 3 rounds**: bounded latency (~30s worst case)
191
+ """
192
+
193
+
194
+ def show_hard8_result(resource_name):
195
+ for r in HARD8_RESULTS:
196
+ if r["resource"] == resource_name:
197
+ summary = f"**Resource:** {r['resource']} \n"
198
+ summary += f"**Debate rounds:** {r['rounds']} \n"
199
+ summary += f"**Total time:** {r['time']:.2f}s \n"
200
+ summary += f"**Solved by:** Agent 2 (Qwen2.5-Coder fallback) \n"
201
+ summary += f"**Single-model K8s%:** 0% (all 4 models failed)\n"
202
+ return summary, r["manifest"]
203
+ return "Not found", ""
204
+
205
+
206
+ def show_benchmark_table():
207
+ rows = []
208
+ for model, m in SINGLE_MODEL_RESULTS.items():
209
+ rows.append([model, f"{m['yaml']:.1f}%", f"{m['k8s']:.1f}%",
210
+ f"{m['sem']:.1f}%", f"{m['lat']:.2f}s"])
211
+ return rows
212
+
213
+
214
+ with gr.Blocks(title="K8s Multi-Agent Debate Demo", theme=gr.themes.Soft()) as demo:
215
+ gr.Markdown("# K8s Multi-Agent Debate (MDA) System")
216
+ gr.Markdown(
217
+ "Combines **AttnRes GPT (48M)** + **Qwen2.5-Coder-7B** + **BM25 RAG** + **kubeconform** "
218
+ "to generate valid Kubernetes manifests. Achieves **100% schema compliance** on the "
219
+ "Hard-8 subset where all single models fail.\n\n"
220
+ "πŸ“„ [Paper](https://github.com/roanbrasil/llm-pocs) | "
221
+ "πŸ€— [Model](https://huggingface.co/roanbrasil/attnres-devops-gpt) | "
222
+ "πŸ“Š [K8sBench](https://huggingface.co/datasets/roanbrasil/k8sbench)"
223
+ )
224
+
225
+ with gr.Tabs():
226
+ with gr.Tab("Hard-8 Results"):
227
+ gr.Markdown("### Hard-8 Subset: Resources all single models fail (0% K8s%)")
228
+ gr.Markdown("The MDA system solves all 8 via the Agent 2 fallback after 3 debate rounds.")
229
+
230
+ resource_dd = gr.Dropdown(
231
+ choices=[r["resource"] for r in HARD8_RESULTS],
232
+ value="HorizontalPodAutoscaler",
233
+ label="Select K8s resource"
234
+ )
235
+ result_info = gr.Markdown()
236
+ manifest_out = gr.Code(language="yaml", label="Final valid manifest (kubeconform βœ“)")
237
+
238
+ resource_dd.change(show_hard8_result,
239
+ inputs=resource_dd,
240
+ outputs=[result_info, manifest_out])
241
+
242
+ demo.load(lambda: show_hard8_result("HorizontalPodAutoscaler"),
243
+ outputs=[result_info, manifest_out])
244
+
245
+ with gr.Tab("K8sBench Leaderboard"):
246
+ gr.Markdown("### K8sBench: 30-prompt evaluation across 4 models")
247
+ leaderboard = gr.Dataframe(
248
+ headers=["Model", "YAML%", "K8s%", "Sem%", "Latency"],
249
+ value=show_benchmark_table(),
250
+ label="K8sBench Results",
251
+ interactive=False
252
+ )
253
+ gr.Markdown(
254
+ "**K8s%** = kubeconform --strict schema compliance \n"
255
+ "Domain-specific 48M models **match or exceed** 7B generalists on schema compliance "
256
+ "while being **3–4Γ— faster**."
257
+ )
258
+
259
+ with gr.Tab("Architecture"):
260
+ gr.Markdown(ARCHITECTURE_MD)
261
+ gr.Markdown("""
262
+ ### Agent Roles
263
+
264
+ | Agent | Model | Role |
265
+ |-------|-------|------|
266
+ | **Agent 1** | AttnRes GPT (48M, local GPU) | Fast domain specialist β€” generates initial draft |
267
+ | **Agent 2** | Qwen2.5-Coder-7B (Ollama) | Reasoning critic β€” diagnoses kubeconform errors, instructs Agent 1, generates final manifest on fallback |
268
+ | **Validator** | kubeconform --strict | External schema arbitrator β€” provides ground-truth correctness signal |
269
+ | **RAG** | BM25Okapi over 4,794 K8s docs | Retrieves top-3 relevant examples per problem |
270
+ """)
271
+
272
+ with gr.Tab("About"):
273
+ gr.Markdown("""
274
+ ## About
275
+
276
+ This demo presents results from:
277
+
278
+ > Brasil, R. (2025). *Can Small Domain-Specific LLMs Compete with General 7B Models on Kubernetes Configuration Generation?*
279
+
280
+ ### Key Findings
281
+
282
+ 1. A **48M domain-specific model** matches **7B generalists** on Kubernetes schema compliance (36.7% vs 33.3%) while being **3.4Γ— faster**
283
+ 2. **All single models fail** on HPA, StatefulSet, Ingress, PVC (cross-field constraint resources)
284
+ 3. The **MDA system achieves 100%** on the Hard-8 subset via asymmetric debate with external validation
285
+ 4. **AttnRes** architectural improvement: βˆ’2.1% perplexity, βˆ’44% convergence steps
286
+
287
+ ### Resources
288
+
289
+ - πŸ”— Code: https://github.com/roanbrasil/llm-pocs
290
+ - πŸ€— Model: https://huggingface.co/roanbrasil/attnres-devops-gpt
291
+ - πŸ“Š Training corpus: https://huggingface.co/datasets/roanbrasil/devops-gitops-corpus
292
+ - πŸ“Š K8sBench: https://huggingface.co/datasets/roanbrasil/k8sbench
293
+ - πŸ“Š RAG corpus: https://huggingface.co/datasets/roanbrasil/k8s-rag-corpus
294
+ """)
295
+
296
+ demo.launch()