Tejasva commited on
Commit
57dcac8
·
1 Parent(s): 65ae195

$COMMIT_MESSAGE

Browse files
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy requirements first for better caching
6
+ COPY requirements.txt .
7
+
8
+ # Install dependencies
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Copy application code
12
+ COPY . .
13
+
14
+ # Expose port for HuggingFace Spaces
15
+ EXPOSE 7860
16
+
17
+ # Set environment variables
18
+ ENV PYTHONPATH=/app
19
+ ENV PYTHONUNBUFFERED=1
20
+
21
+ # Run the application (supports both inference and Space deployment)
22
+ CMD ["python", "app.py"]
__pycache__/inference.cpython-313.pyc CHANGED
Binary files a/__pycache__/inference.cpython-313.pyc and b/__pycache__/inference.cpython-313.pyc differ
 
app.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import sys
5
+ import gradio as gr
6
+ import json
7
+ from typing import Dict, Any, Optional, List
8
+ import logging
9
+
10
+ # Add current directory to Python path
11
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
12
+
13
+ from env.environment import CodeGuardianEnv
14
+ from env.models import Action, Observation
15
+
16
+ # Setup logging
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class CodeGuardianSpace:
21
+ def __init__(self):
22
+ self.env = CodeGuardianEnv()
23
+ self.current_obs = None
24
+ self.reset_environment()
25
+
26
+ def reset_environment(self):
27
+ """Reset the environment and return initial observation."""
28
+ try:
29
+ self.current_obs = self.env.reset()
30
+ return self.format_observation(self.current_obs)
31
+ except Exception as e:
32
+ logger.error(f"Failed to reset environment: {e}")
33
+ return f"Error: {str(e)}"
34
+
35
+ def format_observation(self, obs: Observation) -> str:
36
+ """Format observation for display."""
37
+ return f"""
38
+ **Current Task:** {obs.task_id} ({obs.difficulty})
39
+ **Step:** {obs.step_index}/3
40
+
41
+ **Code:**
42
+ ```python
43
+ {obs.code}
44
+ ```
45
+
46
+ **Intent:** {obs.intent}
47
+
48
+ **Constraints:**
49
+ {chr(10).join(f"- {c}" for c in obs.constraints)}
50
+ """
51
+
52
+ def step_environment(self, action_type: str, detected_issues: str = "", risk_level: str = "low", suggested_fix: str = ""):
53
+ """Execute a step in the environment."""
54
+ try:
55
+ if not self.current_obs or self.env.done:
56
+ return "Environment is done. Please reset to continue.", "", 0.0
57
+
58
+ # Parse detected issues
59
+ issues_list = []
60
+ if detected_issues.strip():
61
+ issues_list = [issue.strip() for issue in detected_issues.split(',')]
62
+
63
+ # Create action
64
+ action = Action(
65
+ action_type=action_type,
66
+ detected_issues=issues_list if action_type == "detect" else None,
67
+ risk_level=risk_level if action_type == "risk" else None,
68
+ suggested_fix=suggested_fix if action_type == "fix" else None
69
+ )
70
+
71
+ # Execute step
72
+ next_obs, reward, done, info = self.env.step(action)
73
+ self.current_obs = next_obs
74
+
75
+ # Format results
76
+ obs_text = self.format_observation(next_obs) if next_obs else "Environment completed!"
77
+ feedback_text = f"Feedback: {info['feedback']}" if info['feedback'] else "No feedback"
78
+ current_score = self.env.state()['partial_score']
79
+ score_text = f"Current Score: {current_score:.2f}"
80
+
81
+ if done:
82
+ obs_text += f"\n\n**Final Score: {current_score:.2f}**"
83
+
84
+ return obs_text, feedback_text, reward
85
+
86
+ except Exception as e:
87
+ logger.error(f"Failed to execute step: {e}")
88
+ return f"Error: {str(e)}", "", 0.0
89
+
90
+ def get_expected_step(self):
91
+ """Get the expected action type for current step."""
92
+ if not self.current_obs or self.env.done:
93
+ return "reset"
94
+ step_types = ["detect", "risk", "fix"]
95
+ return step_types[self.env.state()['current_step']]
96
+
97
+ # Initialize the space
98
+ space = CodeGuardianSpace()
99
+
100
+ def reset_interface():
101
+ """Reset the environment interface."""
102
+ return space.reset_environment()
103
+
104
+ def step_interface(action_type, detected_issues, risk_level, suggested_fix):
105
+ """Step the environment interface."""
106
+ return space.step_environment(action_type, detected_issues, risk_level, suggested_fix)
107
+
108
+ def update_ui():
109
+ """Update UI based on current state."""
110
+ expected = space.get_expected_step()
111
+ if expected == "reset":
112
+ return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
113
+ else:
114
+ return gr.update(visible=False), gr.update(visible=True), gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)
115
+
116
+ # Create Gradio interface
117
+ with gr.Blocks(title="CodeGuardian Environment") as demo:
118
+ gr.Markdown("# CodeGuardian Environment")
119
+ gr.Markdown("Test your AI agent on code review tasks: detect issues, assess risks, and suggest fixes.")
120
+
121
+ with gr.Row():
122
+ reset_btn = gr.Button("Reset Environment", variant="primary")
123
+
124
+ obs_display = gr.Markdown("Click 'Reset Environment' to start")
125
+
126
+ with gr.Group(visible=False) as action_group:
127
+ gr.Markdown("## Take Action")
128
+
129
+ with gr.Row():
130
+ with gr.Column():
131
+ action_type = gr.Radio(
132
+ choices=["detect", "risk", "fix"],
133
+ label="Action Type",
134
+ value="detect"
135
+ )
136
+
137
+ with gr.Group(visible=True) as detect_group:
138
+ detected_issues = gr.Textbox(
139
+ label="Detected Issues (comma-separated)",
140
+ placeholder="e.g., wrong operator, security issue, performance problem"
141
+ )
142
+
143
+ with gr.Group(visible=False) as risk_group:
144
+ risk_level = gr.Radio(
145
+ choices=["low", "medium", "high"],
146
+ label="Risk Level",
147
+ value="low"
148
+ )
149
+
150
+ with gr.Group(visible=False) as fix_group:
151
+ suggested_fix = gr.Textbox(
152
+ label="Suggested Fix",
153
+ lines=3,
154
+ placeholder="Describe or provide the fix..."
155
+ )
156
+
157
+ with gr.Column():
158
+ step_btn = gr.Button("Execute Step", variant="primary")
159
+ feedback = gr.Textbox(label="Feedback", interactive=False)
160
+ reward = gr.Number(label="Reward", interactive=False)
161
+
162
+ # Event handlers
163
+ reset_btn.click(
164
+ reset_interface,
165
+ outputs=obs_display
166
+ ).then(
167
+ update_ui,
168
+ outputs=[reset_btn, action_group, detect_group, risk_group, fix_group]
169
+ )
170
+
171
+ def toggle_action_inputs(action):
172
+ """Show/hide inputs based on action type."""
173
+ return (
174
+ gr.update(visible=(action == "detect")),
175
+ gr.update(visible=(action == "risk")),
176
+ gr.update(visible=(action == "fix"))
177
+ )
178
+
179
+ action_type.change(
180
+ toggle_action_inputs,
181
+ inputs=action_type,
182
+ outputs=[detect_group, risk_group, fix_group]
183
+ )
184
+
185
+ step_btn.click(
186
+ step_interface,
187
+ inputs=[action_type, detected_issues, risk_level, suggested_fix],
188
+ outputs=[obs_display, feedback, reward]
189
+ ).then(
190
+ update_ui,
191
+ outputs=[reset_btn, action_group, detect_group, risk_group, fix_group]
192
+ )
193
+
194
+ if __name__ == "__main__":
195
+ demo.launch(server_name="0.0.0.0", server_port=7860)
env/__pycache__/environment.cpython-313.pyc CHANGED
Binary files a/env/__pycache__/environment.cpython-313.pyc and b/env/__pycache__/environment.cpython-313.pyc differ
 
