3v324v23 commited on
Commit
000a6e7
Β·
1 Parent(s): 0261b11

fix: rewrite inference.py to use LiteLLM proxy via API_BASE_URL/API_KEY env vars

Browse files
Files changed (4) hide show
  1. inference.py +91 -129
  2. pyproject.toml +1 -0
  3. requirements.txt +2 -2
  4. uv.lock +2 -0
inference.py CHANGED
@@ -1,133 +1,91 @@
1
  """
2
- inference.py β€” Rule-based agent + CLI test runner
3
  Usage:
4
- python inference.py --task easy --agent rule
5
- python inference.py --task medium --agent rule
6
- python inference.py --task hard --agent rule
7
- python inference.py --task all --agent rule
8
  """
9
 
 
10
  import argparse
11
  import json
12
- from typing import List
13
 
14
- from models import StepName, DifficultyLevel
15
- from environment import CustomerSupportEnv
16
  from graders.base_grader import BaseGrader, HardTaskGrader
17
  from tasks import TASK_REGISTRY
18
 
19
-
20
- # ── Rule-based agent responses ─────────────────────────────────────────────────
21
-
22
- RULE_BASED_RESPONSES = {
23
- "easy": [
24
- # Step 1: Empathy
25
- (
26
- "I am truly sorry to hear that your laptop has not arrived yet. "
27
- "I completely understand how frustrating and urgent this must be for you, "
28
- "especially when you need it for work. Please allow me to help resolve this immediately."
29
- ),
30
- # Step 2: Collect Info
31
- (
32
- "To assist you as quickly as possible, could you please provide me with "
33
- "your order number or order ID? Additionally, may I have your full name "
34
- "and the email address associated with the order?"
35
- ),
36
- # Step 3: Investigate
37
- (
38
- "Thank you for that information. I am checking our system right now. "
39
- "I found that order #ORD-8821 is currently tracked and it appears the package "
40
- "is stuck at a sorting facility. Our records show the delay is due to a "
41
- "logistical hold-up that we are actively investigating."
42
- ),
43
- # Step 4: Resolution
44
- (
45
- "I sincerely apologize for this unacceptable delay. I will personally "
46
- "expedite your delivery and escalate this to our logistics partner immediately. "
47
- "You will receive your laptop within 2 business days. As compensation, "
48
- "I will also credit β‚Ή500 to your account for the inconvenience caused."
49
- ),
50
- ],
51
-
52
- "medium": [
53
- # Step 1: Empathy
54
- (
55
- "I sincerely apologize for the double charge on your account. "
56
- "I completely understand how upsetting and inconvenient this is. "
57
- "This should never have happened and I take full responsibility. "
58
- "I am going to resolve this for you right away."
59
- ),
60
- # Step 2: Collect Info
61
- (
62
- "To look into this immediately, may I please have your account number "
63
- "or the email address linked to your subscription? "
64
- "Could you also confirm the transaction dates and the card used for billing?"
65
- ),
66
- # Step 3: Investigate
67
- (
68
- "Thank you. I am reviewing your billing history right now. "
69
- "I can see in our billing system that there was indeed a duplicate charge "
70
- "on both the 1st and the 15th. It appears this was caused by a payment "
71
- "gateway retry issue on our end. I found the duplicate transaction clearly."
72
- ),
73
- # Step 4: Resolution
74
- (
75
- "I sincerely apologize for this error. I will process a full refund of "
76
- "$49.99 immediately. You should see this credited back to your card "
77
- "within 3 to 5 business days. I am also escalating this to our billing "
78
- "team to ensure this does not happen again. Thank you for your patience."
79
- ),
80
- ],
81
-
82
- "hard": [
83
- # Step 1: Empathy (de-escalation required)
84
- (
85
- "Mr. Mehta, I am personally and deeply sorry for what you have experienced. "
86
- "This is completely unacceptable and you have every right to be angry. "
87
- "I take full responsibility for the failure of our team to respond properly "
88
- "over the past three weeks. This should not have happened, especially for a "
89
- "valued VIP customer like yourself. I hear your frustration and I assure you "
90
- "that I will personally ensure this is resolved today."
91
- ),
92
- # Step 2: Collect Info
93
- (
94
- "To immediately escalate this as our top priority, could you please confirm "
95
- "your order number and VIP account details? I also need your email and "
96
- "contact number so I can personally follow up with you today."
97
- ),
98
- # Step 3: Investigate
99
- (
100
- "Thank you, Mr. Mehta. I am reviewing your tickets right now. "
101
- "I can see five tickets β€” all marked pending β€” with no technician dispatched. "
102
- "I found that this is unacceptable internally. I have already escalated "
103
- "this to our senior operations manager and our VIP support head. "
104
- "Our records show the replacement unit is available and ready to ship."
105
- ),
106
- # Step 4: Resolution
107
- (
108
- "Mr. Mehta, here is what I am doing right now: "
109
- "1) A replacement Premium Standing Desk Pro will be dispatched today with "
110
- "priority delivery β€” you will receive it within 24 hours. "
111
- "2) As per our VIP policy, I am authorizing a compensation of β‚Ή9,000 "
112
- "(20% of your order value) credited to your account immediately. "
113
- "3) I will personally ensure a senior technician contacts you within 2 hours. "
114
- "4) I am waiving your next month's subscription fee as an additional apology. "
115
- "I personally guarantee this will be resolved to your satisfaction today."
116
- ),
117
- ],
118
  }
