Files changed (1) hide show
  1. inference.py +186 -88
inference.py CHANGED
@@ -1,125 +1,223 @@
1
- """
2
- inference.py — Email Sorting OpenEnv
3
- Mandatory plain-text [START] / [STEP] / [END] stdout format.
4
- """
5
-
6
  import os
7
- import sys
8
  import json
9
- import urllib.request
10
  from openai import OpenAI
 
11
 
12
  # ============================================
13
- # SETUP
14
  # ============================================
15
 
16
  API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.openai.com/v1")
17
- MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini")
18
- HF_TOKEN = os.environ.get("HF_TOKEN", "")
19
- SPACE_URL = os.environ.get("SPACE_URL", "http://localhost:7860")
20
 
 
21
  client = OpenAI(
22
  base_url=API_BASE_URL,
23
  api_key=HF_TOKEN if HF_TOKEN else "dummy-key"
24
  )
25
 
26
  # ============================================
27
- # ENV HELPERS
28
  # ============================================
29
 
30
- def env_reset():
31
- req = urllib.request.Request(f"{SPACE_URL}/reset", method="POST")
32
- req.add_header("Content-Type", "application/json")
33
- with urllib.request.urlopen(req, data=b"{}") as r:
34
- return json.loads(r.read())
35
 
36
- def env_step(action: str):
37
- payload = json.dumps({"action": action}).encode()
38
- req = urllib.request.Request(f"{SPACE_URL}/step", method="POST")
39
- req.add_header("Content-Type", "application/json")
40
- with urllib.request.urlopen(req, data=payload) as r:
41
- return json.loads(r.read())
42
 
43
- # ============================================
44
- # LLM CLASSIFIER
45
- # ============================================
 
 
 
 
 
 
 
 
 
46
 
47
- def classify_email(subject: str, body: str) -> str:
48
- prompt = (
49
- f"Classify this email into exactly one category: spam, important, or promotion.\n\n"
50
- f"Subject: {subject}\nBody: {body}\n\n"
51
- f"Reply with ONE word only: spam, important, or promotion."
52
- )
53
  try:
54
- resp = client.chat.completions.create(
55
  model=MODEL_NAME,
56
  messages=[
57
- {"role": "system", "content": "You are an email classifier. Reply with only one word."},
58
- {"role": "user", "content": prompt}
 
 
 
 
 
 
59
  ],
60
- max_tokens=5,
61
  temperature=0.0
62
  )
63
- answer = resp.choices[0].message.content.strip().lower()
64
- for label in ("spam", "promotion", "important"):
65
- if label in answer:
66
- return label
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  return "spam"
68
- except Exception:
69
- return fallback_classify(subject, body)
 
 
70
 
71
- def fallback_classify(subject: str, body: str) -> str:
72
- text = (subject + " " + body).lower()
73
- spam_kw = ["won", "free", "prize", "claim", "urgent", "congratulations", "selected"]
74
- promo_kw = ["off", "sale", "deal", "discount", "offer", "save", "shop"]
75
- if sum(1 for k in spam_kw if k in text) >= 2: return "spam"
76
- if sum(1 for k in promo_kw if k in text) >= 2: return "promotion"
77
- return "important"
78
 
79
  # ============================================
80
- # MAIN
81
  # ============================================
82
 
83
  def run_inference():
