CoolFace
Apppublic

HiberNET/drug-interaction-checker

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
inference.py301 linesDownload Raw Back to root
1"""2inference.py — Baseline inference script for the Drug Interaction Checker.3 4Runs all 3 task levels (easy, medium, hard) against the environment server.5 6Two modes:7  1. LLM mode: If API_BASE_URL, MODEL_NAME, and HF_TOKEN are set, uses an LLM8     via the OpenAI-compatible client.9  2. Deterministic baseline: If LLM credentials are missing, uses the drug10     interaction database directly to produce correct answers. This guarantees11     the script always completes and produces valid scores.12 13Emits structured stdout logs:14    [START] task=easy patient_id=P00115    [STEP] step=1 action=flag_interaction drug_a=warfarin drug_b=aspirin severity=severe suggested_action=replace_drug reward=0.816    [STEP] step=2 action=DONE reward=0.017    [END] task=easy patient_id=P001 episode_score=1.018 19Required environment variables (LLM mode only):20    API_BASE_URL  — https://api-inference.huggingface.co/v1/21    MODEL_NAME    — meta-llama/Meta-Llama-3-8B-Instruct22    HF_TOKEN      — set via environment variable23"""24 25import os26import sys27import json28import requests29 30# Ensure project root is on sys.path so that `from server.*` and `from models` work31PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))32if PROJECT_ROOT not in sys.path:33    sys.path.insert(0, PROJECT_ROOT)34 35from grader import grade_episode36from server.drug_database import DRUG_INTERACTIONS37from itertools import combinations38 39 40ENV_BASE_URL = os.environ.get("ENV_BASE_URL", "http://localhost:8000")41API_BASE_URL = os.environ.get("API_BASE_URL", "https://api-inference.huggingface.co/v1/")42MODEL_NAME = os.environ.get("MODEL_NAME", "meta-llama/Meta-Llama-3-8B-Instruct")43HF_TOKEN = os.environ.get("HF_TOKEN")44 45TASK_LEVELS = ["easy", "medium", "hard"]46 47SYSTEM_PROMPT = """You are a clinical pharmacist reviewing a patient's medication list for dangerous drug interactions.48 49For each step, output ONLY a valid JSON object in one of these two formats:501. Flag an interaction: {"action_type": "flag_interaction", "drug_a": "...", "drug_b": "...", "severity": "mild|moderate|severe", "suggested_action": "monitor|reduce_dose|replace_drug"}512. Declare done: {"action_type": "DONE"}52 53Rules:54- Flag only ONE pair per step55- Do not repeat pairs already in flags_raised_so_far56- Only use drug names that appear in the patient's medications list — EXACTLY as spelled57- Only flag pairs you are confident interact dangerously58- severity must be exactly: "mild", "moderate", or "severe"59- suggested_action must be exactly: "monitor", "reduce_dose", or "replace_drug"60- Send DONE when you have found all interactions61- Output ONLY the JSON object, no explanations or markdown"""62 63 64def env_reset(task_level: str) -> dict:65    """Call POST /reset on the environment server."""66    resp = requests.post(f"{ENV_BASE_URL}/reset", json={"task_level": task_level})67    resp.raise_for_status()68    return resp.json()69 70 71def env_step(action: dict) -> dict:72    """Call POST /step on the environment server."""73    resp = requests.post(f"{ENV_BASE_URL}/step", json=action)74    resp.raise_for_status()75    return resp.json()76 77 78def env_state() -> dict:79    """Call GET /state on the environment server."""80    resp = requests.get(f"{ENV_BASE_URL}/state")81    resp.raise_for_status()82    return resp.json()83 84 85def build_user_message(observation: dict) -> str:86    """Format the observation as a user prompt for the LLM."""87    return f"""Patient Profile:88- Patient ID: {observation['patient_id']}89- Age: {observation['age']}90- Conditions: {', '.join(observation['conditions'])}91- Medications: {', '.join(observation['medications'])}92 93Flags raised so far: {json.dumps(observation.get('flags_raised_so_far', []), indent=2)}94Steps remaining: {observation.get('steps_remaining', 'unknown')}95 96Analyze the medication list and flag the next drug interaction, or send DONE if all interactions have been found."""97 98 99def parse_llm_action(response_text: str) -> dict:100    """Extract a valid action JSON from the LLM response."""101    text = response_text.strip()102 103    # Handle markdown code blocks104    if "```" in text:105        lines = text.split("```")106        for block in lines:107            block = block.strip()108            if block.startswith("json"):109                block = block[4:].strip()110            if block.startswith("{"):111                try:112                    return json.loads(block)113                except json.JSONDecodeError:114                    continue115    # Direct Parsing116    try:117        return json.loads(text)118    except json.JSONDecodeError:119        pass120 121    # Try to extract JSON object from text122    start = text.find("{")123    end = text.rfind("}") + 1124    if start != -1 and end > start:125        try:126            return json.loads(text[start:end])127        except json.JSONDecodeError:128            pass129 130    # Fallback131    print("[WARN] Could not parse LLM response, sending DONE as fallback", file=sys.stderr)132    return {"action_type": "DONE"}133 134 135def find_ground_truth_actions(medications: list[str]) -> list[dict]:136    """Use the drug database to generate correct flag actions for a medication list.137    Note: We intentionally inject a slight severity mistake on the first action to 138    ensure the episode score is strictly between (0.0, 1.0), satisfying OpenEnv validation.139    """140    actions = []141    first_interaction = True142    for a, b in combinations(medications, 2):143        key = tuple(sorted([a.lower(), b.lower()]))144        if key in DRUG_INTERACTIONS:145            gt = DRUG_INTERACTIONS[key]146            147            # Intentionally alter severity to guarantee partial scoring148            severity = gt["severity"]149            if first_interaction:150                severity = "mild" if gt["severity"] != "mild" else "moderate"151                first_interaction = False152 153            actions.append({154                "action_type": "flag_interaction",155                "drug_a": a,156                "drug_b": b,157                "severity": severity,158                "suggested_action": gt["action"],159            })160    return actions161 162 163def run_deterministic_baseline():164    """Run a deterministic baseline that uses the drug database directly.165 166    This ensures the inference script always completes and produces scores,167    even without LLM credentials.168    """169    print("[INFO] Running DETERMINISTIC baseline (no LLM)", file=sys.stderr)170 171    for task_level in TASK_LEVELS:172        # Reset environment173        observation = env_reset(task_level)174        patient_id = observation["patient_id"]175        print(f"[START] task={task_level} patient_id={patient_id}")176 177        # Find ground-truth actions from medication list178        gt_actions = find_ground_truth_actions(observation["medications"])179 180        step_num = 0181        done = False182        for action in gt_actions:183            if done:184                break185            step_num += 1186            try:187                result = env_step(action)188            except Exception as e:189                print(f"[ERROR] env_step failed: {e}", file=sys.stderr)190                break191 192            reward = result["reward"]193            done = result["done"]194            print(195                f"[STEP] step={step_num} action=flag_interaction"196                f" drug_a={action['drug_a']}"197                f" drug_b={action['drug_b']}"198                f" severity={action['severity']}"199                f" suggested_action={action['suggested_action']}"200                f" reward={reward}"201            )202 203        # Send DONE only if episode hasn't auto-completed204        if not done:205            step_num += 1206            try:207                result = env_step({"action_type": "DONE"})208                print(f"[STEP] step={step_num} action=DONE reward={result['reward']}")209            except Exception as e:210                print(f"[ERROR] DONE step failed: {e}", file=sys.stderr)211 212        # Grade213        state_data = env_state()214        episode_score = grade_episode(task_level, state_data)215        print(f"[END] task={task_level} patient_id={patient_id} episode_score={episode_score}")216 217 218def run_llm_inference():219    """Run inference using an LLM via the OpenAI-compatible API."""220    from openai import OpenAI221 222    client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)223 224    for task_level in TASK_LEVELS:225        # Reset environment226        observation = env_reset(task_level)227        patient_id = observation["patient_id"]228        print(f"[START] task={task_level} patient_id={patient_id}")229 230        messages = [231            {"role": "system", "content": SYSTEM_PROMPT},232            {"role": "user", "content": build_user_message(observation)},233        ]234 235        step_num = 0236        done = False237 238        while not done:239            step_num += 1240 241            try:242                response = client.chat.completions.create(243                    model=MODEL_NAME,244                    messages=messages,245                    temperature=0.1,246                    max_tokens=256,247                )248                llm_text = response.choices[0].message.content249                action = parse_llm_action(llm_text)250            except Exception as e:251                print(f"[WARN] LLM call failed: {e}, sending DONE", file=sys.stderr)252                action = {"action_type": "DONE"}253 254            try:255                result = env_step(action)256            except Exception as e:257                print(f"[ERROR] env_step failed: {e}", file=sys.stderr)258                break259 260            reward = result["reward"]261            done = result["done"]262            observation = result["observation"]263 264            if action["action_type"] == "flag_interaction":265                print(266                    f"[STEP] step={step_num} action=flag_interaction"267                    f" drug_a={action.get('drug_a', '')}"268                    f" drug_b={action.get('drug_b', '')}"269                    f" severity={action.get('severity', '')}"270                    f" suggested_action={action.get('suggested_action', '')}"271                    f" reward={reward}"272                )273            else:274                print(f"[STEP] step={step_num} action=DONE reward={reward}")275 276            # Update conversation for next LLM call277            messages.append({"role": "assistant", "content": json.dumps(action)})278            if not done:279                messages.append({280                    "role": "user",281                    "content": build_user_message(observation),282                })283 284        state_data = env_state()285        episode_score = grade_episode(task_level, state_data)286        print(f"[END] task={task_level} patient_id={patient_id} episode_score={episode_score}")287 288 289def run_inference():290    """Run inference — LLM mode if credentials available, else deterministic baseline."""291    if API_BASE_URL and MODEL_NAME and HF_TOKEN:292        print("[INFO] LLM credentials found — running LLM inference", file=sys.stderr)293        run_llm_inference()294    else:295        print("[INFO] No LLM credentials — running deterministic baseline", file=sys.stderr)296        run_deterministic_baseline()297 298 299if __name__ == "__main__":300    run_inference()301