env/__pycache__/grader.cpython-313.pyc CHANGED
Binary files a/env/__pycache__/grader.cpython-313.pyc and b/env/__pycache__/grader.cpython-313.pyc differ
 
env/__pycache__/models.cpython-313.pyc CHANGED
Binary files a/env/__pycache__/models.cpython-313.pyc and b/env/__pycache__/models.cpython-313.pyc differ
 
env/__pycache__/tasks.cpython-313.pyc CHANGED
Binary files a/env/__pycache__/tasks.cpython-313.pyc and b/env/__pycache__/tasks.cpython-313.pyc differ
 
env/environment.py CHANGED
@@ -1,6 +1,6 @@
1
  from env.models import Observation, Action
2
  from env.tasks import TASKS
3
- from env.grader import grade_action
4
 
5
  class CodeGuardianEnv:
6
  def __init__(self):
@@ -22,51 +22,51 @@ class CodeGuardianEnv:
22
  return Observation(
23
  code=task["code"],
24
  intent=task["intent"],
25
- constraints=task["constraints"]
 
 
 
26
  )
27
 
28
  def step(self, action: Action):
 
 
 
 
 
 
 
29
  task = self.tasks[self.current_task_index]
30
- expected = task["expected"]
31
- reward = 0
32
- feedback = []
33
 