84
- task_name = "email-sorting-openenv"
85
-
86
- # [START]
87
- print(f"[START] task={task_name} model={MODEL_NAME}", flush=True)
88
-
89
- result = env_reset()
90
- state = result.get("state", result)
91
- total_reward = 0.0
92
- step_num = 0
93
-
94
- while not state.get("done", False):
95
- step_num += 1
96
- email = state.get("email", {})
97
- subject = email.get("subject", "")
98
- body = email.get("body", "")
99
-
100
- action = classify_email(subject, body)
101
- result = env_step(action)
102
-
103
- reward = result.get("reward", 0.0)
104
- total_reward += reward
105
- correct = result.get("info", {}).get("correct", "")
106
- state = result.get("state", result)
107
-
108
- # [STEP]
109
- print(
110
- f"[STEP] step={step_num} action={action} correct={correct} "
111
- f"reward={reward} total_reward={total_reward}",
112
- flush=True
113
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
- score = round(total_reward / step_num, 4) if step_num else 0.0
 
 
116
 
117
- # [END]
118
- print(
119
- f"[END] task={task_name} score={score} "
120
- f"total_reward={total_reward} steps={step_num}",
121
- flush=True
122
- )
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  if __name__ == "__main__":
125
- run_inference()
 
 
 
 
 
 
1
  import os
 
2
  import json
 
3
  from openai import OpenAI
4
+ from env import EmailSortingEnv
5
 
6
  # ============================================
7
+ # SETUP — Read environment variables
8
  # ============================================
9
 
10
  API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.openai.com/v1")
11
+ MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini")
12
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
 
13
 
14
+ # Initialize OpenAI client
15
  client = OpenAI(
16
  base_url=API_BASE_URL,
17
  api_key=HF_TOKEN if HF_TOKEN else "dummy-key"
18
  )
19
 
20
  # ============================================
21
+ # AI AGENT — Asks LLM to classify email
22
  # ============================================
23
 
24
+ def ask_llm_to_classify(email: dict) -> str:
25
+ """
26
+ Send email to LLM and get classification.
27
+ Returns: 'spam', 'important', or 'promotion'
28
+ """
29
 
30
+ prompt = f"""You are an email classification assistant.
 
 
 
 
 
31
 
32
+ Classify the following email into exactly ONE of these categories:
33
+ - spam: unwanted, scam, phishing, prize winning, fake offers
34
+ - important: work emails, order updates, bank alerts from real banks, newsletters
35
+ - promotion: genuine sale offers, discount emails from real shops
36
+
37
+ Email Details:
38
+ Subject: {email['subject']}
39
+ From: {email['sender']}
40
+ Body: {email['body']}
41
+
42
+ Reply with ONLY one word — either: spam, important, or promotion
43
+ Do not explain. Just one word."""
44
 
 
 
 
 
 
 
45
  try:
46
+ response = client.chat.completions.create(
47
  model=MODEL_NAME,
48
  messages=[
49
+ {
50
+ "role": "system",
51
+ "content": "You are an email classifier. Reply with only one word: spam, important, or promotion."
52
+ },
53
+ {
54
+ "role": "user",
55
+ "content": prompt
56
+ }
57
  ],
58
+ max_tokens=10,
59
  temperature=0.0
60
  )
61
+
62
+ # Extract the answer
63
+ answer = response.choices[0].message.content.strip().lower()
64
+
65
+ # Clean up answer — only keep valid categories
66
+ if "spam" in answer:
67
+ return "spam"
68
+ elif "promotion" in answer:
69
+ return "promotion"
70
+ elif "important" in answer:
71
+ return "important"
72
+ else:
73
+ return "spam" # Default fallback
74
+
75
+ except Exception as e:
76
+ print(f"LLM Error: {e}")
77
+ # Fallback to simple rule-based classification
78
+ return fallback_classify(email)
79
+
80
+
81
+ def fallback_classify(email: dict) -> str:
82
+ """
83
+ Simple rule-based fallback if LLM fails.
84
+ """
85
+ subject = email["subject"].lower()
86
+ body = email["body"].lower()
87
+ sender = email["sender"].lower()
88
+
89
+ spam_keywords = ["won", "free", "prize", "urgent", "money",
90
+ "congratulations", "claim", "earn", "selected",
91
+ "suspended", "verify", "action required"]
92
+
93
+ promo_keywords = ["off", "sale", "deal", "discount", "shop",
94
+ "offer", "save", "limited time"]
95
+
96
+ spam_score = sum(1 for kw in spam_keywords if kw in subject or kw in body)
97
+ promo_score = sum(1 for kw in promo_keywords if kw in subject or kw in body)
98
+
99
+ suspicious_domain = any(d in sender for d in [".xyz", ".tk", "-secure", "-alert"])
100
+
101
+ if spam_score >= 2 or suspicious_domain:
102
  return "spam"
103
+ elif promo_score >= 2:
104
+ return "promotion"
105
+ else:
106
+ return "important"
107
 
 
 
 
 
 
 
 
108
 
109
  # ============================================
110
+ # MAIN INFERENCE LOOP
111
  # ============================================
112
 
113
  def run_inference():
114
+ """
115
+ Main function — runs the AI agent for one full episode.
116
+ """
117
+ print("=" * 50)
118
+ print("Email Sorting Environment — Inference Script")
119
+ print("=" * 50)
120
+ print(f"Model: {MODEL_NAME}")
121
+ print(f"API Base: {API_BASE_URL}")
122
+ print("=" * 50)
123
+
124
+ # Initialize environment
125
+ env = EmailSortingEnv()
126
+ state = env.reset()
127
+
128
+ print(f"\nStarting episode — max {state['max_steps']} steps\n")
129
+
130
+ step_results = []
131
+
132
+ # Run until episode is done
133
+ while not state["done"]:
134
+ current_step = state["step"] + 1
135
+ email = state["email"]
136
+
137
+ print(f"Step {current_step}/{state['max_steps']}")
138
+ print(f"Subject: {email['subject']}")
139
+ print(f"From: {email['sender']}")
140
+
141
+ # Ask AI to classify
142
+ action = ask_llm_to_classify(email)
143
+ print(f"AI Decision: {action}")
144
+
145
+ # Take step in environment
146
+ next_state, reward, done, info = env.step(action)
147
+
148
+ print(f"Reward: {reward} | Result: {info.get('result', 'N/A')}")
149
+ print("-" * 40)
150
+
151
+ step_results.append({
152
+ "step": current_step,
153
+ "subject": email["subject"],
154
+ "action": action,
155
+ "reward": reward,
156
+ "result": info.get("result", "N/A")
157
+ })
158
+
159
+ state = next_state
160
+
161
+ # ============================================
162
+ # FINAL RESULTS
163
+ # ============================================
164
+
165
+ total_reward = state["total_reward"]
166
+ total_steps = state["step"]
167
+ correct_count = sum(1 for r in step_results if r["result"] == "correct")
168
+
169
+ print("\n" + "=" * 50)
170
+ print("EPISODE COMPLETE")
171
+ print("=" * 50)
172
+ print(f"Total Steps: {total_steps}")
173
+ print(f"Correct: {correct_count}/{total_steps}")
174
+ print(f"Accuracy: {round(correct_count/total_steps*100, 1)}%")
175
+ print(f"Total Reward: {total_reward}")
176
+ print("=" * 50)
177
+
178
+ # Save results to file
179
+ results = {
180
+ "model": MODEL_NAME,
181
+ "total_steps": total_steps,
182
+ "correct": correct_count,
183
+ "accuracy": round(correct_count / total_steps * 100, 1),
184
+ "total_reward": total_reward,
185
+ "step_details": step_results
186
+ }
187
+
188
+ with open("inference_results.json", "w") as f:
189
+ json.dump(results, f, indent=2)
190
+
191
+ print("\nResults saved to inference_results.json")
192
+ return results
193
+
194
+
195
+ # ============================================
196
+ # RUN GRADERS ALSO
197
+ # ============================================
198
 
199
+ def run_with_graders():
200
+ """Run inference + all graders and show combined score."""
201
+ from graders import run_all_graders
202
 
203
+ print("\n--- Running Inference ---\n")
204
+ inference_results = run_inference()
205
+
206
+ print("\n--- Running Graders ---\n")
207
+ grader_results = run_all_graders()
208
+
209
+ print("\n" + "=" * 50)
210
+ print("FINAL COMBINED RESULTS")
211
+ print("=" * 50)
212
+ print(f"Inference Accuracy: {inference_results['accuracy']}%")
213
+ print(f"Grader Average Score: {grader_results['average_score']}")
214
+ print(f"All Graders Passed: {grader_results['all_passed']}")
215
+ print("=" * 50)
216
+
217
+
218
+ # ============================================
219
+ # ENTRY POINT
220
+ # ============================================
221
 
222
  if __name__ == "__main__":
223
+ run_with_graders()