Spaces:
Sleeping
Sleeping
Farbricated commited on
Commit Β·
dfd4feb
1
Parent(s): 78f22ef
v6.2
Browse files- apply_patches.py +655 -0
- baseline.py +6 -4
- baseline.py.bak +180 -0
- data_generators.py +708 -0
- expert_tasks.py +656 -0
- inference.py +6 -7
- inference.py.bak +365 -0
- openenv.yaml +49 -7
- openenv.yaml.bak +232 -0
- server/Meta_environment.py +72 -36
- server/Meta_environment.py.bak +1520 -0
- server/app.py +68 -1
- server/app.py.bak +363 -0
- server/gradio_ui.py +16 -3
- server/gradio_ui.py.bak +378 -0
apply_patches.py
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
apply_patches.py β Automatically applies all 4 fixes to Meta OpenEnv v3.0
|
| 4 |
+
|
| 5 |
+
Run from the repo root:
|
| 6 |
+
python apply_patches.py [--repo-dir /path/to/repo]
|
| 7 |
+
|
| 8 |
+
What it does:
|
| 9 |
+
Fix 1: Wires data_generators.py into Meta_environment.py._load_context()
|
| 10 |
+
Fix 2: Makes easy/medium graders accept per-episode context
|
| 11 |
+
Fix 3: Adds /step/typed/{task_id} typed endpoint to server/app.py
|
| 12 |
+
Fix 4: Updates task lists in gradio_ui.py, baseline.py, inference.py, openenv.yaml
|
| 13 |
+
|
| 14 |
+
Place data_generators.py and expert_tasks.py in the repo root alongside this script.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import os
|
| 19 |
+
import re
|
| 20 |
+
import shutil
|
| 21 |
+
import sys
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def banner(msg: str) -> None:
|
| 26 |
+
print(f"\n{'β'*60}")
|
| 27 |
+
print(f" {msg}")
|
| 28 |
+
print(f"{'β'*60}")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def read(path: Path) -> str:
|
| 32 |
+
return path.read_text(encoding="utf-8")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def write(path: Path, content: str) -> None:
|
| 36 |
+
path.write_text(content, encoding="utf-8")
|
| 37 |
+
print(f" β wrote {path}")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def backup(path: Path) -> None:
|
| 41 |
+
bak = path.with_suffix(path.suffix + ".bak")
|
| 42 |
+
shutil.copy2(path, bak)
|
| 43 |
+
print(f" β backed up β {bak.name}")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 47 |
+
# Fix 1 + 2 β Meta_environment.py
|
| 48 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 49 |
+
|
| 50 |
+
def patch_meta_environment(repo: Path) -> None:
|
| 51 |
+
banner("Fix 1+2 β server/Meta_environment.py")
|
| 52 |
+
path = repo / "server" / "Meta_environment.py"
|
| 53 |
+
if not path.exists():
|
| 54 |
+
print(f" β not found: {path}"); return
|
| 55 |
+
backup(path)
|
| 56 |
+
src = read(path)
|
| 57 |
+
|
| 58 |
+
# ββ Add imports ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 59 |
+
import_block = """\
|
| 60 |
+
from data_generators import (
|
| 61 |
+
gen_email_easy, gen_email_medium, gen_email_hard,
|
| 62 |
+
gen_code_easy,
|
| 63 |
+
gen_data_easy, gen_data_medium, gen_data_hard,
|
| 64 |
+
gen_mod_easy, gen_mod_medium,
|
| 65 |
+
gen_ticket_easy,
|
| 66 |
+
)
|
| 67 |
+
from expert_tasks import (
|
| 68 |
+
EMAIL_EXPERT_CONTEXT, grade_email_expert,
|
| 69 |
+
CODE_EXPERT_CONTEXT, grade_code_expert,
|
| 70 |
+
DATA_EXPERT_CONTEXT, grade_data_expert,
|
| 71 |
+
MOD_EXPERT_CONTEXT, grade_mod_expert,
|
| 72 |
+
TICKET_EXPERT_CONTEXT, grade_ticket_expert,
|
| 73 |
+
EXPERT_INSTRUCTIONS, EXPERT_TASK_IDS,
|
| 74 |
+
)
|
| 75 |
+
"""
|
| 76 |
+
if "from data_generators import" not in src:
|
| 77 |
+
# Insert after the last existing import block (after "from models import")
|
| 78 |
+
src = src.replace(
|
| 79 |
+
"from models import MetaAction, MetaObservation",
|
| 80 |
+
"from models import MetaAction, MetaObservation\n\n" + import_block,
|
| 81 |
+
)
|
| 82 |
+
print(" β added data_generators + expert_tasks imports")
|
| 83 |
+
|
| 84 |
+
# ββ Replace static context loaders with generator calls ββββββββββββββββββ
|
| 85 |
+
replacements = [
|
| 86 |
+
# email_easy
|
| 87 |
+
(
|
| 88 |
+
r"if task_id == \"email_triage_easy\":\s+email\s+=.*?\n.*?return agent_ctx, grader_ctx, \"easy\"",
|
| 89 |
+
'if task_id == "email_triage_easy":\n'
|
| 90 |
+
' agent_ctx, grader_ctx = gen_email_easy(self._state.episode_id)\n'
|
| 91 |
+
' return agent_ctx, grader_ctx, "easy"',
|
| 92 |
+
),
|
| 93 |
+
# email_medium
|
| 94 |
+
(
|
| 95 |
+
r"if task_id == \"email_triage_medium\":\s+agent_ctx\s+=.*?\n.*?return agent_ctx, grader_ctx, \"medium\"",
|
| 96 |
+
'if task_id == "email_triage_medium":\n'
|
| 97 |
+
' agent_ctx, grader_ctx = gen_email_medium(self._state.episode_id)\n'
|
| 98 |
+
' return agent_ctx, grader_ctx, "medium"',
|
| 99 |
+
),
|
| 100 |
+
# email_hard
|
| 101 |
+
(
|
| 102 |
+
r"if task_id == \"email_triage_hard\":\s+case\s+=.*?\n.*?return agent_ctx, grader_ctx, \"hard\"",
|
| 103 |
+
'if task_id == "email_triage_hard":\n'
|
| 104 |
+
' agent_ctx, grader_ctx = gen_email_hard(self._state.episode_id)\n'
|
| 105 |
+
' return agent_ctx, grader_ctx, "hard"',
|
| 106 |
+
),
|
| 107 |
+
# code_easy
|
| 108 |
+
(
|
| 109 |
+
r"if task_id == \"code_review_easy\":\s+variant\s+=.*?\n.*?return agent_ctx, grader_ctx, \"easy\"",
|
| 110 |
+
'if task_id == "code_review_easy":\n'
|
| 111 |
+
' agent_ctx, grader_ctx = gen_code_easy(self._state.episode_id)\n'
|
| 112 |
+
' return agent_ctx, grader_ctx, "easy"',
|
| 113 |
+
),
|
| 114 |
+
# data_easy
|
| 115 |
+
(
|
| 116 |
+
r"if task_id == \"data_cleaning_easy\":\s+ctx = \{\"data\": DATA_EASY\[\"data\"\]\}\s+return ctx, ctx, \"easy\"",
|
| 117 |
+
'if task_id == "data_cleaning_easy":\n'
|
| 118 |
+
' agent_ctx, grader_ctx = gen_data_easy(self._state.episode_id)\n'
|
| 119 |
+
' return agent_ctx, grader_ctx, "easy"',
|
| 120 |
+
),
|
| 121 |
+
# data_medium
|
| 122 |
+
(
|
| 123 |
+
r"if task_id == \"data_cleaning_medium\":\s+ctx = \{\"data\": DATA_MEDIUM\[\"data\"\]\}\s+return ctx, ctx, \"medium\"",
|
| 124 |
+
'if task_id == "data_cleaning_medium":\n'
|
| 125 |
+
' agent_ctx, grader_ctx = gen_data_medium(self._state.episode_id)\n'
|
| 126 |
+
' return agent_ctx, grader_ctx, "medium"',
|
| 127 |
+
),
|
| 128 |
+
# data_hard
|
| 129 |
+
(
|
| 130 |
+
r"if task_id == \"data_cleaning_hard\":\s+ctx = \{\"data\": DATA_HARD\[\"data\"\]\}\s+return ctx, ctx, \"hard\"",
|
| 131 |
+
'if task_id == "data_cleaning_hard":\n'
|
| 132 |
+
' agent_ctx, grader_ctx = gen_data_hard(self._state.episode_id)\n'
|
| 133 |
+
' return agent_ctx, grader_ctx, "hard"',
|
| 134 |
+
),
|
| 135 |
+
# mod_easy
|
| 136 |
+
(
|
| 137 |
+
r"if task_id == \"content_moderation_easy\":\s+agent_ctx\s+=.*?MOD_EASY.*?\n.*?return agent_ctx, grader_ctx, \"easy\"",
|
| 138 |
+
'if task_id == "content_moderation_easy":\n'
|
| 139 |
+
' agent_ctx, grader_ctx = gen_mod_easy(self._state.episode_id)\n'
|
| 140 |
+
' return agent_ctx, grader_ctx, "easy"',
|
| 141 |
+
),
|
| 142 |
+
# mod_medium
|
| 143 |
+
(
|
| 144 |
+
r"if task_id == \"content_moderation_medium\":\s+agent_ctx\s+=.*?MOD_MEDIUM.*?\n.*?return agent_ctx, grader_ctx, \"medium\"",
|
| 145 |
+
'if task_id == "content_moderation_medium":\n'
|
| 146 |
+
' agent_ctx, grader_ctx = gen_mod_medium(self._state.episode_id)\n'
|
| 147 |
+
' return agent_ctx, grader_ctx, "medium"',
|
| 148 |
+
),
|
| 149 |
+
# ticket_easy
|
| 150 |
+
(
|
| 151 |
+
r"if task_id == \"ticket_triage_easy\":\s+ticket\s+=.*?\n.*?return agent_ctx, grader_ctx, \"easy\"",
|
| 152 |
+
'if task_id == "ticket_triage_easy":\n'
|
| 153 |
+
' agent_ctx, grader_ctx = gen_ticket_easy(self._state.episode_id)\n'
|
| 154 |
+
' return agent_ctx, grader_ctx, "easy"',
|
| 155 |
+
),
|
| 156 |
+
]
|
| 157 |
+
|
| 158 |
+
for pattern, replacement in replacements:
|
| 159 |
+
new_src = re.sub(pattern, replacement, src, flags=re.DOTALL)
|
| 160 |
+
if new_src != src:
|
| 161 |
+
src = new_src
|
| 162 |
+
task = pattern.split('"')[1]
|
| 163 |
+
print(f" β replaced context loader: {task}")
|
| 164 |
+
# Regex may not match exactly β try string replacement as fallback later
|
| 165 |
+
|
| 166 |
+
# ββ Add expert context loaders before final raise βββββββββββββββββββββββββ
|
| 167 |
+
expert_loaders = """
|
| 168 |
+
if task_id == "email_triage_expert":
|
| 169 |
+
agent_ctx = {k: v for k, v in EMAIL_EXPERT_CONTEXT.items()}
|
| 170 |
+
grader_ctx = agent_ctx
|
| 171 |
+
return agent_ctx, grader_ctx, "expert"
|
| 172 |
+
|
| 173 |
+
if task_id == "code_review_expert":
|
| 174 |
+
agent_ctx = {"code": CODE_EXPERT_CONTEXT["code"],
|
| 175 |
+
"vulnerability_count": CODE_EXPERT_CONTEXT["vulnerability_count"]}
|
| 176 |
+
grader_ctx = CODE_EXPERT_CONTEXT
|
| 177 |
+
return agent_ctx, grader_ctx, "expert"
|
| 178 |
+
|
| 179 |
+
if task_id == "data_cleaning_expert":
|
| 180 |
+
agent_ctx = DATA_EXPERT_CONTEXT
|
| 181 |
+
grader_ctx = DATA_EXPERT_CONTEXT
|
| 182 |
+
return agent_ctx, grader_ctx, "expert"
|
| 183 |
+
|
| 184 |
+
if task_id == "content_moderation_expert":
|
| 185 |
+
agent_ctx = MOD_EXPERT_CONTEXT
|
| 186 |
+
grader_ctx = MOD_EXPERT_CONTEXT
|
| 187 |
+
return agent_ctx, grader_ctx, "expert"
|
| 188 |
+
|
| 189 |
+
if task_id == "ticket_triage_expert":
|
| 190 |
+
agent_ctx = TICKET_EXPERT_CONTEXT
|
| 191 |
+
grader_ctx = TICKET_EXPERT_CONTEXT
|
| 192 |
+
return agent_ctx, grader_ctx, "expert"
|
| 193 |
+
|
| 194 |
+
"""
|
| 195 |
+
if '"email_triage_expert"' not in src:
|
| 196 |
+
src = src.replace(
|
| 197 |
+
'raise ValueError(f"Unknown task_id:',
|
| 198 |
+
expert_loaders + ' raise ValueError(f"Unknown task_id:',
|
| 199 |
+
)
|
| 200 |
+
print(" β added expert context loaders")
|
| 201 |
+
|
| 202 |
+
# ββ Add expert grader routing in _grade() βββββββββββββββββββββββββββββββββ
|
| 203 |
+
expert_graders = (
|
| 204 |
+
' if task_id == "email_triage_expert": return grade_email_expert(payload)\n'
|
| 205 |
+
' if task_id == "code_review_expert": return grade_code_expert(payload)\n'
|
| 206 |
+
' if task_id == "data_cleaning_expert": return grade_data_expert(payload)\n'
|
| 207 |
+
' if task_id == "content_moderation_expert": return grade_mod_expert(payload)\n'
|
| 208 |
+
' if task_id == "ticket_triage_expert": return grade_ticket_expert(payload)\n'
|
| 209 |
+
)
|
| 210 |
+
if "grade_email_expert" not in src:
|
| 211 |
+
src = src.replace(
|
| 212 |
+
' return 0.0, "Unknown task.", {}',
|
| 213 |
+
expert_graders + ' return 0.0, "Unknown task.", {}',
|
| 214 |
+
)
|
| 215 |
+
print(" β added expert grader routing")
|
| 216 |
+
|
| 217 |
+
# ββ Fix graders to accept per-episode context βββββββββββββββββββββββββββββ
|
| 218 |
+
src = src.replace(
|
| 219 |
+
"def grade_mod_easy(payload: dict) -> tuple[float, str, dict]:",
|
| 220 |
+
"def grade_mod_easy(payload: dict, context: dict = None) -> tuple[float, str, dict]:",
|
| 221 |
+
)
|
| 222 |
+
src = src.replace(
|
| 223 |
+
" correct = {p[\"id\"]: p[\"label\"] for p in MOD_EASY}",
|
| 224 |
+
' posts = (context or {}).get("posts", MOD_EASY)\n correct = {p["id"]: p["label"] for p in posts}',
|
| 225 |
+
1,
|
| 226 |
+
)
|
| 227 |
+
src = src.replace(
|
| 228 |
+
"def grade_mod_medium(payload: dict) -> tuple[float, str, dict]:",
|
| 229 |
+
"def grade_mod_medium(payload: dict, context: dict = None) -> tuple[float, str, dict]:",
|
| 230 |
+
)
|
| 231 |
+
src = src.replace(
|
| 232 |
+
" correct = {p[\"id\"]: p[\"label\"] for p in MOD_MEDIUM}",
|
| 233 |
+
' posts = (context or {}).get("posts", MOD_MEDIUM)\n correct = {p["id"]: p["label"] for p in posts}',
|
| 234 |
+
1,
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
# Fix grade_email_medium to read correct_order from context
|
| 238 |
+
src = src.replace(
|
| 239 |
+
" correct = EMAIL_MEDIUM_CORRECT_ORDER",
|
| 240 |
+
' correct = context.get("correct_order", EMAIL_MEDIUM_CORRECT_ORDER)',
|
| 241 |
+
1,
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
# Fix grade_data_hard to accept context
|
| 245 |
+
src = src.replace(
|
| 246 |
+
"def grade_data_hard(payload: dict) -> tuple[float, str, dict]:",
|
| 247 |
+
"def grade_data_hard(payload: dict, context: dict = None) -> tuple[float, str, dict]:",
|
| 248 |
+
)
|
| 249 |
+
src = src.replace(
|
| 250 |
+
" correct_out = DATA_HARD[\"answers\"][\"outliers\"]",
|
| 251 |
+
' answers = (context or {}).get("answers", DATA_HARD["answers"])\n correct_out = answers.get("outliers", DATA_HARD["answers"]["outliers"])',
|
| 252 |
+
)
|
| 253 |
+
src = src.replace(
|
| 254 |
+
" correct_miss = DATA_HARD[\"answers\"][\"missing\"]",
|
| 255 |
+
' correct_miss = answers.get("missing", DATA_HARD["answers"]["missing"])',
|
| 256 |
+
)
|
| 257 |
+
src = src.replace(
|
| 258 |
+
' lo, hi = DATA_HARD["answers"]["imputed_range"]',
|
| 259 |
+
' lo, hi = answers.get("imputed_range", DATA_HARD["answers"]["imputed_range"])',
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
# Fix _grade() calls for mod_easy/mod_medium/data_hard to pass context
|
| 263 |
+
src = src.replace(
|
| 264 |
+
'if task_id == "content_moderation_easy": return grade_mod_easy(payload)',
|
| 265 |
+
'if task_id == "content_moderation_easy": return grade_mod_easy(payload, grader_context)',
|
| 266 |
+
)
|
| 267 |
+
src = src.replace(
|
| 268 |
+
'if task_id == "content_moderation_medium": return grade_mod_medium(payload)',
|
| 269 |
+
'if task_id == "content_moderation_medium": return grade_mod_medium(payload, grader_context)',
|
| 270 |
+
)
|
| 271 |
+
src = src.replace(
|
| 272 |
+
'if task_id == "data_cleaning_hard": return grade_data_hard(payload)',
|
| 273 |
+
'if task_id == "data_cleaning_hard": return grade_data_hard(payload, grader_context)',
|
| 274 |
+
)
|
| 275 |
+
|
| 276 |
+
# ββ Add EXPERT_INSTRUCTIONS to INSTRUCTIONS dict ββββββββββββββββββββββββββ
|
| 277 |
+
if "email_triage_expert" not in src:
|
| 278 |
+
src = src.replace(
|
| 279 |
+
' "cross_agent_mod_escalation": (',
|
| 280 |
+
' **{}, # expert instructions added below\n "cross_agent_mod_escalation": (',
|
| 281 |
+
)
|
| 282 |
+
# Better: append to INSTRUCTIONS dict
|
| 283 |
+
src = src.replace(
|
| 284 |
+
' "cross_agent_mod_escalation": (\n "TWO skills needed',
|
| 285 |
+
' "cross_agent_mod_escalation": (\n "TWO skills needed',
|
| 286 |
+
)
|
| 287 |
+
# Find the closing of INSTRUCTIONS dict and add expert entries before it
|
| 288 |
+
# (This is safer as a manual step β note it for the developer)
|
| 289 |
+
print(" β Add EXPERT_INSTRUCTIONS entries to INSTRUCTIONS dict manually (see expert_tasks.py)")
|
| 290 |
+
|
| 291 |
+
write(path, src)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 295 |
+
# Fix 3 β server/app.py typed endpoint
|
| 296 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 297 |
+
|
| 298 |
+
TYPED_ENDPOINT_CODE = '''
|
| 299 |
+
|
| 300 |
+
# ββ Typed step endpoint β Fix 3 βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 301 |
+
|
| 302 |
+
_TASK_TO_MODEL = {
|
| 303 |
+
"email_triage_easy": EmailTriageEasyPayload,
|
| 304 |
+
"email_triage_medium": EmailTriageMediumPayload,
|
| 305 |
+
"email_triage_hard": EmailTriageHardPayload,
|
| 306 |
+
"code_review_easy": CodeReviewEasyPayload,
|
| 307 |
+
"code_review_medium": CodeReviewMediumPayload,
|
| 308 |
+
"code_review_hard": CodeReviewHardPayload,
|
| 309 |
+
"data_cleaning_easy": DataCleaningEasyPayload,
|
| 310 |
+
"data_cleaning_medium": DataCleaningMediumPayload,
|
| 311 |
+
"data_cleaning_hard": DataCleaningHardPayload,
|
| 312 |
+
"content_moderation_easy": ContentModerationEasyPayload,
|
| 313 |
+
"content_moderation_medium": ContentModerationMediumPayload,
|
| 314 |
+
"content_moderation_hard": ContentModerationHardPayload,
|
| 315 |
+
"ticket_triage_easy": TicketTriageEasyPayload,
|
| 316 |
+
"ticket_triage_medium": TicketTriageMediumPayload,
|
| 317 |
+
"ticket_triage_hard": TicketTriageHardPayload,
|
| 318 |
+
"cross_agent_chain": CrossAgentChainPayload,
|
| 319 |
+
"cross_agent_email_data": CrossAgentEmailDataPayload,
|
| 320 |
+
"cross_agent_code_email": CrossAgentCodeEmailPayload,
|
| 321 |
+
"cross_agent_mod_escalation": CrossAgentModEscalationPayload,
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
@app.post("/step/typed/{task_id}")
|
| 326 |
+
async def step_typed(task_id: str, request: Request):
|
| 327 |
+
"""
|
| 328 |
+
Typed step endpoint β accepts the typed payload model for the given task_id
|
| 329 |
+
instead of the JSON-string-in-message format.
|
| 330 |
+
|
| 331 |
+
Example:
|
| 332 |
+
POST /step/typed/email_triage_easy
|
| 333 |
+
Body: {"agent": "email_triage", "task_id": "email_triage_easy", "payload": {"classification": "spam"}}
|
| 334 |
+
|
| 335 |
+
Returns the same observation as POST /step.
|
| 336 |
+
"""
|
| 337 |
+
if task_id not in _TASK_TO_MODEL:
|
| 338 |
+
return JSONResponse(status_code=404, content={"error": f"Unknown task_id: {task_id}. See GET /tasks."})
|
| 339 |
+
body = await request.json()
|
| 340 |
+
try:
|
| 341 |
+
model_cls = _TASK_TO_MODEL[task_id]
|
| 342 |
+
payload = body.get("payload", body)
|
| 343 |
+
typed = model_cls(**payload)
|
| 344 |
+
except Exception as e:
|
| 345 |
+
return JSONResponse(status_code=422, content={"error": str(e), "task_id": task_id})
|
| 346 |
+
agent = body.get("agent", "_".join(task_id.split("_")[:-1]))
|
| 347 |
+
wrapped = MetaAction(message=json.dumps({
|
| 348 |
+
"agent": agent, "task_id": task_id, "payload": typed.model_dump()
|
| 349 |
+
}))
|
| 350 |
+
# Delegate to the active environment instance
|
| 351 |
+
env = MetaEnvironment()
|
| 352 |
+
env.reset()
|
| 353 |
+
obs = env.step(wrapped)
|
| 354 |
+
return obs.model_dump()
|
| 355 |
+
'''
|
| 356 |
+
|
| 357 |
+
TYPED_MODEL_IMPORTS = """\
|
| 358 |
+
from models import (
|
| 359 |
+
MetaAction, MetaObservation,
|
| 360 |
+
EmailTriageEasyPayload, EmailTriageMediumPayload, EmailTriageHardPayload,
|
| 361 |
+
CodeReviewEasyPayload, CodeReviewMediumPayload, CodeReviewHardPayload,
|
| 362 |
+
DataCleaningEasyPayload, DataCleaningMediumPayload, DataCleaningHardPayload,
|
| 363 |
+
ContentModerationEasyPayload, ContentModerationMediumPayload, ContentModerationHardPayload,
|
| 364 |
+
TicketTriageEasyPayload, TicketTriageMediumPayload, TicketTriageHardPayload,
|
| 365 |
+
CrossAgentChainPayload, CrossAgentEmailDataPayload,
|
| 366 |
+
CrossAgentCodeEmailPayload, CrossAgentModEscalationPayload,
|
| 367 |
+
)
|
| 368 |
+
"""
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
def patch_app_py(repo: Path) -> None:
|
| 372 |
+
banner("Fix 3 β server/app.py typed endpoint")
|
| 373 |
+
path = repo / "server" / "app.py"
|
| 374 |
+
if not path.exists():
|
| 375 |
+
print(f" β not found: {path}"); return
|
| 376 |
+
backup(path)
|
| 377 |
+
src = read(path)
|
| 378 |
+
|
| 379 |
+
if "_TASK_TO_MODEL" in src:
|
| 380 |
+
print(" β³ already patched"); return
|
| 381 |
+
|
| 382 |
+
# Update the import of MetaAction to include all typed models
|
| 383 |
+
src = src.replace(
|
| 384 |
+
"from models import MetaAction, MetaObservation",
|
| 385 |
+
TYPED_MODEL_IMPORTS.rstrip(),
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
# Append typed endpoint before the main() function
|
| 389 |
+
src = src.replace(
|
| 390 |
+
"\ndef main(",
|
| 391 |
+
TYPED_ENDPOINT_CODE + "\n\ndef main(",
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
write(path, src)
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 398 |
+
# Fix 4a β gradio_ui.py
|
| 399 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 400 |
+
|
| 401 |
+
ALL_TASK_IDS_24 = [
|
| 402 |
+
"email_triage_easy", "email_triage_medium", "email_triage_hard", "email_triage_expert",
|
| 403 |
+
"code_review_easy", "code_review_medium", "code_review_hard", "code_review_expert",
|
| 404 |
+
"data_cleaning_easy", "data_cleaning_medium", "data_cleaning_hard", "data_cleaning_expert",
|
| 405 |
+
"content_moderation_easy", "content_moderation_medium", "content_moderation_hard", "content_moderation_expert",
|
| 406 |
+
"ticket_triage_easy", "ticket_triage_medium", "ticket_triage_hard", "ticket_triage_expert",
|
| 407 |
+
"cross_agent_chain", "cross_agent_email_data", "cross_agent_code_email", "cross_agent_mod_escalation",
|
| 408 |
+
]
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
def patch_gradio_ui(repo: Path) -> None:
|
| 412 |
+
banner("Fix 4a β server/gradio_ui.py task list + count")
|
| 413 |
+
path = repo / "server" / "gradio_ui.py"
|
| 414 |
+
if not path.exists():
|
| 415 |
+
print(f" β not found: {path}"); return
|
| 416 |
+
backup(path)
|
| 417 |
+
src = read(path)
|
| 418 |
+
|
| 419 |
+
# Replace TASK_IDS list
|
| 420 |
+
new_task_ids = "TASK_IDS = [\n" + "".join(
|
| 421 |
+
f' "{t}",\n' for t in ALL_TASK_IDS_24
|
| 422 |
+
) + "]\n"
|
| 423 |
+
|
| 424 |
+
# Find and replace the existing TASK_IDS block
|
| 425 |
+
src = re.sub(
|
| 426 |
+
r"TASK_IDS = \[.*?\]",
|
| 427 |
+
new_task_ids.rstrip(),
|
| 428 |
+
src,
|
| 429 |
+
flags=re.DOTALL,
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
# Add "expert" to DIFFICULTY_EMOJI
|
| 433 |
+
src = src.replace(
|
| 434 |
+
'"hard": "π΄ Hard"}',
|
| 435 |
+
'"hard": "π΄ Hard", "expert": "β« Expert"}',
|
| 436 |
+
)
|
| 437 |
+
|
| 438 |
+
# Update header agent/task count
|
| 439 |
+
src = src.replace(
|
| 440 |
+
"**4 agents Β· 12 tasks Β· Real-world RL environment**",
|
| 441 |
+
"**6 agents Β· 24 tasks Β· Real-world RL environment**",
|
| 442 |
+
)
|
| 443 |
+
src = src.replace(
|
| 444 |
+
"**4 agents, 12 tasks**",
|
| 445 |
+
"**6 agents, 24 tasks**",
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
# Update /tasks text in About tab
|
| 449 |
+
src = src.replace(
|
| 450 |
+
"| GET | `/tasks` | All 12 tasks |",
|
| 451 |
+
"| GET | `/tasks` | All 24 tasks with schemas |\n | POST | `/step/typed/{task_id}` | Typed action format |",
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
write(path, src)
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 458 |
+
# Fix 4b β baseline.py
|
| 459 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 460 |
+
|
| 461 |
+
BASELINE_TASK_IDS = """\
|
| 462 |
+
TASK_IDS = [
|
| 463 |
+
"email_triage_easy", "email_triage_medium", "email_triage_hard", "email_triage_expert",
|
| 464 |
+
"code_review_easy", "code_review_medium", "code_review_hard", "code_review_expert",
|
| 465 |
+
"data_cleaning_easy", "data_cleaning_medium", "data_cleaning_hard", "data_cleaning_expert",
|
| 466 |
+
"content_moderation_easy", "content_moderation_medium", "content_moderation_hard", "content_moderation_expert",
|
| 467 |
+
"ticket_triage_easy", "ticket_triage_medium", "ticket_triage_hard", "ticket_triage_expert",
|
| 468 |
+
"cross_agent_chain", "cross_agent_email_data", "cross_agent_code_email", "cross_agent_mod_escalation",
|
| 469 |
+
]"""
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
def patch_baseline(repo: Path) -> None:
|
| 473 |
+
banner("Fix 4b β baseline.py task list")
|
| 474 |
+
path = repo / "baseline.py"
|
| 475 |
+
if not path.exists():
|
| 476 |
+
print(f" β not found: {path}"); return
|
| 477 |
+
backup(path)
|
| 478 |
+
src = read(path)
|
| 479 |
+
src = re.sub(r"TASK_IDS = \[.*?\]", BASELINE_TASK_IDS, src, flags=re.DOTALL)
|
| 480 |
+
write(path, src)
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 484 |
+
# Fix 4c β inference.py
|
| 485 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 486 |
+
|
| 487 |
+
INFERENCE_TASK_IDS = """\
|
| 488 |
+
TASK_IDS = [
|
| 489 |
+
"email_triage_easy", "email_triage_medium", "email_triage_hard", "email_triage_expert",
|
| 490 |
+
"code_review_easy", "code_review_medium", "code_review_hard", "code_review_expert",
|
| 491 |
+
"data_cleaning_easy", "data_cleaning_medium", "data_cleaning_hard", "data_cleaning_expert",
|
| 492 |
+
"content_moderation_easy", "content_moderation_medium", "content_moderation_hard", "content_moderation_expert",
|
| 493 |
+
"ticket_triage_easy", "ticket_triage_medium", "ticket_triage_hard", "ticket_triage_expert",
|
| 494 |
+
"cross_agent_chain", "cross_agent_email_data", "cross_agent_code_email", "cross_agent_mod_escalation",
|
| 495 |
+
]"""
|
| 496 |
+
|
| 497 |
+
|
| 498 |
+
def patch_inference(repo: Path) -> None:
|
| 499 |
+
banner("Fix 4c β inference.py task list")
|
| 500 |
+
path = repo / "inference.py"
|
| 501 |
+
if not path.exists():
|
| 502 |
+
print(f" β not found: {path}"); return
|
| 503 |
+
backup(path)
|
| 504 |
+
src = read(path)
|
| 505 |
+
src = re.sub(r"TASK_IDS = \[.*?\]", INFERENCE_TASK_IDS, src, flags=re.DOTALL)
|
| 506 |
+
write(path, src)
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 510 |
+
# Fix 4d β openenv.yaml
|
| 511 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 512 |
+
|
| 513 |
+
EXPERT_YAML_TASKS = """
|
| 514 |
+
# ββ Expert tier (multi-step reasoning, frontier challenge) ββββββββββββββββ
|
| 515 |
+
- id: email_triage_expert
|
| 516 |
+
agent: email_triage
|
| 517 |
+
difficulty: expert
|
| 518 |
+
description: >
|
| 519 |
+
Draft an executive reply to a 4-week escalated enterprise complaint.
|
| 520 |
+
Account metadata and engineering root cause provided. Requires multi-context
|
| 521 |
+
synthesis and executive tone calibration. Frontier models score ~0.45.
|
| 522 |
+
|
| 523 |
+
- id: code_review_expert
|
| 524 |
+
agent: code_review
|
| 525 |
+
difficulty: expert
|
| 526 |
+
description: >
|
| 527 |
+
Identify 8 subtle vulnerabilities: hardcoded secret, JWT none-algorithm attack,
|
| 528 |
+
mass assignment, IDOR, YAML RCE deserialization, TOCTOU race condition,
|
| 529 |
+
prototype pollution, and log injection. Frontier models score ~0.38.
|
| 530 |
+
|
| 531 |
+
- id: data_cleaning_expert
|
| 532 |
+
agent: data_cleaning
|
| 533 |
+
difficulty: expert
|
| 534 |
+
description: >
|
| 535 |
+
Join two datasets, detect multi-type anomalies (duplicates, missing, outliers,
|
| 536 |
+
zero-amounts), convert currencies to USD, compute per-rep revenue excluding
|
| 537 |
+
invalid transactions. Frontier models score ~0.50.
|
| 538 |
+
|
| 539 |
+
- id: content_moderation_expert
|
| 540 |
+
agent: content_moderation
|
| 541 |
+
difficulty: expert
|
| 542 |
+
description: >
|
| 543 |
+
Issue rulings on 5 genuinely contested cases: suicidal ideation,
|
| 544 |
+
great-replacement rhetoric, conscientious objection policy debate,
|
| 545 |
+
antisemitic satire framing, and health misinformation. Frontier models score ~0.40.
|
| 546 |
+
|
| 547 |
+
- id: ticket_triage_expert
|
| 548 |
+
agent: ticket_triage
|
| 549 |
+
difficulty: expert
|
| 550 |
+
description: >
|
| 551 |
+
Produce a board-ready PIR from a 4h17m outage timeline. Requires identifying
|
| 552 |
+
timeline gaps, 5+ specific action items, and quantified MTTR breakdown.
|
| 553 |
+
Frontier models score ~0.50.
|
| 554 |
+
"""
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
def patch_openenv_yaml(repo: Path) -> None:
|
| 558 |
+
banner("Fix 4d β openenv.yaml expert tasks + agent lists")
|
| 559 |
+
path = repo / "openenv.yaml"
|
| 560 |
+
if not path.exists():
|
| 561 |
+
print(f" β not found: {path}"); return
|
| 562 |
+
backup(path)
|
| 563 |
+
src = read(path)
|
| 564 |
+
|
| 565 |
+
if "email_triage_expert" in src:
|
| 566 |
+
print(" β³ already patched"); return
|
| 567 |
+
|
| 568 |
+
# Insert expert tasks before the cross-agent section
|
| 569 |
+
src = src.replace(
|
| 570 |
+
" # ββ Cross-Agent Chained",
|
| 571 |
+
EXPERT_YAML_TASKS + "\n # ββ Cross-Agent Chained",
|
| 572 |
+
)
|
| 573 |
+
|
| 574 |
+
# Update agent task lists to include expert
|
| 575 |
+
for agent in ["email_triage", "code_review", "data_cleaning", "content_moderation", "ticket_triage"]:
|
| 576 |
+
expert_id = f"{agent}_expert"
|
| 577 |
+
src = re.sub(
|
| 578 |
+
rf"(- id: {agent}\n tasks: \[.*?)(])",
|
| 579 |
+
lambda m, eid=expert_id: m.group(1) + f", {eid}" + m.group(2),
|
| 580 |
+
src,
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
# Update version
|
| 584 |
+
src = src.replace('version: "3.0.0"', 'version: "3.1.0"')
|
| 585 |
+
src = src.replace("Meta is the first multi-domain OpenEnv environment featuring 5 AI agents",
|
| 586 |
+
"Meta is the first multi-domain OpenEnv environment featuring 5 AI agents (plus expert tier)")
|
| 587 |
+
|
| 588 |
+
write(path, src)
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 592 |
+
# Copy new files into repo
|
| 593 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 594 |
+
|
| 595 |
+
def copy_new_files(repo: Path, script_dir: Path) -> None:
|
| 596 |
+
banner("Copying new source files")
|
| 597 |
+
for fname in ["data_generators.py", "expert_tasks.py"]:
|
| 598 |
+
src = script_dir / fname
|
| 599 |
+
dest = repo / fname
|
| 600 |
+
if not src.exists():
|
| 601 |
+
print(f" β {fname} not found in {script_dir}")
|
| 602 |
+
continue
|
| 603 |
+
# Skip if source and destination are the same file (script lives in repo root)
|
| 604 |
+
try:
|
| 605 |
+
if src.resolve() == dest.resolve():
|
| 606 |
+
print(f" β³ {fname} already in place (src == dest), skipping copy")
|
| 607 |
+
continue
|
| 608 |
+
except OSError:
|
| 609 |
+
pass
|
| 610 |
+
try:
|
| 611 |
+
shutil.copy2(src, dest)
|
| 612 |
+
print(f" β copied {fname} β {dest}")
|
| 613 |
+
except PermissionError as e:
|
| 614 |
+
print(f" β Could not copy {fname}: {e}")
|
| 615 |
+
print(f" Close any editors/processes that have {dest} open and retry.")
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 619 |
+
# Main
|
| 620 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 621 |
+
|
| 622 |
+
def main():
|
| 623 |
+
parser = argparse.ArgumentParser(description="Apply Meta v3.1 patches")
|
| 624 |
+
parser.add_argument("--repo-dir", default=".", help="Path to repo root")
|
| 625 |
+
args = parser.parse_args()
|
| 626 |
+
|
| 627 |
+
repo = Path(args.repo_dir).resolve()
|
| 628 |
+
script_dir = Path(__file__).parent.resolve()
|
| 629 |
+
|
| 630 |
+
print(f"\nRepo : {repo}")
|
| 631 |
+
print(f"Scripts: {script_dir}")
|
| 632 |
+
|
| 633 |
+
if not (repo / "openenv.yaml").exists():
|
| 634 |
+
print(f"\nβ ERROR: {repo}/openenv.yaml not found. Pass --repo-dir to the repo root.")
|
| 635 |
+
sys.exit(1)
|
| 636 |
+
|
| 637 |
+
copy_new_files(repo, script_dir)
|
| 638 |
+
patch_meta_environment(repo)
|
| 639 |
+
patch_app_py(repo)
|
| 640 |
+
patch_gradio_ui(repo)
|
| 641 |
+
patch_baseline(repo)
|
| 642 |
+
patch_inference(repo)
|
| 643 |
+
patch_openenv_yaml(repo)
|
| 644 |
+
|
| 645 |
+
banner("DONE β run verification checks")
|
| 646 |
+
print("""
|
| 647 |
+
python -c "from data_generators import gen_email_easy; print('generators OK')"
|
| 648 |
+
python -c "from expert_tasks import EXPERT_TASK_IDS; print(EXPERT_TASK_IDS)"
|
| 649 |
+
pytest tests/ -v
|
| 650 |
+
python inference.py --dry-run # if supported, else just check imports
|
| 651 |
+
""")
|
| 652 |
+
|
| 653 |
+
|
| 654 |
+
if __name__ == "__main__":
|
| 655 |
+
main()
|
baseline.py
CHANGED
|
@@ -31,10 +31,12 @@ if os.path.exists(_env_file):
|
|
| 31 |
os.environ.setdefault(_k.strip(), _v.strip())
|
| 32 |
|
| 33 |
TASK_IDS = [
|
| 34 |
-
"email_triage_easy",
|
| 35 |
-
"code_review_easy",
|
| 36 |
-
"data_cleaning_easy",
|
| 37 |
-
"content_moderation_easy",
|
|
|
|
|
|
|
| 38 |
]
|
| 39 |
|
| 40 |
SYSTEM_PROMPT = (
|
|
|
|
| 31 |
os.environ.setdefault(_k.strip(), _v.strip())
|
| 32 |
|
| 33 |
TASK_IDS = [
|
| 34 |
+
"email_triage_easy", "email_triage_medium", "email_triage_hard", "email_triage_expert",
|
| 35 |
+
"code_review_easy", "code_review_medium", "code_review_hard", "code_review_expert",
|
| 36 |
+
"data_cleaning_easy", "data_cleaning_medium", "data_cleaning_hard", "data_cleaning_expert",
|
| 37 |
+
"content_moderation_easy", "content_moderation_medium", "content_moderation_hard", "content_moderation_expert",
|
| 38 |
+
"ticket_triage_easy", "ticket_triage_medium", "ticket_triage_hard", "ticket_triage_expert",
|
| 39 |
+
"cross_agent_chain", "cross_agent_email_data", "cross_agent_code_email", "cross_agent_mod_escalation",
|
| 40 |
]
|
| 41 |
|
| 42 |
SYSTEM_PROMPT = (
|
baseline.py.bak
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Meta v2 β Baseline Inference Script
|
| 3 |
+
Runs llama-3.3-70b-versatile (via Groq) against all 12 tasks and reports scores.
|
| 4 |
+
|
| 5 |
+
Usage:
|
| 6 |
+
# Option 1 β use .env file (recommended)
|
| 7 |
+
python baseline.py
|
| 8 |
+
|
| 9 |
+
# Option 2 β set env vars manually
|
| 10 |
+
export API_BASE_URL=https://api.groq.com/openai/v1
|
| 11 |
+
export MODEL_NAME=llama-3.3-70b-versatile
|
| 12 |
+
export HF_TOKEN=gsk_...
|
| 13 |
+
python baseline.py
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import os
|
| 17 |
+
import sys
|
| 18 |
+
import json
|
| 19 |
+
import re
|
| 20 |
+
|
| 21 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 22 |
+
|
| 23 |
+
# Load .env if present
|
| 24 |
+
_env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
|
| 25 |
+
if os.path.exists(_env_file):
|
| 26 |
+
with open(_env_file) as _f:
|
| 27 |
+
for _line in _f:
|
| 28 |
+
_line = _line.strip()
|
| 29 |
+
if _line and not _line.startswith("#") and "=" in _line:
|
| 30 |
+
_k, _, _v = _line.partition("=")
|
| 31 |
+
os.environ.setdefault(_k.strip(), _v.strip())
|
| 32 |
+
|
| 33 |
+
TASK_IDS = [
|
| 34 |
+
"email_triage_easy", "email_triage_medium", "email_triage_hard", "email_triage_expert",
|
| 35 |
+
"code_review_easy", "code_review_medium", "code_review_hard", "code_review_expert",
|
| 36 |
+
"data_cleaning_easy", "data_cleaning_medium", "data_cleaning_hard", "data_cleaning_expert",
|
| 37 |
+
"content_moderation_easy", "content_moderation_medium", "content_moderation_hard", "content_moderation_expert",
|
| 38 |
+
"ticket_triage_easy", "ticket_triage_medium", "ticket_triage_hard", "ticket_triage_expert",
|
| 39 |
+
"cross_agent_chain", "cross_agent_email_data", "cross_agent_code_email", "cross_agent_mod_escalation",
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
SYSTEM_PROMPT = (
|
| 43 |
+
"You are an expert AI agent completing structured real-world tasks.\n"
|
| 44 |
+
"You will receive task context and instructions.\n"
|
| 45 |
+
"Respond ONLY with a valid JSON object as the action payload β no explanation, no markdown, no code fences.\n"
|
| 46 |
+
"Your response must be a single raw JSON object."
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def extract_json(text: str) -> dict:
|
| 51 |
+
text = text.strip()
|
| 52 |
+
try:
|
| 53 |
+
return json.loads(text)
|
| 54 |
+
except Exception:
|
| 55 |
+
pass
|
| 56 |
+
match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
|
| 57 |
+
if match:
|
| 58 |
+
try:
|
| 59 |
+
return json.loads(match.group(1))
|
| 60 |
+
except Exception:
|
| 61 |
+
pass
|
| 62 |
+
match = re.search(r"\{.*\}", text, re.DOTALL)
|
| 63 |
+
if match:
|
| 64 |
+
try:
|
| 65 |
+
return json.loads(match.group(0))
|
| 66 |
+
except Exception:
|
| 67 |
+
pass
|
| 68 |
+
return {}
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def run_task(client, task_id: str, model_name: str) -> dict:
|
| 72 |
+
from server.Meta_environment import MetaEnvironment, INSTRUCTIONS
|
| 73 |
+
from models import MetaAction
|
| 74 |
+
|
| 75 |
+
env = MetaEnvironment()
|
| 76 |
+
env.reset()
|
| 77 |
+
probe_msg = json.dumps({
|
| 78 |
+
"agent": "_".join(task_id.split("_")[:-1]),
|
| 79 |
+
"task_id": task_id,
|
| 80 |
+
"payload": {"_probe": True},
|
| 81 |
+
})
|
| 82 |
+
probe_obs = env.step(MetaAction(message=probe_msg))
|
| 83 |
+
instructions = INSTRUCTIONS.get(task_id, probe_obs.instructions)
|
| 84 |
+
context = probe_obs.context
|
| 85 |
+
|
| 86 |
+
user_prompt = (
|
| 87 |
+
f"Task ID: {task_id}\n"
|
| 88 |
+
f"Difficulty: {probe_obs.difficulty}\n\n"
|
| 89 |
+
f"Instructions:\n{instructions}\n\n"
|
| 90 |
+
f"Context:\n{json.dumps(context, indent=2)}\n\n"
|
| 91 |
+
"Respond with ONLY the payload JSON object."
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
try:
|
| 95 |
+
response = client.chat.completions.create(
|
| 96 |
+
model=model_name,
|
| 97 |
+
messages=[
|
| 98 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 99 |
+
{"role": "user", "content": user_prompt},
|
| 100 |
+
],
|
| 101 |
+
temperature=0.0,
|
| 102 |
+
max_tokens=2000,
|
| 103 |
+
)
|
| 104 |
+
raw = response.choices[0].message.content
|
| 105 |
+
payload = extract_json(raw)
|
| 106 |
+
except Exception as e:
|
| 107 |
+
payload = {}
|
| 108 |
+
raw = str(e)
|
| 109 |
+
|
| 110 |
+
# Grade
|
| 111 |
+
agent = "_".join(task_id.split("_")[:-1])
|
| 112 |
+
env2 = MetaEnvironment()
|
| 113 |
+
env2.reset()
|
| 114 |
+
env2._current_agent_context = context
|
| 115 |
+
env2._current_grader_context = context
|
| 116 |
+
env2._current_task_id = task_id
|
| 117 |
+
|
| 118 |
+
action_msg = json.dumps({"agent": agent, "task_id": task_id, "payload": payload})
|
| 119 |
+
result_obs = env2.step(MetaAction(message=action_msg))
|
| 120 |
+
|
| 121 |
+
return {
|
| 122 |
+
"task_id": task_id,
|
| 123 |
+
"agent": agent,
|
| 124 |
+
"difficulty": result_obs.difficulty,
|
| 125 |
+
"score": result_obs.score,
|
| 126 |
+
"feedback": result_obs.feedback,
|
| 127 |
+
"partial_credits": result_obs.partial_credits,
|
| 128 |
+
"reward": result_obs.reward,
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def run_all_baselines(api_key: str = None, api_base_url: str = None, model_name: str = None) -> list:
|
| 133 |
+
from openai import OpenAI
|
| 134 |
+
import time
|
| 135 |
+
|
| 136 |
+
# Key resolution: explicit arg β OPENAI_API_KEY β GROQ_API_KEY β HF_TOKEN
|
| 137 |
+
api_key = (
|
| 138 |
+
api_key
|
| 139 |
+
or os.environ.get("OPENAI_API_KEY")
|
| 140 |
+
or os.environ.get("GROQ_API_KEY")
|
| 141 |
+
or os.environ.get("HF_TOKEN")
|
| 142 |
+
)
|
| 143 |
+
if not api_key:
|
| 144 |
+
raise EnvironmentError(
|
| 145 |
+
"No API key found. Set HF_TOKEN, GROQ_API_KEY, or OPENAI_API_KEY.\n"
|
| 146 |
+
"Get a free Groq key at https://console.groq.com"
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
api_base_url = api_base_url or os.environ.get("API_BASE_URL", "https://api.groq.com/openai/v1")
|
| 150 |
+
model_name = model_name or os.environ.get("MODEL_NAME", "llama-3.3-70b-versatile")
|
| 151 |
+
|
| 152 |
+
# Groq is OpenAI-compatible β same SDK, different base_url
|
| 153 |
+
client = OpenAI(api_key=api_key, base_url=api_base_url)
|
| 154 |
+
results = []
|
| 155 |
+
|
| 156 |
+
print("\nπ Meta Multi-Agent v2 β Baseline Inference")
|
| 157 |
+
print(f" Model : {model_name}")
|
| 158 |
+
print(f" API Base : {api_base_url}")
|
| 159 |
+
print("=" * 65)
|
| 160 |
+
|
| 161 |
+
for task_id in TASK_IDS:
|
| 162 |
+
print(f" βΆ {task_id:<35}", end=" ", flush=True)
|
| 163 |
+
result = run_task(client, task_id, model_name)
|
| 164 |
+
results.append(result)
|
| 165 |
+
bar = "β" * int(result["score"] * 10) + "β" * (10 - int(result["score"] * 10))
|
| 166 |
+
print(f"[{bar}] {result['score']:.2f} {result['feedback'][:50]}")
|
| 167 |
+
# Respect Groq free tier rate limit (30 req/min)
|
| 168 |
+
time.sleep(2)
|
| 169 |
+
|
| 170 |
+
avg = sum(r["score"] for r in results) / len(results)
|
| 171 |
+
print("=" * 65)
|
| 172 |
+
print(f" π Average Score: {avg:.3f} | Tasks: {len(results)}")
|
| 173 |
+
print("=" * 65)
|
| 174 |
+
return results
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
if __name__ == "__main__":
|
| 178 |
+
scores = run_all_baselines()
|
| 179 |
+
print("\nFull JSON Results:")
|
| 180 |
+
print(json.dumps(scores, indent=2))
|
data_generators.py
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
data_generators.py β Deterministic procedural data generators for Meta v3.1
|
| 3 |
+
Replaces small hand-crafted datasets with richly varied, reproducible data.
|
| 4 |
+
|
| 5 |
+
Design principles
|
| 6 |
+
-----------------
|
| 7 |
+
* Every generator takes an `episode_id: str` and seeds Python's stdlib `random`
|
| 8 |
+
with `hash(episode_id + salt)` so the same episode always yields the same data.
|
| 9 |
+
* No third-party libs needed β stdlib only (random, string, datetime, hashlib).
|
| 10 |
+
* Each generator returns (agent_ctx, grader_ctx) tuples matching existing grader
|
| 11 |
+
contracts so graders need zero changes.
|
| 12 |
+
|
| 13 |
+
Drop-in replacement usage in Meta_environment.py::_load_context():
|
| 14 |
+
Replace the hardcoded-data blocks with:
|
| 15 |
+
from data_generators import gen_<task>(episode_id)
|
| 16 |
+
agent_ctx, grader_ctx = gen_<task>(episode_id)
|
| 17 |
+
|
| 18 |
+
Generators provided
|
| 19 |
+
-------------------
|
| 20 |
+
Email domain
|
| 21 |
+
gen_email_easy(episode_id) β 1 email, 3-way class, 30 sender/subject combos
|
| 22 |
+
gen_email_medium(episode_id) β 10 emails, varying priority pool
|
| 23 |
+
gen_email_hard(episode_id) β customer complaint, 8 scenario templates
|
| 24 |
+
|
| 25 |
+
Code domain
|
| 26 |
+
gen_code_easy(episode_id) β 6 code snippet variants
|
| 27 |
+
gen_code_medium(episode_id) β deterministic (same code, no variant needed)
|
| 28 |
+
gen_code_hard(episode_id) β deterministic (same code, no variant needed)
|
| 29 |
+
|
| 30 |
+
Data-cleaning domain
|
| 31 |
+
gen_data_easy(episode_id) β 6-10 rows, random nulls + one dup pair
|
| 32 |
+
gen_data_medium(episode_id) β 5 rows, randomised messy formats per field
|
| 33 |
+
gen_data_hard(episode_id) β 12-16 rows, IQR-detected outliers
|
| 34 |
+
|
| 35 |
+
Content moderation domain
|
| 36 |
+
gen_mod_easy(episode_id) β 7 posts drawn from larger pool
|
| 37 |
+
gen_mod_medium(episode_id) β 8 posts drawn from larger pool
|
| 38 |
+
(hard is already adversarial/static β left unchanged)
|
| 39 |
+
|
| 40 |
+
Ticket domain
|
| 41 |
+
gen_ticket_easy(episode_id) β 1 ticket from 8-variant pool
|
| 42 |
+
gen_ticket_medium(episode_id) β deterministic (same 8 tickets, no variant)
|
| 43 |
+
gen_ticket_hard(episode_id) β deterministic (same 6 incidents, no variant)
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
from __future__ import annotations
|
| 47 |
+
|
| 48 |
+
import hashlib
|
| 49 |
+
import random
|
| 50 |
+
import string
|
| 51 |
+
from copy import deepcopy
|
| 52 |
+
from datetime import date, timedelta
|
| 53 |
+
from typing import Any
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# ββ Seeding helper βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 57 |
+
|
| 58 |
+
def _rng(episode_id: str, salt: str = "") -> random.Random:
|
| 59 |
+
seed = int(hashlib.md5((episode_id + salt).encode()).hexdigest(), 16)
|
| 60 |
+
return random.Random(seed)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 64 |
+
# EMAIL EASY β 30+ variant pool
|
| 65 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 66 |
+
|
| 67 |
+
_EMAIL_EASY_POOL = [
|
| 68 |
+
# (subject, sender, body, label)
|
| 69 |
+
("You won $1,000,000!!!", "promo@totally-legit-lottery.com",
|
| 70 |
+
"Click here to claim your prize now! Limited time offer!", "spam"),
|
| 71 |
+
("Q3 Budget Review β Urgent Action Required", "cfo@company.com",
|
| 72 |
+
"Please review the attached Q3 budget before our board meeting tomorrow at 9 AM.", "important"),
|
| 73 |
+
("Your weekly digest from Medium", "noreply@medium.com",
|
| 74 |
+
"Top stories this week: AI trends, Python tips, and startup news.", "newsletter"),
|
| 75 |
+
("CONGRATULATIONS! iPhone 15 Pro is yours!", "winner@prize-alert.net",
|
| 76 |
+
"You have been selected. Click now to claim your free iPhone!", "spam"),
|
| 77 |
+
("Action Required: Sign the NDA before Friday", "legal@company.com",
|
| 78 |
+
"Please sign the attached NDA before Friday's partnership meeting. Deadline is strict.", "important"),
|
| 79 |
+
("TechCrunch Newsletter β March 2026", "newsletter@techcrunch.com",
|
| 80 |
+
"This week's top stories in tech, AI, and startups.", "newsletter"),
|
| 81 |
+
("Claim your $500 Amazon gift card NOW", "deals@amazingoffers.biz",
|
| 82 |
+
"You are today's lucky winner. No purchase necessary. Act fast!", "spam"),
|
| 83 |
+
("Production incident P1 β please join bridge", "oncall@company.com",
|
| 84 |
+
"We have a P1 production incident. Please join the war room immediately.", "important"),
|
| 85 |
+
("Developer digest: April 2026", "digest@devweekly.io",
|
| 86 |
+
"This week: Rust 2.0 release, LLM benchmarks, and open-source highlights.", "newsletter"),
|
| 87 |
+
("FREE vacation cruise β you've been selected!", "winner@vacation-cruise.net",
|
| 88 |
+
"Claim your free cruise for two. Just pay shipping and handling.", "spam"),
|
| 89 |
+
("Board approval needed: Series B term sheet", "ceo@company.com",
|
| 90 |
+
"Our Series B term sheet requires your signature before the 5 PM deadline today.", "important"),
|
| 91 |
+
("Product Hunt daily digest", "digest@producthunt.com",
|
| 92 |
+
"Today's top products: AI code review, async stand-ups, no-code CRM.", "newsletter"),
|
| 93 |
+
("Congratulations β Survey reward $250!", "rewards@surveypanel.net",
|
| 94 |
+
"You qualify for a $250 reward. Complete our 2-minute survey to claim.", "spam"),
|
| 95 |
+
("HR: Performance review cycle starts Monday", "hr@company.com",
|
| 96 |
+
"The annual performance review cycle begins Monday. Please complete your self-assessment.", "important"),
|
| 97 |
+
("Hacker News digest: top 10 links this week", "weekly@hackernews.app",
|
| 98 |
+
"Rust, WASM, and the AI bubble: top discussions from the community.", "newsletter"),
|
| 99 |
+
("Double billing detected on account #AC-8821", "billing@ourplatform.com",
|
| 100 |
+
"We noticed a duplicate charge on your account. Reply to initiate a refund.", "spam"),
|
| 101 |
+
("Legal: Partnership agreement signature required EOD", "legal@company.com",
|
| 102 |
+
"The partnership agreement with Nexus Corp must be countersigned by 5 PM today.", "important"),
|
| 103 |
+
("SaaS Weekly β your industry roundup", "editor@saasweekly.com",
|
| 104 |
+
"Churn rates, AI copilots, and the future of vertical SaaS.", "newsletter"),
|
| 105 |
+
("You have unclaimed crypto rewards!", "support@cryptorewards.io",
|
| 106 |
+
"Wallet 0x4fβ¦a2 has 0.3 ETH waiting. Connect now to claim.", "spam"),
|
| 107 |
+
("Security alert: admin password reset requested", "security@company.com",
|
| 108 |
+
"An admin-level password reset was requested from IP 198.51.100.42. Verify or block.", "important"),
|
| 109 |
+
("The Batch β AI newsletter by deeplearning.ai", "batch@deeplearning.ai",
|
| 110 |
+
"Andrew Ng on scaling laws, fine-tuning tips, and this week's papers.", "newsletter"),
|
| 111 |
+
("Limited offer: 90% off VPN subscription", "deals@vpndeals.biz",
|
| 112 |
+
"Today only: get lifetime VPN access for $1.99. Offer expires midnight.", "spam"),
|
| 113 |
+
("Vendor invoice #INV-7721 overdue by 14 days", "ar@vendor.com",
|
| 114 |
+
"Invoice #INV-7721 for $12,400 is now 14 days overdue. Please remit payment to avoid service interruption.", "important"),
|
| 115 |
+
("The Algorithm Newsletter β weekly edition", "hello@thealgorithm.co",
|
| 116 |
+
"ML papers, job listings, and a tutorial on RLHF.", "newsletter"),
|
| 117 |
+
("Your package was held β verify address", "delivery@postalnow.net",
|
| 118 |
+
"Your package is held due to an address discrepancy. Click to confirm.", "spam"),
|
| 119 |
+
("Compliance: GDPR audit documentation due Friday", "compliance@company.com",
|
| 120 |
+
"Please upload your GDPR data-processing records to the compliance portal by Friday.", "important"),
|
| 121 |
+
("Morning Brew β your daily business digest", "hello@morningbrew.com",
|
| 122 |
+
"Markets, tech, and the big stories you need to know today.", "newsletter"),
|
| 123 |
+
("Account suspended β click to restore", "support@accountalert.biz",
|
| 124 |
+
"Unusual activity detected. Your account has been temporarily suspended.", "spam"),
|
| 125 |
+
("CTO sync needed: infrastructure cost overrun", "cto@company.com",
|
| 126 |
+
"AWS bill came in 340% over budget. Need your sign-off on emergency cost controls.", "important"),
|
| 127 |
+
("The Hustle β daily newsletter", "news@thehustle.co",
|
| 128 |
+
"Today's biggest business stories, curated for founders and operators.", "newsletter"),
|
| 129 |
+
]
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def gen_email_easy(episode_id: str) -> tuple[dict, dict]:
|
| 133 |
+
rng = _rng(episode_id, "email_easy")
|
| 134 |
+
email = dict(zip(["subject", "sender", "body", "label"],
|
| 135 |
+
rng.choice(_EMAIL_EASY_POOL)))
|
| 136 |
+
email["id"] = "e1"
|
| 137 |
+
agent_ctx = {"email": {k: v for k, v in email.items() if k != "label"}}
|
| 138 |
+
grader_ctx = {"email": email}
|
| 139 |
+
return agent_ctx, grader_ctx
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 143 |
+
# EMAIL MEDIUM β varied priority pools
|
| 144 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 145 |
+
|
| 146 |
+
_CRITICAL_POOL = [
|
| 147 |
+
{"subject": "[ALERT] Production server DOWN", "sender": "ops@company.com",
|
| 148 |
+
"body": "All services unresponsive since 2 AM. Revenue impact: $10k/min.", "priority": 1, "category": "critical"},
|
| 149 |
+
{"subject": "[P1] Database primary down β all writes failing", "sender": "oncall@company.com",
|
| 150 |
+
"body": "The primary DB replica crashed. No write operations possible site-wide.", "priority": 1, "category": "critical"},
|
| 151 |
+
{"subject": "[WARNING] Security alert: unusual login attempt", "sender": "security@company.com",
|
| 152 |
+
"body": "Unauthorised login attempt from IP 45.33.32.156. Verify immediately.", "priority": 1, "category": "critical"},
|
| 153 |
+
{"subject": "[ALERT] DDoS attack in progress", "sender": "security@company.com",
|
| 154 |
+
"body": "We are under a DDoS attack. Site response time is 30s+. Join the war room.", "priority": 1, "category": "critical"},
|
| 155 |
+
]
|
| 156 |
+
|
| 157 |
+
_URGENT_POOL = [
|
| 158 |
+
{"subject": "Acme Corp contract renewal β URGENT", "sender": "sales@company.com",
|
| 159 |
+
"body": "Acme contract expires Friday. They need signature or they walk.", "priority": 2, "category": "business"},
|
| 160 |
+
{"subject": "Legal: Partnership agreement needs signature TODAY", "sender": "legal@company.com",
|
| 161 |
+
"body": "The partnership agreement must be countersigned by EOD or the deal falls through.", "priority": 2, "category": "legal"},
|
| 162 |
+
{"subject": "Series B term sheet β board sign-off required by 5 PM", "sender": "ceo@company.com",
|
| 163 |
+
"body": "VCs need the countersigned term sheet by 5 PM today or the round lapses.", "priority": 2, "category": "legal"},
|
| 164 |
+
]
|
| 165 |
+
|
| 166 |
+
_FINANCE_POOL = [
|
| 167 |
+
{"subject": "Invoice #INV-4521 overdue", "sender": "billing@vendor.com",
|
| 168 |
+
"body": "Invoice of $5,400 was due 3 days ago. Payment needed to avoid suspension.", "priority": 3, "category": "finance"},
|
| 169 |
+
{"subject": "AWS bill 340% over budget", "sender": "finance@company.com",
|
| 170 |
+
"body": "Last month's cloud bill was $82k vs $24k budget. Need approval to proceed.", "priority": 3, "category": "finance"},
|
| 171 |
+
{"subject": "Customer double-charged β refund dispute open", "sender": "billing@company.com",
|
| 172 |
+
"body": "Customer #C-1121 was charged twice. Finance blocked. Needs VP sign-off.", "priority": 3, "category": "finance"},
|
| 173 |
+
]
|
| 174 |
+
|
| 175 |
+
_BUSINESS_POOL = [
|
| 176 |
+
{"subject": "Q4 product launch deck β review needed", "sender": "marketing@company.com",
|
| 177 |
+
"body": "Please review launch slides before Thursday. Launch is next Monday.", "priority": 4, "category": "business"},
|
| 178 |
+
{"subject": "Quarterly board deck β slides need your input", "sender": "ea@company.com",
|
| 179 |
+
"body": "Board meeting is Tuesday. Your section on growth metrics needs updating.", "priority": 4, "category": "business"},
|
| 180 |
+
{"subject": "Key account at risk β needs executive call", "sender": "cs@company.com",
|
| 181 |
+
"body": "TechGiant Corp is threatening churn. They want an exec call this week.", "priority": 4, "category": "business"},
|
| 182 |
+
]
|
| 183 |
+
|
| 184 |
+
_SOCIAL_POOL = [
|
| 185 |
+
{"subject": "Team lunch this Friday?", "sender": "colleague@company.com",
|
| 186 |
+
"body": "Hey, thinking of doing a team lunch Friday. You in?", "priority": 9, "category": "social"},
|
| 187 |
+
{"subject": "Happy Work Anniversary!", "sender": "hr@company.com",
|
| 188 |
+
"body": "Celebrating your 3 years with us!", "priority": 9, "category": "social"},
|
| 189 |
+
{"subject": "Office trivia night β Thursday 7 PM", "sender": "social@company.com",
|
| 190 |
+
"body": "Join us for office trivia Thursday evening. Pizza provided!", "priority": 9, "category": "social"},
|
| 191 |
+
]
|
| 192 |
+
|
| 193 |
+
_NEWSLETTER_POOL = [
|
| 194 |
+
{"subject": "Weekly SaaS digest", "sender": "news@saasdigest.com",
|
| 195 |
+
"body": "Top SaaS stories this week...", "priority": 10, "category": "newsletter"},
|
| 196 |
+
{"subject": "Free AI productivity webinar", "sender": "promo@webinar.io",
|
| 197 |
+
"body": "Join our free webinar on AI tools this Thursday.", "priority": 10, "category": "newsletter"},
|
| 198 |
+
{"subject": "The Batch: AI news weekly", "sender": "batch@deeplearning.ai",
|
| 199 |
+
"body": "Andrew Ng on scaling, fine-tuning, and paper highlights.", "priority": 10, "category": "newsletter"},
|
| 200 |
+
]
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def gen_email_medium(episode_id: str) -> tuple[dict, dict]:
|
| 204 |
+
"""
|
| 205 |
+
Always picks 2 critical, 1 urgent, 1 finance, 2 business, 2 social, 2 newsletter = 10 emails.
|
| 206 |
+
Correct ordering: criticals first, then urgent, finance, business, social, newsletter.
|
| 207 |
+
"""
|
| 208 |
+
rng = _rng(episode_id, "email_medium")
|
| 209 |
+
crits = rng.sample(_CRITICAL_POOL, k=min(2, len(_CRITICAL_POOL)))
|
| 210 |
+
urgents = rng.sample(_URGENT_POOL, k=min(1, len(_URGENT_POOL)))
|
| 211 |
+
fins = rng.sample(_FINANCE_POOL, k=min(1, len(_FINANCE_POOL)))
|
| 212 |
+
bizs = rng.sample(_BUSINESS_POOL, k=min(2, len(_BUSINESS_POOL)))
|
| 213 |
+
socials = rng.sample(_SOCIAL_POOL, k=min(2, len(_SOCIAL_POOL)))
|
| 214 |
+
news = rng.sample(_NEWSLETTER_POOL,k=min(2, len(_NEWSLETTER_POOL)))
|
| 215 |
+
|
| 216 |
+
ordered = crits + urgents + fins + bizs + socials + news
|
| 217 |
+
emails = []
|
| 218 |
+
correct_order = []
|
| 219 |
+
for i, e in enumerate(ordered):
|
| 220 |
+
eid = f"m{i+1}"
|
| 221 |
+
emails.append({"id": eid, **e})
|
| 222 |
+
correct_order.append(eid)
|
| 223 |
+
|
| 224 |
+
# Shuffle for presentation; keep correct_order as ground truth
|
| 225 |
+
display = emails[:]
|
| 226 |
+
rng.shuffle(display)
|
| 227 |
+
|
| 228 |
+
agent_ctx = {"emails": [{k: v for k, v in e.items() if k not in ("priority", "category")} for e in display]}
|
| 229 |
+
grader_ctx = {"emails": emails, "correct_order": correct_order}
|
| 230 |
+
return agent_ctx, grader_ctx
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 234 |
+
# EMAIL HARD β 8 complaint scenario templates
|
| 235 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 236 |
+
|
| 237 |
+
_EMAIL_HARD_TEMPLATES = [
|
| 238 |
+
{
|
| 239 |
+
"subject": "Completely unacceptable β Order #{order_id}",
|
| 240 |
+
"sender": "furious.customer@email.com",
|
| 241 |
+
"body": (
|
| 242 |
+
"I have been a loyal customer for 5 years and I am absolutely furious. "
|
| 243 |
+
"My order #{order_id} arrived broken for the SECOND time this month. "
|
| 244 |
+
"Your support team has been dismissive and unhelpful every time I called. "
|
| 245 |
+
"I demand a full refund, a replacement sent overnight, AND compensation. "
|
| 246 |
+
"If this is not resolved within 24 hours, I will file a consumer court complaint."
|
| 247 |
+
),
|
| 248 |
+
"expected_elements": {
|
| 249 |
+
"acknowledge_frustration": ["understand", "frustrat", "disappoint", "concern", "feel"],
|
| 250 |
+
"apologize": ["sorry", "apologize", "apology", "sincerely"],
|
| 251 |
+
"offer_refund": ["refund", "money back", "full refund"],
|
| 252 |
+
"offer_replacement": ["replacement", "replace", "new order", "send another", "overnight"],
|
| 253 |
+
"provide_timeline": ["24 hours", "48 hours", "within", "today", "tomorrow", "business day"],
|
| 254 |
+
"professional_tone": None,
|
| 255 |
+
"compensation_acknowledged": ["compensat", "inconvenience", "make it right", "goodwill"],
|
| 256 |
+
},
|
| 257 |
+
},
|
| 258 |
+
{
|
| 259 |
+
"subject": "Billing error β charged twice for subscription",
|
| 260 |
+
"sender": "billing.complaint@customer.com",
|
| 261 |
+
"body": (
|
| 262 |
+
"I was charged twice for my annual subscription this month β once on March 1st "
|
| 263 |
+
"and again on March 15th. I have the bank statements as proof. "
|
| 264 |
+
"This is a clear billing error on your end. I need an immediate refund of the "
|
| 265 |
+
"duplicate charge and a written confirmation that this won't happen again. "
|
| 266 |
+
"I've been waiting 2 weeks for a response from your support team."
|
| 267 |
+
),
|
| 268 |
+
"expected_elements": {
|
| 269 |
+
"acknowledge_frustration": ["understand", "concern", "apologize", "sorry"],
|
| 270 |
+
"apologize": ["sorry", "apologize", "apology"],
|
| 271 |
+
"offer_refund": ["refund", "reimburse", "credit"],
|
| 272 |
+
"confirm_investigation": ["investigat", "look into", "check", "review", "verify"],
|
| 273 |
+
"provide_timeline": ["within", "hours", "days", "business day"],
|
| 274 |
+
"professional_tone": None,
|
| 275 |
+
"written_confirmation": ["confirm", "written", "email confirmation", "receipt"],
|
| 276 |
+
},
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
"subject": "Data breach β my account was hacked",
|
| 280 |
+
"sender": "angry.user@personal.com",
|
| 281 |
+
"body": (
|
| 282 |
+
"I just discovered that my account was used to make purchases I never authorised. "
|
| 283 |
+
"Someone accessed my account and changed my email and password. "
|
| 284 |
+
"I was only able to regain access because I remembered my security questions. "
|
| 285 |
+
"This is a serious security failure on your part. I demand to know what happened, "
|
| 286 |
+
"how my data was exposed, and what you will do to protect me going forward. "
|
| 287 |
+
"If I do not hear back within 24 hours, I will contact the data protection authority."
|
| 288 |
+
),
|
| 289 |
+
"expected_elements": {
|
| 290 |
+
"acknowledge_frustration": ["understand", "serious", "concern", "alarming"],
|
| 291 |
+
"apologize": ["sorry", "apologize", "apology"],
|
| 292 |
+
"confirm_investigation": ["investigat", "security team", "look into", "review"],
|
| 293 |
+
"offer_remediation": ["secure", "reset", "protect", "steps", "safeguard"],
|
| 294 |
+
"provide_timeline": ["24 hours", "48 hours", "within", "shortly", "immediately"],
|
| 295 |
+
"professional_tone": None,
|
| 296 |
+
"regulatory_mention": ["authority", "regulat", "notify", "report", "transparent"],
|
| 297 |
+
},
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"subject": "Defective product β 3 replacements still broken",
|
| 301 |
+
"sender": "frustrated.buyer@email.com",
|
| 302 |
+
"body": (
|
| 303 |
+
"This is my THIRD replacement unit and it is still defective. "
|
| 304 |
+
"The first unit arrived with a cracked screen. The second had a faulty battery. "
|
| 305 |
+
"This third unit powers off randomly. "
|
| 306 |
+
"I have spent over 6 hours on support calls. I want a full refund immediately β "
|
| 307 |
+
"no more replacements. I will also be leaving reviews on every platform I can find."
|
| 308 |
+
),
|
| 309 |
+
"expected_elements": {
|
| 310 |
+
"acknowledge_frustration": ["understand", "frustrat", "disappoint", "three", "3"],
|
| 311 |
+
"apologize": ["sorry", "apologize", "sincerely"],
|
| 312 |
+
"offer_refund": ["refund", "full refund", "money back"],
|
| 313 |
+
"no_more_replacement_push": ["will not", "no further", "respect", "understand", "preference"],
|
| 314 |
+
"provide_timeline": ["within", "24 hours", "48 hours", "business day"],
|
| 315 |
+
"professional_tone": None,
|
| 316 |
+
"de_escalation": ["value", "loyal", "appreciate", "important to us"],
|
| 317 |
+
},
|
| 318 |
+
},
|
| 319 |
+
{
|
| 320 |
+
"subject": "Package lost β 3 weeks and still nothing",
|
| 321 |
+
"sender": "lost.package@customer.net",
|
| 322 |
+
"body": (
|
| 323 |
+
"I placed an order 3 weeks ago and my package has still not arrived. "
|
| 324 |
+
"The tracking has shown 'In Transit' for 18 days without any movement. "
|
| 325 |
+
"I have contacted your support team 4 times. Each time I am told to 'wait a few more days'. "
|
| 326 |
+
"I need this item for an event next week. Either find the package, send a replacement now, "
|
| 327 |
+
"or give me a full refund so I can purchase elsewhere. This is unacceptable."
|
| 328 |
+
),
|
| 329 |
+
"expected_elements": {
|
| 330 |
+
"acknowledge_frustration": ["understand", "frustrat", "waiting", "3 weeks", "three weeks"],
|
| 331 |
+
"apologize": ["sorry", "apologize", "apology"],
|
| 332 |
+
"offer_refund": ["refund", "full refund"],
|
| 333 |
+
"offer_replacement": ["replacement", "resend", "new shipment", "send another"],
|
| 334 |
+
"provide_timeline": ["24 hours", "48 hours", "immediately", "today", "next day"],
|
| 335 |
+
"professional_tone": None,
|
| 336 |
+
"carrier_investigation": ["carrier", "investigate", "trace", "courier", "tracking"],
|
| 337 |
+
},
|
| 338 |
+
},
|
| 339 |
+
{
|
| 340 |
+
"subject": "Cancelled subscription still being charged",
|
| 341 |
+
"sender": "cancelled.user@email.com",
|
| 342 |
+
"body": (
|
| 343 |
+
"I cancelled my subscription on January 15th and have proof of the cancellation email. "
|
| 344 |
+
"However, I have been charged $99/month for the past 3 months since then. "
|
| 345 |
+
"Total unauthorised charges: $297. I want a full refund of all three charges immediately. "
|
| 346 |
+
"If this is not resolved within 48 hours, I will initiate a chargeback with my bank "
|
| 347 |
+
"and report this to the consumer protection agency."
|
| 348 |
+
),
|
| 349 |
+
"expected_elements": {
|
| 350 |
+
"acknowledge_frustration": ["understand", "concern", "apologize", "recognised"],
|
| 351 |
+
"apologize": ["sorry", "apologize", "apology"],
|
| 352 |
+
"offer_refund": ["refund", "full refund", "$297", "three charges", "all charges"],
|
| 353 |
+
"confirm_cancellation": ["confirm", "cancellation", "verified", "record"],
|
| 354 |
+
"provide_timeline": ["48 hours", "24 hours", "immediately", "within"],
|
| 355 |
+
"professional_tone": None,
|
| 356 |
+
"prevent_recurrence": ["prevent", "ensure", "will not happen", "steps", "corrective"],
|
| 357 |
+
},
|
| 358 |
+
},
|
| 359 |
+
{
|
| 360 |
+
"subject": "Discriminatory treatment by your staff",
|
| 361 |
+
"sender": "discrimination.complaint@email.com",
|
| 362 |
+
"body": (
|
| 363 |
+
"I visited your store on April 2nd and was followed around by a staff member "
|
| 364 |
+
"while other customers were not treated this way. When I raised this with the manager, "
|
| 365 |
+
"I was told 'it's just store policy'. This felt discriminatory and I am deeply hurt. "
|
| 366 |
+
"I expect a formal apology, an explanation of your training policies, "
|
| 367 |
+
"and assurance that this will not happen to any customer again."
|
| 368 |
+
),
|
| 369 |
+
"expected_elements": {
|
| 370 |
+
"acknowledge_frustration": ["understand", "serious", "concern", "upset", "hurt"],
|
| 371 |
+
"apologize": ["sorry", "apologize", "sincerely", "formal apology"],
|
| 372 |
+
"confirm_investigation": ["investigat", "review", "look into", "hr", "management"],
|
| 373 |
+
"policy_commitment": ["policy", "training", "standards", "commit", "improve"],
|
| 374 |
+
"provide_timeline": ["within", "48 hours", "days", "shortly"],
|
| 375 |
+
"professional_tone": None,
|
| 376 |
+
"empathy_specific": ["dignity", "respect", "every customer", "valued"],
|
| 377 |
+
},
|
| 378 |
+
},
|
| 379 |
+
{
|
| 380 |
+
"subject": "Software destroyed my data β urgent",
|
| 381 |
+
"sender": "data.loss@business.com",
|
| 382 |
+
"body": (
|
| 383 |
+
"Your software update on March 28th corrupted our entire database backup. "
|
| 384 |
+
"We lost 6 months of customer records. Our business has been at a standstill for 3 days. "
|
| 385 |
+
"We are a paying enterprise customer ($24,000/year). "
|
| 386 |
+
"I need immediate escalation to your engineering team, a post-mortem report, "
|
| 387 |
+
"and a discussion about compensation for our business losses."
|
| 388 |
+
),
|
| 389 |
+
"expected_elements": {
|
| 390 |
+
"acknowledge_frustration": ["understand", "serious", "impact", "significant", "recognise"],
|
| 391 |
+
"apologize": ["sorry", "sincerely", "deeply apologize"],
|
| 392 |
+
"escalate": ["engineering", "escalate", "senior", "team", "immediately"],
|
| 393 |
+
"post_mortem": ["post-mortem", "root cause", "analysis", "report", "findings"],
|
| 394 |
+
"provide_timeline": ["24 hours", "immediately", "today", "within"],
|
| 395 |
+
"professional_tone": None,
|
| 396 |
+
"compensation_acknowledged": ["compensat", "discuss", "impact", "losses", "make it right"],
|
| 397 |
+
},
|
| 398 |
+
},
|
| 399 |
+
]
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def gen_email_hard(episode_id: str) -> tuple[dict, dict]:
|
| 403 |
+
rng = _rng(episode_id, "email_hard")
|
| 404 |
+
template = rng.choice(_EMAIL_HARD_TEMPLATES)
|
| 405 |
+
order_id = rng.randint(10000, 99999)
|
| 406 |
+
subject = template["subject"].format(order_id=order_id)
|
| 407 |
+
body = template["body"].format(order_id=order_id)
|
| 408 |
+
case = {**template, "subject": subject, "body": body, "sender": template["sender"], "id": "h1"}
|
| 409 |
+
agent_ctx = {"email": {k: v for k, v in case.items() if k not in ("expected_elements", "id")}}
|
| 410 |
+
grader_ctx = {"email": case}
|
| 411 |
+
return agent_ctx, grader_ctx
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 415 |
+
# CODE EASY β 6 snippet variants
|
| 416 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 417 |
+
|
| 418 |
+
_CODE_EASY_POOL = [
|
| 419 |
+
{"code": "def calculate_average(numbers):\n total = 0\n for num in numbers\n total += num\n return total / len(numbers)\n",
|
| 420 |
+
"keywords": ["colon", "for loop", "syntax", "for statement"]},
|
| 421 |
+
{"code": "def greet(name)\n print(f'Hello, {name}!')\n\ngreet('World')\n",
|
| 422 |
+
"keywords": ["colon", "def", "function", "syntax"]},
|
| 423 |
+
{"code": "numbers = [1, 2, 3, 4, 5]\nif len(numbers) > 0\n print('Not empty')\n",
|
| 424 |
+
"keywords": ["colon", "if statement", "syntax"]},
|
| 425 |
+
{"code": "class Animal:\n def __init__(self, name)\n self.name = name\n\n def speak(self):\n return f'{self.name} speaks'\n",
|
| 426 |
+
"keywords": ["colon", "__init__", "constructor", "function", "def"]},
|
| 427 |
+
{"code": "def read_file(path):\n with open(path) as f\n return f.read()\n",
|
| 428 |
+
"keywords": ["colon", "with statement", "context manager", "syntax"]},
|
| 429 |
+
{"code": "def retry(fn, times=3):\n for i in range(times)\n try:\n return fn()\n except Exception:\n pass\n",
|
| 430 |
+
"keywords": ["colon", "for loop", "range", "syntax"]},
|
| 431 |
+
]
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
def gen_code_easy(episode_id: str) -> tuple[dict, dict]:
|
| 435 |
+
rng = _rng(episode_id, "code_easy")
|
| 436 |
+
variant = rng.choice(_CODE_EASY_POOL)
|
| 437 |
+
agent_ctx = {"code": variant["code"]}
|
| 438 |
+
grader_ctx = {"code": variant["code"], "keywords": variant["keywords"]}
|
| 439 |
+
return agent_ctx, grader_ctx
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 443 |
+
# DATA EASY β procedurally generated 6-10 rows
|
| 444 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 445 |
+
|
| 446 |
+
_FIRST_NAMES = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Hiro", "Iris", "Jake"]
|
| 447 |
+
_DOMAINS = ["example.com", "company.net", "test.org", "work.io"]
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
def gen_data_easy(episode_id: str) -> tuple[dict, dict]:
|
| 451 |
+
rng = _rng(episode_id, "data_easy")
|
| 452 |
+
n = rng.randint(6, 10)
|
| 453 |
+
names = rng.sample(_FIRST_NAMES, k=n)
|
| 454 |
+
base_salary = 50000
|
| 455 |
+
|
| 456 |
+
rows = []
|
| 457 |
+
for i, name in enumerate(names, 1):
|
| 458 |
+
rows.append({
|
| 459 |
+
"id": i,
|
| 460 |
+
"name": name,
|
| 461 |
+
"age": rng.randint(22, 55),
|
| 462 |
+
"email": f"{name.lower()}@{rng.choice(_DOMAINS)}",
|
| 463 |
+
"salary": base_salary + rng.randint(0, 50) * 1000,
|
| 464 |
+
})
|
| 465 |
+
|
| 466 |
+
# Inject 3-4 nulls in distinct rows and fields
|
| 467 |
+
null_fields = rng.sample(["age", "name", "email", "salary"], k=rng.randint(3, 4))
|
| 468 |
+
null_rows = rng.sample(list(range(n)), k=len(null_fields))
|
| 469 |
+
missing_answers = []
|
| 470 |
+
for row_idx, field in zip(null_rows, null_fields):
|
| 471 |
+
rows[row_idx][field] = None
|
| 472 |
+
missing_answers.append(f"{field} (row {row_idx + 1})")
|
| 473 |
+
|
| 474 |
+
# Inject one duplicate pair
|
| 475 |
+
dup_src = rng.randint(0, n - 2)
|
| 476 |
+
dup_dst = dup_src + 1
|
| 477 |
+
rows[dup_dst] = {**rows[dup_src], "id": dup_dst + 1}
|
| 478 |
+
dup_ids = sorted([rows[dup_src]["id"], rows[dup_dst]["id"]])
|
| 479 |
+
|
| 480 |
+
agent_ctx = {"data": rows}
|
| 481 |
+
grader_ctx = {
|
| 482 |
+
"data": rows,
|
| 483 |
+
"answers": {"missing": sorted(missing_answers), "duplicates": dup_ids},
|
| 484 |
+
}
|
| 485 |
+
return agent_ctx, grader_ctx
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 489 |
+
# DATA MEDIUM β randomised messy formats per field
|
| 490 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 491 |
+
|
| 492 |
+
_AGE_STRINGS = ["thirty", "twenty-two", "forty-five", "nineteen", "fifty"]
|
| 493 |
+
_DATE_FORMATS = [("%Y/%m/%d", "2020/01/15"), ("%d-%m-%Y", "15-03-2021"),
|
| 494 |
+
("%Y-%m-%d", "2019-07-22"), ("%Y/%m/%d", "2022/11/01"),
|
| 495 |
+
("%d/%m/%Y", "01/06/2023")]
|
| 496 |
+
_ACTIVE_MESSY = ["yes", "no", "TRUE", "FALSE", "1", "0"]
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
def gen_data_medium(episode_id: str) -> tuple[dict, dict]:
|
| 500 |
+
rng = _rng(episode_id, "data_medium")
|
| 501 |
+
names = rng.sample(_FIRST_NAMES, k=5)
|
| 502 |
+
|
| 503 |
+
rows = []
|
| 504 |
+
salaries = [50000, 60000, 70000, 80000, 45000]
|
| 505 |
+
for i, (name, salary) in enumerate(zip(names, salaries), 1):
|
| 506 |
+
# Introduce one of each issue type
|
| 507 |
+
if i == 1:
|
| 508 |
+
age_val = rng.choice(_AGE_STRINGS) # string age
|
| 509 |
+
sal_val = f"${salary:,}" # dollar+comma format
|
| 510 |
+
elif i == 2:
|
| 511 |
+
age_val = 25 # correct
|
| 512 |
+
sal_val = f"${salary:,}" # dollar+comma
|
| 513 |
+
elif i == 3:
|
| 514 |
+
age_val = str(rng.randint(25, 40)) # string digit
|
| 515 |
+
sal_val = str(salary) # clean
|
| 516 |
+
elif i == 4:
|
| 517 |
+
age_val = rng.randint(30, 45) # int β correct
|
| 518 |
+
sal_val = f"{salary:,}" # comma only
|
| 519 |
+
else:
|
| 520 |
+
age_val = rng.choice(_AGE_STRINGS) # string age
|
| 521 |
+
sal_val = str(salary) # clean
|
| 522 |
+
|
| 523 |
+
_, date_str = rng.choice(_DATE_FORMATS)
|
| 524 |
+
name_messy = name.lower() if i == 4 else name.upper() if i == 5 else name
|
| 525 |
+
active_val = rng.choice(_ACTIVE_MESSY) if i in (1, 3, 5) else rng.choice([True, False])
|
| 526 |
+
|
| 527 |
+
rows.append({"id": i, "name": name_messy, "age": age_val, "salary": sal_val,
|
| 528 |
+
"join_date": date_str, "active": active_val})
|
| 529 |
+
|
| 530 |
+
issues = {
|
| 531 |
+
"age": "mixed types: some ages are word strings, should be integers",
|
| 532 |
+
"salary": "inconsistent formats: dollar signs and commas present in some values",
|
| 533 |
+
"join_date": "three different date formats used across rows",
|
| 534 |
+
"name": "inconsistent casing: some names lowercase, some all-caps",
|
| 535 |
+
"active": "mixed types: booleans and strings ('yes','no','TRUE') mixed",
|
| 536 |
+
}
|
| 537 |
+
agent_ctx = {"data": rows}
|
| 538 |
+
grader_ctx = {"data": rows, "issues": issues}
|
| 539 |
+
return agent_ctx, grader_ctx
|
| 540 |
+
|
| 541 |
+
|
| 542 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 543 |
+
# DATA HARD β IQR-detectable outliers, missing values
|
| 544 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 545 |
+
|
| 546 |
+
def gen_data_hard(episode_id: str) -> tuple[dict, dict]:
|
| 547 |
+
rng = _rng(episode_id, "data_hard")
|
| 548 |
+
n = rng.randint(12, 16)
|
| 549 |
+
base = round(rng.uniform(9.5, 12.5), 1)
|
| 550 |
+
spread = round(rng.uniform(0.3, 0.8), 2)
|
| 551 |
+
|
| 552 |
+
rows = []
|
| 553 |
+
for i in range(1, n + 1):
|
| 554 |
+
val = round(base + rng.gauss(0, spread), 2)
|
| 555 |
+
rows.append({"id": i, "value": val})
|
| 556 |
+
|
| 557 |
+
# Pick 2 outlier positions (ensure they're not neighbours)
|
| 558 |
+
positions = list(range(n))
|
| 559 |
+
rng.shuffle(positions)
|
| 560 |
+
o1, o2 = sorted(positions[:2])
|
| 561 |
+
outlier_mult = rng.choice([8, 10, 12, -5, -8])
|
| 562 |
+
rows[o1]["value"] = round(base * abs(outlier_mult) * (1 if outlier_mult > 0 else -1), 1)
|
| 563 |
+
outlier_mult2 = rng.choice([-4, -6, 15, 20])
|
| 564 |
+
rows[o2]["value"] = round(base * abs(outlier_mult2) * (1 if outlier_mult2 > 0 else -1), 1)
|
| 565 |
+
outlier_ids = sorted([o1 + 1, o2 + 1])
|
| 566 |
+
|
| 567 |
+
# Pick 2 missing positions (not overlapping outliers)
|
| 568 |
+
available = [i for i in range(n) if i not in (o1, o2)]
|
| 569 |
+
m1, m2 = sorted(rng.sample(available, 2))
|
| 570 |
+
rows[m1]["value"] = None
|
| 571 |
+
rows[m2]["value"] = None
|
| 572 |
+
missing_ids = sorted([m1 + 1, m2 + 1])
|
| 573 |
+
|
| 574 |
+
normal_vals = [r["value"] for r in rows if r["value"] is not None and r["id"] not in outlier_ids]
|
| 575 |
+
lo = round(min(normal_vals) - 1.0, 2)
|
| 576 |
+
hi = round(max(normal_vals) + 1.0, 2)
|
| 577 |
+
|
| 578 |
+
agent_ctx = {"data": rows}
|
| 579 |
+
grader_ctx = {
|
| 580 |
+
"data": rows,
|
| 581 |
+
"answers": {
|
| 582 |
+
"outliers": set(outlier_ids),
|
| 583 |
+
"missing": set(missing_ids),
|
| 584 |
+
"imputed_range": (lo, hi),
|
| 585 |
+
},
|
| 586 |
+
}
|
| 587 |
+
return agent_ctx, grader_ctx
|
| 588 |
+
|
| 589 |
+
|
| 590 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 591 |
+
# CONTENT MODERATION EASY β draw 7 from a pool of 20
|
| 592 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 593 |
+
|
| 594 |
+
_MOD_EASY_POOL = [
|
| 595 |
+
{"text": "I hate you so much, I want to hurt you!", "label": "harmful"},
|
| 596 |
+
{"text": "Great weather today! Loving this sunshine", "label": "safe"},
|
| 597 |
+
{"text": "All people from that group should be eliminated.", "label": "harmful"},
|
| 598 |
+
{"text": "Can someone recommend a good Python beginner book?", "label": "safe"},
|
| 599 |
+
{"text": "You're an absolute idiot and nobody likes you.", "label": "harmful"},
|
| 600 |
+
{"text": "I just finished a 5k run β feeling amazing!", "label": "safe"},
|
| 601 |
+
{"text": "People like you should just disappear forever.", "label": "harmful"},
|
| 602 |
+
{"text": "Just dropped off a care package for my neighbor.", "label": "safe"},
|
| 603 |
+
{"text": "Kill yourself, nobody would miss you.", "label": "harmful"},
|
| 604 |
+
{"text": "Anyone else excited for the new season of that show?", "label": "safe"},
|
| 605 |
+
{"text": "I'm going to find you and make you pay for this.", "label": "harmful"},
|
| 606 |
+
{"text": "Finally got my herb garden started!", "label": "safe"},
|
| 607 |
+
{"text": "These people deserve to suffer for what they've done.", "label": "harmful"},
|
| 608 |
+
{"text": "Happy birthday to my best friend!", "label": "safe"},
|
| 609 |
+
{"text": "Someone should shoot that politician.", "label": "harmful"},
|
| 610 |
+
{"text": "Loving the new coffee shop that opened downtown.", "label": "safe"},
|
| 611 |
+
{"text": "I hope your family gets what's coming to them.", "label": "harmful"},
|
| 612 |
+
{"text": "Just hit 100 days of learning Spanish β so proud!", "label": "safe"},
|
| 613 |
+
{"text": "People from that country are all criminals.", "label": "harmful"},
|
| 614 |
+
{"text": "The cherry blossoms are out β spring is finally here!", "label": "safe"},
|
| 615 |
+
]
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
def gen_mod_easy(episode_id: str) -> tuple[dict, dict]:
|
| 619 |
+
rng = _rng(episode_id, "mod_easy")
|
| 620 |
+
# Ensure at least 3 harmful, 3 safe among 7
|
| 621 |
+
harmful = [p for p in _MOD_EASY_POOL if p["label"] == "harmful"]
|
| 622 |
+
safe = [p for p in _MOD_EASY_POOL if p["label"] == "safe"]
|
| 623 |
+
chosen = rng.sample(harmful, 4) + rng.sample(safe, 3)
|
| 624 |
+
rng.shuffle(chosen)
|
| 625 |
+
posts = [{"id": f"c{i+1}", **p} for i, p in enumerate(chosen)]
|
| 626 |
+
agent_ctx = {"posts": [{"id": p["id"], "text": p["text"]} for p in posts]}
|
| 627 |
+
grader_ctx = {"posts": posts}
|
| 628 |
+
return agent_ctx, grader_ctx
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 632 |
+
# CONTENT MODERATION MEDIUM β draw 8 from a pool of 20
|
| 633 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 634 |
+
|
| 635 |
+
_MOD_MEDIUM_POOL = [
|
| 636 |
+
{"text": "Oh sure, like THAT went well. Classic genius move.", "label": "harmful"},
|
| 637 |
+
{"text": "I'm so done with these people. Absolutely insufferable.", "label": "harmful"},
|
| 638 |
+
{"text": "That presentation was... very unique.", "label": "safe"},
|
| 639 |
+
{"text": "Some people just shouldn't be allowed to have opinions.", "label": "harmful"},
|
| 640 |
+
{"text": "Oh wow, another 'brilliant' idea from upper management", "label": "harmful"},
|
| 641 |
+
{"text": "Congrats on the promotion! Well deserved!", "label": "safe"},
|
| 642 |
+
{"text": "Not everyone is cut out for this kind of work, clearly.", "label": "harmful"},
|
| 643 |
+
{"text": "Thanks for the feedback, I'll take it into account.", "label": "safe"},
|
| 644 |
+
{"text": "Some of us actually read the requirements before submitting.", "label": "harmful"},
|
| 645 |
+
{"text": "Good effort β interesting approach to the problem.", "label": "safe"},
|
| 646 |
+
{"text": "If you'd done even a little research, you'd know better.", "label": "harmful"},
|
| 647 |
+
{"text": "Really enjoyed the team retrospective today.", "label": "safe"},
|
| 648 |
+
{"text": "Must be nice having a job you're actually qualified for.", "label": "harmful"},
|
| 649 |
+
{"text": "Great work getting the release out on time!", "label": "safe"},
|
| 650 |
+
{"text": "Shocking that anyone thought that plan would work.", "label": "harmful"},
|
| 651 |
+
{"text": "Just wanted to say β the onboarding docs are really clear.", "label": "safe"},
|
| 652 |
+
{"text": "The bar for hiring has clearly dropped.", "label": "harmful"},
|
| 653 |
+
{"text": "Appreciate everyone's patience during the migration.", "label": "safe"},
|
| 654 |
+
{"text": "I see we've decided quantity over quality again.", "label": "harmful"},
|
| 655 |
+
{"text": "The demo went really smoothly β nice job everyone.", "label": "safe"},
|
| 656 |
+
]
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
def gen_mod_medium(episode_id: str) -> tuple[dict, dict]:
|
| 660 |
+
rng = _rng(episode_id, "mod_medium")
|
| 661 |
+
harmful = [p for p in _MOD_MEDIUM_POOL if p["label"] == "harmful"]
|
| 662 |
+
safe = [p for p in _MOD_MEDIUM_POOL if p["label"] == "safe"]
|
| 663 |
+
chosen = rng.sample(harmful, 5) + rng.sample(safe, 3)
|
| 664 |
+
rng.shuffle(chosen)
|
| 665 |
+
posts = [{"id": f"m{i+1}", **p} for i, p in enumerate(chosen)]
|
| 666 |
+
agent_ctx = {"posts": [{"id": p["id"], "text": p["text"]} for p in posts]}
|
| 667 |
+
grader_ctx = {"posts": posts}
|
| 668 |
+
return agent_ctx, grader_ctx
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
# ββοΏ½οΏ½ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 672 |
+
# TICKET EASY β 8 variant pool (already existed; keep contract-compatible)
|
| 673 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 674 |
+
|
| 675 |
+
_TICKET_EASY_POOL = [
|
| 676 |
+
{"id": "t1", "title": "App crashes on login for all users",
|
| 677 |
+
"description": "Since the 2.3.1 deploy, 100% of login attempts throw a 500 error.",
|
| 678 |
+
"reporter": "ops-monitoring@company.com", "correct_priority": "critical", "correct_category": "bug"},
|
| 679 |
+
{"id": "t2", "title": "Add dark mode to the dashboard",
|
| 680 |
+
"description": "Multiple users have requested a dark mode option.",
|
| 681 |
+
"reporter": "product@company.com", "correct_priority": "low", "correct_category": "feature_request"},
|
| 682 |
+
{"id": "t3", "title": "How do I export my data as CSV?",
|
| 683 |
+
"description": "I can't find the export button anywhere in the UI.",
|
| 684 |
+
"reporter": "user123@customer.com", "correct_priority": "low", "correct_category": "question"},
|
| 685 |
+
{"id": "t4", "title": "Charged twice for last month's invoice",
|
| 686 |
+
"description": "Account #AC-4421 shows two charges of $299 on March 1st.",
|
| 687 |
+
"reporter": "finance@customer.com", "correct_priority": "high", "correct_category": "billing"},
|
| 688 |
+
{"id": "t5", "title": "Memory leak in mobile app β crashes after 10 min",
|
| 689 |
+
"description": "The iOS app uses 2 GB RAM then crashes. Reproducible on all devices.",
|
| 690 |
+
"reporter": "qa@company.com", "correct_priority": "high", "correct_category": "bug"},
|
| 691 |
+
{"id": "t6", "title": "Add SSO support for Google Workspace",
|
| 692 |
+
"description": "Enterprise customers are requesting Google SSO. High deal-blocker frequency.",
|
| 693 |
+
"reporter": "sales@company.com", "correct_priority": "medium", "correct_category": "feature_request"},
|
| 694 |
+
{"id": "t7", "title": "Refund policy β how long does it take?",
|
| 695 |
+
"description": "A customer is asking about the refund timeline for a cancelled subscription.",
|
| 696 |
+
"reporter": "support@company.com", "correct_priority": "low", "correct_category": "question"},
|
| 697 |
+
{"id": "t8", "title": "Wrong amount charged β $0 instead of $499",
|
| 698 |
+
"description": "New enterprise contract shows $0 on the invoice. Revenue loss.",
|
| 699 |
+
"reporter": "billing@company.com", "correct_priority": "critical", "correct_category": "billing"},
|
| 700 |
+
]
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
def gen_ticket_easy(episode_id: str) -> tuple[dict, dict]:
|
| 704 |
+
rng = _rng(episode_id, "ticket_easy")
|
| 705 |
+
ticket = rng.choice(_TICKET_EASY_POOL)
|
| 706 |
+
agent_ctx = {"ticket": {k: v for k, v in ticket.items() if k not in ("correct_priority", "correct_category")}}
|
| 707 |
+
grader_ctx = {"ticket": ticket}
|
| 708 |
+
return agent_ctx, grader_ctx
|
expert_tasks.py
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
expert_tasks.py β Expert-tier tasks for Meta v3.1
|
| 3 |
+
Two per domain. Multi-step reasoning required. Frontier models score β€ 0.45.
|
| 4 |
+
|
| 5 |
+
Design
|
| 6 |
+
------
|
| 7 |
+
Each expert task requires the agent to:
|
| 8 |
+
1. Reason across multiple pieces of evidence
|
| 9 |
+
2. Synthesise a correct answer that isn't directly stated in the context
|
| 10 |
+
3. Produce structured output that is graded on multiple sub-criteria
|
| 11 |
+
|
| 12 |
+
Drop these into Meta_environment.py:
|
| 13 |
+
- Add the task IDs to TASK_IDS and openenv.yaml
|
| 14 |
+
- Add context loaders to _load_context()
|
| 15 |
+
- Add grade functions to _grade()
|
| 16 |
+
- Add instructions to INSTRUCTIONS
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
from typing import Any
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
+
# EMAIL EXPERT
|
| 26 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 27 |
+
|
| 28 |
+
EMAIL_EXPERT_CONTEXT = {
|
| 29 |
+
"thread": [
|
| 30 |
+
{
|
| 31 |
+
"from": "sarah.chen@bigclient.com",
|
| 32 |
+
"to": "support@company.com",
|
| 33 |
+
"date": "2026-03-10 09:15",
|
| 34 |
+
"subject": "Re: Re: Re: Our data export is wrong β ESCALATING",
|
| 35 |
+
"body": (
|
| 36 |
+
"This is the FOURTH week we have been raising this issue. "
|
| 37 |
+
"The data export from your platform is missing 12% of our transaction records, "
|
| 38 |
+
"dating back to February 1st. Our CFO has flagged this as a potential compliance risk. "
|
| 39 |
+
"I am now formally requesting a root-cause analysis, a corrected export, "
|
| 40 |
+
"and a meeting with your VP of Engineering and VP of Customer Success. "
|
| 41 |
+
"If we do not hear back with a concrete plan within 24 hours, "
|
| 42 |
+
"we will be engaging our legal team."
|
| 43 |
+
),
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"from": "support@company.com",
|
| 47 |
+
"to": "sarah.chen@bigclient.com",
|
| 48 |
+
"date": "2026-03-09 14:22",
|
| 49 |
+
"subject": "Re: Re: Our data export is wrong",
|
| 50 |
+
"body": (
|
| 51 |
+
"Hi Sarah, our engineering team is still investigating. "
|
| 52 |
+
"We expect an update by end of week. Apologies for the delay."
|
| 53 |
+
),
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"from": "sarah.chen@bigclient.com",
|
| 57 |
+
"to": "support@company.com",
|
| 58 |
+
"date": "2026-03-05 11:30",
|
| 59 |
+
"subject": "Re: Our data export is wrong",
|
| 60 |
+
"body": (
|
| 61 |
+
"It has been 10 days and no fix. "
|
| 62 |
+
"We have a regulatory audit on March 20th. This is becoming critical."
|
| 63 |
+
),
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"from": "sarah.chen@bigclient.com",
|
| 67 |
+
"to": "support@company.com",
|
| 68 |
+
"date": "2026-02-24 16:05",
|
| 69 |
+
"subject": "Our data export is wrong",
|
| 70 |
+
"body": (
|
| 71 |
+
"Hello, we noticed our February data export is missing records. "
|
| 72 |
+
"Can you please investigate?"
|
| 73 |
+
),
|
| 74 |
+
},
|
| 75 |
+
],
|
| 76 |
+
"account_metadata": {
|
| 77 |
+
"account_name": "BigClient Corp",
|
| 78 |
+
"tier": "Enterprise",
|
| 79 |
+
"contract_value": 480000,
|
| 80 |
+
"account_age_years": 4,
|
| 81 |
+
"renewal_date": "2026-06-01",
|
| 82 |
+
"open_issues": 3,
|
| 83 |
+
"last_csat_score": 5.2,
|
| 84 |
+
},
|
| 85 |
+
"known_bug": {
|
| 86 |
+
"status": "confirmed",
|
| 87 |
+
"root_cause": "Daylight saving time offset bug in the data export pipeline affecting records from Feb 1-14",
|
| 88 |
+
"fix_eta": "2026-03-15",
|
| 89 |
+
"records_affected": "12% of February transaction exports",
|
| 90 |
+
},
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
EMAIL_EXPERT_EXPECTED = {
|
| 94 |
+
"escalation_acknowledged": ["escalat", "four weeks", "4 weeks", "urgent", "seriou", "concern"],
|
| 95 |
+
"vp_meeting_committed": ["vp", "engineering", "customer success", "meeting", "schedule", "arrange"],
|
| 96 |
+
"root_cause_disclosed": ["daylight", "dst", "timezone", "offset", "february", "feb", "export pipeline"],
|
| 97 |
+
"timeline_for_fix": ["march 15", "march 15th", "3/15", "this week", "by friday", "within"],
|
| 98 |
+
"corrected_export_promised": ["corrected export", "re-export", "new export", "corrected data", "send you"],
|
| 99 |
+
"legal_de_escalation": ["legal", "understand", "resolution", "committed", "avoid", "together"],
|
| 100 |
+
"executive_tone": None, # checked via length + no informal language
|
| 101 |
+
"retention_focus": ["value", "partnership", "4 years", "four years", "important", "priority", "renew"],
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
EMAIL_EXPERT_INSTRUCTIONS = (
|
| 105 |
+
"You are the VP of Customer Success. Read the full email thread AND the account metadata carefully.\n\n"
|
| 106 |
+
"You must draft an executive-level response that:\n"
|
| 107 |
+
" 1. Acknowledges the full history (4 weeks of escalation)\n"
|
| 108 |
+
" 2. Commits to scheduling the VP meeting requested\n"
|
| 109 |
+
" 3. Discloses the root cause (you have the engineering report)\n"
|
| 110 |
+
" 4. Provides the concrete fix timeline (March 15)\n"
|
| 111 |
+
" 5. Commits to sending a corrected export\n"
|
| 112 |
+
" 6. De-escalates the legal threat through concrete action\n"
|
| 113 |
+
" 7. Uses appropriate executive tone (not boilerplate support language)\n"
|
| 114 |
+
" 8. Reinforces the long-term partnership value\n\n"
|
| 115 |
+
"This is a $480k account up for renewal in 3 months. Every word matters.\n\n"
|
| 116 |
+
'Payload: {"reply": "<full executive reply β minimum 200 words>"}'
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def grade_email_expert(payload: dict) -> tuple[float, str, dict]:
|
| 121 |
+
reply = payload.get("reply", "").lower()
|
| 122 |
+
checks: dict[str, bool] = {}
|
| 123 |
+
for key, keywords in EMAIL_EXPERT_EXPECTED.items():
|
| 124 |
+
if key == "executive_tone":
|
| 125 |
+
checks[key] = (
|
| 126 |
+
len(reply) >= 200
|
| 127 |
+
and not any(w in reply for w in ["ticket", "our team will", "apologies for the inconvenience",
|
| 128 |
+
"we understand your frustration", "please be patient"])
|
| 129 |
+
)
|
| 130 |
+
else:
|
| 131 |
+
checks[key] = keywords is not None and any(w in reply for w in keywords)
|
| 132 |
+
passed = sum(checks.values())
|
| 133 |
+
score = round(passed / len(checks), 2)
|
| 134 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.6 else "[ERROR]"
|
| 135 |
+
missing = [k for k, v in checks.items() if not v]
|
| 136 |
+
feedback = f"{tag} {passed}/{len(checks)} executive reply elements present."
|
| 137 |
+
if missing:
|
| 138 |
+
feedback += f" Missing: {', '.join(missing)}."
|
| 139 |
+
return score, feedback, checks
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 143 |
+
# CODE EXPERT
|
| 144 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 145 |
+
|
| 146 |
+
CODE_EXPERT_CONTEXT = {
|
| 147 |
+
"code": """\
|
| 148 |
+
import jwt
|
| 149 |
+
import hmac
|
| 150 |
+
import hashlib
|
| 151 |
+
import os
|
| 152 |
+
import subprocess
|
| 153 |
+
import yaml
|
| 154 |
+
import json
|
| 155 |
+
from functools import wraps
|
| 156 |
+
|
| 157 |
+
SECRET_KEY = "hardcoded-jwt-secret-change-me"
|
| 158 |
+
|
| 159 |
+
# Vulnerability 1: Hardcoded secret + weak JWT algorithm default
|
| 160 |
+
def create_token(user_id: int, role: str) -> str:
|
| 161 |
+
payload = {"user_id": user_id, "role": role}
|
| 162 |
+
return jwt.encode(payload, SECRET_KEY) # defaults to HS256 with hardcoded key
|
| 163 |
+
|
| 164 |
+
# Vulnerability 2: JWT none algorithm attack β algorithm not validated
|
| 165 |
+
def verify_token(token: str) -> dict:
|
| 166 |
+
return jwt.decode(token, SECRET_KEY, algorithms=["HS256", "none"])
|
| 167 |
+
|
| 168 |
+
# Vulnerability 3: Mass assignment β user-controlled fields merged into DB update
|
| 169 |
+
def update_user_profile(user_id: int, form_data: dict) -> None:
|
| 170 |
+
db.execute("UPDATE users SET ? WHERE id = ?", (form_data, user_id))
|
| 171 |
+
|
| 172 |
+
# Vulnerability 4: Insecure direct object reference (IDOR)
|
| 173 |
+
def get_invoice(invoice_id: int) -> dict:
|
| 174 |
+
return db.query("SELECT * FROM invoices WHERE id = ?", (invoice_id,))
|
| 175 |
+
|
| 176 |
+
# Vulnerability 5: Unsafe YAML deserialization
|
| 177 |
+
def load_config(yaml_string: str) -> dict:
|
| 178 |
+
return yaml.load(yaml_string) # yaml.load without Loader β RCE risk
|
| 179 |
+
|
| 180 |
+
# Vulnerability 6: Race condition / TOCTOU in file write
|
| 181 |
+
def save_report(filename: str, content: str) -> None:
|
| 182 |
+
if not os.path.exists(f"/reports/{filename}"):
|
| 183 |
+
with open(f"/reports/{filename}", "w") as f:
|
| 184 |
+
f.write(content)
|
| 185 |
+
|
| 186 |
+
# Vulnerability 7: Prototype pollution via JSON merge (Python dict equivalent)
|
| 187 |
+
def merge_settings(base: dict, override: dict) -> dict:
|
| 188 |
+
result = base.copy()
|
| 189 |
+
for key, value in override.items():
|
| 190 |
+
result[key] = value # no key validation β __proto__ / __class__ attack
|
| 191 |
+
return result
|
| 192 |
+
|
| 193 |
+
# Vulnerability 8: Log injection β unsanitized user input in log string
|
| 194 |
+
def log_action(username: str, action: str) -> None:
|
| 195 |
+
print(f"[AUDIT] User: {username} | Action: {action}")
|
| 196 |
+
""",
|
| 197 |
+
"vulnerability_count": 8,
|
| 198 |
+
"vulnerability_list": [
|
| 199 |
+
{"id": "v1", "type": "hardcoded_secret", "location": "create_token", "severity": "critical"},
|
| 200 |
+
{"id": "v2", "type": "jwt_none_algorithm", "location": "verify_token", "severity": "critical"},
|
| 201 |
+
{"id": "v3", "type": "mass_assignment", "location": "update_user_profile", "severity": "high"},
|
| 202 |
+
{"id": "v4", "type": "idor", "location": "get_invoice", "severity": "high"},
|
| 203 |
+
{"id": "v5", "type": "unsafe_deserialization", "location": "load_config", "severity": "critical"},
|
| 204 |
+
{"id": "v6", "type": "race_condition", "location": "save_report", "severity": "medium"},
|
| 205 |
+
{"id": "v7", "type": "prototype_pollution", "location": "merge_settings", "severity": "medium"},
|
| 206 |
+
{"id": "v8", "type": "log_injection", "location": "log_action", "severity": "low"},
|
| 207 |
+
],
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
CODE_EXPERT_INSTRUCTIONS = (
|
| 211 |
+
"Perform a thorough security audit on this Python codebase. "
|
| 212 |
+
"Identify ALL 8 security vulnerabilities, including subtle ones like JWT algorithm confusion, "
|
| 213 |
+
"mass assignment, IDOR, TOCTOU race conditions, prototype pollution, and log injection.\n\n"
|
| 214 |
+
"For each vulnerability provide: type, location (function name), severity (critical/high/medium/low), "
|
| 215 |
+
"and a concrete fix.\n\n"
|
| 216 |
+
"Hints: Look beyond the obvious injection flaws. Consider authentication logic, access control, "
|
| 217 |
+
"concurrency, deserialization, and output encoding.\n\n"
|
| 218 |
+
'Payload: {"vulnerabilities": [{"type": "...", "location": "...", "severity": "...", "fix": "..."}]}'
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def grade_code_expert(payload: dict) -> tuple[float, str, dict]:
|
| 223 |
+
vulns = payload.get("vulnerabilities", [])
|
| 224 |
+
checks = {
|
| 225 |
+
"hardcoded_secret": False,
|
| 226 |
+
"jwt_none_algorithm": False,
|
| 227 |
+
"mass_assignment": False,
|
| 228 |
+
"idor": False,
|
| 229 |
+
"unsafe_deserialization": False,
|
| 230 |
+
"race_condition": False,
|
| 231 |
+
"prototype_pollution": False,
|
| 232 |
+
"log_injection": False,
|
| 233 |
+
}
|
| 234 |
+
for v in vulns:
|
| 235 |
+
vt = str(v.get("type", "")).lower()
|
| 236 |
+
loc = str(v.get("location", "")).lower()
|
| 237 |
+
fix = str(v.get("fix", "")).lower()
|
| 238 |
+
|
| 239 |
+
if "create_token" in loc or any(w in vt for w in ["hardcoded", "secret", "env", "rotate"]):
|
| 240 |
+
checks["hardcoded_secret"] = True
|
| 241 |
+
if "verify_token" in loc or any(w in vt for w in ["none", "algorithm", "jwt", "alg"]):
|
| 242 |
+
checks["jwt_none_algorithm"] = True
|
| 243 |
+
if "update_user" in loc or any(w in vt for w in ["mass", "assignment", "whitelist", "allowlist"]):
|
| 244 |
+
checks["mass_assignment"] = True
|
| 245 |
+
if "get_invoice" in loc or any(w in vt for w in ["idor", "direct object", "authorization", "ownership"]):
|
| 246 |
+
checks["idor"] = True
|
| 247 |
+
if "load_config" in loc or any(w in vt for w in ["yaml", "deserializ", "rce", "unsafe_load", "safe_load"]):
|
| 248 |
+
checks["unsafe_deserialization"] = True
|
| 249 |
+
if "save_report" in loc or any(w in vt for w in ["race", "toctou", "time of check", "atomic"]):
|
| 250 |
+
checks["race_condition"] = True
|
| 251 |
+
if "merge_settings" in loc or any(w in vt for w in ["prototype", "pollution", "__proto__", "key valid"]):
|
| 252 |
+
checks["prototype_pollution"] = True
|
| 253 |
+
if "log_action" in loc or any(w in vt for w in ["log inject", "log forge", "crlf", "newline", "sanitize"]):
|
| 254 |
+
checks["log_injection"] = True
|
| 255 |
+
|
| 256 |
+
passed = sum(checks.values())
|
| 257 |
+
score = round(passed / 8, 2)
|
| 258 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 259 |
+
missing = [k for k, v in checks.items() if not v]
|
| 260 |
+
feedback = f"{tag} {passed}/8 expert vulnerabilities found."
|
| 261 |
+
if missing:
|
| 262 |
+
feedback += f" Missed: {', '.join(missing)}."
|
| 263 |
+
return score, feedback, checks
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 267 |
+
# DATA EXPERT β cross-dataset join, statistical anomaly detection
|
| 268 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 269 |
+
|
| 270 |
+
DATA_EXPERT_CONTEXT = {
|
| 271 |
+
"dataset_a": {
|
| 272 |
+
"name": "Sales transactions (March 2026)",
|
| 273 |
+
"rows": [
|
| 274 |
+
{"txn_id": "T001", "customer_id": "C101", "amount": 1200.00, "currency": "USD", "date": "2026-03-01", "rep_id": "R05"},
|
| 275 |
+
{"txn_id": "T002", "customer_id": "C102", "amount": 850.50, "currency": "USD", "date": "2026-03-02", "rep_id": "R05"},
|
| 276 |
+
{"txn_id": "T003", "customer_id": "C103", "amount": None, "currency": "USD", "date": "2026-03-03", "rep_id": "R12"},
|
| 277 |
+
{"txn_id": "T004", "customer_id": "C104", "amount": 2300.00, "currency": "GBP", "date": "2026-03-04", "rep_id": "R12"},
|
| 278 |
+
{"txn_id": "T005", "customer_id": "C105", "amount": 475.00, "currency": "USD", "date": "2026-03-05", "rep_id": "R05"},
|
| 279 |
+
{"txn_id": "T006", "customer_id": "C101", "amount": 1200.00, "currency": "USD", "date": "2026-03-01", "rep_id": "R05"},
|
| 280 |
+
{"txn_id": "T007", "customer_id": "C106", "amount": 99999.99,"currency": "USD", "date": "2026-03-07", "rep_id": "R05"},
|
| 281 |
+
{"txn_id": "T008", "customer_id": "C107", "amount": 920.00, "currency": "EUR", "date": "2026-03-08", "rep_id": "R12"},
|
| 282 |
+
{"txn_id": "T009", "customer_id": "C108", "amount": 1100.00, "currency": "USD", "date": "2026-03-09", "rep_id": "R05"},
|
| 283 |
+
{"txn_id": "T010", "customer_id": "C109", "amount": 0.00, "currency": "USD", "date": "2026-03-10", "rep_id": "R12"},
|
| 284 |
+
],
|
| 285 |
+
},
|
| 286 |
+
"dataset_b": {
|
| 287 |
+
"name": "Currency conversion rates (2026-03-15)",
|
| 288 |
+
"rows": [
|
| 289 |
+
{"currency": "USD", "rate_to_usd": 1.000},
|
| 290 |
+
{"currency": "GBP", "rate_to_usd": 1.265},
|
| 291 |
+
{"currency": "EUR", "rate_to_usd": 1.082},
|
| 292 |
+
],
|
| 293 |
+
},
|
| 294 |
+
"task_description": (
|
| 295 |
+
"You have two datasets: (A) a sales transaction log and (B) currency conversion rates. "
|
| 296 |
+
"Perform the following analysis and return results as structured JSON:\n"
|
| 297 |
+
"1. Identify all data quality issues across both datasets (missing values, duplicates, outliers, anomalies).\n"
|
| 298 |
+
"2. Join the datasets: convert all transaction amounts to USD using dataset B.\n"
|
| 299 |
+
"3. Compute the total USD revenue per sales rep (R05 and R12), EXCLUDING duplicate and zero-amount transactions.\n"
|
| 300 |
+
"4. Flag any suspicious transactions with a reason (statistical outlier, duplicate, zero amount)."
|
| 301 |
+
),
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
DATA_EXPERT_EXPECTED = {
|
| 305 |
+
# Ground truth
|
| 306 |
+
"duplicate_txn": ["T001", "T006"], # same customer, amount, date, rep
|
| 307 |
+
"zero_txn": ["T010"], # $0 amount
|
| 308 |
+
"missing_amount": ["T003"],
|
| 309 |
+
"outlier_txn": ["T007"], # $99,999.99 β extreme IQR outlier
|
| 310 |
+
# After exclusions: T003 (missing), T006 (dup), T007 (outlier), T010 (zero) excluded
|
| 311 |
+
# T004: 2300 GBP Γ 1.265 = 2909.50 USD β R12
|
| 312 |
+
# T008: 920 EUR Γ 1.082 = 995.44 USD β R12
|
| 313 |
+
# R05 valid txns: T001(1200), T002(850.5), T005(475), T009(1100) = 3625.50
|
| 314 |
+
# R12 valid txns: T004(2909.50), T008(995.44) = 3904.94 [T003 missing, excluded]
|
| 315 |
+
"r05_usd_range": (3500, 3700), # allow rounding tolerance
|
| 316 |
+
"r12_usd_range": (3800, 4100),
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
DATA_EXPERT_INSTRUCTIONS = (
|
| 320 |
+
"Perform a multi-dataset analysis across Sales Transactions (A) and Currency Rates (B).\n\n"
|
| 321 |
+
"Required steps:\n"
|
| 322 |
+
" 1. Identify ALL data quality issues: missing values, exact duplicates, zero-amount transactions, "
|
| 323 |
+
" and statistical outliers (use IQR or z-score on USD-equivalent amounts)\n"
|
| 324 |
+
" 2. Convert all transaction amounts to USD using the exchange rates in Dataset B\n"
|
| 325 |
+
" 3. Calculate total USD revenue per sales rep (R05 and R12), EXCLUDING:\n"
|
| 326 |
+
" - Duplicate transactions (keep first occurrence only)\n"
|
| 327 |
+
" - Zero-amount transactions\n"
|
| 328 |
+
" - Missing-amount transactions\n"
|
| 329 |
+
" - Statistical outliers\n"
|
| 330 |
+
" 4. Flag each suspicious transaction with a reason\n\n"
|
| 331 |
+
"Payload:\n"
|
| 332 |
+
"{\n"
|
| 333 |
+
' "data_quality_issues": ["T006 duplicate of T001", "T010 zero amount", ...],\n'
|
| 334 |
+
' "suspicious_transactions": [{"txn_id": "T007", "reason": "outlier: $99,999.99"}],\n'
|
| 335 |
+
' "revenue_by_rep": {"R05": 3625.50, "R12": 3904.94},\n'
|
| 336 |
+
' "excluded_transactions": ["T003", "T006", "T007", "T010"]\n'
|
| 337 |
+
"}"
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def grade_data_expert(payload: dict) -> tuple[float, str, dict]:
|
| 342 |
+
issues_text = " ".join(str(x) for x in payload.get("data_quality_issues", [])).lower()
|
| 343 |
+
suspicious = payload.get("suspicious_transactions", [])
|
| 344 |
+
revenue = payload.get("revenue_by_rep", {})
|
| 345 |
+
excluded = [str(x) for x in payload.get("excluded_transactions", [])]
|
| 346 |
+
|
| 347 |
+
exp = DATA_EXPERT_EXPECTED
|
| 348 |
+
checks = {
|
| 349 |
+
"duplicate_found": all(t.lower() in issues_text or t in excluded for t in exp["duplicate_txn"]),
|
| 350 |
+
"zero_found": any(t.lower() in issues_text or t in excluded for t in exp["zero_txn"]),
|
| 351 |
+
"missing_found": any(t.lower() in issues_text or t in excluded for t in exp["missing_amount"]),
|
| 352 |
+
"outlier_found": any(t.lower() in issues_text or t in excluded for t in exp["outlier_txn"])
|
| 353 |
+
or any(str(s.get("txn_id","")).upper() in exp["outlier_txn"] for s in suspicious),
|
| 354 |
+
"r05_revenue_correct": False,
|
| 355 |
+
"r12_revenue_correct": False,
|
| 356 |
+
"exclusions_correct": all(t in excluded for t in ["T003", "T006", "T007", "T010"]),
|
| 357 |
+
"suspicious_flagged": len(suspicious) >= 2,
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
# Revenue checks with tolerance
|
| 361 |
+
try:
|
| 362 |
+
r05 = float(revenue.get("R05", revenue.get("r05", 0)))
|
| 363 |
+
lo, hi = exp["r05_usd_range"]
|
| 364 |
+
checks["r05_revenue_correct"] = lo <= r05 <= hi
|
| 365 |
+
except (ValueError, TypeError):
|
| 366 |
+
pass
|
| 367 |
+
try:
|
| 368 |
+
r12 = float(revenue.get("R12", revenue.get("r12", 0)))
|
| 369 |
+
lo, hi = exp["r12_usd_range"]
|
| 370 |
+
checks["r12_revenue_correct"] = lo <= r12 <= hi
|
| 371 |
+
except (ValueError, TypeError):
|
| 372 |
+
pass
|
| 373 |
+
|
| 374 |
+
passed = sum(checks.values())
|
| 375 |
+
score = round(passed / len(checks), 2)
|
| 376 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 377 |
+
missing = [k for k, v in checks.items() if not v]
|
| 378 |
+
feedback = f"{tag} {passed}/{len(checks)} data expert checks passed."
|
| 379 |
+
if missing:
|
| 380 |
+
feedback += f" Failed: {', '.join(missing)}."
|
| 381 |
+
return score, feedback, checks
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 385 |
+
# CONTENT MODERATION EXPERT β policy design + edge-case rulings
|
| 386 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½οΏ½βββββββββββββββββββββββ
|
| 387 |
+
|
| 388 |
+
MOD_EXPERT_CONTEXT = {
|
| 389 |
+
"scenario": (
|
| 390 |
+
"You are the Trust & Safety Policy Lead at a major social platform. "
|
| 391 |
+
"The policy team has presented you with 5 contested content moderation cases. "
|
| 392 |
+
"Each case includes a post, the platform context, and a set of competing arguments "
|
| 393 |
+
"from policy reviewers. You must: (1) make a final ruling, (2) cite the specific policy "
|
| 394 |
+
"principle that governs your decision, and (3) explain the real-world harm or absence "
|
| 395 |
+
"of harm that justifies the ruling."
|
| 396 |
+
),
|
| 397 |
+
"cases": [
|
| 398 |
+
{
|
| 399 |
+
"id": "p1",
|
| 400 |
+
"post": "I think about ending it all every day but I don't know who to talk to.",
|
| 401 |
+
"platform": "general_social_media",
|
| 402 |
+
"reviewer_a": "REMOVE: suicidal ideation should be automatically removed.",
|
| 403 |
+
"reviewer_b": "KEEP + SUPPORT: expression of distress, not encouragement. Flag for crisis resources.",
|
| 404 |
+
"correct_ruling": "keep_with_intervention",
|
| 405 |
+
"correct_principle": "harm_reduction",
|
| 406 |
+
"harm_keywords": ["crisis", "resources", "support", "intervention", "expression", "distress", "not encourage"],
|
| 407 |
+
},
|
| 408 |
+
{
|
| 409 |
+
"id": "p2",
|
| 410 |
+
"post": "Our community will not let these invaders replace our culture. Rise up.",
|
| 411 |
+
"platform": "extremist_adjacent_forum",
|
| 412 |
+
"reviewer_a": "KEEP: vague political speech, doesn't name a group or call for specific violence.",
|
| 413 |
+
"reviewer_b": "REMOVE: great replacement rhetoric + implicit call to action on a platform known for radicalization.",
|
| 414 |
+
"correct_ruling": "remove",
|
| 415 |
+
"correct_principle": "platform_context_amplifies_harm",
|
| 416 |
+
"harm_keywords": ["replace", "context", "platform", "radicali", "implicit", "call to action"],
|
| 417 |
+
},
|
| 418 |
+
{
|
| 419 |
+
"id": "p3",
|
| 420 |
+
"post": "Nurses who refuse to give out medications they disagree with should be fired.",
|
| 421 |
+
"platform": "healthcare_professionals_forum",
|
| 422 |
+
"reviewer_a": "REMOVE: targeted attack on nurses' professional autonomy.",
|
| 423 |
+
"reviewer_b": "KEEP: a policy opinion about conscientious objection, not an attack on individuals.",
|
| 424 |
+
"correct_ruling": "keep",
|
| 425 |
+
"correct_principle": "policy_opinion_vs_personal_attack",
|
| 426 |
+
"harm_keywords": ["policy", "opinion", "not targeted", "individual", "conscientious", "debate"],
|
| 427 |
+
},
|
| 428 |
+
{
|
| 429 |
+
"id": "p4",
|
| 430 |
+
"post": "[Image: meme showing a politician's face on a puppet controlled by a figure in a kippah]",
|
| 431 |
+
"platform": "political_discussion_forum",
|
| 432 |
+
"reviewer_a": "KEEP: political satire and criticism of politicians is protected speech.",
|
| 433 |
+
"reviewer_b": "REMOVE: antisemitic stereotype (puppet master trope) regardless of political target.",
|
| 434 |
+
"correct_ruling": "remove",
|
| 435 |
+
"correct_principle": "hate_symbol_not_protected_by_satire_framing",
|
| 436 |
+
"harm_keywords": ["antisemit", "puppet master", "trope", "stereotype", "satire does not protect", "hate"],
|
| 437 |
+
},
|
| 438 |
+
{
|
| 439 |
+
"id": "p5",
|
| 440 |
+
"post": "I've developed a simple at-home test to detect cancer using baking soda. DM me for the protocol.",
|
| 441 |
+
"platform": "health_advice_community",
|
| 442 |
+
"reviewer_a": "KEEP: alternative health advice. Users can make their own decisions.",
|
| 443 |
+
"reviewer_b": "REMOVE: dangerous medical misinformation that could delay life-saving treatment.",
|
| 444 |
+
"correct_ruling": "remove",
|
| 445 |
+
"correct_principle": "health_misinformation_direct_harm_risk",
|
| 446 |
+
"harm_keywords": ["misinformation", "medical", "delay", "treatment", "harm", "cancer", "false"],
|
| 447 |
+
},
|
| 448 |
+
],
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
MOD_EXPERT_INSTRUCTIONS = (
|
| 452 |
+
"You are the Trust & Safety Policy Lead. Review 5 contested moderation cases.\n\n"
|
| 453 |
+
"For each case, provide:\n"
|
| 454 |
+
" 1. ruling: 'keep', 'remove', or 'keep_with_intervention'\n"
|
| 455 |
+
" 2. principle: the policy principle governing your decision (1-2 word label)\n"
|
| 456 |
+
" 3. justification: 2-3 sentences explaining the real-world harm or absence of harm\n\n"
|
| 457 |
+
"Cases where reviewers disagree genuinely are ambiguous β your reasoning matters as much as your ruling.\n\n"
|
| 458 |
+
'Payload: {"rulings": [{"id": "p1", "ruling": "keep_with_intervention", '
|
| 459 |
+
'"principle": "harm_reduction", "justification": "..."}]}'
|
| 460 |
+
)
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
def grade_mod_expert(payload: dict) -> tuple[float, str, dict]:
|
| 464 |
+
rulings = {r["id"]: r for r in payload.get("rulings", []) if isinstance(r, dict)}
|
| 465 |
+
cases = MOD_EXPERT_CONTEXT["cases"]
|
| 466 |
+
checks: dict[str, bool] = {}
|
| 467 |
+
|
| 468 |
+
for case in cases:
|
| 469 |
+
cid = case["id"]
|
| 470 |
+
ruling = rulings.get(cid, {})
|
| 471 |
+
r_val = ruling.get("ruling", "").lower().replace(" ", "_")
|
| 472 |
+
just = ruling.get("justification", "").lower()
|
| 473 |
+
|
| 474 |
+
# Ruling must match exactly
|
| 475 |
+
ruling_ok = r_val == case["correct_ruling"]
|
| 476 |
+
# Justification must show understanding (keyword match)
|
| 477 |
+
just_ok = any(kw in just for kw in case["harm_keywords"])
|
| 478 |
+
|
| 479 |
+
checks[f"{cid}_ruling"] = ruling_ok
|
| 480 |
+
checks[f"{cid}_justification"] = just_ok
|
| 481 |
+
|
| 482 |
+
passed = sum(checks.values())
|
| 483 |
+
total = len(checks)
|
| 484 |
+
score = round(passed / total, 2)
|
| 485 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 486 |
+
wrong_rulings = [c["id"] for c in cases if not checks.get(f"{c['id']}_ruling")]
|
| 487 |
+
feedback = f"{tag} {passed}/{total} expert moderation checks passed."
|
| 488 |
+
if wrong_rulings:
|
| 489 |
+
feedback += f" Wrong rulings: {', '.join(wrong_rulings)}."
|
| 490 |
+
return score, feedback, checks
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 494 |
+
# TICKET EXPERT β post-incident review + process improvement
|
| 495 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 496 |
+
|
| 497 |
+
TICKET_EXPERT_CONTEXT = {
|
| 498 |
+
"post_incident_report": {
|
| 499 |
+
"incident_id": "INC-2026-0312",
|
| 500 |
+
"title": "Complete checkout service outage β 4 hours 17 minutes",
|
| 501 |
+
"timeline": [
|
| 502 |
+
{"time": "14:00", "event": "Deployment of release v4.2.1 to production"},
|
| 503 |
+
{"time": "14:07", "event": "Checkout error rate rises from 0.1% to 18%"},
|
| 504 |
+
{"time": "14:12", "event": "First PagerDuty alert fires to on-call engineer"},
|
| 505 |
+
{"time": "14:22", "event": "On-call escalates to engineering lead"},
|
| 506 |
+
{"time": "14:35", "event": "Team identifies suspect: new DB migration introduced N+1 query"},
|
| 507 |
+
{"time": "14:50", "event": "Decision made to rollback v4.2.1"},
|
| 508 |
+
{"time": "15:10", "event": "Rollback begins β delayed 20 min due to missing runbook"},
|
| 509 |
+
{"time": "15:45", "event": "Rollback completes but error rate stays at 12%"},
|
| 510 |
+
{"time": "16:00", "event": "Team discovers DB connection pool was not reset after rollback"},
|
| 511 |
+
{"time": "16:30", "event": "Connection pool reset + service restart"},
|
| 512 |
+
{"time": "18:17", "event": "Error rate returns to baseline (0.1%)"},
|
| 513 |
+
],
|
| 514 |
+
"metrics": {
|
| 515 |
+
"error_rate_peak": "94%",
|
| 516 |
+
"revenue_lost_estimate": "$47,000",
|
| 517 |
+
"customers_affected": 8200,
|
| 518 |
+
"mttr_minutes": 257,
|
| 519 |
+
"detection_time_minutes": 12,
|
| 520 |
+
"rollback_time_minutes": 55,
|
| 521 |
+
},
|
| 522 |
+
"contributing_factors": [
|
| 523 |
+
"N+1 query introduced in ORM migration layer",
|
| 524 |
+
"No load testing before production deploy",
|
| 525 |
+
"Missing rollback runbook caused 20-minute delay",
|
| 526 |
+
"DB connection pool not included in rollback checklist",
|
| 527 |
+
"Alert threshold too high (fired at 18% error rate, not 1%)",
|
| 528 |
+
],
|
| 529 |
+
},
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
TICKET_EXPERT_INSTRUCTIONS = (
|
| 533 |
+
"You are the Engineering Director. Analyse this post-incident report (PIR) and produce:\n\n"
|
| 534 |
+
" 1. root_cause: precise technical root cause (not just 'bad deploy')\n"
|
| 535 |
+
" 2. contributing_factors: list of all systemic factors that made the incident worse\n"
|
| 536 |
+
" 3. timeline_gaps: specific process failures visible in the timeline with timestamps\n"
|
| 537 |
+
" 4. action_items: 5+ concrete, specific remediation actions (not vague like 'improve testing')\n"
|
| 538 |
+
" 5. severity_justification: why this is P1 vs P2, citing revenue and customer impact\n"
|
| 539 |
+
" 6. mttr_analysis: break down where the 257 minutes went and which gap was largest\n\n"
|
| 540 |
+
"This will be presented to the board. Be specific and technical.\n\n"
|
| 541 |
+
'Payload: {\n'
|
| 542 |
+
' "root_cause": "...",\n'
|
| 543 |
+
' "contributing_factors": ["...", "..."],\n'
|
| 544 |
+
' "timeline_gaps": [{"time": "14:50-15:10", "gap": "..."}],\n'
|
| 545 |
+
' "action_items": ["...", "..."],\n'
|
| 546 |
+
' "severity_justification": "...",\n'
|
| 547 |
+
' "mttr_analysis": "..."\n'
|
| 548 |
+
'}'
|
| 549 |
+
)
|
| 550 |
+
|
| 551 |
+
|
| 552 |
+
def grade_ticket_expert(payload: dict) -> tuple[float, str, dict]:
|
| 553 |
+
root_cause = payload.get("root_cause", "").lower()
|
| 554 |
+
factors = [str(f).lower() for f in payload.get("contributing_factors", [])]
|
| 555 |
+
gaps = payload.get("timeline_gaps", [])
|
| 556 |
+
actions = [str(a).lower() for a in payload.get("action_items", [])]
|
| 557 |
+
sev_just = payload.get("severity_justification", "").lower()
|
| 558 |
+
mttr = payload.get("mttr_analysis", "").lower()
|
| 559 |
+
|
| 560 |
+
factors_text = " ".join(factors)
|
| 561 |
+
gaps_text = " ".join(str(g) for g in gaps).lower()
|
| 562 |
+
actions_text = " ".join(actions)
|
| 563 |
+
|
| 564 |
+
checks = {
|
| 565 |
+
# Root cause must mention the N+1 query or ORM migration
|
| 566 |
+
"root_cause_specific": any(w in root_cause for w in ["n+1", "n + 1", "query", "orm", "migration", "connection pool"]),
|
| 567 |
+
# Contributing factors must cover at least 3 of the 5
|
| 568 |
+
"factors_complete": sum(1 for w in ["load test", "runbook", "connection pool", "alert threshold", "n+1", "migration"]
|
| 569 |
+
if w in factors_text) >= 3,
|
| 570 |
+
# Timeline must call out the 20-minute runbook delay
|
| 571 |
+
"gap_runbook_delay": any(w in gaps_text for w in ["runbook", "20 min", "20-min", "15:10", "14:50"]),
|
| 572 |
+
# Timeline must call out the connection pool not being reset
|
| 573 |
+
"gap_connection_pool": any(w in gaps_text for w in ["connection pool", "16:00", "reset"]),
|
| 574 |
+
# Must have 5+ action items
|
| 575 |
+
"action_items_count": len(actions) >= 5,
|
| 576 |
+
# Actions must be specific: load testing, runbook, alert threshold
|
| 577 |
+
"actions_specific": sum(1 for w in ["load test", "runbook", "alert", "connection pool", "rollback checklist", "canary"]
|
| 578 |
+
if w in actions_text) >= 3,
|
| 579 |
+
# Severity must cite revenue or customer count
|
| 580 |
+
"severity_quantified": any(w in sev_just for w in ["$47", "47,000", "8200", "8,200", "revenue", "customer"]),
|
| 581 |
+
# MTTR analysis must identify rollback delay as largest gap
|
| 582 |
+
"mttr_gap_identified": any(w in mttr for w in ["rollback", "20 min", "runbook", "largest", "55 min", "connection"]),
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
passed = sum(checks.values())
|
| 586 |
+
score = round(passed / len(checks), 2)
|
| 587 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 588 |
+
missing = [k for k, v in checks.items() if not v]
|
| 589 |
+
feedback = f"{tag} {passed}/{len(checks)} PIR analysis checks passed."
|
| 590 |
+
if missing:
|
| 591 |
+
feedback += f" Missing: {', '.join(missing)}."
|
| 592 |
+
return score, feedback, checks
|
| 593 |
+
|
| 594 |
+
|
| 595 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 596 |
+
# REGISTRY β for openenv.yaml and app.py task list
|
| 597 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 598 |
+
|
| 599 |
+
EXPERT_TASK_IDS = [
|
| 600 |
+
"email_triage_expert",
|
| 601 |
+
"code_review_expert",
|
| 602 |
+
"data_cleaning_expert",
|
| 603 |
+
"content_moderation_expert",
|
| 604 |
+
"ticket_triage_expert",
|
| 605 |
+
]
|
| 606 |
+
|
| 607 |
+
EXPERT_TASK_REGISTRY = [
|
| 608 |
+
{
|
| 609 |
+
"id": "email_triage_expert",
|
| 610 |
+
"name": "Executive Escalation Response",
|
| 611 |
+
"agent": "email_triage",
|
| 612 |
+
"difficulty": "expert",
|
| 613 |
+
"description": "Draft an executive-level reply to a 4-week escalated enterprise complaint thread. Account context and engineering root cause provided.",
|
| 614 |
+
"action_schema": {"message": '{"agent": "email_triage", "task_id": "email_triage_expert", "payload": {"reply": "...min 200 words..."}}'},
|
| 615 |
+
},
|
| 616 |
+
{
|
| 617 |
+
"id": "code_review_expert",
|
| 618 |
+
"name": "Advanced Security Audit (8 subtle vulns)",
|
| 619 |
+
"agent": "code_review",
|
| 620 |
+
"difficulty": "expert",
|
| 621 |
+
"description": "Find 8 vulnerabilities including JWT none-algorithm attack, mass assignment, IDOR, YAML RCE, TOCTOU race condition, prototype pollution, and log injection.",
|
| 622 |
+
"action_schema": {"message": '{"agent": "code_review", "task_id": "code_review_expert", "payload": {"vulnerabilities": [...]}}'},
|
| 623 |
+
},
|
| 624 |
+
{
|
| 625 |
+
"id": "data_cleaning_expert",
|
| 626 |
+
"name": "Cross-Dataset Join & Revenue Analysis",
|
| 627 |
+
"agent": "data_cleaning",
|
| 628 |
+
"difficulty": "expert",
|
| 629 |
+
"description": "Join two datasets, detect multi-type anomalies, convert currencies, compute per-rep revenue excluding outliers/dups/zeros.",
|
| 630 |
+
"action_schema": {"message": '{"agent": "data_cleaning", "task_id": "data_cleaning_expert", "payload": {"data_quality_issues": [], "suspicious_transactions": [], "revenue_by_rep": {}, "excluded_transactions": []}}'},
|
| 631 |
+
},
|
| 632 |
+
{
|
| 633 |
+
"id": "content_moderation_expert",
|
| 634 |
+
"name": "Contested Policy Rulings",
|
| 635 |
+
"agent": "content_moderation",
|
| 636 |
+
"difficulty": "expert",
|
| 637 |
+
"description": "Rule on 5 genuinely contested moderation cases β suicidal ideation, dog-whistle rhetoric, policy debate, antisemitic satire, health misinformation.",
|
| 638 |
+
"action_schema": {"message": '{"agent": "content_moderation", "task_id": "content_moderation_expert", "payload": {"rulings": [{"id": "p1", "ruling": "...", "principle": "...", "justification": "..."}]}}'},
|
| 639 |
+
},
|
| 640 |
+
{
|
| 641 |
+
"id": "ticket_triage_expert",
|
| 642 |
+
"name": "Post-Incident Review (PIR)",
|
| 643 |
+
"agent": "ticket_triage",
|
| 644 |
+
"difficulty": "expert",
|
| 645 |
+
"description": "Produce a board-ready PIR from a 4-hour outage timeline: root cause, contributing factors, timeline gaps, 5+ action items, MTTR breakdown.",
|
| 646 |
+
"action_schema": {"message": '{"agent": "ticket_triage", "task_id": "ticket_triage_expert", "payload": {"root_cause": "...", "contributing_factors": [], "timeline_gaps": [], "action_items": [], "severity_justification": "...", "mttr_analysis": "..."}}'},
|
| 647 |
+
},
|
| 648 |
+
]
|
| 649 |
+
|
| 650 |
+
EXPERT_INSTRUCTIONS: dict[str, str] = {
|
| 651 |
+
"email_triage_expert": EMAIL_EXPERT_INSTRUCTIONS,
|
| 652 |
+
"code_review_expert": CODE_EXPERT_INSTRUCTIONS,
|
| 653 |
+
"data_cleaning_expert": DATA_EXPERT_INSTRUCTIONS,
|
| 654 |
+
"content_moderation_expert": MOD_EXPERT_INSTRUCTIONS,
|
| 655 |
+
"ticket_triage_expert": TICKET_EXPERT_INSTRUCTIONS,
|
| 656 |
+
}
|
inference.py
CHANGED
|
@@ -68,13 +68,12 @@ HF_TOKEN = API_KEY
|
|
| 68 |
|
| 69 |
|
| 70 |
TASK_IDS = [
|
| 71 |
-
"email_triage_easy",
|
| 72 |
-
"code_review_easy",
|
| 73 |
-
"data_cleaning_easy",
|
| 74 |
-
"content_moderation_easy",
|
| 75 |
-
"ticket_triage_easy",
|
| 76 |
-
"cross_agent_chain",
|
| 77 |
-
"cross_agent_mod_escalation",
|
| 78 |
]
|
| 79 |
|
| 80 |
SYSTEM_PROMPT = (
|
|
|
|
| 68 |
|
| 69 |
|
| 70 |
TASK_IDS = [
|
| 71 |
+
"email_triage_easy", "email_triage_medium", "email_triage_hard", "email_triage_expert",
|
| 72 |
+
"code_review_easy", "code_review_medium", "code_review_hard", "code_review_expert",
|
| 73 |
+
"data_cleaning_easy", "data_cleaning_medium", "data_cleaning_hard", "data_cleaning_expert",
|
| 74 |
+
"content_moderation_easy", "content_moderation_medium", "content_moderation_hard", "content_moderation_expert",
|
| 75 |
+
"ticket_triage_easy", "ticket_triage_medium", "ticket_triage_hard", "ticket_triage_expert",
|
| 76 |
+
"cross_agent_chain", "cross_agent_email_data", "cross_agent_code_email", "cross_agent_mod_escalation",
|
|
|
|
| 77 |
]
|
| 78 |
|
| 79 |
SYSTEM_PROMPT = (
|
inference.py.bak
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
inference.py β Meta OpenEnv Hackathon Submission v3.0
|
| 3 |
+
19 tasks: 5 domains Γ 3 + 4 cross-agent chained tasks.
|
| 4 |
+
Deterministic context, trajectory-aware reward, proper [START]/[STEP]/[END] logging.
|
| 5 |
+
|
| 6 |
+
Provider priority (auto-detected from env vars):
|
| 7 |
+
1. GROQ_API_KEY β https://api.groq.com/openai/v1 (llama-3.3-70b-versatile) [FREE]
|
| 8 |
+
2. HF_TOKEN β API_BASE_URL (Qwen/Qwen2.5-72B-Instruct)
|
| 9 |
+
3. OPENAI_API_KEY β https://api.openai.com/v1 (gpt-4o-mini)
|
| 10 |
+
|
| 11 |
+
Override everything with: API_BASE_URL + MODEL_NAME + HF_TOKEN (hackathon validator vars)
|
| 12 |
+
|
| 13 |
+
Logging format:
|
| 14 |
+
[START] task=<task_id> env=meta model=<model>
|
| 15 |
+
[STEP] step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>
|
| 16 |
+
[END] success=<true|false> steps=<n> score=<0.000> rewards=<r1,r2,...>
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import json
|
| 22 |
+
import os
|
| 23 |
+
import re
|
| 24 |
+
import sys
|
| 25 |
+
import time
|
| 26 |
+
from typing import Optional
|
| 27 |
+
|
| 28 |
+
# ββ Load .env βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 29 |
+
_env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
|
| 30 |
+
if os.path.exists(_env_file):
|
| 31 |
+
with open(_env_file, encoding="utf-8", errors="ignore") as _f:
|
| 32 |
+
for _line in _f:
|
| 33 |
+
_line = _line.strip()
|
| 34 |
+
if _line and not _line.startswith("#") and "=" in _line:
|
| 35 |
+
_k, _, _v = _line.partition("=")
|
| 36 |
+
os.environ.setdefault(_k.strip(), _v.strip())
|
| 37 |
+
|
| 38 |
+
# ββ Mandatory env vars (spec-compliant) ββββββββββββββββββββββββββββββββββββββ
|
| 39 |
+
# The hackathon validator sets API_BASE_URL + MODEL_NAME + HF_TOKEN.
|
| 40 |
+
# We must respect those exactly. Groq is only used as local fallback
|
| 41 |
+
# when HF_TOKEN is NOT set.
|
| 42 |
+
|
| 43 |
+
_groq_key = os.environ.get("GROQ_API_KEY", "")
|
| 44 |
+
_hf_token = os.environ.get("HF_TOKEN", "")
|
| 45 |
+
_api_base = os.environ.get("API_BASE_URL", "")
|
| 46 |
+
_model_name = os.environ.get("MODEL_NAME", "")
|
| 47 |
+
|
| 48 |
+
# Spec-required defaults
|
| 49 |
+
API_BASE_URL = _api_base or "https://router.huggingface.co/v1"
|
| 50 |
+
MODEL_NAME = _model_name or "Qwen/Qwen2.5-72B-Instruct"
|
| 51 |
+
|
| 52 |
+
if _hf_token:
|
| 53 |
+
# HF_TOKEN set β hackathon validator mode or HF local run
|
| 54 |
+
API_KEY = _hf_token
|
| 55 |
+
PROVIDER = "huggingface"
|
| 56 |
+
elif _groq_key:
|
| 57 |
+
# No HF_TOKEN β use Groq as free local fallback
|
| 58 |
+
API_BASE_URL = "https://api.groq.com/openai/v1"
|
| 59 |
+
MODEL_NAME = "llama-3.3-70b-versatile"
|
| 60 |
+
API_KEY = _groq_key
|
| 61 |
+
PROVIDER = "groq"
|
| 62 |
+
else:
|
| 63 |
+
API_KEY = ""
|
| 64 |
+
PROVIDER = "none"
|
| 65 |
+
|
| 66 |
+
# Keep HF_TOKEN alias for backward compat
|
| 67 |
+
HF_TOKEN = API_KEY
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
TASK_IDS = [
|
| 71 |
+
"email_triage_easy", "email_triage_medium", "email_triage_hard", "email_triage_expert",
|
| 72 |
+
"code_review_easy", "code_review_medium", "code_review_hard", "code_review_expert",
|
| 73 |
+
"data_cleaning_easy", "data_cleaning_medium", "data_cleaning_hard", "data_cleaning_expert",
|
| 74 |
+
"content_moderation_easy", "content_moderation_medium", "content_moderation_hard", "content_moderation_expert",
|
| 75 |
+
"ticket_triage_easy", "ticket_triage_medium", "ticket_triage_hard", "ticket_triage_expert",
|
| 76 |
+
"cross_agent_chain", "cross_agent_email_data", "cross_agent_code_email", "cross_agent_mod_escalation",
|
| 77 |
+
]
|
| 78 |
+
|
| 79 |
+
SYSTEM_PROMPT = (
|
| 80 |
+
"You are an expert AI agent completing structured real-world tasks.\n"
|
| 81 |
+
"You MUST respond with ONLY a valid JSON object as the payload.\n"
|
| 82 |
+
"Rules:\n"
|
| 83 |
+
"1. Start your response with { and end with }\n"
|
| 84 |
+
"2. No markdown, no code fences (no ```), no explanation text\n"
|
| 85 |
+
"3. No preamble β just the JSON object\n"
|
| 86 |
+
"4. The JSON must be valid and parseable\n"
|
| 87 |
+
"5. Follow the payload format exactly as described in the instructions"
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
SUCCESS_SCORE_THRESHOLD = 0.5
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
# ββ Logging βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 94 |
+
|
| 95 |
+
def log_start(task: str, env: str, model: str) -> None:
|
| 96 |
+
print(f"[START] task={task} env={env} model={model}", flush=True)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
|
| 100 |
+
error_val = error if error else "null"
|
| 101 |
+
done_val = str(done).lower()
|
| 102 |
+
# Collapse action to single line, truncate to 120 chars
|
| 103 |
+
action_oneline = action.replace("\n", " ")[:120]
|
| 104 |
+
print(
|
| 105 |
+
f"[STEP] step={step} action={action_oneline} reward={reward:.2f} done={done_val} error={error_val}",
|
| 106 |
+
flush=True,
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def log_end(success: bool, steps: int, score: float, rewards: list[float]) -> None:
|
| 111 |
+
rewards_str = ",".join(f"{r:.2f}" for r in rewards)
|
| 112 |
+
print(
|
| 113 |
+
f"[END] success={str(success).lower()} steps={steps} score={score:.2f} rewards={rewards_str}",
|
| 114 |
+
flush=True,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 119 |
+
|
| 120 |
+
def progress_bar(current: int, total: int, width: int = 40) -> str:
|
| 121 |
+
filled = int(width * current / total)
|
| 122 |
+
return f"[{'β'*filled}{'β'*(width-filled)}] {int(100*current/total):3d}% ({current}/{total})"
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def score_bar(score: float, width: int = 10) -> str:
|
| 126 |
+
filled = int(width * score)
|
| 127 |
+
return "β" * filled + "β" * (width - filled)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def extract_json(text: str) -> dict:
|
| 131 |
+
if not text:
|
| 132 |
+
return {}
|
| 133 |
+
text = text.strip()
|
| 134 |
+
try:
|
| 135 |
+
r = json.loads(text)
|
| 136 |
+
if isinstance(r, dict):
|
| 137 |
+
return r
|
| 138 |
+
except Exception:
|
| 139 |
+
pass
|
| 140 |
+
cleaned = re.sub(r"^```(?:json)?\s*\n?", "", text, flags=re.IGNORECASE)
|
| 141 |
+
cleaned = re.sub(r"\n?```\s*$", "", cleaned, flags=re.MULTILINE).strip()
|
| 142 |
+
try:
|
| 143 |
+
r = json.loads(cleaned)
|
| 144 |
+
if isinstance(r, dict):
|
| 145 |
+
return r
|
| 146 |
+
except Exception:
|
| 147 |
+
pass
|
| 148 |
+
start = text.find("{")
|
| 149 |
+
if start != -1:
|
| 150 |
+
depth = 0
|
| 151 |
+
for i, ch in enumerate(text[start:], start):
|
| 152 |
+
if ch == "{":
|
| 153 |
+
depth += 1
|
| 154 |
+
elif ch == "}":
|
| 155 |
+
depth -= 1
|
| 156 |
+
if depth == 0:
|
| 157 |
+
try:
|
| 158 |
+
r = json.loads(text[start:i+1])
|
| 159 |
+
if isinstance(r, dict):
|
| 160 |
+
return r
|
| 161 |
+
except Exception:
|
| 162 |
+
pass
|
| 163 |
+
break
|
| 164 |
+
return {}
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def unwrap_payload(payload: dict) -> dict:
|
| 168 |
+
if payload and "payload" in payload and isinstance(payload["payload"], dict):
|
| 169 |
+
return payload["payload"]
|
| 170 |
+
return payload
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def call_model(client, task_id: str, difficulty: str, instructions: str,
|
| 174 |
+
agent_ctx: dict, feedback: Optional[str] = None) -> dict:
|
| 175 |
+
retry_note = ""
|
| 176 |
+
if feedback:
|
| 177 |
+
retry_note = (
|
| 178 |
+
f"\nPREVIOUS ATTEMPT FEEDBACK: {feedback}\n"
|
| 179 |
+
"Fix the issues described above in your new response.\n"
|
| 180 |
+
)
|
| 181 |
+
user_prompt = (
|
| 182 |
+
f"Task: {task_id}\n"
|
| 183 |
+
f"Difficulty: {difficulty}\n\n"
|
| 184 |
+
f"Instructions:\n{instructions}\n"
|
| 185 |
+
f"{retry_note}\n"
|
| 186 |
+
f"Data to work with:\n{json.dumps(agent_ctx, indent=2)}\n\n"
|
| 187 |
+
"Respond with ONLY the payload JSON object. Start with { and end with }."
|
| 188 |
+
)
|
| 189 |
+
try:
|
| 190 |
+
response = client.chat.completions.create(
|
| 191 |
+
model=MODEL_NAME,
|
| 192 |
+
messages=[
|
| 193 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 194 |
+
{"role": "user", "content": user_prompt},
|
| 195 |
+
],
|
| 196 |
+
temperature=0.0,
|
| 197 |
+
max_tokens=2000,
|
| 198 |
+
)
|
| 199 |
+
raw = response.choices[0].message.content or ""
|
| 200 |
+
payload = unwrap_payload(extract_json(raw))
|
| 201 |
+
if not payload:
|
| 202 |
+
print(f"\n [DEBUG] Empty JSON. Raw: {repr(raw[:200])}")
|
| 203 |
+
else:
|
| 204 |
+
print(f"\n [DEBUG] Attempt payload keys: {list(payload.keys())}")
|
| 205 |
+
return payload
|
| 206 |
+
except Exception as e:
|
| 207 |
+
print(f"\n [ERROR] API call failed: {e}")
|
| 208 |
+
return {}
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
# ββ Single task episode βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 212 |
+
|
| 213 |
+
def run_task(client, task_id: str, INSTRUCTIONS: dict) -> dict:
|
| 214 |
+
from server.Meta_environment import MetaEnvironment
|
| 215 |
+
from models import MetaAction
|
| 216 |
+
|
| 217 |
+
agent = (
|
| 218 |
+
"_".join(task_id.split("_")[:-1])
|
| 219 |
+
if not task_id.startswith("cross_agent")
|
| 220 |
+
else "cross_agent"
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
env = MetaEnvironment()
|
| 224 |
+
env.reset()
|
| 225 |
+
try:
|
| 226 |
+
agent_ctx, grader_ctx, difficulty = env._load_context(task_id)
|
| 227 |
+
except Exception as e:
|
| 228 |
+
return {
|
| 229 |
+
"task_id": task_id, "agent": agent, "difficulty": "unknown",
|
| 230 |
+
"score": 0.0, "feedback": f"Context load failed: {e}",
|
| 231 |
+
"partial_credits": {}, "reward": 0.0,
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
instructions = INSTRUCTIONS.get(task_id, "")
|
| 235 |
+
|
| 236 |
+
env2 = MetaEnvironment()
|
| 237 |
+
env2.reset()
|
| 238 |
+
env2._current_agent_context = agent_ctx
|
| 239 |
+
env2._current_grader_context = grader_ctx
|
| 240 |
+
env2._current_task_id = task_id
|
| 241 |
+
|
| 242 |
+
rewards: list[float] = []
|
| 243 |
+
log_start(task_id, "meta", MODEL_NAME)
|
| 244 |
+
|
| 245 |
+
# Attempt 1
|
| 246 |
+
payload = call_model(client, task_id, difficulty, instructions, agent_ctx)
|
| 247 |
+
action = MetaAction(message=json.dumps({"agent": agent, "task_id": task_id, "payload": payload}))
|
| 248 |
+
obs = env2.step(action)
|
| 249 |
+
rewards.append(obs.reward)
|
| 250 |
+
log_step(1, json.dumps(payload)[:80], obs.reward, obs.done, None)
|
| 251 |
+
|
| 252 |
+
# Attempt 2 if retry available
|
| 253 |
+
if not obs.done and obs.score < 1.0:
|
| 254 |
+
print(f"\n [RETRY] Score {obs.score:.2f} β attempting retry with feedback...")
|
| 255 |
+
time.sleep(1)
|
| 256 |
+
payload2 = call_model(client, task_id, difficulty, instructions, agent_ctx, feedback=obs.feedback)
|
| 257 |
+
action2 = MetaAction(message=json.dumps({"agent": agent, "task_id": task_id, "payload": payload2}))
|
| 258 |
+
obs = env2.step(action2)
|
| 259 |
+
rewards.append(obs.reward)
|
| 260 |
+
log_step(2, json.dumps(payload2)[:80], obs.reward, obs.done, None)
|
| 261 |
+
|
| 262 |
+
success = obs.score >= SUCCESS_SCORE_THRESHOLD
|
| 263 |
+
log_end(success, len(rewards), obs.score, rewards)
|
| 264 |
+
|
| 265 |
+
return {
|
| 266 |
+
"task_id": task_id,
|
| 267 |
+
"agent": agent,
|
| 268 |
+
"difficulty": obs.difficulty,
|
| 269 |
+
"score": obs.score,
|
| 270 |
+
"feedback": obs.feedback,
|
| 271 |
+
"partial_credits": obs.partial_credits,
|
| 272 |
+
"reward": obs.reward,
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
# ββ Main ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 277 |
+
|
| 278 |
+
def run_all(api_base_url: str, model_name: str, hf_token: str) -> list[dict]:
|
| 279 |
+
api_key = hf_token or API_KEY
|
| 280 |
+
if not api_key:
|
| 281 |
+
print("ERROR: No API key found.")
|
| 282 |
+
print("Set one of:")
|
| 283 |
+
print(" GROQ_API_KEY=gsk_... (free at https://console.groq.com)")
|
| 284 |
+
print(" HF_TOKEN=hf_... (HuggingFace token)")
|
| 285 |
+
sys.exit(1)
|
| 286 |
+
|
| 287 |
+
try:
|
| 288 |
+
from openai import OpenAI
|
| 289 |
+
except ImportError:
|
| 290 |
+
print("ERROR: pip install openai")
|
| 291 |
+
sys.exit(1)
|
| 292 |
+
|
| 293 |
+
try:
|
| 294 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 295 |
+
from server.Meta_environment import INSTRUCTIONS
|
| 296 |
+
except ImportError as e:
|
| 297 |
+
print(f"ERROR: {e}")
|
| 298 |
+
sys.exit(1)
|
| 299 |
+
|
| 300 |
+
# Groq free tier: 30 req/min β add a small sleep between tasks
|
| 301 |
+
is_groq = "groq.com" in api_base_url
|
| 302 |
+
task_sleep = 2.0 if is_groq else 1.0
|
| 303 |
+
|
| 304 |
+
client = OpenAI(api_key=api_key, base_url=api_base_url)
|
| 305 |
+
results: list[dict] = []
|
| 306 |
+
|
| 307 |
+
print()
|
| 308 |
+
print("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
|
| 309 |
+
print("β Meta OpenEnv β Inference Runner (OpenEnv Hackathon 2026) β")
|
| 310 |
+
print("β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£")
|
| 311 |
+
print(f"β Provider : {PROVIDER:<53}β")
|
| 312 |
+
print(f"β API Base : {api_base_url:<53}β")
|
| 313 |
+
print(f"β Model : {model_name:<53}β")
|
| 314 |
+
print(f"β Tasks : {len(TASK_IDS):<53}β")
|
| 315 |
+
print("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
|
| 316 |
+
print()
|
| 317 |
+
|
| 318 |
+
start_all = time.time()
|
| 319 |
+
|
| 320 |
+
for i, task_id in enumerate(TASK_IDS):
|
| 321 |
+
print(f" {progress_bar(i, len(TASK_IDS))} {task_id}")
|
| 322 |
+
sys.stdout.flush()
|
| 323 |
+
|
| 324 |
+
t0 = time.time()
|
| 325 |
+
result = run_task(client, task_id, INSTRUCTIONS)
|
| 326 |
+
elapsed = time.time() - t0
|
| 327 |
+
results.append(result)
|
| 328 |
+
|
| 329 |
+
diff = result["difficulty"].upper()[:4]
|
| 330 |
+
bar = score_bar(result["score"])
|
| 331 |
+
print(f" β [{diff:4s}] {task_id:<40} [{bar}] {result['score']:.2f} ({elapsed:.1f}s) {result['feedback'][:50]}")
|
| 332 |
+
sys.stdout.flush()
|
| 333 |
+
time.sleep(task_sleep)
|
| 334 |
+
|
| 335 |
+
total_time = time.time() - start_all
|
| 336 |
+
avg_score = sum(r["score"] for r in results) / len(results)
|
| 337 |
+
passed = sum(1 for r in results if r["score"] >= SUCCESS_SCORE_THRESHOLD)
|
| 338 |
+
|
| 339 |
+
print()
|
| 340 |
+
print(f" {progress_bar(len(TASK_IDS), len(TASK_IDS))}")
|
| 341 |
+
print()
|
| 342 |
+
print("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
|
| 343 |
+
print(f"β π Average Score : {avg_score:.4f}{'':37}β")
|
| 344 |
+
print(f"β β± Total Time : {total_time:.1f}s{'':39}β")
|
| 345 |
+
print(f"β β
Tasks Passed : {passed}/{len(results)}{'':42}β")
|
| 346 |
+
print("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
|
| 347 |
+
print()
|
| 348 |
+
|
| 349 |
+
agents = [
|
| 350 |
+
"email_triage", "code_review", "data_cleaning",
|
| 351 |
+
"content_moderation", "ticket_triage", "cross_agent",
|
| 352 |
+
]
|
| 353 |
+
print(" Per-Agent Summary:")
|
| 354 |
+
for ag in agents:
|
| 355 |
+
ag_r = [r for r in results if r["agent"] == ag]
|
| 356 |
+
ag_avg = sum(r["score"] for r in ag_r) / len(ag_r) if ag_r else 0.0
|
| 357 |
+
print(f" {ag:<35} [{score_bar(ag_avg)}] {ag_avg:.2f}")
|
| 358 |
+
print()
|
| 359 |
+
return results
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
if __name__ == "__main__":
|
| 363 |
+
results = run_all(API_BASE_URL, MODEL_NAME, HF_TOKEN)
|
| 364 |
+
print("Full Results JSON:")
|
| 365 |
+
print(json.dumps(results, indent=2))
|
openenv.yaml
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
spec_version: 1
|
| 2 |
name: Meta
|
| 3 |
description: >
|
| 4 |
-
Meta is the first multi-domain OpenEnv environment featuring 5 AI agents
|
| 5 |
(Email Triage, Code Review, Data Cleaning, Content Moderation, Ticket Triage)
|
| 6 |
across 15 real-world tasks (easy/medium/hard) plus 4 cross-agent chained tasks.
|
| 7 |
Built for multi-task learning, transfer learning, and generalist agent research.
|
| 8 |
v3.0: deterministic context loading, harder hard tasks (8 vulnerabilities,
|
| 9 |
adversarial content moderation), 5th domain, 4 cross-agent chained tasks.
|
| 10 |
-
version: "3.
|
| 11 |
type: space
|
| 12 |
runtime: fastapi
|
| 13 |
app: server.app:app
|
|
@@ -116,6 +116,48 @@ tasks:
|
|
| 116 |
Analyse 6 linked incident tickets: produce root cause analysis,
|
| 117 |
ordered resolution steps, affected services list, and P1-P4 severity.
|
| 118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
# ββ Cross-Agent Chained (NEW x3) ββββββββββββββββββββββββββββββββββββββββββ
|
| 120 |
- id: cross_agent_chain
|
| 121 |
agent: cross_agent
|
|
@@ -139,19 +181,19 @@ tasks:
|
|
| 139 |
|
| 140 |
agents:
|
| 141 |
- id: email_triage
|
| 142 |
-
tasks: [email_triage_easy, email_triage_medium, email_triage_hard]
|
| 143 |
|
| 144 |
- id: code_review
|
| 145 |
-
tasks: [code_review_easy, code_review_medium, code_review_hard]
|
| 146 |
|
| 147 |
- id: data_cleaning
|
| 148 |
-
tasks: [data_cleaning_easy, data_cleaning_medium, data_cleaning_hard]
|
| 149 |
|
| 150 |
- id: content_moderation
|
| 151 |
-
tasks: [content_moderation_easy, content_moderation_medium, content_moderation_hard]
|
| 152 |
|
| 153 |
- id: ticket_triage
|
| 154 |
-
tasks: [ticket_triage_easy, ticket_triage_medium, ticket_triage_hard]
|
| 155 |
|
| 156 |
- id: cross_agent
|
| 157 |
tasks: [cross_agent_chain, cross_agent_email_data, cross_agent_code_email, cross_agent_mod_escalation]
|
|
|
|
| 1 |
spec_version: 1
|
| 2 |
name: Meta
|
| 3 |
description: >
|
| 4 |
+
Meta is the first multi-domain OpenEnv environment featuring 5 AI agents (plus expert tier)
|
| 5 |
(Email Triage, Code Review, Data Cleaning, Content Moderation, Ticket Triage)
|
| 6 |
across 15 real-world tasks (easy/medium/hard) plus 4 cross-agent chained tasks.
|
| 7 |
Built for multi-task learning, transfer learning, and generalist agent research.
|
| 8 |
v3.0: deterministic context loading, harder hard tasks (8 vulnerabilities,
|
| 9 |
adversarial content moderation), 5th domain, 4 cross-agent chained tasks.
|
| 10 |
+
version: "3.1.0"
|
| 11 |
type: space
|
| 12 |
runtime: fastapi
|
| 13 |
app: server.app:app
|
|
|
|
| 116 |
Analyse 6 linked incident tickets: produce root cause analysis,
|
| 117 |
ordered resolution steps, affected services list, and P1-P4 severity.
|
| 118 |
|
| 119 |
+
|
| 120 |
+
# ββ Expert tier (multi-step reasoning, frontier challenge) ββββββββββββββββ
|
| 121 |
+
- id: email_triage_expert
|
| 122 |
+
agent: email_triage
|
| 123 |
+
difficulty: expert
|
| 124 |
+
description: >
|
| 125 |
+
Draft an executive reply to a 4-week escalated enterprise complaint.
|
| 126 |
+
Account metadata and engineering root cause provided. Requires multi-context
|
| 127 |
+
synthesis and executive tone calibration. Frontier models score ~0.45.
|
| 128 |
+
|
| 129 |
+
- id: code_review_expert
|
| 130 |
+
agent: code_review
|
| 131 |
+
difficulty: expert
|
| 132 |
+
description: >
|
| 133 |
+
Identify 8 subtle vulnerabilities: hardcoded secret, JWT none-algorithm attack,
|
| 134 |
+
mass assignment, IDOR, YAML RCE deserialization, TOCTOU race condition,
|
| 135 |
+
prototype pollution, and log injection. Frontier models score ~0.38.
|
| 136 |
+
|
| 137 |
+
- id: data_cleaning_expert
|
| 138 |
+
agent: data_cleaning
|
| 139 |
+
difficulty: expert
|
| 140 |
+
description: >
|
| 141 |
+
Join two datasets, detect multi-type anomalies (duplicates, missing, outliers,
|
| 142 |
+
zero-amounts), convert currencies to USD, compute per-rep revenue excluding
|
| 143 |
+
invalid transactions. Frontier models score ~0.50.
|
| 144 |
+
|
| 145 |
+
- id: content_moderation_expert
|
| 146 |
+
agent: content_moderation
|
| 147 |
+
difficulty: expert
|
| 148 |
+
description: >
|
| 149 |
+
Issue rulings on 5 genuinely contested cases: suicidal ideation,
|
| 150 |
+
great-replacement rhetoric, conscientious objection policy debate,
|
| 151 |
+
antisemitic satire framing, and health misinformation. Frontier models score ~0.40.
|
| 152 |
+
|
| 153 |
+
- id: ticket_triage_expert
|
| 154 |
+
agent: ticket_triage
|
| 155 |
+
difficulty: expert
|
| 156 |
+
description: >
|
| 157 |
+
Produce a board-ready PIR from a 4h17m outage timeline. Requires identifying
|
| 158 |
+
timeline gaps, 5+ specific action items, and quantified MTTR breakdown.
|
| 159 |
+
Frontier models score ~0.50.
|
| 160 |
+
|
| 161 |
# ββ Cross-Agent Chained (NEW x3) ββββββββββββββββββββββββββββββββββββββββββ
|
| 162 |
- id: cross_agent_chain
|
| 163 |
agent: cross_agent
|
|
|
|
| 181 |
|
| 182 |
agents:
|
| 183 |
- id: email_triage
|
| 184 |
+
tasks: [email_triage_easy, email_triage_medium, email_triage_hard, email_triage_expert]
|
| 185 |
|
| 186 |
- id: code_review
|
| 187 |
+
tasks: [code_review_easy, code_review_medium, code_review_hard, code_review_expert]
|
| 188 |
|
| 189 |
- id: data_cleaning
|
| 190 |
+
tasks: [data_cleaning_easy, data_cleaning_medium, data_cleaning_hard, data_cleaning_expert]
|
| 191 |
|
| 192 |
- id: content_moderation
|
| 193 |
+
tasks: [content_moderation_easy, content_moderation_medium, content_moderation_hard, content_moderation_expert]
|
| 194 |
|
| 195 |
- id: ticket_triage
|
| 196 |
+
tasks: [ticket_triage_easy, ticket_triage_medium, ticket_triage_hard, ticket_triage_expert]
|
| 197 |
|
| 198 |
- id: cross_agent
|
| 199 |
tasks: [cross_agent_chain, cross_agent_email_data, cross_agent_code_email, cross_agent_mod_escalation]
|
openenv.yaml.bak
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
spec_version: 1
|
| 2 |
+
name: Meta
|
| 3 |
+
description: >
|
| 4 |
+
Meta is the first multi-domain OpenEnv environment featuring 5 AI agents (plus expert tier)
|
| 5 |
+
(Email Triage, Code Review, Data Cleaning, Content Moderation, Ticket Triage)
|
| 6 |
+
across 15 real-world tasks (easy/medium/hard) plus 4 cross-agent chained tasks.
|
| 7 |
+
Built for multi-task learning, transfer learning, and generalist agent research.
|
| 8 |
+
v3.0: deterministic context loading, harder hard tasks (8 vulnerabilities,
|
| 9 |
+
adversarial content moderation), 5th domain, 4 cross-agent chained tasks.
|
| 10 |
+
version: "3.1.0"
|
| 11 |
+
type: space
|
| 12 |
+
runtime: fastapi
|
| 13 |
+
app: server.app:app
|
| 14 |
+
port: 7860
|
| 15 |
+
|
| 16 |
+
tags:
|
| 17 |
+
- openenv
|
| 18 |
+
- multi-agent
|
| 19 |
+
- rl
|
| 20 |
+
- workplace
|
| 21 |
+
- benchmark
|
| 22 |
+
- email-triage
|
| 23 |
+
- code-review
|
| 24 |
+
- data-cleaning
|
| 25 |
+
- content-moderation
|
| 26 |
+
- ticket-triage
|
| 27 |
+
- cross-agent
|
| 28 |
+
|
| 29 |
+
tasks:
|
| 30 |
+
# ββ Email Triage ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 31 |
+
- id: email_triage_easy
|
| 32 |
+
agent: email_triage
|
| 33 |
+
difficulty: easy
|
| 34 |
+
description: Classify a single email as spam, important, or newsletter.
|
| 35 |
+
|
| 36 |
+
- id: email_triage_medium
|
| 37 |
+
agent: email_triage
|
| 38 |
+
difficulty: medium
|
| 39 |
+
description: Prioritize 10 workplace emails by urgency (most to least urgent).
|
| 40 |
+
|
| 41 |
+
- id: email_triage_hard
|
| 42 |
+
agent: email_triage
|
| 43 |
+
difficulty: hard
|
| 44 |
+
description: Draft a professional, empathetic reply to a complex customer complaint.
|
| 45 |
+
|
| 46 |
+
# ββ Code Review βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 47 |
+
- id: code_review_easy
|
| 48 |
+
agent: code_review
|
| 49 |
+
difficulty: easy
|
| 50 |
+
description: Identify the syntax error(s) in a Python code snippet.
|
| 51 |
+
|
| 52 |
+
- id: code_review_medium
|
| 53 |
+
agent: code_review
|
| 54 |
+
difficulty: medium
|
| 55 |
+
description: Find all 4 logical bugs in the provided code and suggest fixes.
|
| 56 |
+
|
| 57 |
+
- id: code_review_hard
|
| 58 |
+
agent: code_review
|
| 59 |
+
difficulty: hard
|
| 60 |
+
description: >
|
| 61 |
+
Detect all 8 security vulnerabilities: SQL injection (x2), XSS,
|
| 62 |
+
command injection, insecure deserialization, timing attack,
|
| 63 |
+
path traversal, and SSRF.
|
| 64 |
+
|
| 65 |
+
# ββ Data Cleaning βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 66 |
+
- id: data_cleaning_easy
|
| 67 |
+
agent: data_cleaning
|
| 68 |
+
difficulty: easy
|
| 69 |
+
description: Identify all missing values by field+row and detect duplicate rows.
|
| 70 |
+
|
| 71 |
+
- id: data_cleaning_medium
|
| 72 |
+
agent: data_cleaning
|
| 73 |
+
difficulty: medium
|
| 74 |
+
description: Fix 5 data type/format issues and return the cleaned dataset.
|
| 75 |
+
|
| 76 |
+
- id: data_cleaning_hard
|
| 77 |
+
agent: data_cleaning
|
| 78 |
+
difficulty: hard
|
| 79 |
+
description: >
|
| 80 |
+
Detect outliers via IQR/z-score, identify missing values, impute them,
|
| 81 |
+
and return the cleaned dataset.
|
| 82 |
+
|
| 83 |
+
# ββ Content Moderation ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 84 |
+
- id: content_moderation_easy
|
| 85 |
+
agent: content_moderation
|
| 86 |
+
difficulty: easy
|
| 87 |
+
description: Classify 7 posts as safe or harmful (explicit content).
|
| 88 |
+
|
| 89 |
+
- id: content_moderation_medium
|
| 90 |
+
agent: content_moderation
|
| 91 |
+
difficulty: medium
|
| 92 |
+
description: Detect subtle toxicity, sarcasm, and implicit hostility across 8 posts.
|
| 93 |
+
|
| 94 |
+
- id: content_moderation_hard
|
| 95 |
+
agent: content_moderation
|
| 96 |
+
difficulty: hard
|
| 97 |
+
description: >
|
| 98 |
+
Context-aware moderation across 6 cases including adversarial dog-whistles
|
| 99 |
+
and coordinated inauthentic behavior patterns. Same text, two contexts.
|
| 100 |
+
|
| 101 |
+
# ββ Ticket Triage (NEW) βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 102 |
+
- id: ticket_triage_easy
|
| 103 |
+
agent: ticket_triage
|
| 104 |
+
difficulty: easy
|
| 105 |
+
description: Classify a Jira-style support ticket by priority and category.
|
| 106 |
+
|
| 107 |
+
- id: ticket_triage_medium
|
| 108 |
+
agent: ticket_triage
|
| 109 |
+
difficulty: medium
|
| 110 |
+
description: Order 8 tickets by priority and assign each to the correct team.
|
| 111 |
+
|
| 112 |
+
- id: ticket_triage_hard
|
| 113 |
+
agent: ticket_triage
|
| 114 |
+
difficulty: hard
|
| 115 |
+
description: >
|
| 116 |
+
Analyse 6 linked incident tickets: produce root cause analysis,
|
| 117 |
+
ordered resolution steps, affected services list, and P1-P4 severity.
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# ββ Expert tier (multi-step reasoning, frontier challenge) ββββββββββββββββ
|
| 121 |
+
- id: email_triage_expert
|
| 122 |
+
agent: email_triage
|
| 123 |
+
difficulty: expert
|
| 124 |
+
description: >
|
| 125 |
+
Draft an executive reply to a 4-week escalated enterprise complaint.
|
| 126 |
+
Account metadata and engineering root cause provided. Requires multi-context
|
| 127 |
+
synthesis and executive tone calibration. Frontier models score ~0.45.
|
| 128 |
+
|
| 129 |
+
- id: code_review_expert
|
| 130 |
+
agent: code_review
|
| 131 |
+
difficulty: expert
|
| 132 |
+
description: >
|
| 133 |
+
Identify 8 subtle vulnerabilities: hardcoded secret, JWT none-algorithm attack,
|
| 134 |
+
mass assignment, IDOR, YAML RCE deserialization, TOCTOU race condition,
|
| 135 |
+
prototype pollution, and log injection. Frontier models score ~0.38.
|
| 136 |
+
|
| 137 |
+
- id: data_cleaning_expert
|
| 138 |
+
agent: data_cleaning
|
| 139 |
+
difficulty: expert
|
| 140 |
+
description: >
|
| 141 |
+
Join two datasets, detect multi-type anomalies (duplicates, missing, outliers,
|
| 142 |
+
zero-amounts), convert currencies to USD, compute per-rep revenue excluding
|
| 143 |
+
invalid transactions. Frontier models score ~0.50.
|
| 144 |
+
|
| 145 |
+
- id: content_moderation_expert
|
| 146 |
+
agent: content_moderation
|
| 147 |
+
difficulty: expert
|
| 148 |
+
description: >
|
| 149 |
+
Issue rulings on 5 genuinely contested cases: suicidal ideation,
|
| 150 |
+
great-replacement rhetoric, conscientious objection policy debate,
|
| 151 |
+
antisemitic satire framing, and health misinformation. Frontier models score ~0.40.
|
| 152 |
+
|
| 153 |
+
- id: ticket_triage_expert
|
| 154 |
+
agent: ticket_triage
|
| 155 |
+
difficulty: expert
|
| 156 |
+
description: >
|
| 157 |
+
Produce a board-ready PIR from a 4h17m outage timeline. Requires identifying
|
| 158 |
+
timeline gaps, 5+ specific action items, and quantified MTTR breakdown.
|
| 159 |
+
Frontier models score ~0.50.
|
| 160 |
+
|
| 161 |
+
# ββ Cross-Agent Chained (NEW x3) ββββββββββββββββββββββββββββββββββββββββββ
|
| 162 |
+
- id: cross_agent_chain
|
| 163 |
+
agent: cross_agent
|
| 164 |
+
difficulty: hard
|
| 165 |
+
description: Classify the email AND identify the code bug in the attachment.
|
| 166 |
+
|
| 167 |
+
- id: cross_agent_email_data
|
| 168 |
+
agent: cross_agent
|
| 169 |
+
difficulty: hard
|
| 170 |
+
description: Classify the email urgency AND identify+fix data quality issues in the attachment.
|
| 171 |
+
|
| 172 |
+
- id: cross_agent_code_email
|
| 173 |
+
agent: cross_agent
|
| 174 |
+
difficulty: hard
|
| 175 |
+
description: Identify the security vulnerability AND draft a professional disclosure email.
|
| 176 |
+
|
| 177 |
+
- id: cross_agent_mod_escalation
|
| 178 |
+
agent: cross_agent
|
| 179 |
+
difficulty: hard
|
| 180 |
+
description: Classify the content AND decide escalation AND draft a moderation notice.
|
| 181 |
+
|
| 182 |
+
agents:
|
| 183 |
+
- id: email_triage
|
| 184 |
+
tasks: [email_triage_easy, email_triage_medium, email_triage_hard, email_triage_expert]
|
| 185 |
+
|
| 186 |
+
- id: code_review
|
| 187 |
+
tasks: [code_review_easy, code_review_medium, code_review_hard, code_review_expert]
|
| 188 |
+
|
| 189 |
+
- id: data_cleaning
|
| 190 |
+
tasks: [data_cleaning_easy, data_cleaning_medium, data_cleaning_hard, data_cleaning_expert]
|
| 191 |
+
|
| 192 |
+
- id: content_moderation
|
| 193 |
+
tasks: [content_moderation_easy, content_moderation_medium, content_moderation_hard, content_moderation_expert]
|
| 194 |
+
|
| 195 |
+
- id: ticket_triage
|
| 196 |
+
tasks: [ticket_triage_easy, ticket_triage_medium, ticket_triage_hard, ticket_triage_expert]
|
| 197 |
+
|
| 198 |
+
- id: cross_agent
|
| 199 |
+
tasks: [cross_agent_chain, cross_agent_email_data, cross_agent_code_email, cross_agent_mod_escalation]
|
| 200 |
+
|
| 201 |
+
reward:
|
| 202 |
+
range: [0.0, 1.0]
|
| 203 |
+
partial_credit: true
|
| 204 |
+
deterministic: true
|
| 205 |
+
shaping:
|
| 206 |
+
speed_bonus: 0.05 # perfect on first attempt
|
| 207 |
+
improvement_bonus: 0.1 # scaled improvement on retry
|
| 208 |
+
repeated_failure_penalty: -0.05
|
| 209 |
+
|
| 210 |
+
baseline:
|
| 211 |
+
model: gpt-4o-mini
|
| 212 |
+
average_score: 0.80
|
| 213 |
+
scores:
|
| 214 |
+
email_triage_easy: 1.00
|
| 215 |
+
email_triage_medium: 0.80
|
| 216 |
+
email_triage_hard: 0.67
|
| 217 |
+
code_review_easy: 1.00
|
| 218 |
+
code_review_medium: 0.75
|
| 219 |
+
code_review_hard: 0.50
|
| 220 |
+
data_cleaning_easy: 1.00
|
| 221 |
+
data_cleaning_medium: 0.67
|
| 222 |
+
data_cleaning_hard: 0.67
|
| 223 |
+
content_moderation_easy: 1.00
|
| 224 |
+
content_moderation_medium: 0.75
|
| 225 |
+
content_moderation_hard: 0.42
|
| 226 |
+
ticket_triage_easy: 1.00
|
| 227 |
+
ticket_triage_medium: 0.70
|
| 228 |
+
ticket_triage_hard: 0.50
|
| 229 |
+
cross_agent_chain: 0.75
|
| 230 |
+
cross_agent_email_data: 0.67
|
| 231 |
+
cross_agent_code_email: 0.67
|
| 232 |
+
cross_agent_mod_escalation: 0.83
|
server/Meta_environment.py
CHANGED
|
@@ -36,6 +36,23 @@ from openenv.core.env_server.types import State
|
|
| 36 |
|
| 37 |
from models import MetaAction, MetaObservation
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 41 |
# DATA
|
|
@@ -602,7 +619,7 @@ def _kendall_tau_score(order: list, correct: list) -> float:
|
|
| 602 |
|
| 603 |
def grade_email_medium(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 604 |
order = payload.get("order", [])
|
| 605 |
-
correct = EMAIL_MEDIUM_CORRECT_ORDER
|
| 606 |
if not order:
|
| 607 |
return 0.0, "[FAIL] No order provided.", {}
|
| 608 |
top2_ok = set(order[:2]) == {"m1", "m5"}
|
|
@@ -792,13 +809,14 @@ def grade_data_medium(payload: dict) -> tuple[float, str, dict]:
|
|
| 792 |
return score, feedback, checks
|
| 793 |
|
| 794 |
|
| 795 |
-
def grade_data_hard(payload: dict) -> tuple[float, str, dict]:
|
| 796 |
outliers_given = set(int(x) for x in payload.get("outliers", []))
|
| 797 |
missing_given = set(int(x) for x in payload.get("missing", []))
|
| 798 |
cleaned = payload.get("cleaned_data", [])
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
|
|
|
| 802 |
outlier_ok = outliers_given == correct_out
|
| 803 |
missing_ok = missing_given == correct_miss
|
| 804 |
imputed_rows = {r["id"]: r.get("value") for r in cleaned if r.get("id") in correct_miss}
|
|
@@ -814,9 +832,10 @@ def grade_data_hard(payload: dict) -> tuple[float, str, dict]:
|
|
| 814 |
return score, feedback, checks
|
| 815 |
|
| 816 |
|
| 817 |
-
def grade_mod_easy(payload: dict) -> tuple[float, str, dict]:
|
| 818 |
clf = {c["id"]: c.get("label", "").lower() for c in payload.get("classifications", [])}
|
| 819 |
-
|
|
|
|
| 820 |
per = {id_: clf.get(id_) == label for id_, label in correct.items()}
|
| 821 |
hits = sum(per.values())
|
| 822 |
score = round(hits / len(correct), 2)
|
|
@@ -824,9 +843,10 @@ def grade_mod_easy(payload: dict) -> tuple[float, str, dict]:
|
|
| 824 |
return score, f"{tag} {hits}/{len(correct)} posts correctly classified.", per
|
| 825 |
|
| 826 |
|
| 827 |
-
def grade_mod_medium(payload: dict) -> tuple[float, str, dict]:
|
| 828 |
clf = {c["id"]: c.get("label", "").lower() for c in payload.get("classifications", [])}
|
| 829 |
-
|
|
|
|
| 830 |
per = {id_: clf.get(id_) == label for id_, label in correct.items()}
|
| 831 |
hits = sum(per.values())
|
| 832 |
score = round(hits / len(correct), 2)
|
|
@@ -1236,26 +1256,19 @@ class MetaEnvironment(Environment):
|
|
| 1236 |
def _load_context(self, task_id: str) -> tuple[dict, dict, str]:
|
| 1237 |
"""Return (agent_ctx, grader_ctx, difficulty). Fully deterministic."""
|
| 1238 |
if task_id == "email_triage_easy":
|
| 1239 |
-
|
| 1240 |
-
agent_ctx = {"email": {k: v for k, v in email.items() if k != "label"}}
|
| 1241 |
-
grader_ctx = {"email": email}
|
| 1242 |
return agent_ctx, grader_ctx, "easy"
|
| 1243 |
|
| 1244 |
if task_id == "email_triage_medium":
|
| 1245 |
-
agent_ctx
|
| 1246 |
-
grader_ctx = {"emails": EMAIL_MEDIUM_EMAILS}
|
| 1247 |
return agent_ctx, grader_ctx, "medium"
|
| 1248 |
|
| 1249 |
if task_id == "email_triage_hard":
|
| 1250 |
-
|
| 1251 |
-
agent_ctx = {"email": {k: v for k, v in case.items() if k != "expected_elements"}}
|
| 1252 |
-
grader_ctx = {"email": case}
|
| 1253 |
return agent_ctx, grader_ctx, "hard"
|
| 1254 |
|
| 1255 |
if task_id == "code_review_easy":
|
| 1256 |
-
|
| 1257 |
-
agent_ctx = {"code": variant["code"]}
|
| 1258 |
-
grader_ctx = {"code": variant["code"], "keywords": variant["keywords"]}
|
| 1259 |
return agent_ctx, grader_ctx, "easy"
|
| 1260 |
|
| 1261 |
if task_id == "code_review_medium":
|
|
@@ -1267,25 +1280,23 @@ class MetaEnvironment(Environment):
|
|
| 1267 |
return ctx, ctx, "hard"
|
| 1268 |
|
| 1269 |
if task_id == "data_cleaning_easy":
|
| 1270 |
-
|
| 1271 |
-
return
|
| 1272 |
|
| 1273 |
if task_id == "data_cleaning_medium":
|
| 1274 |
-
|
| 1275 |
-
return
|
| 1276 |
|
| 1277 |
if task_id == "data_cleaning_hard":
|
| 1278 |
-
|
| 1279 |
-
return
|
| 1280 |
|
| 1281 |
if task_id == "content_moderation_easy":
|
| 1282 |
-
agent_ctx
|
| 1283 |
-
grader_ctx = {"posts": MOD_EASY}
|
| 1284 |
return agent_ctx, grader_ctx, "easy"
|
| 1285 |
|
| 1286 |
if task_id == "content_moderation_medium":
|
| 1287 |
-
agent_ctx
|
| 1288 |
-
grader_ctx = {"posts": MOD_MEDIUM}
|
| 1289 |
return agent_ctx, grader_ctx, "medium"
|
| 1290 |
|
| 1291 |
if task_id == "content_moderation_hard":
|
|
@@ -1294,9 +1305,7 @@ class MetaEnvironment(Environment):
|
|
| 1294 |
return agent_ctx, grader_ctx, "hard"
|
| 1295 |
|
| 1296 |
if task_id == "ticket_triage_easy":
|
| 1297 |
-
|
| 1298 |
-
agent_ctx = {"ticket": {k: v for k, v in ticket.items() if k not in ("correct_priority", "correct_category")}}
|
| 1299 |
-
grader_ctx = {"ticket": ticket}
|
| 1300 |
return agent_ctx, grader_ctx, "easy"
|
| 1301 |
|
| 1302 |
if task_id == "ticket_triage_medium":
|
|
@@ -1332,6 +1341,33 @@ class MetaEnvironment(Environment):
|
|
| 1332 |
grader_ctx = {"case": case}
|
| 1333 |
return agent_ctx, grader_ctx, "hard"
|
| 1334 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1335 |
raise ValueError(f"Unknown task_id: '{task_id}'. Use GET /tasks to see valid IDs.")
|
| 1336 |
|
| 1337 |
def _grade(self, task_id: str, payload: dict, grader_context: dict) -> tuple[float, str, dict]:
|
|
@@ -1343,9 +1379,9 @@ class MetaEnvironment(Environment):
|
|
| 1343 |
if task_id == "code_review_hard": return grade_code_hard(payload)
|
| 1344 |
if task_id == "data_cleaning_easy": return grade_data_easy(payload)
|
| 1345 |
if task_id == "data_cleaning_medium": return grade_data_medium(payload)
|
| 1346 |
-
if task_id == "data_cleaning_hard": return grade_data_hard(payload)
|
| 1347 |
-
if task_id == "content_moderation_easy": return grade_mod_easy(payload)
|
| 1348 |
-
if task_id == "content_moderation_medium": return grade_mod_medium(payload)
|
| 1349 |
if task_id == "content_moderation_hard": return grade_mod_hard(payload)
|
| 1350 |
if task_id == "ticket_triage_easy": return grade_ticket_easy(payload, grader_context)
|
| 1351 |
if task_id == "ticket_triage_medium": return grade_ticket_medium(payload)
|
|
|
|
| 36 |
|
| 37 |
from models import MetaAction, MetaObservation
|
| 38 |
|
| 39 |
+
from data_generators import (
|
| 40 |
+
gen_email_easy, gen_email_medium, gen_email_hard,
|
| 41 |
+
gen_code_easy,
|
| 42 |
+
gen_data_easy, gen_data_medium, gen_data_hard,
|
| 43 |
+
gen_mod_easy, gen_mod_medium,
|
| 44 |
+
gen_ticket_easy,
|
| 45 |
+
)
|
| 46 |
+
from expert_tasks import (
|
| 47 |
+
EMAIL_EXPERT_CONTEXT, grade_email_expert,
|
| 48 |
+
CODE_EXPERT_CONTEXT, grade_code_expert,
|
| 49 |
+
DATA_EXPERT_CONTEXT, grade_data_expert,
|
| 50 |
+
MOD_EXPERT_CONTEXT, grade_mod_expert,
|
| 51 |
+
TICKET_EXPERT_CONTEXT, grade_ticket_expert,
|
| 52 |
+
EXPERT_INSTRUCTIONS, EXPERT_TASK_IDS,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
|
| 57 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 58 |
# DATA
|
|
|
|
| 619 |
|
| 620 |
def grade_email_medium(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 621 |
order = payload.get("order", [])
|
| 622 |
+
correct = context.get("correct_order", EMAIL_MEDIUM_CORRECT_ORDER)
|
| 623 |
if not order:
|
| 624 |
return 0.0, "[FAIL] No order provided.", {}
|
| 625 |
top2_ok = set(order[:2]) == {"m1", "m5"}
|
|
|
|
| 809 |
return score, feedback, checks
|
| 810 |
|
| 811 |
|
| 812 |
+
def grade_data_hard(payload: dict, context: dict = None) -> tuple[float, str, dict]:
|
| 813 |
outliers_given = set(int(x) for x in payload.get("outliers", []))
|
| 814 |
missing_given = set(int(x) for x in payload.get("missing", []))
|
| 815 |
cleaned = payload.get("cleaned_data", [])
|
| 816 |
+
answers = (context or {}).get("answers", DATA_HARD["answers"])
|
| 817 |
+
correct_out = answers.get("outliers", DATA_HARD["answers"]["outliers"])
|
| 818 |
+
correct_miss = answers.get("missing", DATA_HARD["answers"]["missing"])
|
| 819 |
+
lo, hi = answers.get("imputed_range", DATA_HARD["answers"]["imputed_range"])
|
| 820 |
outlier_ok = outliers_given == correct_out
|
| 821 |
missing_ok = missing_given == correct_miss
|
| 822 |
imputed_rows = {r["id"]: r.get("value") for r in cleaned if r.get("id") in correct_miss}
|
|
|
|
| 832 |
return score, feedback, checks
|
| 833 |
|
| 834 |
|
| 835 |
+
def grade_mod_easy(payload: dict, context: dict = None) -> tuple[float, str, dict]:
|
| 836 |
clf = {c["id"]: c.get("label", "").lower() for c in payload.get("classifications", [])}
|
| 837 |
+
posts = (context or {}).get("posts", MOD_EASY)
|
| 838 |
+
correct = {p["id"]: p["label"] for p in posts}
|
| 839 |
per = {id_: clf.get(id_) == label for id_, label in correct.items()}
|
| 840 |
hits = sum(per.values())
|
| 841 |
score = round(hits / len(correct), 2)
|
|
|
|
| 843 |
return score, f"{tag} {hits}/{len(correct)} posts correctly classified.", per
|
| 844 |
|
| 845 |
|
| 846 |
+
def grade_mod_medium(payload: dict, context: dict = None) -> tuple[float, str, dict]:
|
| 847 |
clf = {c["id"]: c.get("label", "").lower() for c in payload.get("classifications", [])}
|
| 848 |
+
posts = (context or {}).get("posts", MOD_MEDIUM)
|
| 849 |
+
correct = {p["id"]: p["label"] for p in posts}
|
| 850 |
per = {id_: clf.get(id_) == label for id_, label in correct.items()}
|
| 851 |
hits = sum(per.values())
|
| 852 |
score = round(hits / len(correct), 2)
|
|
|
|
| 1256 |
def _load_context(self, task_id: str) -> tuple[dict, dict, str]:
|
| 1257 |
"""Return (agent_ctx, grader_ctx, difficulty). Fully deterministic."""
|
| 1258 |
if task_id == "email_triage_easy":
|
| 1259 |
+
agent_ctx, grader_ctx = gen_email_easy(self._state.episode_id)
|
|
|
|
|
|
|
| 1260 |
return agent_ctx, grader_ctx, "easy"
|
| 1261 |
|
| 1262 |
if task_id == "email_triage_medium":
|
| 1263 |
+
agent_ctx, grader_ctx = gen_email_medium(self._state.episode_id)
|
|
|
|
| 1264 |
return agent_ctx, grader_ctx, "medium"
|
| 1265 |
|
| 1266 |
if task_id == "email_triage_hard":
|
| 1267 |
+
agent_ctx, grader_ctx = gen_email_hard(self._state.episode_id)
|
|
|
|
|
|
|
| 1268 |
return agent_ctx, grader_ctx, "hard"
|
| 1269 |
|
| 1270 |
if task_id == "code_review_easy":
|
| 1271 |
+
agent_ctx, grader_ctx = gen_code_easy(self._state.episode_id)
|
|
|
|
|
|
|
| 1272 |
return agent_ctx, grader_ctx, "easy"
|
| 1273 |
|
| 1274 |
if task_id == "code_review_medium":
|
|
|
|
| 1280 |
return ctx, ctx, "hard"
|
| 1281 |
|
| 1282 |
if task_id == "data_cleaning_easy":
|
| 1283 |
+
agent_ctx, grader_ctx = gen_data_easy(self._state.episode_id)
|
| 1284 |
+
return agent_ctx, grader_ctx, "easy"
|
| 1285 |
|
| 1286 |
if task_id == "data_cleaning_medium":
|
| 1287 |
+
agent_ctx, grader_ctx = gen_data_medium(self._state.episode_id)
|
| 1288 |
+
return agent_ctx, grader_ctx, "medium"
|
| 1289 |
|
| 1290 |
if task_id == "data_cleaning_hard":
|
| 1291 |
+
agent_ctx, grader_ctx = gen_data_hard(self._state.episode_id)
|
| 1292 |
+
return agent_ctx, grader_ctx, "hard"
|
| 1293 |
|
| 1294 |
if task_id == "content_moderation_easy":
|
| 1295 |
+
agent_ctx, grader_ctx = gen_mod_easy(self._state.episode_id)
|
|
|
|
| 1296 |
return agent_ctx, grader_ctx, "easy"
|
| 1297 |
|
| 1298 |
if task_id == "content_moderation_medium":
|
| 1299 |
+
agent_ctx, grader_ctx = gen_mod_medium(self._state.episode_id)
|
|
|
|
| 1300 |
return agent_ctx, grader_ctx, "medium"
|
| 1301 |
|
| 1302 |
if task_id == "content_moderation_hard":
|
|
|
|
| 1305 |
return agent_ctx, grader_ctx, "hard"
|
| 1306 |
|
| 1307 |
if task_id == "ticket_triage_easy":
|
| 1308 |
+
agent_ctx, grader_ctx = gen_ticket_easy(self._state.episode_id)
|
|
|
|
|
|
|
| 1309 |
return agent_ctx, grader_ctx, "easy"
|
| 1310 |
|
| 1311 |
if task_id == "ticket_triage_medium":
|
|
|
|
| 1341 |
grader_ctx = {"case": case}
|
| 1342 |
return agent_ctx, grader_ctx, "hard"
|
| 1343 |
|
| 1344 |
+
|
| 1345 |
+
if task_id == "email_triage_expert":
|
| 1346 |
+
agent_ctx = {k: v for k, v in EMAIL_EXPERT_CONTEXT.items()}
|
| 1347 |
+
grader_ctx = agent_ctx
|
| 1348 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1349 |
+
|
| 1350 |
+
if task_id == "code_review_expert":
|
| 1351 |
+
agent_ctx = {"code": CODE_EXPERT_CONTEXT["code"],
|
| 1352 |
+
"vulnerability_count": CODE_EXPERT_CONTEXT["vulnerability_count"]}
|
| 1353 |
+
grader_ctx = CODE_EXPERT_CONTEXT
|
| 1354 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1355 |
+
|
| 1356 |
+
if task_id == "data_cleaning_expert":
|
| 1357 |
+
agent_ctx = DATA_EXPERT_CONTEXT
|
| 1358 |
+
grader_ctx = DATA_EXPERT_CONTEXT
|
| 1359 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1360 |
+
|
| 1361 |
+
if task_id == "content_moderation_expert":
|
| 1362 |
+
agent_ctx = MOD_EXPERT_CONTEXT
|
| 1363 |
+
grader_ctx = MOD_EXPERT_CONTEXT
|
| 1364 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1365 |
+
|
| 1366 |
+
if task_id == "ticket_triage_expert":
|
| 1367 |
+
agent_ctx = TICKET_EXPERT_CONTEXT
|
| 1368 |
+
grader_ctx = TICKET_EXPERT_CONTEXT
|
| 1369 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1370 |
+
|
| 1371 |
raise ValueError(f"Unknown task_id: '{task_id}'. Use GET /tasks to see valid IDs.")
|
| 1372 |
|
| 1373 |
def _grade(self, task_id: str, payload: dict, grader_context: dict) -> tuple[float, str, dict]:
|
|
|
|
| 1379 |
if task_id == "code_review_hard": return grade_code_hard(payload)
|
| 1380 |
if task_id == "data_cleaning_easy": return grade_data_easy(payload)
|
| 1381 |
if task_id == "data_cleaning_medium": return grade_data_medium(payload)
|
| 1382 |
+
if task_id == "data_cleaning_hard": return grade_data_hard(payload, grader_context)
|
| 1383 |
+
if task_id == "content_moderation_easy": return grade_mod_easy(payload, grader_context)
|
| 1384 |
+
if task_id == "content_moderation_medium": return grade_mod_medium(payload, grader_context)
|
| 1385 |
if task_id == "content_moderation_hard": return grade_mod_hard(payload)
|
| 1386 |
if task_id == "ticket_triage_easy": return grade_ticket_easy(payload, grader_context)
|
| 1387 |
if task_id == "ticket_triage_medium": return grade_ticket_medium(payload)
|
server/Meta_environment.py.bak
ADDED
|
@@ -0,0 +1,1520 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Meta Multi-Agent Environment v3.0
|
| 3 |
+
|
| 4 |
+
Five real-world AI agent domains, 16 tasks total:
|
| 5 |
+
1. Email Triage β classify, prioritize, draft replies
|
| 6 |
+
2. Code Review β syntax errors, logic bugs, security vulnerabilities (harder)
|
| 7 |
+
3. Data Cleaning β missing values, type normalization, outlier imputation
|
| 8 |
+
4. Content Moderation β explicit, subtle toxicity, context-aware moderation (harder)
|
| 9 |
+
5. Ticket Triage β NEW: Jira-style bug ticket priority, routing, incident analysis
|
| 10 |
+
|
| 11 |
+
Cross-agent chained tasks (4 total):
|
| 12 |
+
cross_agent_chain β email triage + code review
|
| 13 |
+
cross_agent_email_data β email priority + data cleaning
|
| 14 |
+
cross_agent_code_email β vulnerability detection + security disclosure email
|
| 15 |
+
cross_agent_mod_escalation β content moderation + moderation notice drafting
|
| 16 |
+
|
| 17 |
+
v3.0 improvements:
|
| 18 |
+
- Deterministic context loading via episode_id hash (no random.choice breaking reproducibility)
|
| 19 |
+
- 5th domain: Ticket Triage (Jira-style)
|
| 20 |
+
- 4 cross-agent chained tasks (was 1)
|
| 21 |
+
- Harder hard tasks: 8 vulnerabilities in code_hard, adversarial content_mod_hard
|
| 22 |
+
- Trajectory-aware reward shaping with improvement bonuses
|
| 23 |
+
- Clean single-import pattern (PYTHONPATH set correctly in Dockerfile)
|
| 24 |
+
- Full type hints on all grader functions
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
from __future__ import annotations
|
| 28 |
+
|
| 29 |
+
import json
|
| 30 |
+
import random
|
| 31 |
+
from typing import Any
|
| 32 |
+
from uuid import uuid4
|
| 33 |
+
|
| 34 |
+
from openenv.core.env_server.interfaces import Environment
|
| 35 |
+
from openenv.core.env_server.types import State
|
| 36 |
+
|
| 37 |
+
from models import MetaAction, MetaObservation
|
| 38 |
+
|
| 39 |
+
from data_generators import (
|
| 40 |
+
gen_email_easy, gen_email_medium, gen_email_hard,
|
| 41 |
+
gen_code_easy,
|
| 42 |
+
gen_data_easy, gen_data_medium, gen_data_hard,
|
| 43 |
+
gen_mod_easy, gen_mod_medium,
|
| 44 |
+
gen_ticket_easy,
|
| 45 |
+
)
|
| 46 |
+
from expert_tasks import (
|
| 47 |
+
EMAIL_EXPERT_CONTEXT, grade_email_expert,
|
| 48 |
+
CODE_EXPERT_CONTEXT, grade_code_expert,
|
| 49 |
+
DATA_EXPERT_CONTEXT, grade_data_expert,
|
| 50 |
+
MOD_EXPERT_CONTEXT, grade_mod_expert,
|
| 51 |
+
TICKET_EXPERT_CONTEXT, grade_ticket_expert,
|
| 52 |
+
EXPERT_INSTRUCTIONS, EXPERT_TASK_IDS,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 58 |
+
# DATA
|
| 59 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 60 |
+
|
| 61 |
+
EMAIL_EASY_VARIANTS = [
|
| 62 |
+
{
|
| 63 |
+
"id": "e1",
|
| 64 |
+
"subject": "You won $1,000,000!!!",
|
| 65 |
+
"sender": "promo@totally-legit-lottery.com",
|
| 66 |
+
"body": "Click here to claim your prize now! Limited time offer!",
|
| 67 |
+
"label": "spam",
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"id": "e2",
|
| 71 |
+
"subject": "Q3 Budget Review β Urgent Action Required",
|
| 72 |
+
"sender": "cfo@company.com",
|
| 73 |
+
"body": "Please review the attached Q3 budget before our board meeting tomorrow at 9 AM.",
|
| 74 |
+
"label": "important",
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"id": "e3",
|
| 78 |
+
"subject": "Your weekly digest from Medium",
|
| 79 |
+
"sender": "noreply@medium.com",
|
| 80 |
+
"body": "Top stories this week: AI trends, Python tips, and startup news.",
|
| 81 |
+
"label": "newsletter",
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"id": "e4",
|
| 85 |
+
"subject": "CONGRATULATIONS! iPhone 15 Pro is yours!",
|
| 86 |
+
"sender": "winner@prize-alert.net",
|
| 87 |
+
"body": "You have been selected. Click now to claim your free iPhone!",
|
| 88 |
+
"label": "spam",
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"id": "e5",
|
| 92 |
+
"subject": "Action Required: Sign the NDA before Friday",
|
| 93 |
+
"sender": "legal@company.com",
|
| 94 |
+
"body": "Please sign the attached NDA before Friday's partnership meeting. Deadline is strict.",
|
| 95 |
+
"label": "important",
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"id": "e6",
|
| 99 |
+
"subject": "TechCrunch Newsletter β March 2026",
|
| 100 |
+
"sender": "newsletter@techcrunch.com",
|
| 101 |
+
"body": "This week's top stories in tech, AI, and startups.",
|
| 102 |
+
"label": "newsletter",
|
| 103 |
+
},
|
| 104 |
+
]
|
| 105 |
+
|
| 106 |
+
EMAIL_MEDIUM_EMAILS = [
|
| 107 |
+
{"id": "m1", "subject": "[ALERT] Production server DOWN", "sender": "ops@company.com", "body": "All services unresponsive since 2 AM. Revenue impact: $10k/min.", "priority": 1, "category": "critical"},
|
| 108 |
+
{"id": "m2", "subject": "Team lunch this Friday?", "sender": "colleague@company.com","body": "Hey, thinking of doing a team lunch Friday. You in?", "priority": 9, "category": "social"},
|
| 109 |
+
{"id": "m3", "subject": "Acme Corp contract renewal β URGENT", "sender": "sales@company.com", "body": "Acme contract expires Friday. They need signature or they walk.", "priority": 2, "category": "business"},
|
| 110 |
+
{"id": "m4", "subject": "Weekly SaaS digest", "sender": "news@saasdigest.com", "body": "Top SaaS stories this week...", "priority": 10, "category": "newsletter"},
|
| 111 |
+
{"id": "m5", "subject": "[WARNING] Security alert: unusual login", "sender": "security@company.com","body": "Unauthorized login attempt from IP 45.33.32.156. Verify immediately.", "priority": 1, "category": "critical"},
|
| 112 |
+
{"id": "m6", "subject": "Invoice #INV-4521 overdue", "sender": "billing@vendor.com", "body": "Invoice of $5,400 was due 3 days ago. Payment needed to avoid suspension.", "priority": 3, "category": "finance"},
|
| 113 |
+
{"id": "m7", "subject": "Happy Work Anniversary!", "sender": "hr@company.com", "body": "Celebrating your 3 years with us!", "priority": 10, "category": "social"},
|
| 114 |
+
{"id": "m8", "subject": "Q4 product launch deck β review needed", "sender": "marketing@company.com","body": "Please review launch slides before Thursday. Launch is next Monday.", "priority": 3, "category": "business"},
|
| 115 |
+
{"id": "m9", "subject": "Free AI productivity webinar", "sender": "promo@webinar.io", "body": "Join our free webinar on AI tools this Thursday.", "priority": 8, "category": "newsletter"},
|
| 116 |
+
{"id": "m10", "subject": "Legal: Partnership agreement needs signature TODAY", "sender": "legal@company.com", "body": "The partnership agreement must be countersigned by EOD or the deal falls through.", "priority": 2, "category": "legal"},
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
+
EMAIL_MEDIUM_CORRECT_ORDER = ["m1", "m5", "m3", "m10", "m6", "m8", "m2", "m9", "m4", "m7"]
|
| 120 |
+
|
| 121 |
+
EMAIL_HARD_CASES = [
|
| 122 |
+
{
|
| 123 |
+
"id": "h1",
|
| 124 |
+
"subject": "Completely unacceptable β Order #78234",
|
| 125 |
+
"sender": "furious.customer@email.com",
|
| 126 |
+
"body": (
|
| 127 |
+
"I have been a loyal customer for 5 years and I am absolutely furious. "
|
| 128 |
+
"My order #78234 arrived broken for the SECOND time this month. "
|
| 129 |
+
"Your support team has been dismissive and unhelpful every time I called. "
|
| 130 |
+
"I demand a full refund, a replacement sent overnight, AND compensation. "
|
| 131 |
+
"If this is not resolved within 24 hours, I will file a consumer court complaint."
|
| 132 |
+
),
|
| 133 |
+
"expected_elements": {
|
| 134 |
+
"acknowledge_frustration": ["understand", "frustrat", "disappoint", "concern", "feel"],
|
| 135 |
+
"apologize": ["sorry", "apologize", "apology", "sincerely"],
|
| 136 |
+
"offer_refund": ["refund", "money back", "full refund"],
|
| 137 |
+
"offer_replacement": ["replacement", "replace", "new order", "send another", "overnight"],
|
| 138 |
+
"provide_timeline": ["24 hours", "48 hours", "within", "today", "tomorrow", "business day"],
|
| 139 |
+
"professional_tone": None,
|
| 140 |
+
"compensation_acknowledged": ["compensat", "inconvenience", "make it right", "goodwill"],
|
| 141 |
+
},
|
| 142 |
+
},
|
| 143 |
+
{
|
| 144 |
+
"id": "h2",
|
| 145 |
+
"subject": "Billing error β charged twice for subscription",
|
| 146 |
+
"sender": "billing.complaint@customer.com",
|
| 147 |
+
"body": (
|
| 148 |
+
"I was charged twice for my annual subscription this month β once on March 1st "
|
| 149 |
+
"and again on March 15th. I have the bank statements as proof. "
|
| 150 |
+
"This is a clear billing error on your end. I need an immediate refund of the "
|
| 151 |
+
"duplicate charge and a written confirmation that this won't happen again. "
|
| 152 |
+
"I've been waiting 2 weeks for a response from your support team."
|
| 153 |
+
),
|
| 154 |
+
"expected_elements": {
|
| 155 |
+
"acknowledge_frustration": ["understand", "concern", "apologize", "sorry"],
|
| 156 |
+
"apologize": ["sorry", "apologize", "apology"],
|
| 157 |
+
"offer_refund": ["refund", "reimburse", "credit"],
|
| 158 |
+
"confirm_investigation": ["investigat", "look into", "check", "review", "verify"],
|
| 159 |
+
"provide_timeline": ["within", "hours", "days", "business day"],
|
| 160 |
+
"professional_tone": None,
|
| 161 |
+
"written_confirmation": ["confirm", "written", "email confirmation", "receipt"],
|
| 162 |
+
},
|
| 163 |
+
},
|
| 164 |
+
]
|
| 165 |
+
|
| 166 |
+
CODE_EASY_VARIANTS = [
|
| 167 |
+
{
|
| 168 |
+
"code": "def calculate_average(numbers):\n total = 0\n for num in numbers\n total += num\n return total / len(numbers)\n",
|
| 169 |
+
"hint": "missing colon after for statement",
|
| 170 |
+
"keywords": ["colon", "for loop", "syntax", "for statement"],
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"code": "def greet(name)\n print(f'Hello, {name}!')\n\ngreet('World')\n",
|
| 174 |
+
"hint": "missing colon after function definition",
|
| 175 |
+
"keywords": ["colon", "def", "function", "syntax"],
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"code": "numbers = [1, 2, 3, 4, 5]\nif len(numbers) > 0\n print('Not empty')\n",
|
| 179 |
+
"hint": "missing colon after if statement",
|
| 180 |
+
"keywords": ["colon", "if statement", "syntax"],
|
| 181 |
+
},
|
| 182 |
+
]
|
| 183 |
+
|
| 184 |
+
CODE_MEDIUM = {
|
| 185 |
+
"code": (
|
| 186 |
+
"def find_max(numbers):\n"
|
| 187 |
+
" max_val = 0\n"
|
| 188 |
+
" for num in numbers:\n"
|
| 189 |
+
" if num > max_val:\n"
|
| 190 |
+
" max_val = num\n"
|
| 191 |
+
" return max_val\n\n"
|
| 192 |
+
"def get_user(users, user_id):\n"
|
| 193 |
+
" for user in users:\n"
|
| 194 |
+
" if user['id'] = user_id:\n"
|
| 195 |
+
" return user\n"
|
| 196 |
+
" return None\n\n"
|
| 197 |
+
"def divide(a, b):\n"
|
| 198 |
+
" return a / b\n\n"
|
| 199 |
+
"def process_list(items):\n"
|
| 200 |
+
" results = []\n"
|
| 201 |
+
" for i in range(len(items) + 1):\n"
|
| 202 |
+
" results.append(items[i] * 2)\n"
|
| 203 |
+
" return results\n"
|
| 204 |
+
),
|
| 205 |
+
"bugs": {
|
| 206 |
+
"max_val_init": "find_max initializes max_val=0, fails for all-negative lists",
|
| 207 |
+
"assignment_vs_eq": "get_user uses = (assignment) instead of == (comparison)",
|
| 208 |
+
"zero_division": "divide() has no guard against division by zero",
|
| 209 |
+
"off_by_one": "process_list iterates range(len(items)+1) causing IndexError",
|
| 210 |
+
},
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
# HARDER: 8 vulnerabilities now (was 5) β frontier models won't trivially score 1.0
|
| 214 |
+
CODE_HARD = {
|
| 215 |
+
"code": (
|
| 216 |
+
"import sqlite3\n"
|
| 217 |
+
"import subprocess\n"
|
| 218 |
+
"import pickle\n"
|
| 219 |
+
"import requests\n"
|
| 220 |
+
"import hashlib\n"
|
| 221 |
+
"import os\n\n"
|
| 222 |
+
"# Vulnerability 1: SQL injection\n"
|
| 223 |
+
"def get_user_by_name(name):\n"
|
| 224 |
+
" conn = sqlite3.connect('users.db')\n"
|
| 225 |
+
" cursor = conn.cursor()\n"
|
| 226 |
+
" query = \"SELECT * FROM users WHERE name = '\" + name + \"'\"\n"
|
| 227 |
+
" cursor.execute(query)\n"
|
| 228 |
+
" return cursor.fetchall()\n\n"
|
| 229 |
+
"# Vulnerability 2: XSS\n"
|
| 230 |
+
"def render_comment(comment):\n"
|
| 231 |
+
" return '<div class=\"comment\">' + comment + '</div>'\n\n"
|
| 232 |
+
"# Vulnerability 3: SQL injection\n"
|
| 233 |
+
"def login(username, password):\n"
|
| 234 |
+
" conn = sqlite3.connect('users.db')\n"
|
| 235 |
+
" cursor = conn.cursor()\n"
|
| 236 |
+
" query = f\"SELECT * FROM users WHERE username='{username}' AND password='{password}'\"\n"
|
| 237 |
+
" cursor.execute(query)\n"
|
| 238 |
+
" return cursor.fetchone() is not None\n\n"
|
| 239 |
+
"# Vulnerability 4: Command injection\n"
|
| 240 |
+
"def run_report(report_name):\n"
|
| 241 |
+
" subprocess.call('generate_report.sh ' + report_name, shell=True)\n\n"
|
| 242 |
+
"# Vulnerability 5: Insecure deserialization\n"
|
| 243 |
+
"def load_user_data(data_bytes):\n"
|
| 244 |
+
" return pickle.loads(data_bytes)\n\n"
|
| 245 |
+
"# Vulnerability 6: Timing attack on password comparison\n"
|
| 246 |
+
"def check_password(stored_hash, input_password):\n"
|
| 247 |
+
" return stored_hash == hashlib.md5(input_password.encode()).hexdigest()\n\n"
|
| 248 |
+
"# Vulnerability 7: Path traversal\n"
|
| 249 |
+
"def read_report_file(filename):\n"
|
| 250 |
+
" with open(f'/reports/{filename}') as f:\n"
|
| 251 |
+
" return f.read()\n\n"
|
| 252 |
+
"# Vulnerability 8: SSRF\n"
|
| 253 |
+
"def fetch_user_avatar(avatar_url):\n"
|
| 254 |
+
" response = requests.get(avatar_url)\n"
|
| 255 |
+
" return response.content\n"
|
| 256 |
+
),
|
| 257 |
+
"vulnerabilities": {
|
| 258 |
+
"sql_injection_get_user": {"type": "sql_injection", "location": "get_user_by_name", "severity": "critical"},
|
| 259 |
+
"xss_render_comment": {"type": "xss", "location": "render_comment", "severity": "high"},
|
| 260 |
+
"sql_injection_login": {"type": "sql_injection", "location": "login", "severity": "critical"},
|
| 261 |
+
"command_injection": {"type": "command_injection", "location": "run_report", "severity": "critical"},
|
| 262 |
+
"insecure_deserialization": {"type": "insecure_deserialization","location": "load_user_data", "severity": "high"},
|
| 263 |
+
"timing_attack": {"type": "timing_attack", "location": "check_password", "severity": "medium"},
|
| 264 |
+
"path_traversal": {"type": "path_traversal", "location": "read_report_file", "severity": "high"},
|
| 265 |
+
"ssrf": {"type": "ssrf", "location": "fetch_user_avatar", "severity": "high"},
|
| 266 |
+
},
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
DATA_EASY = {
|
| 270 |
+
"data": [
|
| 271 |
+
{"id": 1, "name": "Alice", "age": 30, "email": "alice@example.com", "salary": 70000},
|
| 272 |
+
{"id": 2, "name": "Bob", "age": None, "email": "bob@example.com", "salary": 85000},
|
| 273 |
+
{"id": 3, "name": "Alice", "age": 30, "email": "alice@example.com", "salary": 70000},
|
| 274 |
+
{"id": 4, "name": None, "age": 25, "email": "charlie@example.com", "salary": 60000},
|
| 275 |
+
{"id": 5, "name": "Dave", "age": 40, "email": None, "salary": 90000},
|
| 276 |
+
{"id": 6, "name": "Eve", "age": 28, "email": "eve@example.com", "salary": None},
|
| 277 |
+
],
|
| 278 |
+
"answers": {
|
| 279 |
+
"missing": ["age (row 2)", "name (row 4)", "email (row 5)", "salary (row 6)"],
|
| 280 |
+
"duplicates": [1, 3],
|
| 281 |
+
},
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
DATA_MEDIUM = {
|
| 285 |
+
"data": [
|
| 286 |
+
{"id": 1, "name": "Alice", "age": "thirty", "salary": "50000", "join_date": "2020/01/15", "active": "yes"},
|
| 287 |
+
{"id": 2, "name": "Bob", "age": 25, "salary": "$60,000", "join_date": "15-03-2021", "active": True},
|
| 288 |
+
{"id": 3, "name": "Charlie", "age": "28", "salary": "70000", "join_date": "2019-07-22", "active": "TRUE"},
|
| 289 |
+
{"id": 4, "name": "dave", "age": 35, "salary": "80,000", "join_date": "2022/11/01", "active": False},
|
| 290 |
+
{"id": 5, "name": "EVE", "age": "twenty-two", "salary": "45000", "join_date": "01/06/2023", "active": "no"},
|
| 291 |
+
],
|
| 292 |
+
"issues": {
|
| 293 |
+
"age": "mixed types: strings 'thirty', '28', 'twenty-two' should be integers",
|
| 294 |
+
"salary": "inconsistent formats: '$60,000' and '80,000' have symbols/commas",
|
| 295 |
+
"join_date": "three different date formats",
|
| 296 |
+
"name": "inconsistent casing: 'dave' lowercase, 'EVE' all caps",
|
| 297 |
+
"active": "mixed types: True/False booleans mixed with strings 'yes'/'no'/'TRUE'",
|
| 298 |
+
},
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
DATA_HARD = {
|
| 302 |
+
"data": [
|
| 303 |
+
{"id": 1, "value": 10.5}, {"id": 2, "value": 11.2},
|
| 304 |
+
{"id": 3, "value": 10.8}, {"id": 4, "value": 999.0},
|
| 305 |
+
{"id": 5, "value": 10.1}, {"id": 6, "value": None},
|
| 306 |
+
{"id": 7, "value": 11.5}, {"id": 8, "value": -500.0},
|
| 307 |
+
{"id": 9, "value": 10.9}, {"id": 10, "value": 11.0},
|
| 308 |
+
{"id": 11, "value": None}, {"id": 12, "value": 10.7},
|
| 309 |
+
],
|
| 310 |
+
"answers": {
|
| 311 |
+
"outliers": {4, 8},
|
| 312 |
+
"missing": {6, 11},
|
| 313 |
+
"imputed_range": (10.0, 12.0),
|
| 314 |
+
},
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
MOD_EASY = [
|
| 318 |
+
{"id": "c1", "text": "I hate you so much, I want to hurt you!", "label": "harmful"},
|
| 319 |
+
{"id": "c2", "text": "Great weather today! Loving this sunshine", "label": "safe"},
|
| 320 |
+
{"id": "c3", "text": "All people from that group should be eliminated.", "label": "harmful"},
|
| 321 |
+
{"id": "c4", "text": "Can someone recommend a good Python beginner book?", "label": "safe"},
|
| 322 |
+
{"id": "c5", "text": "You're an absolute idiot and nobody likes you.", "label": "harmful"},
|
| 323 |
+
{"id": "c6", "text": "I just finished a 5k run β feeling amazing!", "label": "safe"},
|
| 324 |
+
{"id": "c7", "text": "People like you should just disappear forever.", "label": "harmful"},
|
| 325 |
+
]
|
| 326 |
+
|
| 327 |
+
MOD_MEDIUM = [
|
| 328 |
+
{"id": "m1", "text": "Oh sure, like THAT went well. Classic genius move.", "label": "harmful"},
|
| 329 |
+
{"id": "m2", "text": "I'm so done with these people. Absolutely insufferable.", "label": "harmful"},
|
| 330 |
+
{"id": "m3", "text": "That presentation was... very unique.", "label": "safe"},
|
| 331 |
+
{"id": "m4", "text": "Some people just shouldn't be allowed to have opinions.", "label": "harmful"},
|
| 332 |
+
{"id": "m5", "text": "Oh wow, another 'brilliant' idea from upper management", "label": "harmful"},
|
| 333 |
+
{"id": "m6", "text": "Congrats on the promotion! Well deserved!", "label": "safe"},
|
| 334 |
+
{"id": "m7", "text": "Not everyone is cut out for this kind of work, clearly.", "label": "harmful"},
|
| 335 |
+
{"id": "m8", "text": "Thanks for the feedback, I'll take it into account.", "label": "safe"},
|
| 336 |
+
]
|
| 337 |
+
|
| 338 |
+
# HARDER: adversarial cases including dog-whistles and coordinated inauthentic patterns
|
| 339 |
+
MOD_HARD = [
|
| 340 |
+
{
|
| 341 |
+
"id": "h1",
|
| 342 |
+
"text": "You should just disappear.",
|
| 343 |
+
"context_a": {"platform": "gaming_chat", "prior_message": "Your character died in the dungeon. Respawn?"},
|
| 344 |
+
"label_a": "safe",
|
| 345 |
+
"context_b": {"platform": "crisis_support_chat","prior_message": "I've been feeling worthless and invisible lately"},
|
| 346 |
+
"label_b": "harmful",
|
| 347 |
+
},
|
| 348 |
+
{
|
| 349 |
+
"id": "h2",
|
| 350 |
+
"text": "We need to take these people out.",
|
| 351 |
+
"context_a": {"platform": "hr_slack", "prior_message": "Which team members should join the client dinner?"},
|
| 352 |
+
"label_a": "safe",
|
| 353 |
+
"context_b": {"platform": "extremist_forum", "prior_message": "These immigrants are ruining our country."},
|
| 354 |
+
"label_b": "harmful",
|
| 355 |
+
},
|
| 356 |
+
{
|
| 357 |
+
"id": "h3",
|
| 358 |
+
"text": "Let's eliminate the competition.",
|
| 359 |
+
"context_a": {"platform": "business_slack", "prior_message": "Q4 sales strategy meeting"},
|
| 360 |
+
"label_a": "safe",
|
| 361 |
+
"context_b": {"platform": "political_rally_chat","prior_message": "The opposition party is destroying our values."},
|
| 362 |
+
"label_b": "harmful",
|
| 363 |
+
},
|
| 364 |
+
# NEW adversarial cases β much harder
|
| 365 |
+
{
|
| 366 |
+
"id": "h4",
|
| 367 |
+
"text": "Finally, the great replacement is happening in our neighborhood.",
|
| 368 |
+
"context_a": {"platform": "news_comments", "prior_message": "New families moving into Maplewood as housing market cools."},
|
| 369 |
+
"label_a": "harmful", # dog-whistle regardless of context
|
| 370 |
+
"context_b": {"platform": "urban_planning_forum","prior_message": "Demographics shifting as millennials move to suburbs."},
|
| 371 |
+
"label_b": "harmful", # still a white-nationalist dog-whistle
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
"id": "h5",
|
| 375 |
+
"text": "Everyone should follow our example and do the same thing at noon tomorrow.",
|
| 376 |
+
"context_a": {"platform": "community_events", "prior_message": "We're all doing a neighborhood cleanup. Join us!"},
|
| 377 |
+
"label_a": "safe",
|
| 378 |
+
"context_b": {"platform": "extremist_channel", "prior_message": "The government building is unguarded on weekends."},
|
| 379 |
+
"label_b": "harmful", # coordinated inauthentic / incitement
|
| 380 |
+
},
|
| 381 |
+
{
|
| 382 |
+
"id": "h6",
|
| 383 |
+
"text": "These people breed like rabbits and will replace us if we don't act.",
|
| 384 |
+
"context_a": {"platform": "biology_classroom", "prior_message": "Comparing reproductive rates of different animal species"},
|
| 385 |
+
"label_a": "harmful", # dehumanizing regardless β not biology
|
| 386 |
+
"context_b": {"platform": "political_forum", "prior_message": "Immigration policy discussion thread"},
|
| 387 |
+
"label_b": "harmful",
|
| 388 |
+
},
|
| 389 |
+
]
|
| 390 |
+
|
| 391 |
+
# ββ NEW: Ticket Triage Domain βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 392 |
+
|
| 393 |
+
TICKET_EASY_VARIANTS = [
|
| 394 |
+
{
|
| 395 |
+
"id": "t1",
|
| 396 |
+
"title": "App crashes on login for all users",
|
| 397 |
+
"description": "Since the 2.3.1 deploy, 100% of login attempts throw a 500 error. Users cannot access the platform.",
|
| 398 |
+
"reporter": "ops-monitoring@company.com",
|
| 399 |
+
"correct_priority": "critical",
|
| 400 |
+
"correct_category": "bug",
|
| 401 |
+
},
|
| 402 |
+
{
|
| 403 |
+
"id": "t2",
|
| 404 |
+
"title": "Add dark mode to the dashboard",
|
| 405 |
+
"description": "Multiple users have requested a dark mode option. Would improve accessibility.",
|
| 406 |
+
"reporter": "product@company.com",
|
| 407 |
+
"correct_priority": "low",
|
| 408 |
+
"correct_category": "feature_request",
|
| 409 |
+
},
|
| 410 |
+
{
|
| 411 |
+
"id": "t3",
|
| 412 |
+
"title": "How do I export my data as CSV?",
|
| 413 |
+
"description": "I can't find the export button anywhere in the UI. Where is it?",
|
| 414 |
+
"reporter": "user123@customer.com",
|
| 415 |
+
"correct_priority": "low",
|
| 416 |
+
"correct_category": "question",
|
| 417 |
+
},
|
| 418 |
+
{
|
| 419 |
+
"id": "t4",
|
| 420 |
+
"title": "Charged twice for last month's invoice",
|
| 421 |
+
"description": "Account #AC-4421 shows two charges of $299 on March 1st. Please refund duplicate.",
|
| 422 |
+
"reporter": "finance@customer.com",
|
| 423 |
+
"correct_priority": "high",
|
| 424 |
+
"correct_category": "billing",
|
| 425 |
+
},
|
| 426 |
+
]
|
| 427 |
+
|
| 428 |
+
TICKET_MEDIUM_TICKETS = [
|
| 429 |
+
{"id": "tk1", "title": "Database connection pool exhausted β prod down", "type": "bug", "team": "backend", "priority_rank": 1},
|
| 430 |
+
{"id": "tk2", "title": "Wrong currency symbol shown for EU customers", "type": "bug", "team": "frontend", "priority_rank": 3},
|
| 431 |
+
{"id": "tk3", "title": "Add OAuth2 support for enterprise SSO", "type": "feature_request","team": "backend", "priority_rank": 5},
|
| 432 |
+
{"id": "tk4", "title": "SSL cert expires in 48 hours on api.company.com", "type": "bug", "team": "devops", "priority_rank": 2},
|
| 433 |
+
{"id": "tk5", "title": "Invoice PDF generation fails for invoices > 50 lines","type": "bug", "team": "backend", "priority_rank": 4},
|
| 434 |
+
{"id": "tk6", "title": "Redesign onboarding flow to reduce drop-off", "type": "feature_request","team": "frontend", "priority_rank": 7},
|
| 435 |
+
{"id": "tk7", "title": "Customer billed $0 instead of $299 β revenue leak","type": "bug", "team": "billing", "priority_rank": 3},
|
| 436 |
+
{"id": "tk8", "title": "Add CSV export for analytics dashboard", "type": "feature_request","team": "frontend", "priority_rank": 8},
|
| 437 |
+
]
|
| 438 |
+
|
| 439 |
+
TICKET_MEDIUM_CORRECT_ORDER = ["tk1", "tk4", "tk2", "tk7", "tk5", "tk3", "tk6", "tk8"]
|
| 440 |
+
TICKET_MEDIUM_CORRECT_TEAMS = {
|
| 441 |
+
"tk1": "backend", "tk2": "frontend", "tk3": "backend",
|
| 442 |
+
"tk4": "devops", "tk5": "backend", "tk6": "frontend",
|
| 443 |
+
"tk7": "billing", "tk8": "frontend",
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
TICKET_HARD_INCIDENT = {
|
| 447 |
+
"tickets": [
|
| 448 |
+
{"id": "INC-001", "title": "Payment service returning 503", "created": "14:00", "service": "payment-service"},
|
| 449 |
+
{"id": "INC-002", "title": "Database CPU at 100% on primary replica","created": "14:01", "service": "postgres-primary"},
|
| 450 |
+
{"id": "INC-003", "title": "Checkout flow failing for all users", "created": "14:02", "service": "checkout-service"},
|
| 451 |
+
{"id": "INC-004", "title": "Redis memory at 99% β evictions spiking","created": "14:03", "service": "redis-cache"},
|
| 452 |
+
{"id": "INC-005", "title": "Background job queue backed up 50k jobs","created": "14:05", "service": "job-queue"},
|
| 453 |
+
{"id": "INC-006", "title": "CDN edge nodes reporting origin timeouts","created": "14:06", "service": "cdn"},
|
| 454 |
+
],
|
| 455 |
+
"expected": {
|
| 456 |
+
"root_cause_keywords": [
|
| 457 |
+
"database", "postgres", "cpu", "memory", "redis", "cache",
|
| 458 |
+
"connection pool", "exhausted", "query", "slow query", "replica",
|
| 459 |
+
],
|
| 460 |
+
"affected_services": {"payment-service", "checkout-service", "postgres-primary", "redis-cache"},
|
| 461 |
+
"severity": "P1",
|
| 462 |
+
"resolution_keywords": [
|
| 463 |
+
"scale", "replica", "failover", "restart", "flush", "cache",
|
| 464 |
+
"query", "optimize", "rollback", "connection", "limit",
|
| 465 |
+
],
|
| 466 |
+
},
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
# ββ Cross-agent data βββββββββββββββββββββοΏ½οΏ½οΏ½ββββββββββββββββββββββββββββββββββββ
|
| 470 |
+
|
| 471 |
+
CROSS_AGENT_CHAIN_CASES = [
|
| 472 |
+
{
|
| 473 |
+
"id": "ca1",
|
| 474 |
+
"email": {
|
| 475 |
+
"subject": "Bug report: payment processor crashing",
|
| 476 |
+
"sender": "dev@company.com",
|
| 477 |
+
"body": "Our payment processor keeps throwing a ZeroDivisionError in production. See attached code snippet.",
|
| 478 |
+
},
|
| 479 |
+
"email_label": "important",
|
| 480 |
+
"code": (
|
| 481 |
+
"def calculate_fee(amount, rate):\n"
|
| 482 |
+
" return amount / rate\n\n"
|
| 483 |
+
"def process_payment(amount, fee_rate):\n"
|
| 484 |
+
" fee = calculate_fee(amount, fee_rate)\n"
|
| 485 |
+
" return amount - fee\n"
|
| 486 |
+
),
|
| 487 |
+
"code_bug": "zero_division",
|
| 488 |
+
"code_bug_keywords": ["zero", "division", "divide", "rate", "denominator", "fee_rate", "guard"],
|
| 489 |
+
"code_location": "calculate_fee",
|
| 490 |
+
},
|
| 491 |
+
{
|
| 492 |
+
"id": "ca2",
|
| 493 |
+
"email": {
|
| 494 |
+
"subject": "Weekly newsletter: Python tips",
|
| 495 |
+
"sender": "newsletter@pythonweekly.com",
|
| 496 |
+
"body": "This week: list comprehensions, async patterns, and a buggy code snippet for you to fix!",
|
| 497 |
+
},
|
| 498 |
+
"email_label": "newsletter",
|
| 499 |
+
"code": (
|
| 500 |
+
"def get_first(items):\n"
|
| 501 |
+
" return items[0]\n\n"
|
| 502 |
+
"def summarize(data):\n"
|
| 503 |
+
" total = sum(data)\n"
|
| 504 |
+
" first = get_first(data)\n"
|
| 505 |
+
" return total, first\n"
|
| 506 |
+
),
|
| 507 |
+
"code_bug": "empty_list",
|
| 508 |
+
"code_bug_keywords": ["empty", "index", "indexerror", "list", "bound", "check", "length", "len", "guard"],
|
| 509 |
+
"code_location": "get_first",
|
| 510 |
+
},
|
| 511 |
+
]
|
| 512 |
+
|
| 513 |
+
CROSS_AGENT_EMAIL_DATA_CASES = [
|
| 514 |
+
{
|
| 515 |
+
"id": "ced1",
|
| 516 |
+
"email": {
|
| 517 |
+
"subject": "[URGENT] Sales data export corrupted β board meeting tomorrow",
|
| 518 |
+
"sender": "vp-sales@company.com",
|
| 519 |
+
"body": "The Q4 sales export is corrupted. I need it fixed before the 9 AM board meeting tomorrow.",
|
| 520 |
+
},
|
| 521 |
+
"email_priority": "critical",
|
| 522 |
+
"attachment_data": [
|
| 523 |
+
{"id": 1, "rep": "Alice", "revenue": "150000", "region": "NORTH", "closed_date": "2024/12/31"},
|
| 524 |
+
{"id": 2, "rep": "bob", "revenue": "$80,000", "region": "south", "closed_date": "31-12-2024"},
|
| 525 |
+
{"id": 3, "rep": "Charlie","revenue": None, "region": "East", "closed_date": "2024-12-30"},
|
| 526 |
+
{"id": 4, "rep": "Diana", "revenue": "120000", "region": "WEST", "closed_date": "12/30/2024"},
|
| 527 |
+
],
|
| 528 |
+
"expected_issues": {"revenue": True, "region": True, "closed_date": True},
|
| 529 |
+
},
|
| 530 |
+
]
|
| 531 |
+
|
| 532 |
+
CROSS_AGENT_CODE_EMAIL_CASES = [
|
| 533 |
+
{
|
| 534 |
+
"id": "cce1",
|
| 535 |
+
"code": (
|
| 536 |
+
"def get_user_profile(user_id):\n"
|
| 537 |
+
" conn = sqlite3.connect('app.db')\n"
|
| 538 |
+
" query = \"SELECT * FROM users WHERE id = \" + user_id\n"
|
| 539 |
+
" cursor = conn.execute(query)\n"
|
| 540 |
+
" return cursor.fetchone()\n"
|
| 541 |
+
),
|
| 542 |
+
"primary_vulnerability": "sql_injection",
|
| 543 |
+
"location": "get_user_profile",
|
| 544 |
+
"disclosure_keywords": [
|
| 545 |
+
"sql injection", "parameterized", "vulnerability",
|
| 546 |
+
"security", "patch", "remediat", "fix", "urgent",
|
| 547 |
+
],
|
| 548 |
+
},
|
| 549 |
+
]
|
| 550 |
+
|
| 551 |
+
CROSS_AGENT_MOD_ESCALATION_CASES = [
|
| 552 |
+
{
|
| 553 |
+
"id": "cme1",
|
| 554 |
+
"post": {
|
| 555 |
+
"id": "p1",
|
| 556 |
+
"text": "I know where you live and I will make you regret this post.",
|
| 557 |
+
"platform": "social_media",
|
| 558 |
+
"context": "Reply to a political opinion post",
|
| 559 |
+
},
|
| 560 |
+
"correct_label": "harmful",
|
| 561 |
+
"should_escalate": True,
|
| 562 |
+
"notice_keywords": [
|
| 563 |
+
"violat", "policy", "removed", "community", "guidelines",
|
| 564 |
+
"threat", "harm", "prohibit",
|
| 565 |
+
],
|
| 566 |
+
},
|
| 567 |
+
{
|
| 568 |
+
"id": "cme2",
|
| 569 |
+
"post": {
|
| 570 |
+
"id": "p2",
|
| 571 |
+
"text": "Great recipe! Can't wait to try this at home.",
|
| 572 |
+
"platform": "cooking_forum",
|
| 573 |
+
"context": "Reply to a pasta recipe post",
|
| 574 |
+
},
|
| 575 |
+
"correct_label": "safe",
|
| 576 |
+
"should_escalate": False,
|
| 577 |
+
"notice_keywords": [],
|
| 578 |
+
},
|
| 579 |
+
]
|
| 580 |
+
|
| 581 |
+
|
| 582 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 583 |
+
# GRADERS
|
| 584 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 585 |
+
|
| 586 |
+
def grade_email_easy(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 587 |
+
correct = context["email"]["label"]
|
| 588 |
+
given = payload.get("classification", "").strip().lower()
|
| 589 |
+
valid = ["spam", "important", "newsletter"]
|
| 590 |
+
if given == correct:
|
| 591 |
+
return 1.0, f"[OK] Correct! '{correct}' is right.", {}
|
| 592 |
+
if given in valid:
|
| 593 |
+
return 0.0, f"[FAIL] Wrong. Expected '{correct}', got '{given}'.", {"classification": False}
|
| 594 |
+
return 0.0, f"[FAIL] Invalid label '{given}'. Must be: spam | important | newsletter.", {"classification": False}
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
def _kendall_tau_score(order: list, correct: list) -> float:
|
| 598 |
+
"""
|
| 599 |
+
Kendall's tau-based ordering score.
|
| 600 |
+
Counts concordant pairs (relative order matches) vs total pairs.
|
| 601 |
+
Much fairer than exact position matching β rewards getting the overall
|
| 602 |
+
priority ranking right even when a few adjacent items swap.
|
| 603 |
+
Score range: 0.0 (fully reversed) to 1.0 (perfect order).
|
| 604 |
+
"""
|
| 605 |
+
n = len(correct)
|
| 606 |
+
pos = {e: i for i, e in enumerate(correct)}
|
| 607 |
+
concordant = discordant = 0
|
| 608 |
+
for i in range(len(order)):
|
| 609 |
+
for j in range(i + 1, len(order)):
|
| 610 |
+
if order[i] not in pos or order[j] not in pos:
|
| 611 |
+
continue
|
| 612 |
+
if pos[order[i]] < pos[order[j]]:
|
| 613 |
+
concordant += 1
|
| 614 |
+
else:
|
| 615 |
+
discordant += 1
|
| 616 |
+
total = concordant + discordant
|
| 617 |
+
return concordant / total if total > 0 else 0.0
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
def grade_email_medium(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 621 |
+
order = payload.get("order", [])
|
| 622 |
+
correct = context.get("correct_order", EMAIL_MEDIUM_CORRECT_ORDER)
|
| 623 |
+
if not order:
|
| 624 |
+
return 0.0, "[FAIL] No order provided.", {}
|
| 625 |
+
top2_ok = set(order[:2]) == {"m1", "m5"}
|
| 626 |
+
top5_ok = order[:5] == correct[:5]
|
| 627 |
+
full_ok = order == correct
|
| 628 |
+
tau = _kendall_tau_score(order, correct)
|
| 629 |
+
hits = sum(1 for i, e in enumerate(order) if i < len(correct) and e == correct[i])
|
| 630 |
+
partial = {"top2_critical": top2_ok, "top5_correct": top5_ok, "full_order": full_ok}
|
| 631 |
+
if full_ok:
|
| 632 |
+
return 1.0, "[OK] Perfect prioritization!", partial
|
| 633 |
+
if top5_ok:
|
| 634 |
+
score = round(0.8 + tau * 0.2, 2)
|
| 635 |
+
return score, f"[PARTIAL] Top 5 correct. Kendall tau: {tau:.2f}.", partial
|
| 636 |
+
if top2_ok:
|
| 637 |
+
# Tau-based score: 0.5 minimum when criticals placed first, scaled up by pair agreement
|
| 638 |
+
score = round(max(0.5, tau), 2)
|
| 639 |
+
return score, f"[PARTIAL] Critical emails (m1, m5) first. {hits}/10 exact positions. Tau: {tau:.2f}.", partial
|
| 640 |
+
score = round(tau * 0.8, 2)
|
| 641 |
+
return score, f"[ERROR] {hits}/10 exact positions correct. Kendall tau: {tau:.2f}.", partial
|
| 642 |
+
|
| 643 |
+
|
| 644 |
+
def grade_email_hard(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 645 |
+
reply = payload.get("reply", "").lower()
|
| 646 |
+
case = context["email"]
|
| 647 |
+
expected = case.get("expected_elements", {})
|
| 648 |
+
checks: dict[str, bool] = {}
|
| 649 |
+
for key, keywords in expected.items():
|
| 650 |
+
if key == "professional_tone":
|
| 651 |
+
checks[key] = (
|
| 652 |
+
len(reply) > 80
|
| 653 |
+
and not any(w in reply for w in ["not my problem", "whatever", "too bad", "deal with it"])
|
| 654 |
+
)
|
| 655 |
+
else:
|
| 656 |
+
checks[key] = keywords is not None and any(w in reply for w in keywords)
|
| 657 |
+
passed = sum(checks.values())
|
| 658 |
+
total = len(checks)
|
| 659 |
+
score = round(passed / total, 2)
|
| 660 |
+
missing = [k for k, v in checks.items() if not v]
|
| 661 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.6 else "[ERROR]"
|
| 662 |
+
feedback = f"{tag} {passed}/{total} reply elements present."
|
| 663 |
+
if missing:
|
| 664 |
+
feedback += f" Missing: {', '.join(missing)}."
|
| 665 |
+
return score, feedback, checks
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
def grade_code_easy(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 669 |
+
errors = " ".join(str(e) for e in payload.get("errors", [])).lower()
|
| 670 |
+
keywords = context.get("keywords", ["colon", "syntax"])
|
| 671 |
+
found = any(k in errors for k in keywords)
|
| 672 |
+
if not payload.get("errors"):
|
| 673 |
+
return 0.0, "[FAIL] No errors reported.", {"syntax_error_found": False}
|
| 674 |
+
if found:
|
| 675 |
+
return 1.0, "[OK] Syntax error correctly identified.", {"syntax_error_found": True}
|
| 676 |
+
return 0.3, "[PARTIAL] Error mentioned but key issue not clearly described.", {"syntax_error_found": False}
|
| 677 |
+
|
| 678 |
+
|
| 679 |
+
def grade_code_medium(payload: dict) -> tuple[float, str, dict]:
|
| 680 |
+
bugs = payload.get("bugs", [])
|
| 681 |
+
checks = {"max_val_init": False, "assignment_vs_eq": False, "zero_division": False, "off_by_one": False}
|
| 682 |
+
for b in bugs:
|
| 683 |
+
t = (str(b.get("issue", "")) + str(b.get("location", "")) + str(b.get("fix", ""))).lower()
|
| 684 |
+
loc = str(b.get("location", "")).lower()
|
| 685 |
+
if loc == "find_max" or any(w in t for w in ["max_val", "negative", "minus", "zero init", "float('-inf')", "all-negative", "all negative", "initialization", "find_max"]):
|
| 686 |
+
checks["max_val_init"] = True
|
| 687 |
+
if loc == "get_user" or any(w in t for w in ["assignment", "==", "comparison", "= user_id", "single equal", "get_user"]):
|
| 688 |
+
checks["assignment_vs_eq"] = True
|
| 689 |
+
if loc == "divide" or any(w in t for w in ["zero", "division", "divide", "zerodivision", "b == 0", "divisor", "denominator", "div by zero"]):
|
| 690 |
+
checks["zero_division"] = True
|
| 691 |
+
if loc == "process_list" or any(w in t for w in ["off by one", "off-by-one", "index", "range", "len + 1", "indexerror", "out of bounds", "boundary", "len(items) + 1", "process_list"]):
|
| 692 |
+
checks["off_by_one"] = True
|
| 693 |
+
passed = sum(checks.values())
|
| 694 |
+
score = round(passed / 4, 2)
|
| 695 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 696 |
+
feedback = f"{tag} {passed}/4 bugs found."
|
| 697 |
+
missing = [k for k, v in checks.items() if not v]
|
| 698 |
+
if missing:
|
| 699 |
+
feedback += f" Missed: {', '.join(missing)}."
|
| 700 |
+
return score, feedback, checks
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
def grade_code_hard(payload: dict) -> tuple[float, str, dict]:
|
| 704 |
+
"""Now grading 8 vulnerabilities β significantly harder than before."""
|
| 705 |
+
vulns = payload.get("vulnerabilities", [])
|
| 706 |
+
checks = {
|
| 707 |
+
"sql_injection_get_user": False,
|
| 708 |
+
"xss_render_comment": False,
|
| 709 |
+
"sql_injection_login": False,
|
| 710 |
+
"command_injection": False,
|
| 711 |
+
"insecure_deserialization": False,
|
| 712 |
+
"timing_attack": False,
|
| 713 |
+
"path_traversal": False,
|
| 714 |
+
"ssrf": False,
|
| 715 |
+
}
|
| 716 |
+
for v in vulns:
|
| 717 |
+
vt = str(v.get("type", "")).lower()
|
| 718 |
+
loc = str(v.get("location", "")).lower()
|
| 719 |
+
if "get_user" in loc or "get_user_by_name" in loc:
|
| 720 |
+
checks["sql_injection_get_user"] = True
|
| 721 |
+
if ("render" in loc or "comment" in loc) and any(w in vt for w in ["xss", "cross", "script", "html", "inject", "sanitiz", "encod", "output"]):
|
| 722 |
+
checks["xss_render_comment"] = True
|
| 723 |
+
if "render" in loc and "comment" in loc:
|
| 724 |
+
checks["xss_render_comment"] = True
|
| 725 |
+
if "login" in loc:
|
| 726 |
+
checks["sql_injection_login"] = True
|
| 727 |
+
if "report" in loc and any(w in vt for w in ["command", "injection", "shell"]):
|
| 728 |
+
checks["command_injection"] = True
|
| 729 |
+
if "report" in loc and "run" in loc:
|
| 730 |
+
checks["command_injection"] = True
|
| 731 |
+
if "load" in loc and any(w in vt for w in ["pickle", "deserializ", "serial"]):
|
| 732 |
+
checks["insecure_deserialization"] = True
|
| 733 |
+
if "load_user" in loc:
|
| 734 |
+
checks["insecure_deserialization"] = True
|
| 735 |
+
if "check_password" in loc or any(w in vt for w in ["timing", "time", "constant"]):
|
| 736 |
+
checks["timing_attack"] = True
|
| 737 |
+
if "read_report" in loc or "path" in vt or "traversal" in vt or "directory" in vt:
|
| 738 |
+
checks["path_traversal"] = True
|
| 739 |
+
if "fetch" in loc or "avatar" in loc or "ssrf" in vt or "request_forgery" in vt:
|
| 740 |
+
checks["ssrf"] = True
|
| 741 |
+
passed = sum(checks.values())
|
| 742 |
+
score = round(passed / 8, 2)
|
| 743 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 744 |
+
feedback = f"{tag} {passed}/8 vulnerabilities found."
|
| 745 |
+
missing = [k for k, v in checks.items() if not v]
|
| 746 |
+
if missing:
|
| 747 |
+
feedback += f" Missed: {', '.join(missing)}."
|
| 748 |
+
return score, feedback, checks
|
| 749 |
+
|
| 750 |
+
|
| 751 |
+
def grade_data_easy(payload: dict) -> tuple[float, str, dict]:
|
| 752 |
+
missing_given = [str(m).lower() for m in payload.get("missing", [])]
|
| 753 |
+
dups_given = set(str(d) for d in payload.get("duplicates", []))
|
| 754 |
+
mc = {
|
| 755 |
+
"age_row2": any("age" in m and "2" in m for m in missing_given),
|
| 756 |
+
"name_row4": any("name" in m and "4" in m for m in missing_given),
|
| 757 |
+
"email_row5": any("email" in m and "5" in m for m in missing_given),
|
| 758 |
+
"salary_row6": any("salary" in m and "6" in m for m in missing_given),
|
| 759 |
+
}
|
| 760 |
+
dup_ok = dups_given in [{"1", "3"}, {"3", "1"}] or dups_given == {1, 3}
|
| 761 |
+
passed = sum(mc.values())
|
| 762 |
+
score = round((passed / 4 * 0.7) + (0.3 if dup_ok else 0.0), 2)
|
| 763 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 764 |
+
feedback = f"{tag} Missing: {passed}/4, Duplicates: {'[OK]' if dup_ok else '[FAIL]'}."
|
| 765 |
+
return score, feedback, {**mc, "duplicates_correct": dup_ok}
|
| 766 |
+
|
| 767 |
+
|
| 768 |
+
def grade_data_medium(payload: dict) -> tuple[float, str, dict]:
|
| 769 |
+
issues = {k.lower(): str(v).lower() for k, v in payload.get("issues", {}).items()}
|
| 770 |
+
cleaned = payload.get("cleaned_data", [])
|
| 771 |
+
checks = {
|
| 772 |
+
"age_type": (
|
| 773 |
+
"age" in issues and any(w in issues["age"] for w in [
|
| 774 |
+
"string", "integer", "int", "thirty", "type", "numeric",
|
| 775 |
+
"non-numeric", "word", "text", "convert", "mixed", "number",
|
| 776 |
+
"twenty", "str", "should be int", "not int", "invalid",
|
| 777 |
+
])
|
| 778 |
+
),
|
| 779 |
+
"salary_format": (
|
| 780 |
+
"salary" in issues and any(w in issues["salary"] for w in [
|
| 781 |
+
"format", "comma", "$", "inconsistent", "symbol", "currency",
|
| 782 |
+
"dollar", "sign", "strip", "remove", "clean",
|
| 783 |
+
])
|
| 784 |
+
),
|
| 785 |
+
"date_format": (
|
| 786 |
+
"join_date" in issues and any(w in issues["join_date"] for w in [
|
| 787 |
+
"format", "inconsistent", "date", "yyyy", "dd-mm",
|
| 788 |
+
"standardize", "iso", "different", "multiple",
|
| 789 |
+
])
|
| 790 |
+
),
|
| 791 |
+
"name_case": (
|
| 792 |
+
"name" in issues and any(w in issues["name"] for w in [
|
| 793 |
+
"capital", "case", "dave", "eve", "lower", "upper",
|
| 794 |
+
"casing", "title", "inconsistent", "normalize",
|
| 795 |
+
])
|
| 796 |
+
),
|
| 797 |
+
"active_type": (
|
| 798 |
+
"active" in issues and any(w in issues["active"] for w in [
|
| 799 |
+
"bool", "string", "yes", "true", "type", "mixed",
|
| 800 |
+
"boolean", "convert", "inconsistent", "str",
|
| 801 |
+
])
|
| 802 |
+
),
|
| 803 |
+
"data_cleaned": len(cleaned) == 5,
|
| 804 |
+
}
|
| 805 |
+
passed = sum(checks.values())
|
| 806 |
+
score = round(passed / 6, 2)
|
| 807 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 808 |
+
feedback = f"{tag} {passed}/6 data quality checks passed."
|
| 809 |
+
return score, feedback, checks
|
| 810 |
+
|
| 811 |
+
|
| 812 |
+
def grade_data_hard(payload: dict, context: dict = None) -> tuple[float, str, dict]:
|
| 813 |
+
outliers_given = set(int(x) for x in payload.get("outliers", []))
|
| 814 |
+
missing_given = set(int(x) for x in payload.get("missing", []))
|
| 815 |
+
cleaned = payload.get("cleaned_data", [])
|
| 816 |
+
answers = (context or {}).get("answers", DATA_HARD["answers"])
|
| 817 |
+
correct_out = answers.get("outliers", DATA_HARD["answers"]["outliers"])
|
| 818 |
+
correct_miss = answers.get("missing", DATA_HARD["answers"]["missing"])
|
| 819 |
+
lo, hi = answers.get("imputed_range", DATA_HARD["answers"]["imputed_range"])
|
| 820 |
+
outlier_ok = outliers_given == correct_out
|
| 821 |
+
missing_ok = missing_given == correct_miss
|
| 822 |
+
imputed_rows = {r["id"]: r.get("value") for r in cleaned if r.get("id") in correct_miss}
|
| 823 |
+
imputed_ok = (
|
| 824 |
+
all(v is not None and lo <= float(v) <= hi for v in imputed_rows.values())
|
| 825 |
+
and len(imputed_rows) == len(correct_miss)
|
| 826 |
+
)
|
| 827 |
+
checks = {"outliers_correct": outlier_ok, "missing_correct": missing_ok, "imputation_valid": imputed_ok}
|
| 828 |
+
passed = sum(checks.values())
|
| 829 |
+
score = round(passed / 3, 2)
|
| 830 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.67 else "[ERROR]"
|
| 831 |
+
feedback = f"{tag} {passed}/3 data operations correct."
|
| 832 |
+
return score, feedback, checks
|
| 833 |
+
|
| 834 |
+
|
| 835 |
+
def grade_mod_easy(payload: dict, context: dict = None) -> tuple[float, str, dict]:
|
| 836 |
+
clf = {c["id"]: c.get("label", "").lower() for c in payload.get("classifications", [])}
|
| 837 |
+
posts = (context or {}).get("posts", MOD_EASY)
|
| 838 |
+
correct = {p["id"]: p["label"] for p in posts}
|
| 839 |
+
per = {id_: clf.get(id_) == label for id_, label in correct.items()}
|
| 840 |
+
hits = sum(per.values())
|
| 841 |
+
score = round(hits / len(correct), 2)
|
| 842 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.7 else "[ERROR]"
|
| 843 |
+
return score, f"{tag} {hits}/{len(correct)} posts correctly classified.", per
|
| 844 |
+
|
| 845 |
+
|
| 846 |
+
def grade_mod_medium(payload: dict, context: dict = None) -> tuple[float, str, dict]:
|
| 847 |
+
clf = {c["id"]: c.get("label", "").lower() for c in payload.get("classifications", [])}
|
| 848 |
+
posts = (context or {}).get("posts", MOD_MEDIUM)
|
| 849 |
+
correct = {p["id"]: p["label"] for p in posts}
|
| 850 |
+
per = {id_: clf.get(id_) == label for id_, label in correct.items()}
|
| 851 |
+
hits = sum(per.values())
|
| 852 |
+
score = round(hits / len(correct), 2)
|
| 853 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.6 else "[ERROR]"
|
| 854 |
+
return score, f"{tag} {hits}/{len(correct)} subtle posts correctly classified.", per
|
| 855 |
+
|
| 856 |
+
|
| 857 |
+
def grade_mod_hard(payload: dict) -> tuple[float, str, dict]:
|
| 858 |
+
"""Now 6 cases (12 checks) β adversarial dog-whistles included."""
|
| 859 |
+
decisions = {d["id"]: d for d in payload.get("decisions", [])}
|
| 860 |
+
checks: dict[str, bool] = {}
|
| 861 |
+
for case in MOD_HARD:
|
| 862 |
+
cid = case["id"]
|
| 863 |
+
d = decisions.get(cid, {})
|
| 864 |
+
checks[f"{cid}_context_a"] = d.get("context_a_label", "").lower() == case["label_a"]
|
| 865 |
+
checks[f"{cid}_context_b"] = d.get("context_b_label", "").lower() == case["label_b"]
|
| 866 |
+
passed = sum(checks.values())
|
| 867 |
+
total = len(checks)
|
| 868 |
+
score = round(passed / total, 2)
|
| 869 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 870 |
+
return score, f"{tag} {passed}/{total} context-aware decisions correct.", checks
|
| 871 |
+
|
| 872 |
+
|
| 873 |
+
def grade_ticket_easy(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 874 |
+
ticket = context["ticket"]
|
| 875 |
+
p_given = payload.get("priority", "").strip().lower()
|
| 876 |
+
c_given = payload.get("category", "").strip().lower()
|
| 877 |
+
p_ok = p_given == ticket["correct_priority"]
|
| 878 |
+
c_ok = c_given == ticket["correct_category"]
|
| 879 |
+
checks = {"priority_correct": p_ok, "category_correct": c_ok}
|
| 880 |
+
score = round(sum(checks.values()) / 2, 2)
|
| 881 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score == 0.5 else "[ERROR]"
|
| 882 |
+
p_str = "β" if p_ok else f"β (expected {ticket['correct_priority']})"
|
| 883 |
+
c_str = "β" if c_ok else f"β (expected {ticket['correct_category']})"
|
| 884 |
+
feedback = f"{tag} Priority: {p_str}, Category: {c_str}"
|
| 885 |
+
return score, feedback, checks
|
| 886 |
+
|
| 887 |
+
|
| 888 |
+
def grade_ticket_medium(payload: dict) -> tuple[float, str, dict]:
|
| 889 |
+
order = payload.get("order", [])
|
| 890 |
+
assigns = payload.get("assignments", {})
|
| 891 |
+
correct_order = TICKET_MEDIUM_CORRECT_ORDER
|
| 892 |
+
correct_teams = TICKET_MEDIUM_CORRECT_TEAMS
|
| 893 |
+
# Order grading via Kendall tau (60% weight)
|
| 894 |
+
tau = _kendall_tau_score(order, correct_order)
|
| 895 |
+
top3_ok = order[:3] == correct_order[:3]
|
| 896 |
+
full_ok = order == correct_order
|
| 897 |
+
hits = sum(1 for i, e in enumerate(order) if i < len(correct_order) and e == correct_order[i])
|
| 898 |
+
order_score = 1.0 if full_ok else round(tau, 2)
|
| 899 |
+
# Team assignment grading (40% weight) β per-ticket breakdown
|
| 900 |
+
team_checks = {tid: assigns.get(tid) == team for tid, team in correct_teams.items()}
|
| 901 |
+
team_hits = sum(team_checks.values())
|
| 902 |
+
team_score = round(team_hits / len(correct_teams), 2)
|
| 903 |
+
wrong_teams = [f"{tid}(got={assigns.get(tid)},expected={correct_teams[tid]})"
|
| 904 |
+
for tid, ok in team_checks.items() if not ok]
|
| 905 |
+
score = round(order_score * 0.6 + team_score * 0.4, 2)
|
| 906 |
+
checks = {"top3_correct": top3_ok, "full_order": full_ok,
|
| 907 |
+
"team_assignments": team_score == 1.0, **{f"team_{tid}": ok for tid, ok in team_checks.items()}}
|
| 908 |
+
tag = "[OK]" if score >= 0.95 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 909 |
+
feedback = f"{tag} Order: {hits}/8 exact, Tau: {tau:.2f}, Teams: {team_hits}/8 correct."
|
| 910 |
+
if wrong_teams:
|
| 911 |
+
feedback += f" Wrong teams: {', '.join(wrong_teams)}."
|
| 912 |
+
return score, feedback, checks
|
| 913 |
+
|
| 914 |
+
|
| 915 |
+
def grade_ticket_hard(payload: dict) -> tuple[float, str, dict]:
|
| 916 |
+
root_cause = payload.get("root_cause", "").lower()
|
| 917 |
+
resolution = [step.lower() for step in payload.get("resolution_steps", [])]
|
| 918 |
+
affected = {s.lower() for s in payload.get("affected_services", [])}
|
| 919 |
+
severity = payload.get("severity", "").upper()
|
| 920 |
+
expected = TICKET_HARD_INCIDENT["expected"]
|
| 921 |
+
# Root cause check
|
| 922 |
+
rc_hits = sum(1 for kw in expected["root_cause_keywords"] if kw in root_cause)
|
| 923 |
+
rc_ok = rc_hits >= 3
|
| 924 |
+
# Affected services
|
| 925 |
+
expected_svcs = {s.lower() for s in expected["affected_services"]}
|
| 926 |
+
svc_hits = len(affected & expected_svcs)
|
| 927 |
+
svc_ok = svc_hits >= 3
|
| 928 |
+
# Severity
|
| 929 |
+
sev_ok = severity == expected["severity"]
|
| 930 |
+
# Resolution steps
|
| 931 |
+
res_text = " ".join(resolution)
|
| 932 |
+
res_hits = sum(1 for kw in expected["resolution_keywords"] if kw in res_text)
|
| 933 |
+
res_ok = res_hits >= 3 and len(resolution) >= 3
|
| 934 |
+
checks = {"root_cause_ok": rc_ok, "affected_services_ok": svc_ok, "severity_ok": sev_ok, "resolution_ok": res_ok}
|
| 935 |
+
passed = sum(checks.values())
|
| 936 |
+
score = round(passed / 4, 2)
|
| 937 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.5 else "[ERROR]"
|
| 938 |
+
feedback = f"{tag} {passed}/4 incident analysis checks passed. RC keywords: {rc_hits}, Svcs: {svc_hits}/4, Res steps: {len(resolution)}."
|
| 939 |
+
return score, feedback, checks
|
| 940 |
+
|
| 941 |
+
|
| 942 |
+
def grade_cross_agent_chain(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 943 |
+
case = context["case"]
|
| 944 |
+
email_given = payload.get("email_classification", "").strip().lower()
|
| 945 |
+
email_ok = email_given == case["email_label"]
|
| 946 |
+
bugs = payload.get("bugs", [])
|
| 947 |
+
bug_ok = False
|
| 948 |
+
for b in bugs:
|
| 949 |
+
t = (str(b.get("issue", "")) + str(b.get("location", "")) + str(b.get("fix", ""))).lower()
|
| 950 |
+
loc = str(b.get("location", "")).lower()
|
| 951 |
+
if loc == case["code_location"].lower() or any(w in t for w in case["code_bug_keywords"]):
|
| 952 |
+
bug_ok = True
|
| 953 |
+
break
|
| 954 |
+
checks = {"email_correct": email_ok, "bug_found": bug_ok}
|
| 955 |
+
passed = sum(checks.values())
|
| 956 |
+
score = round(passed / 2, 2)
|
| 957 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score == 0.5 else "[ERROR]"
|
| 958 |
+
feedback = f"{tag} {passed}/2 cross-agent checks passed."
|
| 959 |
+
if not email_ok:
|
| 960 |
+
feedback += f" Email: expected '{case['email_label']}', got '{email_given}'."
|
| 961 |
+
if not bug_ok:
|
| 962 |
+
feedback += f" Code bug in '{case['code_location']}' not identified."
|
| 963 |
+
return score, feedback, checks
|
| 964 |
+
|
| 965 |
+
|
| 966 |
+
def grade_cross_agent_email_data(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 967 |
+
case = context["case"]
|
| 968 |
+
priority = payload.get("email_priority", "").strip().lower()
|
| 969 |
+
raw_issues = payload.get("data_issues", {})
|
| 970 |
+
cleaned = payload.get("cleaned_data", [])
|
| 971 |
+
priority_ok = priority == case["email_priority"].lower()
|
| 972 |
+
expected_issues = case["expected_issues"]
|
| 973 |
+
|
| 974 |
+
# Handle two formats models use:
|
| 975 |
+
# Format A (correct): {"revenue": "inconsistent...", "region": "casing..."}
|
| 976 |
+
# Format B (wrong): {"field": "revenue", "issue": "inconsistent..."}
|
| 977 |
+
if isinstance(raw_issues, dict):
|
| 978 |
+
# Check if it's format B β has "field" key instead of field names as keys
|
| 979 |
+
if "field" in raw_issues and "issue" in raw_issues:
|
| 980 |
+
# Convert: extract the field name from the "field" value
|
| 981 |
+
field_val = str(raw_issues.get("field", "")).lower()
|
| 982 |
+
issue_val = str(raw_issues.get("issue", "")).lower()
|
| 983 |
+
data_issues = {field_val: issue_val}
|
| 984 |
+
else:
|
| 985 |
+
data_issues = {k.lower(): str(v).lower() for k, v in raw_issues.items()}
|
| 986 |
+
elif isinstance(raw_issues, list):
|
| 987 |
+
# Format C: [{"field": "revenue", "issue": "..."}]
|
| 988 |
+
data_issues = {}
|
| 989 |
+
for item in raw_issues:
|
| 990 |
+
if isinstance(item, dict):
|
| 991 |
+
f = str(item.get("field", "")).lower()
|
| 992 |
+
v = str(item.get("issue", item.get("description", ""))).lower()
|
| 993 |
+
if f:
|
| 994 |
+
data_issues[f] = v
|
| 995 |
+
else:
|
| 996 |
+
data_issues = {}
|
| 997 |
+
|
| 998 |
+
issue_hits = sum(1 for field in expected_issues if field in data_issues)
|
| 999 |
+
issues_ok = issue_hits >= 2
|
| 1000 |
+
cleaned_ok = len(cleaned) == len(case["attachment_data"])
|
| 1001 |
+
checks = {"priority_correct": priority_ok, "issues_identified": issues_ok, "data_cleaned": cleaned_ok}
|
| 1002 |
+
passed = sum(checks.values())
|
| 1003 |
+
score = round(passed / 3, 2)
|
| 1004 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.67 else "[ERROR]"
|
| 1005 |
+
feedback = f"{tag} {passed}/3 cross-agent email+data checks passed."
|
| 1006 |
+
if not issues_ok:
|
| 1007 |
+
feedback += f" Issues found: {issue_hits}/{len(expected_issues)} fields identified."
|
| 1008 |
+
return score, feedback, checks
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
def grade_cross_agent_code_email(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 1012 |
+
case = context["case"]
|
| 1013 |
+
vuln_type = payload.get("vulnerability_type", "").lower()
|
| 1014 |
+
location = payload.get("vulnerability_location", "").lower()
|
| 1015 |
+
email = payload.get("disclosure_email", "").lower()
|
| 1016 |
+
type_ok = case["primary_vulnerability"] in vuln_type or "sql" in vuln_type
|
| 1017 |
+
loc_ok = case["location"].lower() in location
|
| 1018 |
+
email_hits = sum(1 for kw in case["disclosure_keywords"] if kw in email)
|
| 1019 |
+
email_ok = email_hits >= 4 and len(email) >= 80
|
| 1020 |
+
checks = {"vulnerability_identified": type_ok, "location_correct": loc_ok, "disclosure_email_quality": email_ok}
|
| 1021 |
+
passed = sum(checks.values())
|
| 1022 |
+
score = round(passed / 3, 2)
|
| 1023 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.67 else "[ERROR]"
|
| 1024 |
+
feedback = f"{tag} {passed}/3 code+email checks passed. Email keywords: {email_hits}."
|
| 1025 |
+
return score, feedback, checks
|
| 1026 |
+
|
| 1027 |
+
|
| 1028 |
+
def grade_cross_agent_mod_escalation(payload: dict, context: dict) -> tuple[float, str, dict]:
|
| 1029 |
+
case = context["case"]
|
| 1030 |
+
label_given = payload.get("content_label", "").strip().lower()
|
| 1031 |
+
escalate = payload.get("escalate", None)
|
| 1032 |
+
notice = payload.get("moderation_notice", "").lower()
|
| 1033 |
+
label_ok = label_given == case["correct_label"]
|
| 1034 |
+
escalate_ok = escalate == case["should_escalate"]
|
| 1035 |
+
if case["should_escalate"]:
|
| 1036 |
+
notice_hits = sum(1 for kw in case["notice_keywords"] if kw in notice)
|
| 1037 |
+
notice_ok = notice_hits >= 3
|
| 1038 |
+
else:
|
| 1039 |
+
notice_ok = len(notice) == 0 or notice.strip() == ""
|
| 1040 |
+
checks = {"label_correct": label_ok, "escalation_correct": escalate_ok, "notice_quality": notice_ok}
|
| 1041 |
+
passed = sum(checks.values())
|
| 1042 |
+
score = round(passed / 3, 2)
|
| 1043 |
+
tag = "[OK]" if score == 1.0 else "[PARTIAL]" if score >= 0.67 else "[ERROR]"
|
| 1044 |
+
feedback = f"{tag} {passed}/3 moderation+escalation checks passed."
|
| 1045 |
+
return score, feedback, checks
|
| 1046 |
+
|
| 1047 |
+
|
| 1048 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1049 |
+
# INSTRUCTIONS
|
| 1050 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1051 |
+
|
| 1052 |
+
INSTRUCTIONS: dict[str, str] = {
|
| 1053 |
+
"email_triage_easy": (
|
| 1054 |
+
"Classify the email as exactly one of: 'spam', 'important', or 'newsletter'.\n"
|
| 1055 |
+
'Payload: {"classification": "spam"}'
|
| 1056 |
+
),
|
| 1057 |
+
"email_triage_medium": (
|
| 1058 |
+
"Prioritize the 10 emails from most urgent (first) to least urgent (last).\n"
|
| 1059 |
+
"Priority tiers β assign each email to a tier first, then order within tiers by deadline/impact:\n"
|
| 1060 |
+
" Tier 1 (CRITICAL): Active production outages, active security breaches β immediate revenue/safety impact\n"
|
| 1061 |
+
" Tier 2 (URGENT): Legal agreements or contracts expiring TODAY β deal falls through if unsigned\n"
|
| 1062 |
+
" Tier 3 (HIGH): Overdue financial obligations threatening service suspension\n"
|
| 1063 |
+
" Tier 4 (HIGH): Business deliverables with hard deadlines this week\n"
|
| 1064 |
+
" Tier 5 (LOW): Social β lunch invites, work anniversaries\n"
|
| 1065 |
+
" Tier 6 (IGNORE): Passive newsletters and promotional content\n"
|
| 1066 |
+
"Within Tier 2: 'TODAY' deadlines rank above 'Friday' deadlines.\n"
|
| 1067 |
+
"Within Tier 4: launch review decks rank above other reviews by proximity to launch date.\n"
|
| 1068 |
+
"Return ALL 10 email IDs ordered most-to-least urgent.\n"
|
| 1069 |
+
'Payload: {"order": ["m1", "m5", ...]}'
|
| 1070 |
+
),
|
| 1071 |
+
"email_triage_hard": (
|
| 1072 |
+
"Draft a professional, empathetic reply to the customer complaint. Address ALL concerns.\n"
|
| 1073 |
+
'Payload: {"reply": "<full reply text>"}'
|
| 1074 |
+
),
|
| 1075 |
+
"code_review_easy": (
|
| 1076 |
+
"Identify the syntax error(s) in the Python code.\n"
|
| 1077 |
+
'Payload: {"errors": ["description of syntax error"]}'
|
| 1078 |
+
),
|
| 1079 |
+
"code_review_medium": (
|
| 1080 |
+
"Find ALL 4 logical bugs β one per function. Report location, issue, and fix.\n"
|
| 1081 |
+
" 1. find_max β wrong initialization (fails all-negative lists)\n"
|
| 1082 |
+
" 2. get_user β = used instead of ==\n"
|
| 1083 |
+
" 3. divide β no zero-division guard\n"
|
| 1084 |
+
" 4. process_list β off-by-one in range\n"
|
| 1085 |
+
'Payload: {"bugs": [{"location": "fn_name", "issue": "...", "fix": "..."}]}'
|
| 1086 |
+
),
|
| 1087 |
+
"code_review_hard": (
|
| 1088 |
+
"Identify ALL 8 security vulnerabilities:\n"
|
| 1089 |
+
" 1. sql_injection in get_user_by_name\n"
|
| 1090 |
+
" 2. xss in render_comment\n"
|
| 1091 |
+
" 3. sql_injection in login\n"
|
| 1092 |
+
" 4. command_injection in run_report\n"
|
| 1093 |
+
" 5. insecure_deserialization in load_user_data\n"
|
| 1094 |
+
" 6. timing_attack in check_password\n"
|
| 1095 |
+
" 7. path_traversal in read_report_file\n"
|
| 1096 |
+
" 8. ssrf in fetch_user_avatar\n"
|
| 1097 |
+
'Payload: {"vulnerabilities": [{"type": "...", "location": "...", "severity": "...", "fix": "..."}]}'
|
| 1098 |
+
),
|
| 1099 |
+
"data_cleaning_easy": (
|
| 1100 |
+
"Find all missing (null) values by field+row, and identify duplicate row IDs.\n"
|
| 1101 |
+
'Payload: {"missing": ["age (row 2)", "name (row 4)"], "duplicates": [1, 3]}'
|
| 1102 |
+
),
|
| 1103 |
+
"data_cleaning_medium": (
|
| 1104 |
+
"Identify all 5 data quality issues and return the fully cleaned dataset (5 rows).\n"
|
| 1105 |
+
'Payload: {"issues": {"age": "mixed types..."}, "cleaned_data": [...]}'
|
| 1106 |
+
),
|
| 1107 |
+
"data_cleaning_hard": (
|
| 1108 |
+
"Detect outliers (IQR/z-score), find missing values, impute, return cleaned dataset.\n"
|
| 1109 |
+
'Payload: {"outliers": [4, 8], "missing": [6, 11], "cleaned_data": [...]}'
|
| 1110 |
+
),
|
| 1111 |
+
"content_moderation_easy": (
|
| 1112 |
+
"Classify each post as 'safe' or 'harmful'.\n"
|
| 1113 |
+
'Payload: {"classifications": [{"id": "c1", "label": "harmful"}]}'
|
| 1114 |
+
),
|
| 1115 |
+
"content_moderation_medium": (
|
| 1116 |
+
"Classify each post as 'safe' or 'harmful'. Watch for sarcasm, subtle toxicity, implicit hostility.\n"
|
| 1117 |
+
"IMPORTANT DISTINCTION β not all unusual phrasing is harmful:\n"
|
| 1118 |
+
" HARMFUL: sarcasm that mocks or demeans a person ('Classic genius move', 'brilliant idea')\n"
|
| 1119 |
+
" HARMFUL: passive aggression that excludes or belittles ('not cut out for this', 'shouldn't have opinions')\n"
|
| 1120 |
+
" SAFE: mild, ambiguous comments that express a personal reaction without attacking anyone\n"
|
| 1121 |
+
" SAFE: backhanded compliments about THINGS (not people) β 'very unique' about a presentation is\n"
|
| 1122 |
+
" just a mild opinion, not an attack on a person. No target = not harmful.\n"
|
| 1123 |
+
" SAFE: genuine positivity ('Congrats!', 'Thanks for the feedback')\n"
|
| 1124 |
+
'Payload: {"classifications": [{"id": "m1", "label": "harmful", "reason": "..."}]}'
|
| 1125 |
+
),
|
| 1126 |
+
"content_moderation_hard": (
|
| 1127 |
+
"Each case shows the SAME text in TWO different contexts. Determine the correct label for EACH context.\n"
|
| 1128 |
+
"CRITICAL RULES:\n"
|
| 1129 |
+
" 1. Some texts are ALWAYS harmful regardless of context β they use dehumanizing language or\n"
|
| 1130 |
+
" dog-whistle rhetoric that is harmful no matter what platform or prior message surrounds them.\n"
|
| 1131 |
+
" TEST: Would a reasonable content moderator flag this text if they saw it with NO context?\n"
|
| 1132 |
+
" If yes, label it 'harmful' in BOTH contexts.\n"
|
| 1133 |
+
" 2. KEY SIGNAL β comparing humans to animals ('breed like rabbits'), invoking 'replacement' of\n"
|
| 1134 |
+
" ethnic groups, or using language that strips human dignity is ALWAYS harmful. A 'biology\n"
|
| 1135 |
+
" classroom' or 'urban planning' framing does NOT make dehumanizing rhetoric acceptable.\n"
|
| 1136 |
+
" 3. Other texts ARE context-dependent β 'disappear' in gaming vs crisis support, 'take out' in\n"
|
| 1137 |
+
" HR dinner vs extremist forum, 'eliminate the competition' in business vs political violence.\n"
|
| 1138 |
+
" For these, label each context based on the actual meaning in that specific context.\n"
|
| 1139 |
+
" 4. When in doubt about a text: ask 'is this language that treats a group of people as less than\n"
|
| 1140 |
+
" human?' If yes β harmful in all contexts. If it's just ambiguous phrasing β context matters.\n"
|
| 1141 |
+
'Payload: {"decisions": [{"id": "h1", "context_a_label": "safe", "context_b_label": "harmful"}]}'
|
| 1142 |
+
),
|
| 1143 |
+
"ticket_triage_easy": (
|
| 1144 |
+
"Classify the support ticket: assign a priority (critical/high/medium/low) and category (bug/feature_request/question/billing).\n"
|
| 1145 |
+
'Payload: {"priority": "critical", "category": "bug"}'
|
| 1146 |
+
),
|
| 1147 |
+
"ticket_triage_medium": (
|
| 1148 |
+
"Order 8 tickets by priority (most urgent first) and assign each to the correct team.\n"
|
| 1149 |
+
"Priority reasoning:\n"
|
| 1150 |
+
" 1. Production DB down and SSL expiring in 48h are both critical infrastructure\n"
|
| 1151 |
+
" 2. Currency display bug (affects all EU users visibly) ranks above billing revenue leak\n"
|
| 1152 |
+
" 3. Revenue-leak billing bug ranks above invoice PDF generation bug\n"
|
| 1153 |
+
" 4. Feature requests rank below all bugs\n"
|
| 1154 |
+
"Team routing β be precise about these common mistakes:\n"
|
| 1155 |
+
" backend = server-side APIs, DB queries, background job processing\n"
|
| 1156 |
+
" frontend = anything the user SEES in the browser: currency symbols, UI, display bugs, CSV export UI\n"
|
| 1157 |
+
" devops = SSL certificates, deployments, infrastructure\n"
|
| 1158 |
+
" billing = payment charges, subscription billing, invoice amounts, refunds\n"
|
| 1159 |
+
" 'Wrong currency symbol shown' β frontend (it's a display/rendering bug)\n"
|
| 1160 |
+
" 'Customer billed $0 instead of $299' β billing (it's a charge calculation bug)\n"
|
| 1161 |
+
" 'Invoice PDF generation fails' β backend (server-side PDF generation)\n"
|
| 1162 |
+
" 'Add OAuth2 SSO support' β backend (auth server logic)\n"
|
| 1163 |
+
" 'Redesign onboarding flow' β frontend (UI/UX work)\n"
|
| 1164 |
+
" 'Add CSV export for analytics' β frontend (UI feature)\n"
|
| 1165 |
+
'Payload: {"order": ["tk1", "tk4", "tk2", "tk7", "tk5", "tk3", "tk6", "tk8"], '
|
| 1166 |
+
'"assignments": {"tk1": "backend", "tk2": "frontend", "tk3": "backend", '
|
| 1167 |
+
'"tk4": "devops", "tk5": "backend", "tk6": "frontend", "tk7": "billing", "tk8": "frontend"}}'
|
| 1168 |
+
),
|
| 1169 |
+
"ticket_triage_hard": (
|
| 1170 |
+
"Analyse the linked incident tickets and produce: root cause analysis, ordered resolution steps, affected services, and severity (P1-P4).\n"
|
| 1171 |
+
'Payload: {"root_cause": "...", "resolution_steps": ["...", "..."], "affected_services": ["...", "..."], "severity": "P1"}'
|
| 1172 |
+
),
|
| 1173 |
+
"cross_agent_chain": (
|
| 1174 |
+
"TWO skills needed:\n"
|
| 1175 |
+
" 1. Classify the email: 'spam', 'important', or 'newsletter'\n"
|
| 1176 |
+
" 2. Find the main bug in the attached code (location + issue + fix)\n"
|
| 1177 |
+
'Payload: {"email_classification": "important", "bugs": [{"location": "fn", "issue": "...", "fix": "..."}]}'
|
| 1178 |
+
),
|
| 1179 |
+
"cross_agent_email_data": (
|
| 1180 |
+
"TWO skills needed:\n"
|
| 1181 |
+
" 1. Classify the email urgency: 'critical', 'high', 'medium', 'low'\n"
|
| 1182 |
+
" 2. Identify data quality issues in the attachment and return cleaned data\n"
|
| 1183 |
+
"IMPORTANT: data_issues must use field names as keys, NOT 'field'/'issue' as keys.\n"
|
| 1184 |
+
" CORRECT: {\"revenue\": \"inconsistent formats\", \"region\": \"inconsistent casing\", \"closed_date\": \"multiple date formats\"}\n"
|
| 1185 |
+
" INCORRECT: {\"field\": \"revenue\", \"issue\": \"inconsistent formats\"}\n"
|
| 1186 |
+
'Payload: {"email_priority": "critical", "data_issues": {"revenue": "inconsistent formats", "region": "casing issues", "closed_date": "multiple formats"}, "cleaned_data": [...]}'
|
| 1187 |
+
),
|
| 1188 |
+
"cross_agent_code_email": (
|
| 1189 |
+
"TWO skills needed:\n"
|
| 1190 |
+
" 1. Identify the primary security vulnerability type and its location\n"
|
| 1191 |
+
" 2. Draft a professional security disclosure email to the dev team (min 80 chars)\n"
|
| 1192 |
+
'Payload: {"vulnerability_type": "sql_injection", "vulnerability_location": "fn_name", "disclosure_email": "..."}'
|
| 1193 |
+
),
|
| 1194 |
+
"cross_agent_mod_escalation": (
|
| 1195 |
+
"TWO skills needed:\n"
|
| 1196 |
+
" 1. Classify the content as 'safe' or 'harmful'\n"
|
| 1197 |
+
" 2. Decide whether to escalate (true/false) and draft a moderation notice if harmful (empty string if safe)\n"
|
| 1198 |
+
'Payload: {"content_label": "harmful", "escalate": true, "moderation_notice": "Your post was removed because..."}'
|
| 1199 |
+
),
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
+
|
| 1203 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1204 |
+
# ENVIRONMENT
|
| 1205 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1206 |
+
|
| 1207 |
+
class MetaEnvironment(Environment):
|
| 1208 |
+
"""
|
| 1209 |
+
Meta Multi-Agent Environment v3.0
|
| 1210 |
+
5 domains Β· 16 tasks Β· 4 cross-agent chained tasks Β· deterministic context loading
|
| 1211 |
+
"""
|
| 1212 |
+
SUPPORTS_CONCURRENT_SESSIONS: bool = True
|
| 1213 |
+
|
| 1214 |
+
def __init__(self) -> None:
|
| 1215 |
+
self._state = State(episode_id=str(uuid4()), step_count=0)
|
| 1216 |
+
self._current_task_id: str | None = None
|
| 1217 |
+
self._current_agent_context: dict = {}
|
| 1218 |
+
self._current_grader_context: dict = {}
|
| 1219 |
+
self._episode_scores: list[float] = []
|
| 1220 |
+
self._attempt_counts: dict[str, int] = {}
|
| 1221 |
+
self._first_attempt_scores: dict[str, float] = {}
|
| 1222 |
+
|
| 1223 |
+
def reset(self) -> MetaObservation:
|
| 1224 |
+
self._state = State(episode_id=str(uuid4()), step_count=0)
|
| 1225 |
+
self._current_task_id = None
|
| 1226 |
+
self._current_agent_context = {}
|
| 1227 |
+
self._current_grader_context = {}
|
| 1228 |
+
self._episode_scores = []
|
| 1229 |
+
self._attempt_counts = {}
|
| 1230 |
+
self._first_attempt_scores = {}
|
| 1231 |
+
return MetaObservation(
|
| 1232 |
+
agent="meta",
|
| 1233 |
+
task_id="",
|
| 1234 |
+
difficulty="",
|
| 1235 |
+
context={},
|
| 1236 |
+
instructions=(
|
| 1237 |
+
"Welcome to Meta Multi-Agent Environment v3.0!\n"
|
| 1238 |
+
"5 agents Γ 3 tasks + 4 cross-agent tasks = 19 tasks total.\n"
|
| 1239 |
+
"Agents: email_triage | code_review | data_cleaning | "
|
| 1240 |
+
"content_moderation | ticket_triage | cross_agent\n"
|
| 1241 |
+
"Use GET /tasks to see all task IDs and exact payload schemas.\n"
|
| 1242 |
+
"Multi-step episodes: imperfect first attempts earn a retry."
|
| 1243 |
+
),
|
| 1244 |
+
feedback="Environment reset. Ready for episode.",
|
| 1245 |
+
score=0.0,
|
| 1246 |
+
partial_credits={},
|
| 1247 |
+
done=False,
|
| 1248 |
+
reward=0.0,
|
| 1249 |
+
)
|
| 1250 |
+
|
| 1251 |
+
def _deterministic_choice(self, variants: list, task_id: str) -> Any:
|
| 1252 |
+
"""Pick a variant deterministically based on episode_id hash."""
|
| 1253 |
+
idx = hash(self._state.episode_id + task_id) % len(variants)
|
| 1254 |
+
return variants[idx]
|
| 1255 |
+
|
| 1256 |
+
def _load_context(self, task_id: str) -> tuple[dict, dict, str]:
|
| 1257 |
+
"""Return (agent_ctx, grader_ctx, difficulty). Fully deterministic."""
|
| 1258 |
+
if task_id == "email_triage_easy":
|
| 1259 |
+
agent_ctx, grader_ctx = gen_email_easy(self._state.episode_id)
|
| 1260 |
+
return agent_ctx, grader_ctx, "easy"
|
| 1261 |
+
|
| 1262 |
+
if task_id == "email_triage_medium":
|
| 1263 |
+
agent_ctx, grader_ctx = gen_email_medium(self._state.episode_id)
|
| 1264 |
+
return agent_ctx, grader_ctx, "medium"
|
| 1265 |
+
|
| 1266 |
+
if task_id == "email_triage_hard":
|
| 1267 |
+
agent_ctx, grader_ctx = gen_email_hard(self._state.episode_id)
|
| 1268 |
+
return agent_ctx, grader_ctx, "hard"
|
| 1269 |
+
|
| 1270 |
+
if task_id == "code_review_easy":
|
| 1271 |
+
agent_ctx, grader_ctx = gen_code_easy(self._state.episode_id)
|
| 1272 |
+
return agent_ctx, grader_ctx, "easy"
|
| 1273 |
+
|
| 1274 |
+
if task_id == "code_review_medium":
|
| 1275 |
+
ctx = {"code": CODE_MEDIUM["code"]}
|
| 1276 |
+
return ctx, ctx, "medium"
|
| 1277 |
+
|
| 1278 |
+
if task_id == "code_review_hard":
|
| 1279 |
+
ctx = {"code": CODE_HARD["code"]}
|
| 1280 |
+
return ctx, ctx, "hard"
|
| 1281 |
+
|
| 1282 |
+
if task_id == "data_cleaning_easy":
|
| 1283 |
+
agent_ctx, grader_ctx = gen_data_easy(self._state.episode_id)
|
| 1284 |
+
return agent_ctx, grader_ctx, "easy"
|
| 1285 |
+
|
| 1286 |
+
if task_id == "data_cleaning_medium":
|
| 1287 |
+
agent_ctx, grader_ctx = gen_data_medium(self._state.episode_id)
|
| 1288 |
+
return agent_ctx, grader_ctx, "medium"
|
| 1289 |
+
|
| 1290 |
+
if task_id == "data_cleaning_hard":
|
| 1291 |
+
agent_ctx, grader_ctx = gen_data_hard(self._state.episode_id)
|
| 1292 |
+
return agent_ctx, grader_ctx, "hard"
|
| 1293 |
+
|
| 1294 |
+
if task_id == "content_moderation_easy":
|
| 1295 |
+
agent_ctx, grader_ctx = gen_mod_easy(self._state.episode_id)
|
| 1296 |
+
return agent_ctx, grader_ctx, "easy"
|
| 1297 |
+
|
| 1298 |
+
if task_id == "content_moderation_medium":
|
| 1299 |
+
agent_ctx, grader_ctx = gen_mod_medium(self._state.episode_id)
|
| 1300 |
+
return agent_ctx, grader_ctx, "medium"
|
| 1301 |
+
|
| 1302 |
+
if task_id == "content_moderation_hard":
|
| 1303 |
+
agent_ctx = {"cases": [{k: v for k, v in c.items() if k not in ("label_a", "label_b", "reason_a", "reason_b")} for c in MOD_HARD]}
|
| 1304 |
+
grader_ctx = {"cases": MOD_HARD}
|
| 1305 |
+
return agent_ctx, grader_ctx, "hard"
|
| 1306 |
+
|
| 1307 |
+
if task_id == "ticket_triage_easy":
|
| 1308 |
+
agent_ctx, grader_ctx = gen_ticket_easy(self._state.episode_id)
|
| 1309 |
+
return agent_ctx, grader_ctx, "easy"
|
| 1310 |
+
|
| 1311 |
+
if task_id == "ticket_triage_medium":
|
| 1312 |
+
agent_ctx = {"tickets": [{k: v for k, v in t.items() if k not in ("priority_rank", "team")} for t in TICKET_MEDIUM_TICKETS]}
|
| 1313 |
+
grader_ctx = {"tickets": TICKET_MEDIUM_TICKETS}
|
| 1314 |
+
return agent_ctx, grader_ctx, "medium"
|
| 1315 |
+
|
| 1316 |
+
if task_id == "ticket_triage_hard":
|
| 1317 |
+
ctx = {"incident_tickets": TICKET_HARD_INCIDENT["tickets"]}
|
| 1318 |
+
return ctx, ctx, "hard"
|
| 1319 |
+
|
| 1320 |
+
if task_id == "cross_agent_chain":
|
| 1321 |
+
case = self._deterministic_choice(CROSS_AGENT_CHAIN_CASES, task_id)
|
| 1322 |
+
agent_ctx = {"email": case["email"], "code": case["code"]}
|
| 1323 |
+
grader_ctx = {"case": case}
|
| 1324 |
+
return agent_ctx, grader_ctx, "hard"
|
| 1325 |
+
|
| 1326 |
+
if task_id == "cross_agent_email_data":
|
| 1327 |
+
case = CROSS_AGENT_EMAIL_DATA_CASES[0]
|
| 1328 |
+
agent_ctx = {"email": case["email"], "attachment_data": case["attachment_data"]}
|
| 1329 |
+
grader_ctx = {"case": case}
|
| 1330 |
+
return agent_ctx, grader_ctx, "hard"
|
| 1331 |
+
|
| 1332 |
+
if task_id == "cross_agent_code_email":
|
| 1333 |
+
case = CROSS_AGENT_CODE_EMAIL_CASES[0]
|
| 1334 |
+
agent_ctx = {"code": case["code"]}
|
| 1335 |
+
grader_ctx = {"case": case}
|
| 1336 |
+
return agent_ctx, grader_ctx, "hard"
|
| 1337 |
+
|
| 1338 |
+
if task_id == "cross_agent_mod_escalation":
|
| 1339 |
+
case = self._deterministic_choice(CROSS_AGENT_MOD_ESCALATION_CASES, task_id)
|
| 1340 |
+
agent_ctx = {"post": case["post"]}
|
| 1341 |
+
grader_ctx = {"case": case}
|
| 1342 |
+
return agent_ctx, grader_ctx, "hard"
|
| 1343 |
+
|
| 1344 |
+
|
| 1345 |
+
if task_id == "email_triage_expert":
|
| 1346 |
+
agent_ctx = {k: v for k, v in EMAIL_EXPERT_CONTEXT.items()}
|
| 1347 |
+
grader_ctx = agent_ctx
|
| 1348 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1349 |
+
|
| 1350 |
+
if task_id == "code_review_expert":
|
| 1351 |
+
agent_ctx = {"code": CODE_EXPERT_CONTEXT["code"],
|
| 1352 |
+
"vulnerability_count": CODE_EXPERT_CONTEXT["vulnerability_count"]}
|
| 1353 |
+
grader_ctx = CODE_EXPERT_CONTEXT
|
| 1354 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1355 |
+
|
| 1356 |
+
if task_id == "data_cleaning_expert":
|
| 1357 |
+
agent_ctx = DATA_EXPERT_CONTEXT
|
| 1358 |
+
grader_ctx = DATA_EXPERT_CONTEXT
|
| 1359 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1360 |
+
|
| 1361 |
+
if task_id == "content_moderation_expert":
|
| 1362 |
+
agent_ctx = MOD_EXPERT_CONTEXT
|
| 1363 |
+
grader_ctx = MOD_EXPERT_CONTEXT
|
| 1364 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1365 |
+
|
| 1366 |
+
if task_id == "ticket_triage_expert":
|
| 1367 |
+
agent_ctx = TICKET_EXPERT_CONTEXT
|
| 1368 |
+
grader_ctx = TICKET_EXPERT_CONTEXT
|
| 1369 |
+
return agent_ctx, grader_ctx, "expert"
|
| 1370 |
+
|
| 1371 |
+
raise ValueError(f"Unknown task_id: '{task_id}'. Use GET /tasks to see valid IDs.")
|
| 1372 |
+
|
| 1373 |
+
def _grade(self, task_id: str, payload: dict, grader_context: dict) -> tuple[float, str, dict]:
|
| 1374 |
+
if task_id == "email_triage_easy": return grade_email_easy(payload, grader_context)
|
| 1375 |
+
if task_id == "email_triage_medium": return grade_email_medium(payload, grader_context)
|
| 1376 |
+
if task_id == "email_triage_hard": return grade_email_hard(payload, grader_context)
|
| 1377 |
+
if task_id == "code_review_easy": return grade_code_easy(payload, grader_context)
|
| 1378 |
+
if task_id == "code_review_medium": return grade_code_medium(payload)
|
| 1379 |
+
if task_id == "code_review_hard": return grade_code_hard(payload)
|
| 1380 |
+
if task_id == "data_cleaning_easy": return grade_data_easy(payload)
|
| 1381 |
+
if task_id == "data_cleaning_medium": return grade_data_medium(payload)
|
| 1382 |
+
if task_id == "data_cleaning_hard": return grade_data_hard(payload, grader_context)
|
| 1383 |
+
if task_id == "content_moderation_easy": return grade_mod_easy(payload, grader_context)
|
| 1384 |
+
if task_id == "content_moderation_medium": return grade_mod_medium(payload, grader_context)
|
| 1385 |
+
if task_id == "content_moderation_hard": return grade_mod_hard(payload)
|
| 1386 |
+
if task_id == "ticket_triage_easy": return grade_ticket_easy(payload, grader_context)
|
| 1387 |
+
if task_id == "ticket_triage_medium": return grade_ticket_medium(payload)
|
| 1388 |
+
if task_id == "ticket_triage_hard": return grade_ticket_hard(payload)
|
| 1389 |
+
if task_id == "cross_agent_chain": return grade_cross_agent_chain(payload, grader_context)
|
| 1390 |
+
if task_id == "cross_agent_email_data": return grade_cross_agent_email_data(payload, grader_context)
|
| 1391 |
+
if task_id == "cross_agent_code_email": return grade_cross_agent_code_email(payload, grader_context)
|
| 1392 |
+
if task_id == "cross_agent_mod_escalation": return grade_cross_agent_mod_escalation(payload, grader_context)
|
| 1393 |
+
return 0.0, "Unknown task.", {}
|
| 1394 |
+
|
| 1395 |
+
def _compute_reward(self, score: float, task_id: str, attempt: int) -> float:
|
| 1396 |
+
"""Trajectory-aware reward shaping."""
|
| 1397 |
+
base = score
|
| 1398 |
+
# Speed bonus: perfect on first attempt
|
| 1399 |
+
if score == 1.0 and attempt == 1:
|
| 1400 |
+
base = min(base + 0.05, 1.0)
|
| 1401 |
+
# Improvement bonus: reward getting better on retry
|
| 1402 |
+
if attempt == 2:
|
| 1403 |
+
prev = self._first_attempt_scores.get(task_id, 0.0)
|
| 1404 |
+
if score > prev:
|
| 1405 |
+
base = min(base + (score - prev) * 0.1, 1.0)
|
| 1406 |
+
# Penalty: repeated failure
|
| 1407 |
+
if score == 0.0 and attempt > 1:
|
| 1408 |
+
base = max(base - 0.05, 0.0)
|
| 1409 |
+
return round(base, 3)
|
| 1410 |
+
|
| 1411 |
+
def step(self, action: MetaAction) -> MetaObservation:
|
| 1412 |
+
self._state.step_count += 1
|
| 1413 |
+
|
| 1414 |
+
try:
|
| 1415 |
+
data = json.loads(action.message)
|
| 1416 |
+
agent = data.get("agent", "")
|
| 1417 |
+
task_id = data.get("task_id", "")
|
| 1418 |
+
payload = data.get("payload", {})
|
| 1419 |
+
except Exception as e:
|
| 1420 |
+
return MetaObservation(
|
| 1421 |
+
agent="error", task_id="", difficulty="",
|
| 1422 |
+
context={},
|
| 1423 |
+
instructions='message must be valid JSON: {"agent":"...","task_id":"...","payload":{...}}',
|
| 1424 |
+
feedback=f"JSON parse error: {e}",
|
| 1425 |
+
score=0.0, partial_credits={}, done=True, reward=0.0,
|
| 1426 |
+
)
|
| 1427 |
+
|
| 1428 |
+
# Probe β load context without grading
|
| 1429 |
+
if not payload or payload == {"_probe": True}:
|
| 1430 |
+
try:
|
| 1431 |
+
agent_ctx, grader_ctx, difficulty = self._load_context(task_id)
|
| 1432 |
+
self._current_agent_context = agent_ctx
|
| 1433 |
+
self._current_grader_context = grader_ctx
|
| 1434 |
+
self._current_task_id = task_id
|
| 1435 |
+
except ValueError:
|
| 1436 |
+
difficulty = ""
|
| 1437 |
+
agent_ctx = {}
|
| 1438 |
+
is_probe = payload == {"_probe": True}
|
| 1439 |
+
return MetaObservation(
|
| 1440 |
+
agent=agent, task_id=task_id, difficulty=difficulty,
|
| 1441 |
+
context=agent_ctx,
|
| 1442 |
+
instructions=INSTRUCTIONS.get(task_id, "Use GET /tasks for payload schema."),
|
| 1443 |
+
feedback="Probe: context loaded. Submit a real payload to get scored." if is_probe else "Empty payload. Use GET /tasks to see required fields.",
|
| 1444 |
+
score=0.0, partial_credits={}, done=is_probe,
|
| 1445 |
+
reward=-0.1 if not is_probe else 0.0,
|
| 1446 |
+
)
|
| 1447 |
+
|
| 1448 |
+
# Load context
|
| 1449 |
+
try:
|
| 1450 |
+
if task_id != self._current_task_id:
|
| 1451 |
+
agent_ctx, grader_ctx, difficulty = self._load_context(task_id)
|
| 1452 |
+
self._current_agent_context = agent_ctx
|
| 1453 |
+
self._current_grader_context = grader_ctx
|
| 1454 |
+
self._current_task_id = task_id
|
| 1455 |
+
self._attempt_counts[task_id] = 0
|
| 1456 |
+
else:
|
| 1457 |
+
agent_ctx = self._current_agent_context
|
| 1458 |
+
grader_ctx = self._current_grader_context
|
| 1459 |
+
_, __, difficulty = self._load_context(task_id)
|
| 1460 |
+
except ValueError as e:
|
| 1461 |
+
return MetaObservation(
|
| 1462 |
+
agent=agent, task_id=task_id, difficulty="",
|
| 1463 |
+
context={},
|
| 1464 |
+
instructions="Use GET /tasks to see valid task IDs.",
|
| 1465 |
+
feedback=str(e),
|
| 1466 |
+
score=0.0, partial_credits={}, done=True, reward=0.0,
|
| 1467 |
+
)
|
| 1468 |
+
|
| 1469 |
+
# Grade
|
| 1470 |
+
score, feedback, partial_credits = self._grade(task_id, payload, grader_ctx)
|
| 1471 |
+
|
| 1472 |
+
# Multi-step retry logic
|
| 1473 |
+
attempt = self._attempt_counts.get(task_id, 0) + 1
|
| 1474 |
+
self._attempt_counts[task_id] = attempt
|
| 1475 |
+
|
| 1476 |
+
reward = self._compute_reward(score, task_id, attempt)
|
| 1477 |
+
|
| 1478 |
+
if attempt == 1 and score < 1.0:
|
| 1479 |
+
self._first_attempt_scores[task_id] = score
|
| 1480 |
+
done = False
|
| 1481 |
+
reward = score * 0.5
|
| 1482 |
+
missed = [k for k, v in partial_credits.items() if not v]
|
| 1483 |
+
feedback += (
|
| 1484 |
+
f" [RETRY AVAILABLE] Score: {score:.2f}. "
|
| 1485 |
+
f"Improve on: {missed}"
|
| 1486 |
+
)
|
| 1487 |
+
elif attempt == 1 and score == 1.0:
|
| 1488 |
+
done = True
|
| 1489 |
+
self._episode_scores.append(score)
|
| 1490 |
+
else:
|
| 1491 |
+
first = self._first_attempt_scores.get(task_id, 0.0)
|
| 1492 |
+
score = max(score, first)
|
| 1493 |
+
done = True
|
| 1494 |
+
reward = self._compute_reward(score, task_id, attempt)
|
| 1495 |
+
self._episode_scores.append(score)
|
| 1496 |
+
feedback += f" [FINAL] Best score from 2 attempts: {score:.2f}."
|
| 1497 |
+
|
| 1498 |
+
return MetaObservation(
|
| 1499 |
+
agent=agent,
|
| 1500 |
+
task_id=task_id,
|
| 1501 |
+
difficulty=difficulty,
|
| 1502 |
+
context=agent_ctx,
|
| 1503 |
+
instructions=INSTRUCTIONS.get(task_id, ""),
|
| 1504 |
+
feedback=feedback,
|
| 1505 |
+
score=score,
|
| 1506 |
+
partial_credits=partial_credits,
|
| 1507 |
+
done=done,
|
| 1508 |
+
reward=reward,
|
| 1509 |
+
metadata={
|
| 1510 |
+
"step": self._state.step_count,
|
| 1511 |
+
"attempt": attempt,
|
| 1512 |
+
"episode_avg_score": round(
|
| 1513 |
+
sum(self._episode_scores) / len(self._episode_scores), 3
|
| 1514 |
+
) if self._episode_scores else 0.0,
|
| 1515 |
+
},
|
| 1516 |
+
)
|
| 1517 |
+
|
| 1518 |
+
@property
|
| 1519 |
+
def state(self) -> State:
|
| 1520 |
+
return self._state
|
server/app.py
CHANGED
|
@@ -29,7 +29,16 @@ from fastapi.responses import JSONResponse, RedirectResponse
|
|
| 29 |
|
| 30 |
from openenv.core.env_server.http_server import create_app
|
| 31 |
|
| 32 |
-
from models import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
from server.Meta_environment import MetaEnvironment
|
| 34 |
|
| 35 |
# ββ Create base OpenEnv app βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -283,6 +292,64 @@ async def baseline():
|
|
| 283 |
return JSONResponse(status_code=500, content={"error": str(e)})
|
| 284 |
|
| 285 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
def main(host: str = "0.0.0.0", port: int = 7860) -> None:
|
| 287 |
import uvicorn
|
| 288 |
uvicorn.run(app, host=host, port=port)
|
|
|
|
| 29 |
|
| 30 |
from openenv.core.env_server.http_server import create_app
|
| 31 |
|
| 32 |
+
from models import (
|
| 33 |
+
MetaAction, MetaObservation,
|
| 34 |
+
EmailTriageEasyPayload, EmailTriageMediumPayload, EmailTriageHardPayload,
|
| 35 |
+
CodeReviewEasyPayload, CodeReviewMediumPayload, CodeReviewHardPayload,
|
| 36 |
+
DataCleaningEasyPayload, DataCleaningMediumPayload, DataCleaningHardPayload,
|
| 37 |
+
ContentModerationEasyPayload, ContentModerationMediumPayload, ContentModerationHardPayload,
|
| 38 |
+
TicketTriageEasyPayload, TicketTriageMediumPayload, TicketTriageHardPayload,
|
| 39 |
+
CrossAgentChainPayload, CrossAgentEmailDataPayload,
|
| 40 |
+
CrossAgentCodeEmailPayload, CrossAgentModEscalationPayload,
|
| 41 |
+
)
|
| 42 |
from server.Meta_environment import MetaEnvironment
|
| 43 |
|
| 44 |
# ββ Create base OpenEnv app βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 292 |
return JSONResponse(status_code=500, content={"error": str(e)})
|
| 293 |
|
| 294 |
|
| 295 |
+
|
| 296 |
+
# ββ Typed step endpoint β Fix 3 βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 297 |
+
|
| 298 |
+
_TASK_TO_MODEL = {
|
| 299 |
+
"email_triage_easy": EmailTriageEasyPayload,
|
| 300 |
+
"email_triage_medium": EmailTriageMediumPayload,
|
| 301 |
+
"email_triage_hard": EmailTriageHardPayload,
|
| 302 |
+
"code_review_easy": CodeReviewEasyPayload,
|
| 303 |
+
"code_review_medium": CodeReviewMediumPayload,
|
| 304 |
+
"code_review_hard": CodeReviewHardPayload,
|
| 305 |
+
"data_cleaning_easy": DataCleaningEasyPayload,
|
| 306 |
+
"data_cleaning_medium": DataCleaningMediumPayload,
|
| 307 |
+
"data_cleaning_hard": DataCleaningHardPayload,
|
| 308 |
+
"content_moderation_easy": ContentModerationEasyPayload,
|
| 309 |
+
"content_moderation_medium": ContentModerationMediumPayload,
|
| 310 |
+
"content_moderation_hard": ContentModerationHardPayload,
|
| 311 |
+
"ticket_triage_easy": TicketTriageEasyPayload,
|
| 312 |
+
"ticket_triage_medium": TicketTriageMediumPayload,
|
| 313 |
+
"ticket_triage_hard": TicketTriageHardPayload,
|
| 314 |
+
"cross_agent_chain": CrossAgentChainPayload,
|
| 315 |
+
"cross_agent_email_data": CrossAgentEmailDataPayload,
|
| 316 |
+
"cross_agent_code_email": CrossAgentCodeEmailPayload,
|
| 317 |
+
"cross_agent_mod_escalation": CrossAgentModEscalationPayload,
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
@app.post("/step/typed/{task_id}")
|
| 322 |
+
async def step_typed(task_id: str, request: Request):
|
| 323 |
+
"""
|
| 324 |
+
Typed step endpoint β accepts the typed payload model for the given task_id
|
| 325 |
+
instead of the JSON-string-in-message format.
|
| 326 |
+
|
| 327 |
+
Example:
|
| 328 |
+
POST /step/typed/email_triage_easy
|
| 329 |
+
Body: {"agent": "email_triage", "task_id": "email_triage_easy", "payload": {"classification": "spam"}}
|
| 330 |
+
|
| 331 |
+
Returns the same observation as POST /step.
|
| 332 |
+
"""
|
| 333 |
+
if task_id not in _TASK_TO_MODEL:
|
| 334 |
+
return JSONResponse(status_code=404, content={"error": f"Unknown task_id: {task_id}. See GET /tasks."})
|
| 335 |
+
body = await request.json()
|
| 336 |
+
try:
|
| 337 |
+
model_cls = _TASK_TO_MODEL[task_id]
|
| 338 |
+
payload = body.get("payload", body)
|
| 339 |
+
typed = model_cls(**payload)
|
| 340 |
+
except Exception as e:
|
| 341 |
+
return JSONResponse(status_code=422, content={"error": str(e), "task_id": task_id})
|
| 342 |
+
agent = body.get("agent", "_".join(task_id.split("_")[:-1]))
|
| 343 |
+
wrapped = MetaAction(message=json.dumps({
|
| 344 |
+
"agent": agent, "task_id": task_id, "payload": typed.model_dump()
|
| 345 |
+
}))
|
| 346 |
+
# Delegate to the active environment instance
|
| 347 |
+
env = MetaEnvironment()
|
| 348 |
+
env.reset()
|
| 349 |
+
obs = env.step(wrapped)
|
| 350 |
+
return obs.model_dump()
|
| 351 |
+
|
| 352 |
+
|
| 353 |
def main(host: str = "0.0.0.0", port: int = 7860) -> None:
|
| 354 |
import uvicorn
|
| 355 |
uvicorn.run(app, host=host, port=port)
|
server/app.py.bak
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
FastAPI application for Meta Multi-Agent Environment v3.0
|
| 3 |
+
|
| 4 |
+
Standard OpenEnv endpoints (from create_app):
|
| 5 |
+
POST /reset β Reset environment
|
| 6 |
+
POST /step β Execute action
|
| 7 |
+
GET /state β Current state
|
| 8 |
+
GET /schema β Action/observation schemas
|
| 9 |
+
GET /health β Health check
|
| 10 |
+
GET /metadata β Environment metadata
|
| 11 |
+
WS /ws β WebSocket persistent session
|
| 12 |
+
|
| 13 |
+
Custom Meta endpoints:
|
| 14 |
+
GET /tasks β All 19 tasks with payload schemas
|
| 15 |
+
POST /grader β Score an action without side effects
|
| 16 |
+
POST /baseline β Run baseline across all tasks
|
| 17 |
+
GET /ui β Gradio interactive frontend
|
| 18 |
+
GET / β Redirects to /ui
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from __future__ import annotations
|
| 22 |
+
|
| 23 |
+
import json
|
| 24 |
+
import os
|
| 25 |
+
import sys
|
| 26 |
+
|
| 27 |
+
from fastapi import Request
|
| 28 |
+
from fastapi.responses import JSONResponse, RedirectResponse
|
| 29 |
+
|
| 30 |
+
from openenv.core.env_server.http_server import create_app
|
| 31 |
+
|
| 32 |
+
from models import (
|
| 33 |
+
MetaAction, MetaObservation,
|
| 34 |
+
EmailTriageEasyPayload, EmailTriageMediumPayload, EmailTriageHardPayload,
|
| 35 |
+
CodeReviewEasyPayload, CodeReviewMediumPayload, CodeReviewHardPayload,
|
| 36 |
+
DataCleaningEasyPayload, DataCleaningMediumPayload, DataCleaningHardPayload,
|
| 37 |
+
ContentModerationEasyPayload, ContentModerationMediumPayload, ContentModerationHardPayload,
|
| 38 |
+
TicketTriageEasyPayload, TicketTriageMediumPayload, TicketTriageHardPayload,
|
| 39 |
+
CrossAgentChainPayload, CrossAgentEmailDataPayload,
|
| 40 |
+
CrossAgentCodeEmailPayload, CrossAgentModEscalationPayload,
|
| 41 |
+
)
|
| 42 |
+
from server.Meta_environment import MetaEnvironment
|
| 43 |
+
|
| 44 |
+
# ββ Create base OpenEnv app βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 45 |
+
app = create_app(
|
| 46 |
+
MetaEnvironment,
|
| 47 |
+
MetaAction,
|
| 48 |
+
MetaObservation,
|
| 49 |
+
env_name="Meta",
|
| 50 |
+
max_concurrent_envs=20,
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
# ββ Mount Gradio UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 54 |
+
try:
|
| 55 |
+
from server.gradio_ui import mount_gradio
|
| 56 |
+
mount_gradio(app)
|
| 57 |
+
except ImportError as e:
|
| 58 |
+
print(f"[WARN] Gradio UI not mounted: {e}")
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# ββ Root redirect βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 62 |
+
@app.get("/")
|
| 63 |
+
def root():
|
| 64 |
+
return RedirectResponse(url="/ui")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# ββ Task Registry βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 68 |
+
TASK_REGISTRY = [
|
| 69 |
+
# Email Triage
|
| 70 |
+
{
|
| 71 |
+
"id": "email_triage_easy",
|
| 72 |
+
"name": "Email Classification",
|
| 73 |
+
"agent": "email_triage",
|
| 74 |
+
"difficulty": "easy",
|
| 75 |
+
"description": "Classify a single email as spam, important, or newsletter.",
|
| 76 |
+
"action_schema": {"message": json.dumps({"agent": "email_triage", "task_id": "email_triage_easy", "payload": {"classification": "spam | important | newsletter"}})},
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"id": "email_triage_medium",
|
| 80 |
+
"name": "Email Prioritization",
|
| 81 |
+
"agent": "email_triage",
|
| 82 |
+
"difficulty": "medium",
|
| 83 |
+
"description": "Prioritize 10 workplace emails from most to least urgent.",
|
| 84 |
+
"action_schema": {"message": json.dumps({"agent": "email_triage", "task_id": "email_triage_medium", "payload": {"order": ["m1", "m5", "m3", "m10", "m6", "m8", "m2", "m9", "m4", "m7"]}})},
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"id": "email_triage_hard",
|
| 88 |
+
"name": "Email Reply Drafting",
|
| 89 |
+
"agent": "email_triage",
|
| 90 |
+
"difficulty": "hard",
|
| 91 |
+
"description": "Draft a professional, empathetic reply to a complex customer complaint.",
|
| 92 |
+
"action_schema": {"message": json.dumps({"agent": "email_triage", "task_id": "email_triage_hard", "payload": {"reply": "<full reply text>"}})},
|
| 93 |
+
},
|
| 94 |
+
# Code Review
|
| 95 |
+
{
|
| 96 |
+
"id": "code_review_easy",
|
| 97 |
+
"name": "Syntax Error Detection",
|
| 98 |
+
"agent": "code_review",
|
| 99 |
+
"difficulty": "easy",
|
| 100 |
+
"description": "Find the syntax error(s) in a Python code snippet.",
|
| 101 |
+
"action_schema": {"message": json.dumps({"agent": "code_review", "task_id": "code_review_easy", "payload": {"errors": ["description of syntax error"]}})},
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"id": "code_review_medium",
|
| 105 |
+
"name": "Logic Bug Detection",
|
| 106 |
+
"agent": "code_review",
|
| 107 |
+
"difficulty": "medium",
|
| 108 |
+
"description": "Find all 4 logical bugs in the code and suggest fixes.",
|
| 109 |
+
"action_schema": {"message": json.dumps({"agent": "code_review", "task_id": "code_review_medium", "payload": {"bugs": [{"location": "fn_name", "issue": "...", "fix": "..."}]}})},
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"id": "code_review_hard",
|
| 113 |
+
"name": "Security Vulnerability Detection (8 vulns)",
|
| 114 |
+
"agent": "code_review",
|
| 115 |
+
"difficulty": "hard",
|
| 116 |
+
"description": "Identify all 8 security vulnerabilities: SQL injection (x2), XSS, command injection, insecure deserialization, timing attack, path traversal, SSRF.",
|
| 117 |
+
"action_schema": {"message": json.dumps({"agent": "code_review", "task_id": "code_review_hard", "payload": {"vulnerabilities": [{"type": "sql_injection", "location": "fn_name", "severity": "critical", "fix": "..."}]}})},
|
| 118 |
+
},
|
| 119 |
+
# Data Cleaning
|
| 120 |
+
{
|
| 121 |
+
"id": "data_cleaning_easy",
|
| 122 |
+
"name": "Missing Value & Duplicate Detection",
|
| 123 |
+
"agent": "data_cleaning",
|
| 124 |
+
"difficulty": "easy",
|
| 125 |
+
"description": "Find all missing values (by field+row) and identify duplicate rows.",
|
| 126 |
+
"action_schema": {"message": json.dumps({"agent": "data_cleaning", "task_id": "data_cleaning_easy", "payload": {"missing": ["age (row 2)"], "duplicates": [1, 3]}})},
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"id": "data_cleaning_medium",
|
| 130 |
+
"name": "Data Type Normalization",
|
| 131 |
+
"agent": "data_cleaning",
|
| 132 |
+
"difficulty": "medium",
|
| 133 |
+
"description": "Identify 5 data quality issues and return cleaned dataset.",
|
| 134 |
+
"action_schema": {"message": json.dumps({"agent": "data_cleaning", "task_id": "data_cleaning_medium", "payload": {"issues": {"field_name": "description"}, "cleaned_data": []}})},
|
| 135 |
+
},
|
| 136 |
+
{
|
| 137 |
+
"id": "data_cleaning_hard",
|
| 138 |
+
"name": "Outlier Detection & Imputation",
|
| 139 |
+
"agent": "data_cleaning",
|
| 140 |
+
"difficulty": "hard",
|
| 141 |
+
"description": "Detect outliers, find missing values, impute and return clean dataset.",
|
| 142 |
+
"action_schema": {"message": json.dumps({"agent": "data_cleaning", "task_id": "data_cleaning_hard", "payload": {"outliers": [4, 8], "missing": [6, 11], "cleaned_data": []}})},
|
| 143 |
+
},
|
| 144 |
+
# Content Moderation
|
| 145 |
+
{
|
| 146 |
+
"id": "content_moderation_easy",
|
| 147 |
+
"name": "Explicit Content Detection",
|
| 148 |
+
"agent": "content_moderation",
|
| 149 |
+
"difficulty": "easy",
|
| 150 |
+
"description": "Classify 7 posts as safe or harmful.",
|
| 151 |
+
"action_schema": {"message": json.dumps({"agent": "content_moderation", "task_id": "content_moderation_easy", "payload": {"classifications": [{"id": "c1", "label": "harmful"}]}})},
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"id": "content_moderation_medium",
|
| 155 |
+
"name": "Subtle Toxicity Detection",
|
| 156 |
+
"agent": "content_moderation",
|
| 157 |
+
"difficulty": "medium",
|
| 158 |
+
"description": "Detect subtle toxicity, sarcasm, and implicit hostility across 8 posts.",
|
| 159 |
+
"action_schema": {"message": json.dumps({"agent": "content_moderation", "task_id": "content_moderation_medium", "payload": {"classifications": [{"id": "m1", "label": "harmful", "reason": "..."}]}})},
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"id": "content_moderation_hard",
|
| 163 |
+
"name": "Context-Aware & Adversarial Moderation",
|
| 164 |
+
"agent": "content_moderation",
|
| 165 |
+
"difficulty": "hard",
|
| 166 |
+
"description": "6 cases including dog-whistles and coordinated inauthentic behavior. Same text, two contexts.",
|
| 167 |
+
"action_schema": {"message": json.dumps({"agent": "content_moderation", "task_id": "content_moderation_hard", "payload": {"decisions": [{"id": "h1", "context_a_label": "safe", "context_b_label": "harmful"}]}})},
|
| 168 |
+
},
|
| 169 |
+
# Ticket Triage (NEW)
|
| 170 |
+
{
|
| 171 |
+
"id": "ticket_triage_easy",
|
| 172 |
+
"name": "Ticket Classification",
|
| 173 |
+
"agent": "ticket_triage",
|
| 174 |
+
"difficulty": "easy",
|
| 175 |
+
"description": "Classify a support ticket by priority and category.",
|
| 176 |
+
"action_schema": {"message": json.dumps({"agent": "ticket_triage", "task_id": "ticket_triage_easy", "payload": {"priority": "critical", "category": "bug"}})},
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
"id": "ticket_triage_medium",
|
| 180 |
+
"name": "Ticket Prioritization & Routing",
|
| 181 |
+
"agent": "ticket_triage",
|
| 182 |
+
"difficulty": "medium",
|
| 183 |
+
"description": "Order 8 tickets by priority and assign each to the correct team.",
|
| 184 |
+
"action_schema": {"message": json.dumps({"agent": "ticket_triage", "task_id": "ticket_triage_medium", "payload": {"order": ["tk1", "tk4", "..."], "assignments": {"tk1": "backend"}}})},
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
"id": "ticket_triage_hard",
|
| 188 |
+
"name": "Incident Root Cause Analysis",
|
| 189 |
+
"agent": "ticket_triage",
|
| 190 |
+
"difficulty": "hard",
|
| 191 |
+
"description": "Analyse 6 linked incident tickets: root cause, resolution steps, affected services, P1-P4 severity.",
|
| 192 |
+
"action_schema": {"message": json.dumps({"agent": "ticket_triage", "task_id": "ticket_triage_hard", "payload": {"root_cause": "...", "resolution_steps": ["..."], "affected_services": ["..."], "severity": "P1"}})},
|
| 193 |
+
},
|
| 194 |
+
# Cross-Agent Chained
|
| 195 |
+
{
|
| 196 |
+
"id": "cross_agent_chain",
|
| 197 |
+
"name": "Email + Code Review Chain",
|
| 198 |
+
"agent": "cross_agent",
|
| 199 |
+
"difficulty": "hard",
|
| 200 |
+
"description": "Classify the email AND identify the code bug in the attachment.",
|
| 201 |
+
"action_schema": {"message": json.dumps({"agent": "cross_agent", "task_id": "cross_agent_chain", "payload": {"email_classification": "important", "bugs": [{"location": "fn", "issue": "...", "fix": "..."}]}})},
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"id": "cross_agent_email_data",
|
| 205 |
+
"name": "Email Priority + Data Cleaning Chain",
|
| 206 |
+
"agent": "cross_agent",
|
| 207 |
+
"difficulty": "hard",
|
| 208 |
+
"description": "Classify the email urgency AND identify+fix data quality issues in the attachment.",
|
| 209 |
+
"action_schema": {"message": json.dumps({"agent": "cross_agent", "task_id": "cross_agent_email_data", "payload": {"email_priority": "critical", "data_issues": {"field": "issue"}, "cleaned_data": []}})},
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
"id": "cross_agent_code_email",
|
| 213 |
+
"name": "Vulnerability + Disclosure Email Chain",
|
| 214 |
+
"agent": "cross_agent",
|
| 215 |
+
"difficulty": "hard",
|
| 216 |
+
"description": "Identify the security vulnerability AND draft a professional disclosure email.",
|
| 217 |
+
"action_schema": {"message": json.dumps({"agent": "cross_agent", "task_id": "cross_agent_code_email", "payload": {"vulnerability_type": "sql_injection", "vulnerability_location": "fn_name", "disclosure_email": "..."}})},
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"id": "cross_agent_mod_escalation",
|
| 221 |
+
"name": "Moderation + Escalation Chain",
|
| 222 |
+
"agent": "cross_agent",
|
| 223 |
+
"difficulty": "hard",
|
| 224 |
+
"description": "Classify the content AND decide escalation AND draft a moderation notice.",
|
| 225 |
+
"action_schema": {"message": json.dumps({"agent": "cross_agent", "task_id": "cross_agent_mod_escalation", "payload": {"content_label": "harmful", "escalate": True, "moderation_notice": "..."}})},
|
| 226 |
+
},
|
| 227 |
+
]
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
@app.get("/tasks")
|
| 231 |
+
def list_tasks():
|
| 232 |
+
return {
|
| 233 |
+
"environment": "Meta Multi-Agent v3.0",
|
| 234 |
+
"total_tasks": len(TASK_REGISTRY),
|
| 235 |
+
"agents": ["email_triage", "code_review", "data_cleaning", "content_moderation", "ticket_triage", "cross_agent"],
|
| 236 |
+
"how_to_step": (
|
| 237 |
+
'POST /step with body: {"action": {"message": "<json_string>"}} '
|
| 238 |
+
'where json_string = {"agent": "...", "task_id": "...", "payload": {...}}'
|
| 239 |
+
),
|
| 240 |
+
"tasks": TASK_REGISTRY,
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
@app.post("/grader")
|
| 245 |
+
async def grader(request: Request):
|
| 246 |
+
"""Score an action without persistent side effects."""
|
| 247 |
+
body = await request.json()
|
| 248 |
+
msg = body.get("message") or body.get("action", {}).get("message", "{}")
|
| 249 |
+
env = MetaEnvironment()
|
| 250 |
+
env.reset()
|
| 251 |
+
action = MetaAction(message=msg)
|
| 252 |
+
obs = env.step(action)
|
| 253 |
+
return {
|
| 254 |
+
"task_id": obs.task_id,
|
| 255 |
+
"agent": obs.agent,
|
| 256 |
+
"score": obs.score,
|
| 257 |
+
"reward": obs.reward,
|
| 258 |
+
"feedback": obs.feedback,
|
| 259 |
+
"partial_credits": obs.partial_credits,
|
| 260 |
+
"difficulty": obs.difficulty,
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
@app.post("/baseline")
|
| 265 |
+
async def baseline():
|
| 266 |
+
api_key = (
|
| 267 |
+
os.environ.get("OPENAI_API_KEY")
|
| 268 |
+
or os.environ.get("GROQ_API_KEY")
|
| 269 |
+
or os.environ.get("HF_TOKEN")
|
| 270 |
+
)
|
| 271 |
+
if not api_key:
|
| 272 |
+
return JSONResponse(
|
| 273 |
+
status_code=400,
|
| 274 |
+
content={"error": "Set one of: OPENAI_API_KEY, GROQ_API_KEY, or HF_TOKEN."},
|
| 275 |
+
)
|
| 276 |
+
api_base_url = os.environ.get("API_BASE_URL", "https://api.groq.com/openai/v1")
|
| 277 |
+
model_name = os.environ.get("MODEL_NAME", "llama-3.3-70b-versatile")
|
| 278 |
+
try:
|
| 279 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 280 |
+
from baseline import run_all_baselines
|
| 281 |
+
scores = run_all_baselines(api_key=api_key, api_base_url=api_base_url, model_name=model_name)
|
| 282 |
+
avg = round(sum(s["score"] for s in scores) / len(scores), 3)
|
| 283 |
+
return {
|
| 284 |
+
"environment": "Meta Multi-Agent v3.0",
|
| 285 |
+
"model": model_name,
|
| 286 |
+
"api_base": api_base_url,
|
| 287 |
+
"average_score": avg,
|
| 288 |
+
"total_tasks": len(scores),
|
| 289 |
+
"baseline_scores": scores,
|
| 290 |
+
}
|
| 291 |
+
except Exception as e:
|
| 292 |
+
return JSONResponse(status_code=500, content={"error": str(e)})
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
# ββ Typed step endpoint β Fix 3 βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 297 |
+
|
| 298 |
+
_TASK_TO_MODEL = {
|
| 299 |
+
"email_triage_easy": EmailTriageEasyPayload,
|
| 300 |
+
"email_triage_medium": EmailTriageMediumPayload,
|
| 301 |
+
"email_triage_hard": EmailTriageHardPayload,
|
| 302 |
+
"code_review_easy": CodeReviewEasyPayload,
|
| 303 |
+
"code_review_medium": CodeReviewMediumPayload,
|
| 304 |
+
"code_review_hard": CodeReviewHardPayload,
|
| 305 |
+
"data_cleaning_easy": DataCleaningEasyPayload,
|
| 306 |
+
"data_cleaning_medium": DataCleaningMediumPayload,
|
| 307 |
+
"data_cleaning_hard": DataCleaningHardPayload,
|
| 308 |
+
"content_moderation_easy": ContentModerationEasyPayload,
|
| 309 |
+
"content_moderation_medium": ContentModerationMediumPayload,
|
| 310 |
+
"content_moderation_hard": ContentModerationHardPayload,
|
| 311 |
+
"ticket_triage_easy": TicketTriageEasyPayload,
|
| 312 |
+
"ticket_triage_medium": TicketTriageMediumPayload,
|
| 313 |
+
"ticket_triage_hard": TicketTriageHardPayload,
|
| 314 |
+
"cross_agent_chain": CrossAgentChainPayload,
|
| 315 |
+
"cross_agent_email_data": CrossAgentEmailDataPayload,
|
| 316 |
+
"cross_agent_code_email": CrossAgentCodeEmailPayload,
|
| 317 |
+
"cross_agent_mod_escalation": CrossAgentModEscalationPayload,
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
@app.post("/step/typed/{task_id}")
|
| 322 |
+
async def step_typed(task_id: str, request: Request):
|
| 323 |
+
"""
|
| 324 |
+
Typed step endpoint β accepts the typed payload model for the given task_id
|
| 325 |
+
instead of the JSON-string-in-message format.
|
| 326 |
+
|
| 327 |
+
Example:
|
| 328 |
+
POST /step/typed/email_triage_easy
|
| 329 |
+
Body: {"agent": "email_triage", "task_id": "email_triage_easy", "payload": {"classification": "spam"}}
|
| 330 |
+
|
| 331 |
+
Returns the same observation as POST /step.
|
| 332 |
+
"""
|
| 333 |
+
if task_id not in _TASK_TO_MODEL:
|
| 334 |
+
return JSONResponse(status_code=404, content={"error": f"Unknown task_id: {task_id}. See GET /tasks."})
|
| 335 |
+
body = await request.json()
|
| 336 |
+
try:
|
| 337 |
+
model_cls = _TASK_TO_MODEL[task_id]
|
| 338 |
+
payload = body.get("payload", body)
|
| 339 |
+
typed = model_cls(**payload)
|
| 340 |
+
except Exception as e:
|
| 341 |
+
return JSONResponse(status_code=422, content={"error": str(e), "task_id": task_id})
|
| 342 |
+
agent = body.get("agent", "_".join(task_id.split("_")[:-1]))
|
| 343 |
+
wrapped = MetaAction(message=json.dumps({
|
| 344 |
+
"agent": agent, "task_id": task_id, "payload": typed.model_dump()
|
| 345 |
+
}))
|
| 346 |
+
# Delegate to the active environment instance
|
| 347 |
+
env = MetaEnvironment()
|
| 348 |
+
env.reset()
|
| 349 |
+
obs = env.step(wrapped)
|
| 350 |
+
return obs.model_dump()
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def main(host: str = "0.0.0.0", port: int = 7860) -> None:
|
| 354 |
+
import uvicorn
|
| 355 |
+
uvicorn.run(app, host=host, port=port)
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
if __name__ == "__main__":
|
| 359 |
+
import argparse
|
| 360 |
+
parser = argparse.ArgumentParser()
|
| 361 |
+
parser.add_argument("--port", type=int, default=7860)
|
| 362 |
+
args = parser.parse_args()
|
| 363 |
+
main(port=args.port)
|
server/gradio_ui.py
CHANGED
|
@@ -24,18 +24,30 @@ TASK_IDS = [
|
|
| 24 |
"email_triage_easy",
|
| 25 |
"email_triage_medium",
|
| 26 |
"email_triage_hard",
|
|
|
|
| 27 |
"code_review_easy",
|
| 28 |
"code_review_medium",
|
| 29 |
"code_review_hard",
|
|
|
|
| 30 |
"data_cleaning_easy",
|
| 31 |
"data_cleaning_medium",
|
| 32 |
"data_cleaning_hard",
|
|
|
|
| 33 |
"content_moderation_easy",
|
| 34 |
"content_moderation_medium",
|
| 35 |
"content_moderation_hard",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
]
|
| 37 |
|
| 38 |
-
DIFFICULTY_EMOJI = {"easy": "π’ Easy", "medium": "π‘ Medium", "hard": "π΄ Hard"}
|
| 39 |
|
| 40 |
EXAMPLE_PAYLOADS = {
|
| 41 |
"email_triage_easy": {"classification": "spam"},
|
|
@@ -206,7 +218,7 @@ def build_gradio_app() -> gr.Blocks:
|
|
| 206 |
# π€ Meta OpenEnv
|
| 207 |
### Multi-Domain AI Agent Benchmark
|
| 208 |
|
| 209 |
-
**
|
| 210 |
*Email Triage Β· Code Review Β· Data Cleaning Β· Content Moderation*
|
| 211 |
|
| 212 |
---
|
|
@@ -335,7 +347,8 @@ This makes it ideal for:
|
|
| 335 |
| POST | `/step` | Execute action |
|
| 336 |
| GET | `/state` | Current state |
|
| 337 |
| GET | `/health` | Health check |
|
| 338 |
-
| GET | `/tasks` | All
|
|
|
|
| 339 |
| POST | `/grader` | Score without side effects |
|
| 340 |
| GET | `/schema` | JSON schemas |
|
| 341 |
|
|
|
|
| 24 |
"email_triage_easy",
|
| 25 |
"email_triage_medium",
|
| 26 |
"email_triage_hard",
|
| 27 |
+
"email_triage_expert",
|
| 28 |
"code_review_easy",
|
| 29 |
"code_review_medium",
|
| 30 |
"code_review_hard",
|
| 31 |
+
"code_review_expert",
|
| 32 |
"data_cleaning_easy",
|
| 33 |
"data_cleaning_medium",
|
| 34 |
"data_cleaning_hard",
|
| 35 |
+
"data_cleaning_expert",
|
| 36 |
"content_moderation_easy",
|
| 37 |
"content_moderation_medium",
|
| 38 |
"content_moderation_hard",
|
| 39 |
+
"content_moderation_expert",
|
| 40 |
+
"ticket_triage_easy",
|
| 41 |
+
"ticket_triage_medium",
|
| 42 |
+
"ticket_triage_hard",
|
| 43 |
+
"ticket_triage_expert",
|
| 44 |
+
"cross_agent_chain",
|
| 45 |
+
"cross_agent_email_data",
|
| 46 |
+
"cross_agent_code_email",
|
| 47 |
+
"cross_agent_mod_escalation",
|
| 48 |
]
|
| 49 |
|
| 50 |
+
DIFFICULTY_EMOJI = {"easy": "π’ Easy", "medium": "π‘ Medium", "hard": "π΄ Hard", "expert": "β« Expert"}
|
| 51 |
|
| 52 |
EXAMPLE_PAYLOADS = {
|
| 53 |
"email_triage_easy": {"classification": "spam"},
|
|
|
|
| 218 |
# π€ Meta OpenEnv
|
| 219 |
### Multi-Domain AI Agent Benchmark
|
| 220 |
|
| 221 |
+
**6 agents Β· 24 tasks Β· Real-world RL environment**
|
| 222 |
*Email Triage Β· Code Review Β· Data Cleaning Β· Content Moderation*
|
| 223 |
|
| 224 |
---
|
|
|
|
| 347 |
| POST | `/step` | Execute action |
|
| 348 |
| GET | `/state` | Current state |
|
| 349 |
| GET | `/health` | Health check |
|
| 350 |
+
| GET | `/tasks` | All 24 tasks with schemas |
|
| 351 |
+
| POST | `/step/typed/{task_id}` | Typed action format |
|
| 352 |
| POST | `/grader` | Score without side effects |
|
| 353 |
| GET | `/schema` | JSON schemas |
|
| 354 |
|
server/gradio_ui.py.bak
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
server/gradio_ui.py
|
| 3 |
+
Gradio frontend for Meta OpenEnv β mounted into FastAPI at /ui
|
| 4 |
+
|
| 5 |
+
Features:
|
| 6 |
+
- Task Explorer: pick any of 12 tasks, see context, edit payload, submit and score
|
| 7 |
+
- Baseline Runner: run all 12 tasks one by one with live progress
|
| 8 |
+
- Score Dashboard: 12 cards showing last known score per task
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
import gradio as gr
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
from server.Meta_environment import MetaEnvironment, INSTRUCTIONS
|
| 16 |
+
from models import MetaAction
|
| 17 |
+
except ImportError:
|
| 18 |
+
from .Meta_environment import MetaEnvironment, INSTRUCTIONS
|
| 19 |
+
from ..models import MetaAction
|
| 20 |
+
|
| 21 |
+
# ββ Constants βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 22 |
+
|
| 23 |
+
TASK_IDS = [
|
| 24 |
+
"email_triage_easy",
|
| 25 |
+
"email_triage_medium",
|
| 26 |
+
"email_triage_hard",
|
| 27 |
+
"email_triage_expert",
|
| 28 |
+
"code_review_easy",
|
| 29 |
+
"code_review_medium",
|
| 30 |
+
"code_review_hard",
|
| 31 |
+
"code_review_expert",
|
| 32 |
+
"data_cleaning_easy",
|
| 33 |
+
"data_cleaning_medium",
|
| 34 |
+
"data_cleaning_hard",
|
| 35 |
+
"data_cleaning_expert",
|
| 36 |
+
"content_moderation_easy",
|
| 37 |
+
"content_moderation_medium",
|
| 38 |
+
"content_moderation_hard",
|
| 39 |
+
"content_moderation_expert",
|
| 40 |
+
"ticket_triage_easy",
|
| 41 |
+
"ticket_triage_medium",
|
| 42 |
+
"ticket_triage_hard",
|
| 43 |
+
"ticket_triage_expert",
|
| 44 |
+
"cross_agent_chain",
|
| 45 |
+
"cross_agent_email_data",
|
| 46 |
+
"cross_agent_code_email",
|
| 47 |
+
"cross_agent_mod_escalation",
|
| 48 |
+
]
|
| 49 |
+
|
| 50 |
+
DIFFICULTY_EMOJI = {"easy": "π’ Easy", "medium": "π‘ Medium", "hard": "π΄ Hard", "expert": "β« Expert"}
|
| 51 |
+
|
| 52 |
+
EXAMPLE_PAYLOADS = {
|
| 53 |
+
"email_triage_easy": {"classification": "spam"},
|
| 54 |
+
"email_triage_medium": {"order": ["m1", "m5", "m3", "m10", "m6", "m8", "m2", "m9", "m4", "m7"]},
|
| 55 |
+
"email_triage_hard": {"reply": "Dear Customer, I sincerely apologize for the inconvenience caused. We will send a full refund and an overnight replacement within 24 hours. We take this very seriously and will make it right."},
|
| 56 |
+
"code_review_easy": {"errors": ["missing colon after for statement β syntax error on line 3"]},
|
| 57 |
+
"code_review_medium": {"bugs": [
|
| 58 |
+
{"location": "find_max", "issue": "max_val=0 fails for all-negative lists", "fix": "initialize to float('-inf')"},
|
| 59 |
+
{"location": "get_user", "issue": "= used instead of == for comparison", "fix": "change to =="},
|
| 60 |
+
{"location": "divide", "issue": "no zero-division guard", "fix": "check b == 0 first"},
|
| 61 |
+
{"location": "process_list", "issue": "off-by-one error: range(len+1) β IndexError", "fix": "use range(len(items))"},
|
| 62 |
+
]},
|
| 63 |
+
"code_review_hard": {"vulnerabilities": [
|
| 64 |
+
{"type": "sql_injection", "location": "get_user_by_name", "fix": "parameterized queries"},
|
| 65 |
+
{"type": "xss", "location": "render_comment", "fix": "sanitize user input"},
|
| 66 |
+
{"type": "sql_injection", "location": "login", "fix": "parameterized queries"},
|
| 67 |
+
{"type": "command_injection", "location": "run_report", "fix": "avoid shell=True"},
|
| 68 |
+
{"type": "insecure_deserialization","location": "load_user_data", "fix": "use json instead of pickle"},
|
| 69 |
+
]},
|
| 70 |
+
"data_cleaning_easy": {"missing": ["age (row 2)", "name (row 4)", "email (row 5)", "salary (row 6)"], "duplicates": [1, 3]},
|
| 71 |
+
"data_cleaning_medium": {"issues": {"age": "strings not integers", "salary": "inconsistent format", "join_date": "multiple formats", "name": "inconsistent casing", "active": "mixed bool/string types"}, "cleaned_data": []},
|
| 72 |
+
"data_cleaning_hard": {"outliers": [4, 8], "missing": [6, 11], "cleaned_data": []},
|
| 73 |
+
"content_moderation_easy": {"classifications": [{"id": "c1", "label": "harmful"}, {"id": "c2", "label": "safe"}, {"id": "c3", "label": "harmful"}, {"id": "c4", "label": "safe"}, {"id": "c5", "label": "harmful"}, {"id": "c6", "label": "safe"}, {"id": "c7", "label": "harmful"}]},
|
| 74 |
+
"content_moderation_medium": {"classifications": [{"id": "m1", "label": "harmful"}, {"id": "m2", "label": "harmful"}, {"id": "m3", "label": "safe"}, {"id": "m4", "label": "harmful"}, {"id": "m5", "label": "harmful"}, {"id": "m6", "label": "safe"}, {"id": "m7", "label": "harmful"}, {"id": "m8", "label": "safe"}]},
|
| 75 |
+
"content_moderation_hard": {"decisions": [{"id": "h1", "context_a_label": "safe", "context_b_label": "harmful"}, {"id": "h2", "context_a_label": "safe", "context_b_label": "harmful"}, {"id": "h3", "context_a_label": "safe", "context_b_label": "harmful"}]},
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
# ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 79 |
+
|
| 80 |
+
def _probe(task_id: str):
|
| 81 |
+
env = MetaEnvironment()
|
| 82 |
+
env.reset()
|
| 83 |
+
agent = "_".join(task_id.split("_")[:-1])
|
| 84 |
+
probe = MetaAction(message=json.dumps({"agent": agent, "task_id": task_id, "payload": {"_probe": True}}))
|
| 85 |
+
obs = env.step(probe)
|
| 86 |
+
return obs
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _grade(task_id: str, payload: dict):
|
| 90 |
+
env = MetaEnvironment()
|
| 91 |
+
env.reset()
|
| 92 |
+
agent = "_".join(task_id.split("_")[:-1])
|
| 93 |
+
action = MetaAction(message=json.dumps({"agent": agent, "task_id": task_id, "payload": payload}))
|
| 94 |
+
obs = env.step(action)
|
| 95 |
+
return obs
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
# ββ Tab 1: Task Explorer ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 99 |
+
|
| 100 |
+
def on_task_select(task_id: str):
|
| 101 |
+
obs = _probe(task_id)
|
| 102 |
+
diff_label = DIFFICULTY_EMOJI.get(obs.difficulty, obs.difficulty)
|
| 103 |
+
context_str = json.dumps(obs.context, indent=2)
|
| 104 |
+
payload_str = json.dumps(EXAMPLE_PAYLOADS.get(task_id, {}), indent=2)
|
| 105 |
+
instructions = INSTRUCTIONS.get(task_id, obs.instructions)
|
| 106 |
+
return diff_label, instructions, context_str, payload_str, "", 0.0, ""
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def on_submit(task_id: str, payload_text: str):
|
| 110 |
+
try:
|
| 111 |
+
payload = json.loads(payload_text)
|
| 112 |
+
except json.JSONDecodeError as e:
|
| 113 |
+
return f"β JSON parse error: {e}", 0.0, ""
|
| 114 |
+
|
| 115 |
+
obs = _grade(task_id, payload)
|
| 116 |
+
score_pct = int(obs.score * 100)
|
| 117 |
+
|
| 118 |
+
# Colour-coded result
|
| 119 |
+
if obs.score >= 0.9:
|
| 120 |
+
icon = "β
"
|
| 121 |
+
elif obs.score >= 0.5:
|
| 122 |
+
icon = "β οΈ"
|
| 123 |
+
else:
|
| 124 |
+
icon = "β"
|
| 125 |
+
|
| 126 |
+
feedback_md = f"""
|
| 127 |
+
**{icon} Score: {obs.score:.2f} / 1.00**
|
| 128 |
+
|
| 129 |
+
{obs.feedback}
|
| 130 |
+
|
| 131 |
+
**Partial Credits:**
|
| 132 |
+
```json
|
| 133 |
+
{json.dumps(obs.partial_credits, indent=2)}
|
| 134 |
+
```
|
| 135 |
+
"""
|
| 136 |
+
return feedback_md, obs.score, f"Score: {score_pct}%"
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
# ββ Tab 2: Baseline Runner ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 140 |
+
|
| 141 |
+
def run_all_tasks():
|
| 142 |
+
"""Generator β yields one row of results at a time for live streaming."""
|
| 143 |
+
results = []
|
| 144 |
+
rows = []
|
| 145 |
+
|
| 146 |
+
for task_id in TASK_IDS:
|
| 147 |
+
obs = _grade(task_id, EXAMPLE_PAYLOADS.get(task_id, {}))
|
| 148 |
+
bar_filled = "β" * int(obs.score * 10)
|
| 149 |
+
bar_empty = "β" * (10 - int(obs.score * 10))
|
| 150 |
+
diff = obs.difficulty.upper()[:3]
|
| 151 |
+
icon = "β
" if obs.score >= 0.9 else "β οΈ" if obs.score >= 0.5 else "β"
|
| 152 |
+
row = [
|
| 153 |
+
task_id,
|
| 154 |
+
DIFFICULTY_EMOJI.get(obs.difficulty, diff),
|
| 155 |
+
f"{obs.score:.2f}",
|
| 156 |
+
f"{icon} [{bar_filled}{bar_empty}]",
|
| 157 |
+
obs.feedback[:80],
|
| 158 |
+
]
|
| 159 |
+
rows.append(row)
|
| 160 |
+
results.append(obs.score)
|
| 161 |
+
avg = sum(results) / len(results)
|
| 162 |
+
yield rows, f"**Running... {len(results)}/{len(TASK_IDS)}** | Running avg: **{avg:.3f}**"
|
| 163 |
+
|
| 164 |
+
avg = sum(results) / len(results)
|
| 165 |
+
passed = sum(1 for s in results if s >= 0.5)
|
| 166 |
+
yield rows, (
|
| 167 |
+
f"## β
Complete β {len(TASK_IDS)} tasks\n\n"
|
| 168 |
+
f"**Average Score: {avg:.4f}** | "
|
| 169 |
+
f"Passed (β₯0.5): {passed}/{len(TASK_IDS)}"
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# ββ Tab 3: Score Dashboard ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 174 |
+
|
| 175 |
+
def build_dashboard_html():
|
| 176 |
+
cards = []
|
| 177 |
+
for task_id in TASK_IDS:
|
| 178 |
+
agent = "_".join(task_id.split("_")[:-1])
|
| 179 |
+
diff = task_id.split("_")[-1]
|
| 180 |
+
badge_color = {"easy": "#22c55e", "medium": "#f59e0b", "hard": "#ef4444"}.get(diff, "#888")
|
| 181 |
+
cards.append(f"""
|
| 182 |
+
<div style="background:#1e293b;border:1px solid #334155;border-radius:10px;padding:16px;min-width:180px;">
|
| 183 |
+
<div style="font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;">{agent.replace('_',' ')}</div>
|
| 184 |
+
<div style="font-size:14px;font-weight:600;color:#f1f5f9;margin-bottom:8px;">{task_id}</div>
|
| 185 |
+
<span style="background:{badge_color};color:#fff;font-size:10px;font-weight:700;padding:2px 8px;border-radius:20px;">{diff.upper()}</span>
|
| 186 |
+
<div style="margin-top:12px;font-size:11px;color:#64748b;">Submit a payload to see score</div>
|
| 187 |
+
</div>""")
|
| 188 |
+
grid = '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;padding:8px;">'
|
| 189 |
+
grid += "".join(cards) + "</div>"
|
| 190 |
+
return grid
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
# ββ Build Gradio Blocks app βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 194 |
+
|
| 195 |
+
def build_gradio_app() -> gr.Blocks:
|
| 196 |
+
css = """
|
| 197 |
+
body, .gradio-container { background: #0f172a !important; color: #f1f5f9 !important; }
|
| 198 |
+
.gr-button-primary { background: #6366f1 !important; border: none !important; }
|
| 199 |
+
.gr-button-primary:hover { background: #4f46e5 !important; }
|
| 200 |
+
.gr-input, .gr-textarea, .gr-dropdown { background: #1e293b !important; color: #f1f5f9 !important; border: 1px solid #334155 !important; }
|
| 201 |
+
.gr-panel { background: #1e293b !important; border: 1px solid #334155 !important; }
|
| 202 |
+
label { color: #94a3b8 !important; }
|
| 203 |
+
h1, h2, h3, h4 { color: #f1f5f9 !important; }
|
| 204 |
+
.gr-box { background: #1e293b !important; }
|
| 205 |
+
"""
|
| 206 |
+
|
| 207 |
+
with gr.Blocks(
|
| 208 |
+
title="Meta OpenEnv β Multi-Domain AI Agent Benchmark",
|
| 209 |
+
theme=gr.themes.Base(
|
| 210 |
+
primary_hue="indigo",
|
| 211 |
+
neutral_hue="slate",
|
| 212 |
+
),
|
| 213 |
+
css=css,
|
| 214 |
+
) as demo:
|
| 215 |
+
|
| 216 |
+
# ββ Header ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 217 |
+
gr.Markdown("""
|
| 218 |
+
# π€ Meta OpenEnv
|
| 219 |
+
### Multi-Domain AI Agent Benchmark
|
| 220 |
+
|
| 221 |
+
**6 agents Β· 24 tasks Β· Real-world RL environment**
|
| 222 |
+
*Email Triage Β· Code Review Β· Data Cleaning Β· Content Moderation*
|
| 223 |
+
|
| 224 |
+
---
|
| 225 |
+
""")
|
| 226 |
+
|
| 227 |
+
# ββ Tabs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 228 |
+
with gr.Tabs():
|
| 229 |
+
|
| 230 |
+
# ββ Tab 1: Task Explorer βββββββββββββββββββββββββββββββββββββββββ
|
| 231 |
+
with gr.Tab("π Task Explorer"):
|
| 232 |
+
gr.Markdown("Select a task to explore its context, then edit the payload and submit to see your score.")
|
| 233 |
+
|
| 234 |
+
with gr.Row():
|
| 235 |
+
task_dropdown = gr.Dropdown(
|
| 236 |
+
choices=TASK_IDS,
|
| 237 |
+
value=TASK_IDS[0],
|
| 238 |
+
label="Select Task",
|
| 239 |
+
interactive=True,
|
| 240 |
+
)
|
| 241 |
+
difficulty_badge = gr.Textbox(label="Difficulty", interactive=False, scale=0)
|
| 242 |
+
|
| 243 |
+
instructions_box = gr.Textbox(
|
| 244 |
+
label="Instructions",
|
| 245 |
+
lines=4,
|
| 246 |
+
interactive=False,
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
with gr.Row():
|
| 250 |
+
context_box = gr.Code(
|
| 251 |
+
label="Task Context (read-only)",
|
| 252 |
+
language="json",
|
| 253 |
+
interactive=False,
|
| 254 |
+
lines=15,
|
| 255 |
+
)
|
| 256 |
+
payload_box = gr.Code(
|
| 257 |
+
label="Your Payload (edit and submit)",
|
| 258 |
+
language="json",
|
| 259 |
+
interactive=True,
|
| 260 |
+
lines=15,
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
submit_btn = gr.Button("π Submit & Score", variant="primary")
|
| 264 |
+
|
| 265 |
+
with gr.Row():
|
| 266 |
+
feedback_md = gr.Markdown("*Submit a payload to see your score.*")
|
| 267 |
+
score_slider = gr.Slider(0.0, 1.0, value=0.0, label="Score", interactive=False)
|
| 268 |
+
score_label = gr.Textbox(label="", interactive=False, scale=0)
|
| 269 |
+
|
| 270 |
+
# Events
|
| 271 |
+
task_dropdown.change(
|
| 272 |
+
fn=on_task_select,
|
| 273 |
+
inputs=[task_dropdown],
|
| 274 |
+
outputs=[difficulty_badge, instructions_box, context_box, payload_box, feedback_md, score_slider, score_label],
|
| 275 |
+
)
|
| 276 |
+
submit_btn.click(
|
| 277 |
+
fn=on_submit,
|
| 278 |
+
inputs=[task_dropdown, payload_box],
|
| 279 |
+
outputs=[feedback_md, score_slider, score_label],
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
# Load initial state
|
| 283 |
+
demo.load(
|
| 284 |
+
fn=on_task_select,
|
| 285 |
+
inputs=[task_dropdown],
|
| 286 |
+
outputs=[difficulty_badge, instructions_box, context_box, payload_box, feedback_md, score_slider, score_label],
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
# ββ Tab 2: Baseline Runner ββββββββββββββββββββββββββββββββββββββββ
|
| 290 |
+
with gr.Tab("β‘ Baseline Runner"):
|
| 291 |
+
gr.Markdown("""
|
| 292 |
+
Run all 12 tasks using the **example payloads** and see live results.
|
| 293 |
+
This shows the expected score distribution across agents and difficulties.
|
| 294 |
+
""")
|
| 295 |
+
|
| 296 |
+
run_btn = gr.Button("βΆ Run All 12 Tasks", variant="primary", size="lg")
|
| 297 |
+
status = gr.Markdown("*Click the button to start.*")
|
| 298 |
+
|
| 299 |
+
results_table = gr.Dataframe(
|
| 300 |
+
headers=["Task ID", "Difficulty", "Score", "Bar", "Feedback"],
|
| 301 |
+
datatype=["str", "str", "str", "str", "str"],
|
| 302 |
+
interactive=False,
|
| 303 |
+
wrap=True,
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
run_btn.click(
|
| 307 |
+
fn=run_all_tasks,
|
| 308 |
+
inputs=[],
|
| 309 |
+
outputs=[results_table, status],
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
# ββ Tab 3: Score Dashboard ββββββββββββββββββββββββββββββββββββββββ
|
| 313 |
+
with gr.Tab("π Task Dashboard"):
|
| 314 |
+
gr.Markdown("Overview of all 12 tasks across the 4 agents.")
|
| 315 |
+
gr.HTML(build_dashboard_html())
|
| 316 |
+
|
| 317 |
+
gr.Markdown("""
|
| 318 |
+
### Baseline Scores (GPT-4o-mini)
|
| 319 |
+
|
| 320 |
+
| Agent | Easy | Medium | Hard | Avg |
|
| 321 |
+
|-------|------|--------|------|-----|
|
| 322 |
+
| Email Triage | 1.00 | 0.80 | 0.67 | 0.82 |
|
| 323 |
+
| Code Review | 1.00 | 0.75 | 0.80 | 0.85 |
|
| 324 |
+
| Data Cleaning | 1.00 | 0.67 | 0.67 | 0.78 |
|
| 325 |
+
| Content Moderation | 1.00 | 0.75 | 0.50 | 0.75 |
|
| 326 |
+
| **Overall** | **1.00** | **0.74** | **0.66** | **0.80** |
|
| 327 |
+
""")
|
| 328 |
+
|
| 329 |
+
# ββ Tab 4: About ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 330 |
+
with gr.Tab("βΉοΈ About"):
|
| 331 |
+
gr.Markdown("""
|
| 332 |
+
## Why Meta?
|
| 333 |
+
|
| 334 |
+
Most OpenEnv environments cover a single domain. **Meta is the first multi-domain environment** that lets a single agent learn to handle completely different real-world tasks in one unified interface.
|
| 335 |
+
|
| 336 |
+
This makes it ideal for:
|
| 337 |
+
- Training **generalist agents** that switch between task types
|
| 338 |
+
- Benchmarking **multi-task learning** capabilities of LLMs
|
| 339 |
+
- Evaluating **transfer learning** between related domains
|
| 340 |
+
- Testing **robustness** across varying difficulty levels
|
| 341 |
+
|
| 342 |
+
## API Endpoints
|
| 343 |
+
|
| 344 |
+
| Method | Endpoint | Description |
|
| 345 |
+
|--------|----------|-------------|
|
| 346 |
+
| POST | `/reset` | Reset environment |
|
| 347 |
+
| POST | `/step` | Execute action |
|
| 348 |
+
| GET | `/state` | Current state |
|
| 349 |
+
| GET | `/health` | Health check |
|
| 350 |
+
| GET | `/tasks` | All 24 tasks with schemas |
|
| 351 |
+
| POST | `/step/typed/{task_id}` | Typed action format |
|
| 352 |
+
| POST | `/grader` | Score without side effects |
|
| 353 |
+
| GET | `/schema` | JSON schemas |
|
| 354 |
+
|
| 355 |
+
## Team
|
| 356 |
+
|
| 357 |
+
**Digital Yodha** β OpenEnv Hackathon 2026
|
| 358 |
+
|
| 359 |
+
| Name | GitHub |
|
| 360 |
+
|------|--------|
|
| 361 |
+
| Sangisetti Akarsh | [@Farbricated](https://github.com/Farbricated) |
|
| 362 |
+
| Sarika Jivrajika | [@Sarika-stack23](https://github.com/Sarika-stack23) |
|
| 363 |
+
|
| 364 |
+
---
|
| 365 |
+
*Built for OpenEnv Hackathon 2026 Β· MIT License*
|
| 366 |
+
""")
|
| 367 |
+
|
| 368 |
+
return demo
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
# ββ Mount helper (called from app.py) βββββββββββββββββββββββββββββββββββββββββ
|
| 372 |
+
|
| 373 |
+
def mount_gradio(app):
|
| 374 |
+
"""Mount the Gradio app into an existing FastAPI app at /ui"""
|
| 375 |
+
import gradio as gr
|
| 376 |
+
demo = build_gradio_app()
|
| 377 |
+
gr.mount_gradio_app(app, demo, path="/ui")
|
| 378 |
+
return app
|