Team Ai
Apppublic

Shumaque/code-review-agent

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
inference.py261 linesDownload Raw Back to root
1"""2Inference Script for Code Review Agent3===================================4MANDATORY5- Before submitting, ensure the following variables are defined in your environment configuration:6    API_BASE_URL   The API endpoint for the LLM.7    MODEL_NAME     The model identifier to use for inference.8    HF_TOKEN       Your Hugging Face / API key.9 10- Defaults are set only for API_BASE_URL and MODEL_NAME11    (and should reflect your active inference setup):12    API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")13    MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")14 15- The inference script must be named `inference.py` and placed in the root directory of the project16- Participants must use OpenAI Client for all LLM calls using above variables17 18STDOUT FORMAT19- The script must emit exactly three line types to stdout, in this order:20 21    [START] task=<task_name> env=<benchmark> model=<model_name>22    [STEP]  step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>23    [END]   success=<true|false> steps=<n> score=<score> rewards=<r1,r2,...,rn>24 25  Rules:26    - One [START] line at episode begin.27    - One [STEP] line per step, immediately after env.step() returns.28    - One [END] line after env.close(), always emitted (even on exception).29    - reward and rewards are formatted to 2 decimal places.30    - done and success are lowercase booleans: true or false.31    - error is the raw last_action_error string, or null if none.32    - All fields on a single line with no newlines within a line.33    - Each tasks should return score in [0, 1]34 35  Example:36    [START] task=detect_logic_bug env=code-review-agent model=Qwen/Qwen2.5-72B-Instruct37    [STEP] step=1 action=submit_findings(count=2) reward=0.70 done=true error=null38    [END] success=true steps=1 score=0.700 rewards=0.7039"""40 41import os42import json43from typing import List, Optional44from dotenv import load_dotenv45 46# Load environment variables from .env file47load_dotenv()48 49from openai import OpenAI50 51API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY")52API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")53MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")54BENCHMARK = "code-review-agent"55MAX_STEPS = 556SUCCESS_SCORE_THRESHOLD = 0.5  # F1 score threshold for success57 58if API_KEY is None:59    raise ValueError("HF_TOKEN or API_KEY environment variable is required")60 61client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)62 63SYSTEM_PROMPT = """You are a senior software engineer performing a code review.64You will receive a unified diff where EVERY line is prefixed with its line number like "LINE   1: ...".65Your job is to identify ALL bugs, security vulnerabilities, and significant issues.66 67CRITICAL INSTRUCTIONS:681. Line Numbers: Use EXACTLY the "LINE X" number shown on the left of each buggy line.69   Example: If you see "LINE 017: + buggy_code_here" then report line_number: 1770   Do NOT count lines yourself - just read the LINE number directly from the left side.712. Keywords: Use EXACT terms in descriptions - "sql injection", "hardcoded", "off-by-one", "path traversal", "command injection", "pickle", "deserialization", "ceiling division"723. Precision: Only report REAL issues - don't hallucinate or report non-issues734. Categories: Use "bug", "security", "style", or "performance"745. Severity: Use "critical", "high", "medium", "low", or "info"75 76COMMON PATTERNS TO IDENTIFY:77- SQL Injection: f-string or string formatting in SQL queries78- Hardcoded Secrets: API keys, passwords, JWT secrets in code79- Command Injection: subprocess.run with shell=True and user input80- Path Traversal: Using unsanitized filenames in file paths81- Unsafe Deserialization: pickle.loads() on untrusted data82- Off-by-one Errors: Wrong index math like (page * size) instead of ((page-1) * size)83- Missing Edge Cases: Integer division instead of ceiling division84 85Respond ONLY with a valid JSON object matching this exact schema:86{87  "findings": [88    {89      "line_number": <integer, 1-indexed line in diff where issue appears>,90      "severity": "<critical|high|medium|low|info>",91      "category": "<bug|security|style|performance>",92      "description": "<clear explanation using exact keywords>"93    }94  ]95}96 97EXAMPLES:98Example 1 - SQL Injection:99Diff shows: sql = f"SELECT * FROM users WHERE name LIKE '%{query}%'"100Finding: {"line_number": 5, "severity": "critical", "category": "security", "description": "SQL injection vulnerability - unsanitized user input in f-string query allows SQL injection"}101 102Example 2 - Off-by-one Error:103Diff shows: start = page * page_size (should be (page - 1) * page_size)104Finding: {"line_number": 3, "severity": "high", "category": "bug", "description": "Off-by-one error in pagination - should use (page - 1) * page_size instead of page * page_size"}105 106Example 3 - Hardcoded Secret:107Diff shows: SECRET_KEY = "hardcoded_jwt_secret_abc123"108Finding: {"line_number": 10, "severity": "critical", "category": "security", "description": "Hardcoded JWT secret key exposed in source code - should use environment variable"}109 110Example 4 - Missing Ceiling Division:111Diff shows: return total_items // page_size (should be (total_items + page_size - 1) // page_size)112Finding: {"line_number": 8, "severity": "medium", "category": "bug", "description": "Missing ceiling division in total_pages calculation - use (total_items + page_size - 1) // page_size to include last partial page"}113 114Rules:115- Count line numbers from the FIRST line of the diff output shown116- Use the exact keywords listed above in descriptions117- Do NOT include findings for things that are fine118- Return only the JSON, no preamble, no markdown fences"""119 120 121def log_start(task: str, env: str, model: str) -> None:122    print(f"[START] task={task} env={env} model={model}", flush=True)123 124 125def log_step(126    step: int, action: str, reward: float, done: bool, error: Optional[str]127) -> None:128    error_val = error if error else "null"129    done_val = str(done).lower()130    print(131        f"[STEP] step={step} action={action} reward={reward:.2f} done={done_val} error={error_val}",132        flush=True,133    )134 135 136def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:137    rewards_str = ",".join(f"{r:.2f}" for r in rewards)138    print(139        f"[END] success={str(success).lower()} steps={steps} score={score:.3f} rewards={rewards_str}",140        flush=True,141    )142 143 144def review_diff(diff: str, file_path: str, pr_context: str, task_name: str) -> dict:145    # Number every line sequentially so AI can read exact line numbers146    numbered_diff = "\n".join(147        f"LINE {i + 1:3}: {line}" for i, line in enumerate(diff.split("\n"))148    )149 150    user_content = f"""PR Context: {pr_context}151File: {file_path}152Task: {task_name}153 154Diff (use the LINE numbers on the left as your line_number values):155{numbered_diff}"""156 157    try:158        response = client.chat.completions.create(159            model=MODEL_NAME,160            messages=[161                {"role": "system", "content": SYSTEM_PROMPT},162                {"role": "user", "content": user_content},163            ],164            temperature=0.0,165        )166        raw = response.choices[0].message.content.strip()167        # Strip markdown fences if model added them despite instructions168        if raw.startswith("```"):169            raw = raw.split("```")[1]170            if raw.startswith("json"):171                raw = raw[4:]172        return json.loads(raw.strip())173    except Exception as exc:174        print(f"[DEBUG] Model request failed: {exc}", flush=True)175        return {"findings": []}176 177 178def run_task(task_name: str, diff: str, file_path: str, pr_context: str) -> dict:179    from server.code_review_environment import CodeReviewEnv180    from server.models import CodeReviewAction, Finding181    from server.models import Severity, Category182 183    env = CodeReviewEnv(task_name=task_name)184    obs = env.reset()185 186    rewards: List[float] = []187    steps_taken = 0188    score = 0.0189    success = False190    last_error = None191 192    log_start(task=task_name, env=BENCHMARK, model=MODEL_NAME)193 194    try:195        result = review_diff(obs.diff, obs.file_path, obs.pr_context, task_name)196        findings = []197        for f in result.get("findings", []):198            try:199                findings.append(200                    Finding(201                        line_number=int(f["line_number"]),202                        severity=Severity(f["severity"]),203                        category=Category(f["category"]),204                        description=str(f["description"]),205                    )206                )207            except Exception:208                continue209 210        action = CodeReviewAction(findings=findings)211        obs = env.step(action)212        reward = obs.reward or 0.0213        done = obs.done214        error = None215 216        rewards.append(reward)217        steps_taken = 1218        action_str = f"submit_findings(count={len(findings)})"219 220        log_step(221            step=steps_taken, action=action_str, reward=reward, done=done, error=error222        )223 224        # Clamp score to (0, 1) range - hackathon requires strictly between 0 and 1225        score = min(0.999, max(0.001, reward))226        success = score >= SUCCESS_SCORE_THRESHOLD227 228    except Exception as exc:229        last_error = str(exc).replace("\n", " ")230        log_step(step=1, action="error", reward=0.0, done=True, error=last_error)231 232    finally:233        try:234            env.close()235        except Exception as e:236            print(f"[DEBUG] env.close() error: {e}", flush=True)237        log_end(success=success, steps=steps_taken, score=score, rewards=rewards)238 239    return {240        "task": task_name,241        "score": score,242        "success": success,243        "steps": steps_taken,244    }245 246 247if __name__ == "__main__":248    from tasks.seeds import TASK_REGISTRY249 250    all_results = []251    for task_name, seed in TASK_REGISTRY.items():252        result = run_task(task_name, seed.diff, seed.file_path, seed.pr_context)253        all_results.append(result)254 255    print("\n--- BASELINE SCORES ---", flush=True)256    total = 0.0257    for r in all_results:258        print(f"  {r['task']}: {r['score']:.4f} | success={r['success']}", flush=True)259        total += r["score"]260    print(f"  AVERAGE: {total / len(all_results):.4f}", flush=True)261