File size: 4,469 Bytes
dbf5e96
 
b2b578f
 
 
 
 
 
 
 
 
dbf5e96
 
 
 
74c9d03
dbf5e96
 
 
 
74c9d03
 
 
 
b2b578f
 
 
 
 
 
 
 
74c9d03
b2b578f
 
74c9d03
b2b578f
74c9d03
 
 
b2b578f
 
74c9d03
 
b2b578f
74c9d03
b2b578f
74c9d03
 
b2b578f
74c9d03
 
 
b2b578f
74c9d03
b2b578f
74c9d03
 
b2b578f
 
dbf5e96
 
74c9d03
b2b578f
74c9d03
b2b578f
74c9d03
dbf5e96
b2b578f
 
 
 
 
 
 
74c9d03
dbf5e96
b2b578f
 
 
 
74c9d03
 
 
dbf5e96
b2b578f
 
 
 
 
 
 
74c9d03
b2b578f
dbf5e96
 
 
 
74c9d03
dbf5e96
 
b2b578f
dbf5e96
 
b2b578f
 
dbf5e96
b2b578f
 
dbf5e96
 
b2b578f
dbf5e96
b2b578f
dbf5e96
 
 
b2b578f
74c9d03
dbf5e96
b2b578f
 
dbf5e96
 
74c9d03
b2b578f
 
dbf5e96
b2b578f
 
 
 
 
dbf5e96
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/usr/bin/env python3
"""
CodeReviewEnv -- Baseline Agent (MCP tool-calling pattern)
==========================================================
Runs a heuristic agent using the MCP tool-calling interface:
  1. get_code → retrieve code
  2. analyze_code → structural analysis
  3. check_line × N → flag suspicious lines
  4. submit_review → final review

No LLM required. Uses keyword heuristics.
"""

import json
import os
import re
import statistics
import time
from typing import Dict, List, Tuple

from server.code_review_environment import CodeReviewEnvironment
from models import CodeReviewAction


def _tool_call(tool_name: str, arguments: Dict = None) -> CodeReviewAction:
    """Helper to create a ToolCallAction."""
    return CodeReviewAction(
        action_type="ToolCallAction",
        tool_name=tool_name,
        arguments=arguments or {},
    )


def heuristic_find_bugs(code: str) -> Tuple[List[str], List[int], str]:
    """Simple keyword-based bug detection. Returns (issues, lines, suggestion)."""
    issues = []
    flagged = []
    lines = code.split('\n')

    for i, line in enumerate(lines, 1):
        stripped = line.strip().lower()
        if stripped.startswith('#') or stripped.startswith('//'):
            continue

        if re.search(r'<=\s*len\b', line) or re.search(r'range\(.*-\s*1\)', line):
            issues.append(f"Potential off-by-one error on line {i}")
            flagged.append(i)
        if re.search(r'\w\s*-\s*\w', line) and 'range(' in line:
            issues.append(f"Suspicious range bound on line {i}")
            flagged.append(i)

    if not issues:
        for i, line in enumerate(lines, 1):
            if line.strip() and not line.strip().startswith(('#', '//', 'def ', 'func ')):
                issues.append(f"Potential issue on line {i}")
                flagged.append(i)
                break

    suggestion = f"Check boundary conditions on lines {flagged}" if flagged else "Review carefully"
    return issues, flagged, suggestion


def run_episode(difficulty: str, seed: int) -> Tuple[float, int]:
    """Run one episode using MCP tool-calling."""
    env = CodeReviewEnvironment()
    env.reset(seed=seed, difficulty=difficulty)
    steps = 0

    # Step 1: Get code
    obs = env.step(_tool_call("get_code"))
    steps += 1
    code = obs.tool_result.get("code", "") if obs.tool_result else ""

    # Step 2: Analyze
    obs = env.step(_tool_call("analyze_code"))
    steps += 1

    # Step 3: Find bugs heuristically and check lines
    issues, flagged, suggestion = heuristic_find_bugs(code)
    for line in flagged[:2]:
        obs = env.step(_tool_call("check_line", {"line": line}))
        steps += 1
        if obs.done:
            return obs.reward or 0.0, steps

    # Step 4: Submit review
    obs = env.step(_tool_call("submit_review", {
        "issues": issues,
        "flagged_lines": flagged,
        "suggestion": suggestion,
        "comment": f"Found {len(issues)} potential issue(s).",
    }))
    steps += 1
    return obs.reward or 0.0, steps


def main():
    SEED = 42
    N_EPISODES = 5

    print("=" * 60)
    print("CodeReviewEnv -- Baseline Agent (MCP tool-calling)")
    print("=" * 60)

    start = time.time()
    results: Dict[str, Dict] = {}

    for diff in ["easy", "medium", "hard"]:
        print(f"\n--- {diff.upper()} ---")
        scores = []
        for ep in range(N_EPISODES):
            score, steps = run_episode(diff, SEED + ep)
            scores.append(score)
            print(f"  Episode {ep + 1}: score={score:.4f} ({steps} steps)")

        mean = statistics.mean(scores)
        std = statistics.stdev(scores) if len(scores) > 1 else 0.0
        results[diff] = {"mean": round(mean, 4), "std": round(std, 4), "scores": [round(s, 4) for s in scores]}
        print(f"  -> Mean: {mean:.4f} +/- {std:.4f}")

    elapsed = time.time() - start
    composite = round(sum(r["mean"] for r in results.values()) / len(results), 4)

    print(f"\n{'=' * 60}")
    for tier in ["easy", "medium", "hard"]:
        r = results[tier]
        print(f"{tier:<10} | {r['mean']:>8.4f} | {r['std']:>8.4f}")
    print(f"{'=' * 60}")
    print(f"Composite: {composite:.4f} | Elapsed: {elapsed:.1f}s")

    os.makedirs("baseline", exist_ok=True)
    with open("baseline/heuristic_results.json", "w") as f:
        json.dump({"agent": "heuristic_mcp_baseline", "composite": composite, **results}, f, indent=2)


if __name__ == "__main__":
    main()