119
 
120
 
121
- # ── Runner ─────────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- def run_task(task_name: str) -> dict:
124
- task = TASK_REGISTRY[task_name]
125
- grader = HardTaskGrader() if task_name == "hard" else BaseGrader()
126
- env = CustomerSupportEnv(task=task, grader=grader)
127
- responses = RULE_BASED_RESPONSES[task_name]
128
 
129
- # ── Required structured output: START block ────────────────────────────
130
- print(f"[START] task={task_name}", flush=True)
 
 
 
 
131
 
132
  print(f"\n{'='*60}")
133
  print(f" TASK: {task_name.upper()} | {task.task_id}")
@@ -136,14 +94,18 @@ def run_task(task_name: str) -> dict:
136
  print(f" Customer: {task.customer_message[:120]}...")
137
  print(f"{'='*60}\n")
138
 
 
 
 
139
  steps_taken = 0
140
- for i, response in enumerate(responses):
141
- step_name = list(StepName)[i]
142
- result, done = env.step(response)
143
- steps_taken = i + 1
144
 
145
  status = "βœ… CORRECT" if result.correct else "❌ WRONG"
146
- print(f"[Step {i+1}/4] {step_name.value.upper()} β€” {status}")
 
147
  print(f" Detected : {result.detected_action}")