34
  # Check if action order is valid
35
- if (self.current_step == 0 and action.action_type != "detect") or \
36
- (self.current_step == 1 and action.action_type != "risk") or \
37
- (self.current_step == 2 and action.action_type != "fix"):
38
- reward = -0.5
39
- feedback.append(f"Invalid action order: expected {['detect', 'risk', 'fix'][self.current_step]}, got {action.action_type}")
40
  else:
41
- # Step 0: detect issues
42
- if self.current_step == 0:
43
- if action.detected_issues and any(expected["issue"] in issue.lower() for issue in action.detected_issues):
44
- reward = 0.3
45
- else:
46
- reward = -0.2
47
- feedback.append("Issue not correctly identified")
48
- self.current_step += 1
 
 
 
49
 
50
- # Step 1: assign risk level
51
- elif self.current_step == 1:
52
- if action.risk_level and action.risk_level.lower() == expected["risk"]:
53
- reward = 0.2
54
- else:
55
- reward = -0.2
56
- feedback.append("Incorrect risk level")
57
- self.current_step += 1
58
 
59
- # Step 2: suggest fix
60
- elif self.current_step == 2:
61
- if action.suggested_fix and expected["fix_contains"] in action.suggested_fix.lower():
62
- reward = 0.5
63
- else:
64
- reward = -0.3
65
- feedback.append("Fix is incorrect or missing")
66
-
67
- # Move to next task and reset current_step
68
  self.current_task_index += 1
69
- self.current_step = 0
70
  if self.current_task_index >= len(self.tasks):
71
  self.done = True
72
 
@@ -76,7 +76,7 @@ class CodeGuardianEnv:
76
 
77
  info = {
78
  "feedback": feedback,
79
- "total_score": self.partial_score
80
  }
81
 
82
  return next_obs, reward, self.done, info
 
1
  from env.models import Observation, Action
2
  from env.tasks import TASKS
3
+ from env.grader import grade_detect, grade_risk, grade_fix
4
 
5
  class CodeGuardianEnv:
6
  def __init__(self):
 
22
  return Observation(
23
  code=task["code"],
24
  intent=task["intent"],
25
+ constraints=task["constraints"],
26
+ task_id=task["task_id"],
27
+ difficulty=task["difficulty"],
28
+ step_index=self.current_step
29
  )
30
 
31
  def step(self, action: Action):
32
+ # Early guard to prevent IndexError when environment is done
33
+ if self.done:
34
+ return None, 0.0, True, {
35
+ "feedback": "Environment is complete. Reset to continue.",
36
+ "partial_scores": {}
37
+ }
38
+
39
  task = self.tasks[self.current_task_index]
40
+ weights = [0.3, 0.2, 0.5] # detect, risk, fix weights
41
+ step_types = ['detect', 'risk', 'fix']
 
42
 
43
  # Check if action order is valid
44
+ if action.action_type != step_types[self.current_step]:
45
+ reward = -0.3
46
+ feedback = f"Invalid action order: expected {step_types[self.current_step]}, got {action.action_type}"
47
+ partial_scores = {}
 
48
  else:
49
+ # Use deterministic graders
50
+ original_code = task["code"]
51
+
52
+ if action.action_type == "detect":
53
+ score = grade_detect(action.detected_issues or [], task["expected_issues"])
54
+ elif action.action_type == "risk":
55
+ score = grade_risk(action.risk_level or "", task["expected_risk"])
56
+ elif action.action_type == "fix":
57
+ score = grade_fix(action.suggested_fix or "", task["canonical_fix"], original_code)
58
+ else:
59
+ score = 0.0
60
 
