Spaces:
Sleeping
Sleeping
File size: 7,164 Bytes
753b5ee d2b1c92 753b5ee d2b1c92 753b5ee d2b1c92 753b5ee d2b1c92 753b5ee d2b1c92 753b5ee d2b1c92 753b5ee | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | """
baseline.py β Baseline inference script for the Email Triage environment.
Runs an OpenAI-compatible model against all three task levels via the WebSocket client
and reports reproducible scores.
Reads credentials from:
API_KEY (required β injected by hackathon validator)
API_BASE_URL (required β injected by hackathon validator)
MODEL_NAME (optional, default: Qwen/Qwen2.5-72B-Instruct)
ENV_BASE_URL (optional, default: http://localhost:8000)
Usage:
# 1. Start the server:
uvicorn email_triage_rl_hackathon.server.app:app --port 8000
# 2. Run the baseline:
API_KEY=<key> API_BASE_URL=<url> python baseline.py
# Or against a deployed HF Space:
ENV_BASE_URL=https://Shivacode-rl-hackathon.hf.space API_KEY=<key> API_BASE_URL=<url> python baseline.py
"""
import os
import json
import sys
import time
# Auto-load .env file if present (pip install python-dotenv)
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass # dotenv not installed β fall back to real env vars
from openai import OpenAI
from email_triage_rl_hackathon.client import EmailTriageEnv
from email_triage_rl_hackathon.models import TriageAction
# ββ Config ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# API_KEY and API_BASE_URL are injected by the hackathon validator β always use them first.
API_KEY = os.environ.get("API_KEY") or os.environ.get("HF_TOKEN") or os.environ.get("OPENAI_API_KEY", "")
BASE_URL = os.environ.get("API_BASE_URL") or os.environ.get("OPENAI_BASE_URL", "https://router.huggingface.co/v1")
MODEL_NAME = os.environ.get("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
ENV_BASE_URL = os.environ.get("ENV_BASE_URL", "http://localhost:8000")
HF_TOKEN = os.environ.get("HF_TOKEN", "") # used by openenv push / huggingface_hub
if not API_KEY:
print("ERROR: API_KEY not set. The hackathon validator injects API_KEY automatically.")
sys.exit(1)
client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
# ββ System prompt βββββββββββββββββββββββββββββββββββββββββββββββββ
SYSTEM_PROMPT = """You are an expert email triage agent.
For each email you receive, respond with a JSON object with EXACTLY these fields:
{
"category": one of ["spam", "billing", "technical", "hr", "general"],
"priority": one of ["low", "medium", "high", "urgent"],
"department": one of ["support", "finance", "engineering", "hr", "management"],
"reply": "your professional reply draft (empty string for easy tasks)"
}
Rules:
- spam emails: low priority, route to support, no reply needed
- billing issues (payments, invoices, refunds): finance department
- technical issues (bugs, login, API, outages): engineering department
- HR matters (leave, harassment, complaints): hr department
- board/executive escalations: management department
- For medium/hard tasks, write a professional reply (2-4 sentences minimum)
- Urgent issues need immediate acknowledgment in the reply
Respond ONLY with the JSON object, no other text."""
def build_user_prompt(obs) -> str:
return f"""Task level: {obs.task_level}
Email ID: {obs.email_id}
From: {obs.sender}
Subject: {obs.subject}
{obs.body}
Emails remaining after this one: {obs.emails_remaining}
{"Note: Write a professional reply for this task level." if obs.task_level in ("medium", "hard") else "Note: Classification only (reply can be empty)."}"""
def call_model(obs) -> TriageAction:
"""Call the OpenAI model and parse the JSON action."""
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": build_user_prompt(obs)},
]
try:
response = client.chat.completions.create(
model=MODEL_NAME,
messages=messages,
temperature=0.0,
max_tokens=512,
)
content = response.choices[0].message.content.strip()
# Strip markdown code fences if present
if content.startswith("```"):
content = content.split("```")[1]
if content.startswith("json"):
content = content[4:]
data = json.loads(content)
return TriageAction(**data)
except Exception as e:
print(f" [model error] {e} β using fallback action")
return TriageAction(category="general", priority="low",
department="support", reply="")
def run_task(task_level: str) -> float:
"""Run one full episode for the given task level using the WS client. Returns average score."""
print(f"\n{'='*60}")
print(f" TASK LEVEL: {task_level.upper()}")
print(f"{'='*60}")
total_reward = 0.0
steps = 0
with EmailTriageEnv(base_url=ENV_BASE_URL).sync() as env:
obs = env.reset(seed=42, task_level=task_level)
while not obs.done and obs.email_id != "none":
print(f"\n π§ [{obs.email_id}] {obs.subject[:60]}")
action = call_model(obs)
print(f" β category={action.category}, priority={action.priority}, "
f"department={action.department}")
if action.reply:
preview = action.reply[:80].replace("\n", " ")
print(f" β reply: {preview}...")
result = env.step(action)
reward = result.reward or 0.0
total_reward += reward
steps += 1
obs = result.observation
feedback = getattr(obs, "feedback", "")
print(f" β reward={reward:.2f} | {feedback[:100]}")
time.sleep(0.3) # rate-limit courtesy
avg = total_reward / steps if steps > 0 else 0.0
print(f"\n β
{task_level.upper()} complete β steps={steps}, avg_score={avg:.3f}")
return avg
def main():
print(f"\nπ Email Triage Baseline")
print(f" model : {MODEL_NAME}")
print(f" env : {ENV_BASE_URL}")
print(f" running all 3 task levels...\n")
results = {}
for level in ["easy", "medium", "hard"]:
try:
score = run_task(level)
results[level] = score
except Exception as e:
print(f" ERROR on {level}: {e}")
results[level] = 0.0
print(f"\n{'='*60}")
print(" BASELINE RESULTS SUMMARY")
print(f"{'='*60}")
for level, score in results.items():
bar = "β" * int(score * 20)
print(f" {level:8s}: {score:.3f} {bar}")
overall = sum(results.values()) / len(results)
print(f" {'overall':8s}: {overall:.3f}")
print(f"{'='*60}\n")
os.makedirs("outputs/evals", exist_ok=True)
output = {
"model": MODEL_NAME,
"env_base_url": ENV_BASE_URL,
"scores": results,
"overall": overall,
}
with open("outputs/evals/baseline_results.json", "w") as f:
json.dump(output, f, indent=2)
print(" π Results saved to outputs/evals/baseline_results.json")
if __name__ == "__main__":
main()
|