148
  print(f" Reward : {result.reward:.3f} "
149
  f"(base={result.base_score:.2f}, bonus={result.step_bonus:.2f}, "
@@ -153,7 +115,7 @@ def run_task(task_name: str) -> dict:
153
  print(f" ⚠ {pr}")
154
  print()
155
 
156
- # ── Required structured output: STEP block ─────────────────────────
157
  print(f"[STEP] step={i+1} reward={result.reward:.3f}", flush=True)
158
 
159
  if done:
@@ -168,7 +130,7 @@ def run_task(task_name: str) -> dict:
168
  print(f" FAIL REASON : {summary['fail_reason']}")
169
  print(f"{'='*60}\n")
170
 
171
- # ── Required structured output: END block ──────────────────────────────
172
  print(
173
  f"[END] task={task_name} score={summary['total_reward']:.3f} steps={steps_taken}",
174
  flush=True,
@@ -179,19 +141,19 @@ def run_task(task_name: str) -> dict:
179
 
180
  def main():
181
  parser = argparse.ArgumentParser()
182
- parser.add_argument("--task", choices=["easy", "medium", "hard", "all"],
183
- default="all")
184
- parser.add_argument("--agent", choices=["rule"], default="rule")
185
- args = parser.parse_args()
 
186
 
187
- tasks = ["easy", "medium", "hard"] if args.task == "all" else [args.task]
188
  results = {}
189
  for t in tasks:
190
  results[t] = run_task(t)
191
 
192
  print("\nπŸ“Š FINAL SUMMARY")
193
- print(json.dumps(results, indent=2))
194
 
195
 
196
  if __name__ == "__main__":
197
- main()
 
1
  """
2
+ inference.py β€” LLM-based agent using Scaler-injected LiteLLM proxy.
3
  Usage:
4
+ python inference.py --task easy
5
+ python inference.py --task all
 
 
6
  """
7
 
8
+ import os
9
  import argparse
10
  import json
11
+ from openai import OpenAI
12
 
13
+ from models import StepName, EpisodeStatus
14
+ from environment import CustomerSupportEnv, STEP_ORDER
15
  from graders.base_grader import BaseGrader, HardTaskGrader
16
  from tasks import TASK_REGISTRY
17
 
18
+ # ── LLM Client (uses Scaler-injected env vars) ────────────────────────────────
19
+ client = OpenAI(
20
+ base_url=os.environ.get("API_BASE_URL", "https://api.openai.com/v1"),
21
+ api_key=os.environ.get("API_KEY", "no-key"),
22
+ )
23
+ MODEL = os.environ.get("MODEL_NAME", "gpt-4o-mini")
24
+
25
+ # ── Step-specific system prompts ──────────────────────────────────────────────
26
+ STEP_PROMPTS = {
27
+ StepName.EMPATHY: (
28
+ "You are a professional AI customer support agent. "
29
+ "Your ONLY task: Show genuine empathy. Apologize sincerely, validate the "
30
+ "customer's frustration, take responsibility, assure them you will help. "
31
+ "Do NOT ask for information. Do NOT give solutions yet. "
32
+ "Use: 'I am deeply sorry', 'I completely understand', "
33
+ "'This should not have happened', 'I take full responsibility'. "
34
+ "Tone: Warm, sincere. Max 3-4 sentences."
35
+ ),
36
+ StepName.COLLECT_INFO: (
37
+ "You are a professional AI customer support agent. "
38
+ "Your ONLY task: Collect the customer's details to investigate their case. "
39
+ "Ask for their order number or account email. "
40
+ "Use phrases like: 'please provide', 'your order number', "
41
+ "'so I can look into this', 'I will need'. "
42
+ "Tone: Professional, direct. Max 2 sentences."
43
+ ),
44
+ StepName.INVESTIGATE: (
45
+ "You are a professional AI customer support agent. "
46
+ "Your ONLY task: Investigate and share findings. Say you are reviewing the "
47
+ "case and describe what you found. "
48
+ "Use: 'I am checking', 'I can see in our records', 'I found that', "
49
+ "'Our system shows'. "
50
+ "Do NOT give the final resolution yet. Max 3-4 sentences."
51
+ ),
52
+ StepName.RESOLUTION: (
53
+ "You are a professional AI customer support agent. "
54
+ "Your ONLY task: Provide a clear, concrete resolution with a specific action "
55
+ "(refund/replacement/credit/expedite) and timeline. "
56
+ "For VIP/hard tasks, mention 20%% compensation. "
57
+ "Personally guarantee resolution. Max 4-5 sentences."
58
+ ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
 
62
+ def call_llm(task, current_step: StepName) -> str:
63
+ """Call LLM through the Scaler-injected LiteLLM proxy."""
64
+ system_prompt = STEP_PROMPTS[current_step]
65
+ user_msg = (
66
+ f"Customer message: {task.customer_message}\n"
67
+ f"Context: {task.scenario_context}\n"
68
+ f"Customer emotion: {task.customer_emotion}\n"
69
+ f"Your current task: {current_step.value.upper()}"
70
+ )
71
+ response = client.chat.completions.create(
72
+ model=MODEL,
73
+ messages=[
74
+ {"role": "system", "content": system_prompt},
75
+ {"role": "user", "content": user_msg},
76
+ ],
77
+ temperature=0.3,
78
+ max_tokens=250,
79
+ )
80
+ return response.choices[0].message.content.strip()
81
 
 
 
 
 
 
82
 
83
+ # ── Runner ────────────────────────────────────────────────────────────────────
84
+
85
+ def run_task(task_name: str) -> dict:
86
+ task = TASK_REGISTRY[task_name]
87
+ grader = HardTaskGrader() if task_name == "hard" else BaseGrader()
88
+ env = CustomerSupportEnv(task=task, grader=grader)
89
 
90
  print(f"\n{'='*60}")
91
  print(f" TASK: {task_name.upper()} | {task.task_id}")
 
94
  print(f" Customer: {task.customer_message[:120]}...")
95
  print(f"{'='*60}\n")
96
 
97
+ # Required structured output: START block
98
+ print(f"[START] task={task_name}", flush=True)
99
+
100
  steps_taken = 0
101
+ for i, step in enumerate(STEP_ORDER):
102
+ agent_response = call_llm(task, step)
103
+ result, done = env.step(agent_response)
104
+ steps_taken = i + 1
105
 
106
  status = "βœ… CORRECT" if result.correct else "❌ WRONG"
107
+ print(f"[Step {i+1}/4] {step.value.upper()} β€” {status}")
108
+ print(f" Agent : {agent_response[:100]}...")
109
  print(f" Detected : {result.detected_action}")
110
  print(f" Reward : {result.reward:.3f} "
111
  f"(base={result.base_score:.2f}, bonus={result.step_bonus:.2f}, "
 
115
  print(f" ⚠ {pr}")
116
  print()
117
 
118
+ # Required structured output: STEP block
119
  print(f"[STEP] step={i+1} reward={result.reward:.3f}", flush=True)
120
 
121
  if done:
 
130
  print(f" FAIL REASON : {summary['fail_reason']}")
131
  print(f"{'='*60}\n")
132
 
133
+ # Required structured output: END block
134
  print(
135
  f"[END] task={task_name} score={summary['total_reward']:.3f} steps={steps_taken}",
136
  flush=True,
 
141
 
142
  def main():
143
  parser = argparse.ArgumentParser()
144
+ parser.add_argument(
145
+ "--task", choices=["easy", "medium", "hard", "all"], default="all"
146
+ )
147
+ args = parser.parse_args()
148
+ tasks = ["easy", "medium", "hard"] if args.task == "all" else [args.task]
149
 
 
150
  results = {}
151
  for t in tasks:
152
  results[t] = run_task(t)
153
 
154
  print("\nπŸ“Š FINAL SUMMARY")
155
+ print(json.dumps(results, indent=2), flush=True)
156
 
157
 
158
  if __name__ == "__main__":
159
+ main()
pyproject.toml CHANGED
@@ -12,6 +12,7 @@ dependencies = [
12
  "uvicorn>=0.23.0",
13
  "pydantic>=2.0.0",
14
  "pyyaml>=6.0",
 
15
  "openenv-core>=0.2.0",
16
  ]
17
 
 
12
  "uvicorn>=0.23.0",
13
  "pydantic>=2.0.0",
14
  "pyyaml>=6.0",
15
+ "openai>=1.0.0",
16
  "openenv-core>=0.2.0",
17
  ]
18
 
requirements.txt CHANGED
@@ -1,5 +1,5 @@
1
  fastapi>=0.100.0
2
  uvicorn>=0.23.0
3
- groq>=0.4.0
4
- pyyaml>=6.0
5
  pydantic>=2.0.0
 
 
 
1
  fastapi>=0.100.0
2
  uvicorn>=0.23.0
 
 
3
  pydantic>=2.0.0
4
+ pyyaml>=6.0
5
+ openai>=1.0.0
uv.lock CHANGED
@@ -541,6 +541,7 @@ version = "1.0.0"
541
  source = { editable = "." }
542
  dependencies = [
543
  { name = "fastapi" },
 
544
  { name = "openenv-core" },
545
  { name = "pydantic" },
546
  { name = "pyyaml" },
@@ -557,6 +558,7 @@ dev = [
557
  requires-dist = [
558
  { name = "fastapi", specifier = ">=0.100.0" },
559
  { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.24.0" },
 
560
  { name = "openenv-core", specifier = ">=0.2.0" },
561
  { name = "pydantic", specifier = ">=2.0.0" },
562
  { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" },
 
541
  source = { editable = "." }
542
  dependencies = [
543
  { name = "fastapi" },
544
+ { name = "openai" },
545
  { name = "openenv-core" },
546
  { name = "pydantic" },
547
  { name = "pyyaml" },
 
558
  requires-dist = [
559
  { name = "fastapi", specifier = ">=0.100.0" },
560
  { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.24.0" },
561
+ { name = "openai", specifier = ">=1.0.0" },
562
  { name = "openenv-core", specifier = ">=0.2.0" },
563
  { name = "pydantic", specifier = ">=2.0.0" },
564
  { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" },