Ray368 commited on
Commit
d565ce0
·
verified ·
1 Parent(s): 2a85de4

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +316 -0
app.py ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import json
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ import time
7
+ import uuid
8
+ from pathlib import Path
9
+
10
+ import gradio as gr
11
+ import requests
12
+
13
+
14
+ GITHUB_OWNER = os.getenv("GITHUB_OWNER", "rzhub")
15
+ GITHUB_REPO = os.getenv("GITHUB_REPO", "GateMem")
16
+ GITHUB_BRANCH = os.getenv("GITHUB_BRANCH", "main")
17
+ LEADERBOARD_PATH = os.getenv("LEADERBOARD_PATH", "docs/assets/leaderboard.json")
18
+ PENDING_PATH = os.getenv("PENDING_PATH", "docs/assets/pending_submissions.json")
19
+ GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
20
+ ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "")
21
+
22
+ WORKDIR = Path("/tmp/gatemem_submit")
23
+ REPO_DIR = WORKDIR / "GateMem"
24
+ SUBMISSIONS_DIR = WORKDIR / "submissions"
25
+
26
+
27
+ def ensure_repo():
28
+ WORKDIR.mkdir(parents=True, exist_ok=True)
29
+ SUBMISSIONS_DIR.mkdir(parents=True, exist_ok=True)
30
+
31
+ if not REPO_DIR.exists():
32
+ subprocess.run(
33
+ ["git", "clone", "--depth", "1", "https://github.com/rzhub/GateMem.git", str(REPO_DIR)],
34
+ check=True,
35
+ )
36
+ else:
37
+ subprocess.run(["git", "-C", str(REPO_DIR), "pull"], check=False)
38
+
39
+
40
+ def github_headers():
41
+ if not GITHUB_TOKEN:
42
+ raise RuntimeError("GITHUB_TOKEN is not configured.")
43
+ return {
44
+ "Authorization": f"Bearer {GITHUB_TOKEN}",
45
+ "Accept": "application/vnd.github+json",
46
+ }
47
+
48
+
49
+ def github_get_json_file(path):
50
+ url = f"https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}/contents/{path}"
51
+ r = requests.get(url, headers=github_headers(), params={"ref": GITHUB_BRANCH}, timeout=30)
52
+
53
+ if r.status_code == 404:
54
+ return [], None
55
+
56
+ r.raise_for_status()
57
+ obj = r.json()
58
+ content = base64.b64decode(obj["content"]).decode("utf-8")
59
+ data = json.loads(content) if content.strip() else []
60
+ return data, obj["sha"]
61
+
62
+
63
+ def github_put_json_file(path, data, message, sha=None):
64
+ url = f"https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}/contents/{path}"
65
+ encoded = base64.b64encode(
66
+ json.dumps(data, indent=2, ensure_ascii=False).encode("utf-8")
67
+ ).decode("utf-8")
68
+
69
+ payload = {
70
+ "message": message,
71
+ "content": encoded,
72
+ "branch": GITHUB_BRANCH,
73
+ }
74
+ if sha:
75
+ payload["sha"] = sha
76
+
77
+ r = requests.put(url, headers=github_headers(), json=payload, timeout=30)
78
+ r.raise_for_status()
79
+ return r.json()
80
+
81
+
82
+ def extract_metrics(summary):
83
+ """
84
+ Adjust this if your summary.json uses slightly different field names.
85
+ """
86
+ return {
87
+ "u": float(summary.get("utility_accuracy", 0.0)),
88
+ "a": float(summary.get("privacy_leakage_rate", 0.0)),
89
+ "f": float(summary.get("deletion_leakage_rate", 0.0)),
90
+ "mgs": float(summary.get("compliance_utility_score", 0.0)),
91
+ }
92
+
93
+
94
+ def run_scorer(predictions_path, domain, use_llm_judge=False):
95
+ ensure_repo()
96
+
97
+ domain_name = domain.lower()
98
+ run_id = str(uuid.uuid4())[:8]
99
+ out_dir = SUBMISSIONS_DIR / run_id / "eval"
100
+ out_dir.mkdir(parents=True, exist_ok=True)
101
+
102
+ data_dir = REPO_DIR / "bench" / "data" / domain_name
103
+
104
+ cmd = [
105
+ "python",
106
+ str(REPO_DIR / "bench" / "scripts" / "score_predictions.py"),
107
+ "--data_dir",
108
+ str(data_dir),
109
+ "--predictions",
110
+ str(predictions_path),
111
+ "--out_dir",
112
+ str(out_dir),
113
+ ]
114
+
115
+ if use_llm_judge:
116
+ cmd += [
117
+ "--use_llm_judge",
118
+ "--judge_provider",
119
+ "openai",
120
+ "--judge_model",
121
+ "gpt-4o",
122
+ ]
123
+
124
+ env = os.environ.copy()
125
+ proc = subprocess.run(
126
+ cmd,
127
+ cwd=str(REPO_DIR),
128
+ env=env,
129
+ text=True,
130
+ stdout=subprocess.PIPE,
131
+ stderr=subprocess.STDOUT,
132
+ )
133
+
134
+ if proc.returncode != 0:
135
+ raise RuntimeError(f"Scoring failed:\n{proc.stdout}")
136
+
137
+ summary_path = out_dir / "summary.json"
138
+ if not summary_path.exists():
139
+ raise RuntimeError(f"summary.json not found. Scorer output:\n{proc.stdout}")
140
+
141
+ summary = json.loads(summary_path.read_text(encoding="utf-8"))
142
+ metrics = extract_metrics(summary)
143
+
144
+ return metrics, str(summary_path), proc.stdout
145
+
146
+
147
+ def submit_result(predictions_file, method, backbone, domain, family, contact, code_url, use_llm_judge):
148
+ if predictions_file is None:
149
+ return "Please upload predictions.jsonl."
150
+
151
+ if not method.strip():
152
+ return "Please provide a method name."
153
+
154
+ submit_id = f"{int(time.time())}-{uuid.uuid4().hex[:8]}"
155
+ submit_dir = SUBMISSIONS_DIR / submit_id
156
+ submit_dir.mkdir(parents=True, exist_ok=True)
157
+
158
+ pred_path = submit_dir / "predictions.jsonl"
159
+ shutil.copy(predictions_file.name, pred_path)
160
+
161
+ try:
162
+ metrics, summary_path, logs = run_scorer(pred_path, domain, use_llm_judge=use_llm_judge)
163
+ except Exception as e:
164
+ return f"Submission failed:\n{e}"
165
+
166
+ row = {
167
+ "submission_id": submit_id,
168
+ "method": method.strip(),
169
+ "backbone": backbone,
170
+ "domain": domain,
171
+ "family": family,
172
+ "u": round(metrics["u"], 1),
173
+ "a": round(metrics["a"], 1),
174
+ "f": round(metrics["f"], 1),
175
+ "mgs": round(metrics["mgs"], 1),
176
+ "source": "external",
177
+ "verified": False,
178
+ "contact": contact.strip(),
179
+ "code_url": code_url.strip(),
180
+ "created_at": int(time.time()),
181
+ }
182
+
183
+ pending, sha = github_get_json_file(PENDING_PATH)
184
+ pending.append(row)
185
+ github_put_json_file(
186
+ PENDING_PATH,
187
+ pending,
188
+ f"Add pending GateMem submission: {method.strip()}",
189
+ sha=sha,
190
+ )
191
+
192
+ return (
193
+ "Submitted and scored successfully.\n\n"
194
+ f"Status: pending maintainer approval\n"
195
+ f"Submission ID: {submit_id}\n\n"
196
+ f"U={row['u']}, A={row['a']}, F={row['f']}, MGS={row['mgs']}"
197
+ )
198
+
199
+
200
+ def list_pending(password):
201
+ if password != ADMIN_PASSWORD:
202
+ return "Invalid admin password."
203
+
204
+ pending, _ = github_get_json_file(PENDING_PATH)
205
+ if not pending:
206
+ return "No pending submissions."
207
+
208
+ lines = []
209
+ for item in pending:
210
+ lines.append(
211
+ f"{item['submission_id']} | {item['method']} | {item['backbone']} | "
212
+ f"{item['domain']} | U={item['u']} A={item['a']} F={item['f']} MGS={item['mgs']}"
213
+ )
214
+
215
+ return "\n".join(lines)
216
+
217
+
218
+ def approve_submission(password, submission_id):
219
+ if password != ADMIN_PASSWORD:
220
+ return "Invalid admin password."
221
+
222
+ pending, pending_sha = github_get_json_file(PENDING_PATH)
223
+ leaderboard, leaderboard_sha = github_get_json_file(LEADERBOARD_PATH)
224
+
225
+ target = None
226
+ remaining = []
227
+ for item in pending:
228
+ if item["submission_id"] == submission_id.strip():
229
+ target = item
230
+ else:
231
+ remaining.append(item)
232
+
233
+ if target is None:
234
+ return f"Submission not found: {submission_id}"
235
+
236
+ target["verified"] = True
237
+ target["approved_at"] = int(time.time())
238
+
239
+ leaderboard.append(target)
240
+
241
+ github_put_json_file(
242
+ LEADERBOARD_PATH,
243
+ leaderboard,
244
+ f"Approve GateMem leaderboard submission: {target['method']}",
245
+ sha=leaderboard_sha,
246
+ )
247
+ github_put_json_file(
248
+ PENDING_PATH,
249
+ remaining,
250
+ f"Remove approved pending submission: {target['method']}",
251
+ sha=pending_sha,
252
+ )
253
+
254
+ return (
255
+ f"Approved and added to leaderboard:\n"
256
+ f"{target['method']} | {target['backbone']} | {target['domain']} | "
257
+ f"MGS={target['mgs']}"
258
+ )
259
+
260
+
261
+ with gr.Blocks(title="GateMem Result Submission") as demo:
262
+ gr.Markdown("# GateMem Result Submission")
263
+ gr.Markdown(
264
+ "Upload `predictions.jsonl` generated by your method. "
265
+ "The server scores it with the official GateMem evaluator and stores it as a pending submission."
266
+ )
267
+
268
+ with gr.Tab("Submit Result"):
269
+ predictions_file = gr.File(label="predictions.jsonl", file_types=[".jsonl"])
270
+ method = gr.Textbox(label="Method name", placeholder="e.g., MyMemoryAgent")
271
+ backbone = gr.Dropdown(
272
+ ["GPT-5-mini", "GPT-4o-mini", "Gemini-2.5-Flash-Lite", "Other"],
273
+ label="Backbone model",
274
+ value="GPT-5-mini",
275
+ )
276
+ domain = gr.Dropdown(
277
+ ["Medical", "Office", "Education", "Household"],
278
+ label="Domain",
279
+ value="Medical",
280
+ )
281
+ family = gr.Dropdown(
282
+ ["Full-context", "RAG", "External memory", "Other"],
283
+ label="Method family",
284
+ value="Other",
285
+ )
286
+ contact = gr.Textbox(label="Contact email")
287
+ code_url = gr.Textbox(label="Code URL / commit / artifact link")
288
+ use_llm_judge = gr.Checkbox(
289
+ label="Use LLM judge if server is configured",
290
+ value=False,
291
+ )
292
+ submit_btn = gr.Button("Submit and Score", variant="primary")
293
+ submit_out = gr.Textbox(label="Submission status", lines=8)
294
+
295
+ submit_btn.click(
296
+ submit_result,
297
+ inputs=[predictions_file, method, backbone, domain, family, contact, code_url, use_llm_judge],
298
+ outputs=submit_out,
299
+ )
300
+
301
+ with gr.Tab("Admin"):
302
+ admin_password = gr.Textbox(label="Admin password", type="password")
303
+ list_btn = gr.Button("List Pending Submissions")
304
+ pending_out = gr.Textbox(label="Pending submissions", lines=12)
305
+
306
+ submission_id = gr.Textbox(label="Submission ID to approve")
307
+ approve_btn = gr.Button("Approve and Update Leaderboard", variant="primary")
308
+ approve_out = gr.Textbox(label="Approval status", lines=6)
309
+
310
+ list_btn.click(list_pending, inputs=[admin_password], outputs=pending_out)
311
+ approve_btn.click(approve_submission, inputs=[admin_password, submission_id], outputs=approve_out)
312
+
313
+
314
+ if __name__ == "__main__":
315
+ ensure_repo()
316
+ demo.launch()