Files changed (1) hide show
  1. inference.py +64 -147
inference.py CHANGED
@@ -1,5 +1,4 @@
1
  import os
2
- import json
3
  from openai import OpenAI
4
  from env import EmailSortingEnv
5
 
@@ -7,14 +6,17 @@ from env import EmailSortingEnv
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
  # ============================================
@@ -22,88 +24,53 @@ client = OpenAI(
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
  # ============================================
@@ -111,105 +78,55 @@ def fallback_classify(email: dict) -> str:
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
- print(f"[START] task=email_sorting", flush=True)
133
-
134
- # Run until episode is done
135
- while not state["done"]:
136
- current_step = state["step"] + 1
137
- email = state["email"]
138
-
139
- print(f"Subject: {email['subject']}")
140
- print(f"From: {email['sender']}")
141
-
142
- # Ask AI to classify
143
- action = ask_llm_to_classify(email)
144
-
145
- # Take step in environment
146
- next_state, reward, done, info = env.step(action)
147
-
148
- result = info.get("result", "N/A")
149
- print(f"[STEP] step={current_step} action={action} reward={reward} result={result}", flush=True)
150
-
151
- step_results.append({
152
- "step": current_step,
153
- "subject": email["subject"],
154
- "action": action,
155
- "reward": reward,
156
- "result": result
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
- results = {
179
- "model": MODEL_NAME,
180
- "total_steps": total_steps,
181
- "correct": correct_count,
182
- "accuracy": round(correct_count / total_steps * 100, 1),
183
- "total_reward": total_reward,
184
- "step_details": step_results
185
- }
186
 
187
- score = round(correct_count / total_steps, 4) if total_steps > 0 else 0.0
188
- print(f"[END] task=email_sorting score={score} steps={total_steps}", flush=True)
 
 
189
 
190
- return results
191
 
 
192
 
193
- # ============================================
194
- # RUN GRADERS ALSO
195
- # ============================================
196
 
197
- def run_with_graders():
198
- """Run inference + all graders and show combined score."""
199
- from graders import run_all_graders
200
 
201
- print("\n--- Running Inference ---\n")
202
- inference_results = run_inference()
203
 
204
- print("\n--- Running Graders ---\n")
205
- grader_results = run_all_graders()
206
 
207
- print("\n" + "=" * 50)
208
- print("FINAL COMBINED RESULTS")
209
- print("=" * 50)
210
- print(f"Inference Accuracy: {inference_results['accuracy']}%")
211
- print(f"Grader Average Score: {grader_results['average_score']}")
212
- print("=" * 50)
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
 
215
  # ============================================
@@ -217,4 +134,4 @@ def run_with_graders():
217
  # ============================================
218
 
219
  if __name__ == "__main__":
220
- run_with_graders()
 
1
  import os
 
2
  from openai import OpenAI
3
  from env import EmailSortingEnv
4
 
 
6
  # SETUP — Read environment variables
7
  # ============================================
8
 
9
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://api.openai.com/v1")
10
+ MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4o-mini")
11
+ HF_TOKEN = os.getenv("HF_TOKEN")
12
+
13
+ if HF_TOKEN is None:
14
+ raise ValueError("HF_TOKEN environment variable is required")
15
 
16
  # Initialize OpenAI client
17
  client = OpenAI(
18
  base_url=API_BASE_URL,
19
+ api_key=HF_TOKEN
20
  )
21
 
22
  # ============================================
 
24
  # ============================================
25
 
26
  def ask_llm_to_classify(email: dict) -> str:
 
 
 
 
 
27
  prompt = f"""You are an email classification assistant.
 
28
  Classify the following email into exactly ONE of these categories:
29
  - spam: unwanted, scam, phishing, prize winning, fake offers
30
+ - important: work emails, order updates, bank alerts, urgent notices
31
  - promotion: genuine sale offers, discount emails from real shops
32
 
 
33
  Subject: {email['subject']}
34
  From: {email['sender']}
35
  Body: {email['body']}
36
 
37
+ Reply with ONLY one word: spam, important, or promotion"""
 
38
 
39
  try:
40
  response = client.chat.completions.create(
41
  model=MODEL_NAME,
42
  messages=[
43
+ {"role": "system", "content": "You are an email classifier. Reply with only one word: spam, important, or promotion."},
44
+ {"role": "user", "content": prompt}
 
 
 
 
 
 
45
  ],
46
  max_tokens=10,
47
  temperature=0.0
48
  )
 
 
49
  answer = response.choices[0].message.content.strip().lower()
50
+ if "spam" in answer: return "spam"
51
+ elif "promotion" in answer: return "promotion"
52
+ elif "important" in answer: return "important"
53
+ else: return "spam"
 
 
 
 
 
 
 
54
  except Exception as e:
 
 
55
  return fallback_classify(email)
56
 
57
 
58
  def fallback_classify(email: dict) -> str:
 
 
 
59
  subject = email["subject"].lower()
60
+ body = email["body"].lower()
61
+ sender = email["sender"].lower()
 
 
 
 
62
 
63
+ spam_kw = ["won", "free", "prize", "urgent", "money", "congratulations",
64
+ "claim", "earn", "selected", "suspended", "verify"]
65
+ promo_kw = ["off", "sale", "deal", "discount", "shop", "offer", "save", "limited time"]
66
 
67
+ spam_score = sum(1 for kw in spam_kw if kw in subject or kw in body)
68
+ promo_score = sum(1 for kw in promo_kw if kw in subject or kw in body)
69
+ suspicious = any(d in sender for d in [".xyz", ".tk", "-secure", "-alert"])
70
 
71
+ if spam_score >= 2 or suspicious: return "spam"
72
+ elif promo_score >= 2: return "promotion"
73
+ else: return "important"
 
 
 
 
 
74
 
75
 
76
  # ============================================
 
78
  # ============================================
79
 
80
  def run_inference():
81
+ env = EmailSortingEnv()
 
 
 
 
 
 
 
 
 
 
 
82
  state = env.reset()
83
 
84
+ rewards = []
 
85
  step_results = []
86
+ error = None
87
 
88
+ # [START] required format
89
+ print(f"[START] task=email_sorting env=email-sorting-openenv model={MODEL_NAME}", flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
+ try:
92
+ while not state["done"]:
93
+ current_step = state["step"] + 1
94
+ email = state["email"]
95
 
96
+ action = ask_llm_to_classify(email)
97
 
98
+ next_state, reward, done, info = env.step(action)
99
 
100
+ error_msg = info.get("error", "null") or "null"
101
+ done_str = "true" if done else "false"
 
102
 
103
+ # [STEP] — exact required format
104
+ print(f"[STEP] step={current_step} action={action} reward={reward:.2f} done={done_str} error={error_msg}", flush=True)
 
105
 
106
+ rewards.append(reward)
107
+ step_results.append({"step": current_step, "action": action, "reward": reward, "result": info.get("result", "N/A")})
108
 
109
+ state = next_state
 
110
 
111
+ success = True
112
+
113
+ except Exception as e:
114
+ error = str(e)
115
+ success = False
116
+
117
+ # [END] — exact required format
118
+ total_steps = len(rewards)
119
+ rewards_str = ",".join(f"{r:.2f}" for r in rewards)
120
+ success_str = "true" if success else "false"
121
+ print(f"[END] success={success_str} steps={total_steps} rewards={rewards_str}", flush=True)
122
+
123
+ return {
124
+ "model": MODEL_NAME,
125
+ "total_steps": total_steps,
126
+ "total_reward": round(sum(rewards), 2),
127
+ "success": success,
128
+ "step_details": step_results
129
+ }
130
 
131
 
132
  # ============================================
 
134
  # ============================================
135
 
136
  if __name__ == "__main__":
137
+ run_inference()