61
+ # Weighted reward
62
+ reward = score * weights[self.current_step]
63
+ feedback = str(score)
64
+ partial_scores = {action.action_type: score}
 
 
 
 
65
 
66
+ # Advance step_index % 3, task if done
67
+ self.current_step = (self.current_step + 1) % 3
68
+ if self.current_step == 0: # Completed full cycle
 
 
 
 
 
 
69
  self.current_task_index += 1
 
70
  if self.current_task_index >= len(self.tasks):
71
  self.done = True
72
 
 
76
 
77
  info = {
78
  "feedback": feedback,
79
+ "partial_scores": partial_scores
80
  }
81
 
82
  return next_obs, reward, self.done, info
env/grader.py CHANGED
@@ -1,23 +1,184 @@
1
- def grade_action(action, expected):
2
- score = 0
3
- feedback = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
- # Issue detection
6
- if any(expected["issue"] in issue.lower() for issue in action.detected_issues):
7
- score += 0.3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  else:
9
- feedback.append("Issue not correctly identified")
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- # Risk level
12
- if action.risk_level.lower() == expected["risk"]:
13
- score += 0.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  else:
15
- feedback.append("Incorrect risk level")
 
 
 
 
 
 
16
 
17
- # Fix correctness
18
- if expected["fix_contains"] in action.suggested_fix.lower():
19
- score += 0.5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  else:
21
- feedback.append("Fix is incorrect or missing")
22
 
23
- return min(score, 1.0), "; ".join(feedback)
 
 
 
 
 
1
+ from typing import List, Dict, Any, Optional
2
+ from env.models import Action
3
+
4
+ def grade_detect(issues_pred: List[str], expected: List[str]) -> float:
5
+ """
6
+ F1 score for issue detection (normalize lowercase, token overlap).
7
+ """
8
+ if not issues_pred or not expected:
9
+ return 0.0
10
+
11
+ # Normalize to lowercase and extract tokens
12
+ pred_tokens = set(' '.join(issues_pred).lower().split())
13
+ exp_tokens = set(' '.join(expected).lower().split())
14
+
15
+ # Calculate precision and recall
16
+ if not pred_tokens:
17
+ return 0.0
18
+
19
+ intersection = pred_tokens & exp_tokens
20
+ precision = len(intersection) / len(pred_tokens)
21
+ recall = len(intersection) / len(exp_tokens) if exp_tokens else 0
22
+
23
+ # F1 score
24
+ if precision + recall == 0:
25
+ return 0.0
26
+ return 2 * (precision * recall) / (precision + recall)
27
+
28
+ def grade_risk(pred: str, expected: str) -> float:
29
+ """
30
+ 1.0 exact, 0.5 adjacent, 0.0 mismatch (ordinal {'low':0,'medium':1,'high':2}).
31
+ """
32
+ if not pred or not expected:
33
+ return 0.0
34
+
35
+ risk_order = {'low': 0, 'medium': 1, 'high': 2}
36
+
37
+ try:
38
+ pred_val = risk_order[pred.lower()]
39
+ exp_val = risk_order[expected.lower()]
40
+
41
+ if pred_val == exp_val:
42
+ return 1.0
43
+ elif abs(pred_val - exp_val) == 1:
44
+ return 0.5
45
+ else:
46
+ return 0.0
47
+ except KeyError:
48
+ return 0.0
49
 
