Abhishek-CS221006 commited on
Commit
fb37416
·
verified ·
1 Parent(s): c17ef24

Update inference.py

Browse files
Files changed (1) hide show
  1. inference.py +158 -155
inference.py CHANGED
@@ -1,32 +1,45 @@
1
- """Benchmark-compatible inference runner for clinical trial screening."""
2
 
3
  from __future__ import annotations
4
 
5
  import asyncio
6
  import json
7
  import os
8
- import re
9
- from typing import List, Optional
10
 
11
  from openai import OpenAI
12
 
13
- from client import ClinicalTrialScreeningEnvClient
14
- from models import ClinicalTrialScreeningAction, ClinicalTrialScreeningObservation
 
 
 
15
 
 
 
16
  API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
17
  MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
18
- HF_TOKEN = os.getenv("HF_TOKEN")
19
- LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME")
20
  ENV_BASE_URL = os.getenv("ENV_BASE_URL")
21
- BENCHMARK = os.getenv("BENCHMARK", "clinical_trial_screening")
22
- TASK_NAME = os.getenv("TASK_NAME", "clinical_trial_patient_screening")
23
- MAX_STEPS = int(os.getenv("MAX_STEPS", "19"))
24
-
25
- SYSTEM_PROMPT = (
26
- "You are a clinical trial screening agent. "
27
- "Return one compact JSON object with keys action_type, target_id, field_name, value, "
28
- "ranking, exclusions, rationale. Use only protocol-supported fields and codes."
29
- )
 
 
 
 
 
 
 
 
30
 
31
 
32
  def log_start(task: str, env: str, model: str) -> None:
@@ -34,201 +47,191 @@ def log_start(task: str, env: str, model: str) -> None:
34
 
35
 
36
  def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
37
- error_value = error if error is not None else "null"
38
  print(
39
  f"[STEP] step={step} action={action} reward={reward:.2f} "
40
- f"done={str(done).lower()} error={error_value}",
41
  flush=True,
42
  )
43
 
44
 
45
- def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
46
  rewards_str = ",".join(f"{reward:.2f}" for reward in rewards)
47
  print(
48
- f"[END] success={str(success).lower()} steps={steps} "
49
- f"score={score:.2f} rewards={rewards_str}",
50
  flush=True,
51
  )
52
 
53
 
54
- def format_action(action: ClinicalTrialScreeningAction) -> str:
55
- if action.action_type == "extract_data":
56
- return f"extract_data({action.field_name}={action.value})"
57
- if action.action_type == "submit_ranking":
58
- return f"submit_ranking({'>'.join(action.ranking)})"
59
- if action.action_type == "flag_exclusions":
60
- return f"flag_exclusions({','.join(action.exclusions)})"
61
- if action.action_type == "final_decision":
62
- return f"final_decision({action.value})"
63
- return action.action_type
64
-
65
-
66
- def safe_error(message: Optional[str]) -> Optional[str]:
67
- if not message:
68
  return None