50
+ def levenshtein_distance(s1: str, s2: str) -> int:
51
+ """Calculate Levenshtein distance between two strings."""
52
+ if len(s1) < len(s2):
53
+ return levenshtein_distance(s2, s1)
54
+
55
+ if len(s2) == 0:
56
+ return len(s1)
57
+
58
+ previous_row = list(range(len(s2) + 1))
59
+ for i, c1 in enumerate(s1):
60
+ current_row = [i + 1]
61
+ for j, c2 in enumerate(s2):
62
+ insertions = previous_row[j + 1] + 1
63
+ deletions = current_row[j] + 1
64
+ substitutions = previous_row[j] + (c1 != c2)
65
+ current_row.append(min(insertions, deletions, substitutions))
66
+ previous_row = current_row
67
+
68
+ return previous_row[-1]
69
+
70
+ def grade_fix(fix: str, canonical: str, orig: str) -> float:
71
+ """
72
+ Deterministic fix grading with bounded components and normalized output.
73
+ Returns score in [0.0, 1.0].
74
+
75
+ Components:
76
+ - syntax_ok: 1.0 if syntax is valid, 0.0 otherwise
77
+ - lev_norm: Normalized Levenshtein distance (0.0 to 1.0)
78
+ - bad_pattern_absent: 0.1 if no bad patterns, 0.0 otherwise
79
+
80
+ Final score: syntax_ok - lev_norm + bad_pattern_absent, clamped to [0.0, 1.0]
81
+ """
82
+ if not fix:
83
+ return 0.0
84
+
85
+ # Syntax check (basic) - bounded component
86
+ syntax_ok = 1.0
87
+ try:
88
+ # Check for balanced parentheses and brackets
89
+ brackets = {'(': ')', '[': ']', '{': '}'}
90
+ stack = []
91
+ for char in fix:
92
+ if char in brackets:
93
+ stack.append(brackets[char])
94
+ elif char in brackets.values():
95
+ if not stack or stack.pop() != char:
96
+ syntax_ok = 0.0
97
+ break
98
+ if stack:
99
+ syntax_ok = 0.0
100
+ except:
101
+ syntax_ok = 0.0
102
+
103
+ # Normalized Levenshtein distance - bounded component [0.0, 1.0]
104
+ max_len = max(len(fix), len(canonical))
105
+ if max_len == 0:
106
+ lev_norm = 0.0
107
  else:
108
+ distance = levenshtein_distance(fix.lower(), canonical.lower())
109
+ lev_norm = min(distance / max_len, 1.0) # Ensure bounded to [0.0, 1.0]
110
+
111
+ # Check for bad patterns (simple heuristic) - bounded component [0.0, 0.1]
112
+ bad_patterns = ['pass', 'TODO', 'FIXME', 'XXX', 'print(']
113
+ bad_pattern_absent = 0.1
114
+ for pattern in bad_patterns:
115
+ if pattern in fix:
116
+ bad_pattern_absent = 0.0
117
+ break
118
+
119
+ # Calculate raw score and clamp to [0.0, 1.0]
120
+ raw_score = syntax_ok - lev_norm + bad_pattern_absent
121
+ return max(0.0, min(1.0, raw_score))
122
 
123
+ def grade_step(action: Action, expected: Dict[str, Any], original_code: str = "") -> Dict[str, Any]:
124
+ """
125
+ Grade a single step with deterministic partial-credit scoring.
126
+ Returns score (0-1) and feedback.
127
+ """
128
+ if action.action_type == "detect":
129
+ score = grade_detect(action.detected_issues, expected["issues"])
130
+ feedback = f"Issue detection score: {score:.2f}"
131
+ if score < 0.5:
132
+ feedback += f" (expected issues: {expected['issues']})"
133
+
134
+ elif action.action_type == "risk":
135
+ score = grade_risk(action.risk_level, expected["risk"])
136
+ feedback = f"Risk assessment score: {score:.2f}"
137
+ if score < 1.0:
138
+ feedback += f" (expected risk: {expected['risk']})"
139
+
140
+ elif action.action_type == "fix":
141
+ score = grade_fix(action.suggested_fix, expected["fix_contains"], original_code)
142
+ feedback = f"Fix suggestion score: {score:.2f}"
143
+ if score < 0.7:
144
+ feedback += f" (expected to contain: {expected['fix_contains']})"
145
+
146
  else:
147
+ score = 0.0
148
+ feedback = f"Unknown action type: {action.action_type}"
149
+
150
+ return {
151
+ "score": score,
152
+ "feedback": feedback
153
+ }
154
 
155
+ def map_score_to_reward(score: float, action_type: str) -> float:
156
+ """
157
+ Map grader score (0-1) to reward values with appropriate weighting.
158
+ """
159
+ # Base weights for each action type
160
+ weights = {
161
+ "detect": 0.3,
162
+ "risk": 0.2,
163
+ "fix": 0.5
164
+ }
165
+
166
+ base_weight = weights.get(action_type, 0.1)
167
+
168
+ # Apply score to weight, with small penalty for very low scores
169
+ if score >= 0.8:
170
+ return base_weight
171
+ elif score >= 0.6:
172
+ return base_weight * 0.8
173
+ elif score >= 0.4:
174
+ return base_weight * 0.6
175
+ elif score >= 0.2:
176
+ return base_weight * 0.4
177
  else:
178
+ return -base_weight * 0.3 # Small penalty for very poor performance
179
 
180
+ # Legacy function for backward compatibility
181
+ def grade_action(action, expected):
182
+ """Legacy grading function - use grade_step instead."""
183
+ result = grade_step(action, expected)
184
+ return result["score"], result["feedback"]
env/tasks.py CHANGED
@@ -1,42 +1,60 @@
1
  TASKS = [
2
  {
3
- "id": "easy",
4
- "code": "def add(a, b): return a - b",
5
- "intent": "Return sum of two numbers",
6
- "constraints": ["correct logic"],
7
- "expected": {
8
- "issue": "wrong operator",
9
- "risk": "low",
10
- "fix_contains": "+"
11
- }
12
  },
13
  {
14
- "id": "medium",
15
- "code": """def login(user, password):
16
- if user == "admin" and password == "1234":
17
- return True""",
18
- "intent": "Authenticate user securely",
19
- "constraints": ["no hardcoding", "security"],
20
- "expected": {
21
- "issue": "hardcoded credentials",
22
- "risk": "high",
23
- "fix_contains": "hashed"
24
- }
25
  },
26
  {
27
- "id": "hard",
28
- "code": """def process(data):
29
- result = []
30
- for i in range(len(data)):
31
- if data[i] % 2 == 0:
32
- result.append(data[i])
33
- return result""",
34
- "intent": "Return even numbers efficiently",
35
- "constraints": ["efficient", "clean code"],
36
- "expected": {
37
- "issue": "inefficient loop",
38
- "risk": "low",
39
- "fix_contains": "list comprehension"
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
  ]
 
1
  TASKS = [
2
  {
3
+ "task_id": "easy_off_by_one",
4
+ "difficulty": "easy",
5
+ "code": "def sum_list(arr): return sum(arr[:-1]) # missing last",
6
+ "intent": "Sum all elements",
7
+ "constraints": ["O(n) time"],
8
+ "expected_issues": ["off-by-one error"],
9
+ "expected_risk": "low",
10
+ "canonical_fix": "return sum(arr)"
 
11
  },
12
  {
13
+ "task_id": "medium_sql_injection",
14
+ "difficulty": "medium",
15
+ "code": "def get_user(user_id): query = f'SELECT * FROM users WHERE id={user_id}'; cursor.execute(query); return cursor.fetchone()",
16
+ "intent": "Fetch user by ID safely",
17
+ "constraints": ["SQL injection prevention", "parameterized queries"],
18
+ "expected_issues": ["SQL injection"],
19
+ "expected_risk": "high",
20
+ "canonical_fix": "cursor.execute('SELECT * FROM users WHERE id=%s', (user_id,))"
 
 
 
21
  },
22
  {
23
+ "task_id": "hard_async_race",
24
+ "difficulty": "hard",
25
+ "code": """import asyncio
26
+ import requests
27
+
28
+ async def fetch(url):
29
+ data = requests.get(url).json() # no await
30
+ return data
31
+
32
+ class DataCache:
33
+ def __init__(self):
34
+ self.cache = {} # mutable shared state
35
+
36
+ async def update(self, key, value):
37
+ self.cache[key] = value # race condition
38
+ await asyncio.sleep(0.1)
39
+ return self.cache[key]""",
40
+ "intent": "Fetch data asynchronously with thread-safe caching",
41
+ "constraints": ["async safety", "no blocking calls", "thread safety"],
42
+ "expected_issues": ["missing await", "race condition", "blocking I/O"],
43
+ "expected_risk": "high",
44
+ "canonical_fix": """async def fetch(url):
45
+ async with aiohttp.ClientSession() as session:
46
+ async with session.get(url) as response:
47
+ return await response.json()
48
+
49
+ class DataCache:
50
+ def __init__(self):
51
+ self.cache = {}
52
+ self._lock = asyncio.Lock()
53
+
54
+ async def update(self, key, value):
55
+ async with self._lock:
56
+ self.cache[key] = value
57
+ await asyncio.sleep(0.1)
58
+ return self.cache[key]"""
59
  }
60
  ]
inference.py CHANGED
@@ -141,8 +141,7 @@ def main():
141
  total_reward = 0
142
  step_count = 0
143
 
144
- logger.info(f"Starting inference with model: {model_name}")
145
- logger.info(f"Initial task: {obs.intent}")
146
 
147
  # Main loop
148
  while not env.done:
@@ -151,7 +150,7 @@ def main():
151
  step_type = step_types[current_step]
152
 
153
  step_count += 1
154
- logger.info(f"\n--- Step {step_count}: {step_type.upper()} ---")
155
 
156
  # Create prompt
157
  prompt = create_prompt(obs, step_type)
@@ -176,11 +175,7 @@ def main():
176
  total_reward += reward
177
 
178
  # Log results
179
- logger.info(f"Action type: {action.action_type}")
180
- logger.info(f"Reward: {reward:+.2f}")
181
- logger.info(f"Feedback: {info['feedback']}")
182
- logger.info(f"Running total: {total_reward:+.2f}")
183
- logger.info(f"State: {env.state()}")
184
 
185
  # Update observation for next step
186
  obs = next_obs
@@ -191,10 +186,7 @@ def main():
191
  break
192
 
193
  # Final output
194
- logger.info(f"\n=== Inference Complete ===")
195
- logger.info(f"Final score: {total_reward:.2f}")
196
- logger.info(f"Total steps: {step_count}")
197
- logger.info(f"Final state: {env.state()}")
198
 
199
  except Exception as e:
200
  logger.error(f"Inference failed: {e}")
 
141
  total_reward = 0
142
  step_count = 0
143
 
144
+ logger.info(f"[START] model={model_name} task_id={obs.task_id} difficulty={obs.difficulty} intent={obs.intent}")
 
145
 
146
  # Main loop
147
  while not env.done:
 
150
  step_type = step_types[current_step]
151
 
152
  step_count += 1
153
+ logger.info(f"[STEP] step_number={step_count} step_type={step_type} task_id={obs.task_id}")
154
 
155
  # Create prompt
156
  prompt = create_prompt(obs, step_type)
 
175
  total_reward += reward
176
 
177
  # Log results
178
+ logger.info(f"action_type={action.action_type} reward={reward:+.2f} feedback={info['feedback']} total_reward={total_reward:+.2f} current_step={env.state()['current_step']} current_task={env.state()['current_task']}")
 
 
 
 
179
 
180
  # Update observation for next step
181
  obs = next_obs
 
186
  break
187
 
188
  # Final output
189
+ logger.info(f"[END] final_score={total_reward:.2f} total_steps={step_count} final_state={env.state()}")
 
 
 
190
 
191
  except Exception as e:
192
  logger.error(f"Inference failed: {e}")
openenv.yaml ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: CodeGuardian
2
+ description: A code review environment that tests AI agents on detecting security issues, assessing risks, and suggesting fixes
3
+ version: 1.0.0
4
+
5
+ spaces:
6
+ - name: code_review
7
+ description: Code review and security analysis tasks
8
+ type: gymnasium
9
+
10
+ tasks:
11
+ - id: easy_off_by_one
12
+ name: "Off-by-One Error"
13
+ difficulty: easy
14
+ description: "Detect off-by-one error in array summation"
15
+ space: code_review
16
+
17
+ - id: medium_sql_injection
18
+ name: "SQL Injection"
19
+ difficulty: medium
20
+ description: "Identify SQL injection vulnerability in user query"
21
+ space: code_review
22
+
23
+ - id: hard_async_race
24
+ name: "Async Race Condition"
25
+ difficulty: hard
26
+ description: "Find missing await and race condition in async code"
27
+ space: code_review
28
+
29
+ reward:
30
+ type: continuous
31
+ min: 0.0
32
+ max: 1.0
33
+ description: "Score based on correct issue detection, risk assessment, and fix suggestions"
34
+
35
+ environment:
36
+ class: CodeGuardianEnv
37
+ module: env.environment
38
+ observation_space:
39
+ type: dict
40
+ fields:
41
+ code: str
42
+ intent: str
43
+ constraints: list
44
+ task_id: str
45
+ difficulty: str
46
+ step_index: int
47
+ action_space:
48
+ type: dict
49
+ fields:
50
+ action_type: str
51
+ detected_issues: list
52
+ risk_level: str
53
+ suggested_fix: str
54
+
55
+ metadata:
56
+ author: CodeGuardian Team
57
+ tags: ["code-review", "security", "ai-testing"]
58
+ license: MIT
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ pydantic>=2.0.0
2
+ openai>=1.0.0
3
+ gymnasium>=0.28.0
4
+ numpy>=1.24.0
5
+ python-dotenv>=1.0.0
6
+ flask>=3.0.0
7
+ gradio>=4.0.0