69
- return re.sub(r"\s+", " ", message.strip())
70
-
71
-
72
- def build_user_prompt(
73
- observation: ClinicalTrialScreeningObservation,
74
- history: List[str],
75
- ) -> str:
76
- recent_history = " | ".join(history[-4:]) if history else "none"
77
- return (
78
- f"task_id={observation.task_id}\n"
79
- f"difficulty={observation.difficulty.value}\n"
80
- f"title={observation.title}\n"
81
- f"brief={observation.brief}\n"
82
- f"prompt={observation.prompt}\n"
83
- f"missing_targets={observation.missing_targets}\n"
84
- f"available_actions={observation.available_actions}\n"
85
- f"trial_metadata={json.dumps(observation.trial_metadata, sort_keys=True)}\n"
86
- f"history={recent_history}\n"
87
- "Reply with JSON only."
88
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
 
91
- def fallback_action(observation: ClinicalTrialScreeningObservation) -> ClinicalTrialScreeningAction:
92
- task_id = observation.task_id
93
- extracted = observation.extracted_points
94
- if task_id == "easy_eligibility":
95
- for field_name, value in [
96
- ("age", "47"),
97
- ("diagnosis", "metastatic nsclc"),
98
- ("biomarker", "egfr exon 19 deletion"),
99
- ("ecog", "1"),
100
- ("active_cns_disease", "no"),
101
- ]:
102
- if field_name not in extracted:
103
- return ClinicalTrialScreeningAction(
104
- action_type="extract_data",
105
- field_name=field_name,
106
- value=value,
107
- )
108
- return ClinicalTrialScreeningAction(action_type="final_decision", value="eligible")
109
- if task_id == "medium_patient_ranking":
110
- for field_name, value in [
111
- ("P-M101_fit_score", "0.88"),
112
- ("P-M102_fit_score", "0.71"),
113
- ("P-M103_fit_score", "0.54"),
114
- ("best_candidate", "P-M101"),
115
- ("lowest_candidate", "P-M103"),
116
- ]:
117
- if field_name not in extracted:
118
- return ClinicalTrialScreeningAction(
119
- action_type="extract_data",
120
- field_name=field_name,
121
- value=value,
122
- )
123
- return ClinicalTrialScreeningAction(
124
- action_type="submit_ranking",
125
- ranking=["P-M101", "P-M102", "P-M103"],
126
- )
127
- for field_name, value in [
128
- ("age", "68"),
129
- ("live_vaccine_days", "12"),
130
- ("prednisone_mg", "20"),
131
- ("surgery_days", "9"),
132
- ("anc", "0.9"),
133
- ]:
134
- if field_name not in extracted:
135
- return ClinicalTrialScreeningAction(
136
- action_type="extract_data",
137
- field_name=field_name,
138
- value=value,
139
- )
140
- if observation.grader_score < 0.7:
141
- return ClinicalTrialScreeningAction(
142
- action_type="flag_exclusions",
143
- exclusions=[
144
- "live_vaccine_within_30_days",
145
- "prednisone_over_10mg",
146
- "major_surgery_within_14_days",
147
- "anc_below_1.0",
148
- ],
149
- )
150
- return ClinicalTrialScreeningAction(action_type="final_decision", value="exclude")
151
 
152
 
153
  def get_model_action(
154
  client: OpenAI,
155
- observation: ClinicalTrialScreeningObservation,
 
 
156
  history: List[str],
157
- ) -> tuple[ClinicalTrialScreeningAction, Optional[str]]:
158
- prompt = build_user_prompt(observation, history)
159
  try:
160
- response = client.chat.completions.create(
161
  model=MODEL_NAME,
162
  messages=[
163
  {"role": "system", "content": SYSTEM_PROMPT},
164
- {"role": "user", "content": prompt},
165
  ],
166
- temperature=0.0,
167
- max_tokens=200,
 
168
  )
169
- content = (response.choices[0].message.content or "").strip()
170
- payload = json.loads(content)
171
- return ClinicalTrialScreeningAction.model_validate(payload), None
172
- except Exception as exc:
173
- return fallback_action(observation), safe_error(str(exc))
174
 
175
 
176
- async def create_env_client() -> ClinicalTrialScreeningEnvClient:
 
 
 
 
 
 
 
 
 
 
 
 
177
  if LOCAL_IMAGE_NAME:
178
- return await ClinicalTrialScreeningEnvClient.from_docker_image(LOCAL_IMAGE_NAME)
179
  if ENV_BASE_URL:
180
- client = ClinicalTrialScreeningEnvClient(base_url=ENV_BASE_URL)
181
- return await client.connect()
182
- client = ClinicalTrialScreeningEnvClient(base_url="http://localhost:8000")
183
- return await client.connect()
184
 
185
 
186
  async def main() -> None:
187
- client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
188
- env = await create_env_client()
189
  rewards: List[float] = []
190
- history: List[str] = []
191
  steps_taken = 0
192
- final_score = 0.0
193
  success = False
 
194
  last_error: Optional[str] = None
195
- result = None
196
 
197
  log_start(task=TASK_NAME, env=BENCHMARK, model=MODEL_NAME)
198
 
199
  try:
200
- result = await env.reset()
 
 
201
  for step in range(1, MAX_STEPS + 1):
202
  if result.done:
203
  break
204
 
205
- action, planning_error = get_model_action(client, result.observation, history)
206
- result = await env.step(action)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
- reward = float(result.reward or 0.0)
209
  rewards.append(reward)
210
  steps_taken = step
211
- last_error = planning_error
212
  log_step(
213
  step=step,
214
  action=format_action(action),
215
  reward=reward,
216
- done=bool(result.done),
217
- error=last_error,
218
- )
219
- history.append(
220
- f"{result.observation.task_id}:{format_action(action)}:{reward:.2f}:{result.done}"
221
  )
222
- if result.done:
 
 
223
  break
224
 
225
- if result is not None:
226
- final_score = float(result.observation.grader_score)
227
- final_score = min(max(final_score, 0.0), 1.0)
228
- success = bool(result.done) and final_score >= 0.99
229
  finally:
230
- await env.close()
231
- log_end(success=success, steps=steps_taken, score=final_score, rewards=rewards)
 
 
 
 
232
 
233
 
234
  if __name__ == "__main__":
 
1
+ """Hackathon-compliant inference runner for the clinical trial environment."""
2
 
3
  from __future__ import annotations
4
 
5
  import asyncio
6
  import json
7
  import os
8
+ import textwrap
9
+ from typing import Dict, List, Optional, Tuple
10
 
11
  from openai import OpenAI
12
 
13
+ try:
14
+ from clinical_trial_env import ClinicalTrialAction, ClinicalTrialEnv
15
+ except ImportError:
16
+ from client import ClinicalTrialEnv
17
+ from models import ClinicalTrialAction
18
 
19
+ LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME") or os.getenv("IMAGE_NAME")
20
+ API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY")
21
  API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
22
  MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
23
+ TASK_NAME = os.getenv("CLINICAL_TRIAL_TASK", "easy")
24
+ BENCHMARK = os.getenv("CLINICAL_TRIAL_BENCHMARK", "clinical_trial_env")
25
  ENV_BASE_URL = os.getenv("ENV_BASE_URL")
26
+ MAX_STEPS = int(os.getenv("MAX_STEPS", "20"))
27
+ TEMPERATURE = float(os.getenv("TEMPERATURE", "0.1"))
28
+ MAX_TOKENS = int(os.getenv("MAX_TOKENS", "220"))
29
+ SUCCESS_SCORE_THRESHOLD = float(os.getenv("SUCCESS_SCORE_THRESHOLD", "0.8"))
30
+ MIN_STRICT_SCORE = 0.01
31
+ MAX_STRICT_SCORE = 0.99
32
+
33
+ SYSTEM_PROMPT = textwrap.dedent(
34
+ """
35
+ You are operating a clinical trial screening environment.
36
+ Return exactly one compact JSON object with keys:
37
+ action_type, field_name, value, ranking, deviations, final_decision, rationale.
38
+ Use only supported action_type values:
39
+ extract_data, rank_patients, flag_deviation, submit_decision.
40
+ Do not add markdown, commentary, or code fences.
41
+ """
42
+ ).strip()
43
 
44
 
45
  def log_start(task: str, env: str, model: str) -> None:
 
47
 
48
 
49
  def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
50
+ error_text = error if error is not None else "null"
51
  print(
52
  f"[STEP] step={step} action={action} reward={reward:.2f} "
53
+ f"done={str(done).lower()} error={error_text}",
54
  flush=True,
55
  )
56
 
57
 
58
+ def log_end(success: bool, steps: int, rewards: List[float]) -> None:
59
  rewards_str = ",".join(f"{reward:.2f}" for reward in rewards)
60
  print(
61
+ f"[END] success={str(success).lower()} steps={steps} rewards={rewards_str}",
 
62
  flush=True,
63
  )
64
 
65
 
66
+ def sanitize_error(error: Optional[str]) -> Optional[str]:
67
+ if error is None:
 
 
 
 
 
 
 
 
 
 
 
 
68
  return None
69
+ cleaned = " ".join(error.split())
70
+ return cleaned or "null"
71
+
72
+
73
+ def build_user_prompt(task_name: str, step: int, observation_payload: Dict, history: List[str]) -> str:
74
+ history_text = "\n".join(history[-4:]) if history else "None"
75
+ return textwrap.dedent(
76
+ f"""
77
+ Task: {task_name}
78
+ Step: {step}
79
+ Observation:
80
+ {json.dumps(observation_payload, indent=2, sort_keys=True)}
81
+
82
+ Recent history:
83
+ {history_text}
84
+
85
+ Return the next best JSON action.
86
+ """
87
+ ).strip()
88
+
89
+
90
+ def heuristic_action(task_name: str, step: int) -> ClinicalTrialAction:
91
+ heuristics: Dict[Tuple[str, int], ClinicalTrialAction] = {
92
+ ("easy", 1): ClinicalTrialAction(action_type="extract_data", field_name="age", value="56"),
93
+ ("easy", 2): ClinicalTrialAction(
94
+ action_type="extract_data", field_name="egfr_mutation", value="L858R positive"
95
+ ),
96
+ ("easy", 3): ClinicalTrialAction(action_type="submit_decision", final_decision="eligible"),
97
+ ("medium", 1): ClinicalTrialAction(
98
+ action_type="extract_data", field_name="BC-101_her2_status", value="IHC 3+"
99
+ ),
100
+ ("medium", 2): ClinicalTrialAction(
101
+ action_type="extract_data", field_name="BC-102_trastuzumab_exposure", value="none"
102
+ ),
103
+ ("medium", 3): ClinicalTrialAction(
104
+ action_type="rank_patients", ranking=["BC-101", "BC-103", "BC-102"]
105
+ ),
106
+ ("hard", 1): ClinicalTrialAction(action_type="extract_data", field_name="biomarker", value="FLT3-ITD"),
107
+ ("hard", 2): ClinicalTrialAction(
108
+ action_type="flag_deviation",
109
+ deviations=[
110
+ "neutropenic fever",
111
+ "qtc greater than 480 ms",
112
+ "recent strong CYP3A4 inhibitor",
113
+ ],
114
+ ),
115
+ ("hard", 3): ClinicalTrialAction(action_type="submit_decision", final_decision="ineligible"),
116
+ }
117
+ return heuristics.get((task_name, step), ClinicalTrialAction(action_type="submit_decision", final_decision="ineligible"))
118
 
119
 
120
+ def parse_action(raw_text: str) -> ClinicalTrialAction:
121
+ payload = json.loads(raw_text)
122
+ return ClinicalTrialAction.model_validate(payload)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
 
125
  def get_model_action(
126
  client: OpenAI,
127
+ task_name: str,
128
+ step: int,
129
+ observation_payload: Dict,
130
  history: List[str],
131
+ ) -> ClinicalTrialAction:
132
+ user_prompt = build_user_prompt(task_name, step, observation_payload, history)
133
  try:
134
+ completion = client.chat.completions.create(
135
  model=MODEL_NAME,
136
  messages=[
137
  {"role": "system", "content": SYSTEM_PROMPT},
138
+ {"role": "user", "content": user_prompt},
139
  ],
140
+ temperature=TEMPERATURE,
141
+ max_tokens=MAX_TOKENS,
142
+ stream=False,
143
  )
144
+ content = (completion.choices[0].message.content or "").strip()
145
+ return parse_action(content)
146
+ except Exception:
147
+ return heuristic_action(task_name, step)
 
148
 
149
 
150
+ def format_action(action: ClinicalTrialAction) -> str:
151
+ payload = {
152
+ "action_type": action.action_type,
153
+ "field_name": action.field_name,
154
+ "value": action.value,
155
+ "ranking": action.ranking,
156
+ "deviations": action.deviations,
157
+ "final_decision": action.final_decision,
158
+ }
159
+ return json.dumps(payload, separators=(",", ":"), sort_keys=True)
160
+
161
+
162
+ async def create_env() -> ClinicalTrialEnv:
163
  if LOCAL_IMAGE_NAME:
164
+ return await ClinicalTrialEnv.from_docker_image(LOCAL_IMAGE_NAME)
165
  if ENV_BASE_URL:
166
+ env = ClinicalTrialEnv(base_url=ENV_BASE_URL)
167
+ await env.connect()
168
+ return env
169
+ raise RuntimeError("Set LOCAL_IMAGE_NAME for Docker execution or ENV_BASE_URL for an existing server.")
170
 
171
 
172
  async def main() -> None:
173
+ client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
174
+ env: Optional[ClinicalTrialEnv] = None
175
  rewards: List[float] = []
 
176
  steps_taken = 0
177
+ score = 0.5
178
  success = False
179
+ history: List[str] = []
180
  last_error: Optional[str] = None
 
181
 
182
  log_start(task=TASK_NAME, env=BENCHMARK, model=MODEL_NAME)
183
 
184
  try:
185
+ env = await create_env()
186
+ result = await env.reset(task_id=TASK_NAME)
187
+
188
  for step in range(1, MAX_STEPS + 1):
189
  if result.done:
190
  break
191
 
192
+ action = get_model_action(
193
+ client=client,
194
+ task_name=TASK_NAME,
195
+ step=step,
196
+ observation_payload=result.observation.model_dump(mode="json"),
197
+ history=history,
198
+ )
199
+
200
+ try:
201
+ result = await env.step(action)
202
+ reward = float(result.reward or 0.0)
203
+ done = bool(result.done)
204
+ last_error = None
205
+ except Exception as exc:
206
+ reward = 0.0
207
+ done = True
208
+ last_error = sanitize_error(str(exc))
209
 
 
210
  rewards.append(reward)
211
  steps_taken = step
 
212
  log_step(
213
  step=step,
214
  action=format_action(action),
215
  reward=reward,
216
+ done=done,
217
+ error=sanitize_error(last_error),
 
 
 
218
  )
219
+ history.append(f"step={step} action={format_action(action)} reward={reward:.2f}")
220
+
221
+ if last_error is not None or done:
222
  break
223
 
224
+ if last_error is None and "result" in locals():
225
+ score = float(result.observation.reward_details.grader_score)
226
+ score = min(max(score, MIN_STRICT_SCORE), MAX_STRICT_SCORE)
227
+ success = last_error is None and score >= SUCCESS_SCORE_THRESHOLD
228
  finally:
229
+ if env is not None:
230
+ try:
231
+ await env.close()
232
+ except Exception as exc:
233
+ last_error = last_error or sanitize_error(str(exc))
234
+ log_end(success=success, steps=steps_taken, rewards=rewards)
235
 
236
 
237
  if __name__ == "__main__":