Tejasva commited on
Commit
14b6f9b
·
0 Parent(s):

initial-setup

Browse files
README_inference.md ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CodeGuardian Inference Script
2
+
3
+ This script runs an LLM agent against the CodeGuardian multi-step code review environment.
4
+
5
+ ## Setup
6
+
7
+ ### Environment Variables
8
+
9
+ Set the following environment variables before running:
10
+
11
+ ```bash
12
+ # Required: API endpoint (for OpenAI-compatible APIs)
13
+ export API_BASE_URL="https://api.openai.com/v1"
14
+
15
+ # Required: Model name to use
16
+ export MODEL_NAME="gpt-4"
17
+
18
+ # Required: Authentication token (HF_TOKEN takes precedence over API_KEY)
19
+ export HF_TOKEN="your-huggingface-token"
20
+ # OR
21
+ export API_KEY="your-openai-api-key"
22
+ ```
23
+
24
+ ### Dependencies
25
+
26
+ Install required packages:
27
+
28
+ ```bash
29
+ pip install openai pydantic
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ Run the inference script:
35
+
36
+ ```bash
37
+ python inference.py
38
+ ```
39
+
40
+ ## What It Does
41
+
42
+ The script:
43
+
44
+ 1. **Initializes** the CodeGuardian environment and OpenAI client
45
+ 2. **Loops through** all tasks in the environment (3 tasks × 3 steps each = 9 total steps)
46
+ 3. **For each step**:
47
+ - Step 0: Detect issues in the code
48
+ - Step 1: Assess risk level (low/medium/high)
49
+ - Step 2: Suggest corrected code
50
+ 4. **Sends structured prompts** to the LLM with the current observation
51
+ 5. **Parses JSON responses** safely with fallback handling
52
+ 6. **Executes actions** in the environment and accumulates rewards
53
+ 7. **Logs progress** including rewards, feedback, and running totals
54
+
55
+ ## Output Format
56
+
57
+ ```
58
+ 2026-04-06 02:26:51,542 - INFO - Starting inference with model: gpt-4
59
+ 2026-04-06 02:26:51,561 - INFO - Initial task: Return sum of two numbers
60
+ 2026-04-06 02:26:51,561 - INFO - --- Step 1: DETECT ---
61
+ 2026-04-06 02:26:51,562 - INFO - LLM response: {"detected_issues": ["wrong operator"], ...}
62
+ 2026-04-06 02:26:51,564 - INFO - Action type: detect
63
+ 2026-04-06 02:26:51,564 - INFO - Reward: +0.30
64
+ 2026-04-06 02:26:51,564 - INFO - Feedback: []
65
+ 2026-04-06 02:26:51,565 - INFO - Running total: +0.30
66
+ ...
67
+ 2026-04-06 02:26:51,586 - INFO - === Inference Complete ===
68
+ 2026-04-06 02:26:51,586 - INFO - Final score: 0.00
69
+ 2026-04-06 02:26:51,586 - INFO - Total steps: 9
70
+ ```
71
+
72
+ ## Reward Structure
73
+
74
+ - **Detect step**: +0.3 (correct) / -0.2 (incorrect)
75
+ - **Risk step**: +0.2 (correct) / -0.2 (incorrect)
76
+ - **Fix step**: +0.5 (correct) / -0.3 (incorrect)
77
+ - **Invalid action order**: -0.5
78
+
79
+ ## Error Handling
80
+
81
+ The script is robust against:
82
+ - Invalid JSON responses (falls back to empty responses)
83
+ - API errors (falls back to empty responses)
84
+ - Missing fields (provides sensible defaults)
85
+ - Infinite loops (50-step safety limit)
86
+
87
+ ## Expected Performance
88
+
89
+ - **Perfect score**: 3.0 (all 3 tasks completed perfectly)
90
+ - **Random baseline**: ~0.0 (rewards balance out)
91
+ - **Typical LLM**: 0.5-2.0 (depending on model capability)
92
+
93
+ The script provides a stable, reproducible baseline for evaluating LLM performance on multi-step code review tasks.
__pycache__/inference.cpython-313.pyc ADDED
Binary file (8.6 kB). View file
 
env/__pycache__/environment.cpython-313.pyc ADDED
Binary file (4.51 kB). View file
 
env/__pycache__/grader.cpython-313.pyc ADDED
Binary file (1.36 kB). View file
 
env/__pycache__/models.cpython-313.pyc ADDED
Binary file (1.25 kB). View file
 
env/__pycache__/tasks.cpython-313.pyc ADDED
Binary file (978 Bytes). View file
 
env/environment.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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):
7
+ self.tasks = TASKS
8
+ self.current_task_index = 0
9
+ self.current_step = 0 # 0 = detect, 1 = risk, 2 = fix
10
+ self.partial_score = 0
11
+ self.done = False
12
+
13
+ def reset(self):
14
+ self.current_task_index = 0
15
+ self.current_step = 0
16
+ self.done = False
17
+ self.partial_score = 0
18
+ return self._get_obs()
19
+
20
+ def _get_obs(self):
21
+ task = self.tasks[self.current_task_index]
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
+
73
+ self.partial_score += reward
74
+
75
+ next_obs = self._get_obs() if not self.done else None
76
+
77
+ info = {
78
+ "feedback": feedback,
79
+ "total_score": self.partial_score
80
+ }
81
+
82
+ return next_obs, reward, self.done, info
83
+
84
+ def state(self):
85
+ return {
86
+ "current_task": self.current_task_index,
87
+ "current_step": self.current_step,
88
+ "partial_score": self.partial_score,
89
+ "done": self.done
90
+ }
env/grader.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)
env/models.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List, Optional
3
+
4
+ # What agent sees
5
+ class Observation(BaseModel):
6
+ code: str
7
+ intent: str
8
+ constraints: List[str]
9
+
10
+ # What agent sends
11
+ class Action(BaseModel):
12
+ action_type: str # "detect", "risk", "fix"
13
+ detected_issues: Optional[List[str]] = None
14
+ risk_level: Optional[str] = None # "low", "medium", "high"
15
+ suggested_fix: Optional[str] = None
16
+
17
+ # What env returns
18
+ class Reward(BaseModel):
19
+ score: float
20
+ feedback: str
env/tasks.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ]
inference.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import json
5
+ import logging
6
+ from typing import Dict, Any, Optional, List
7
+
8
+ try:
9
+ from openai import OpenAI
10
+ except ImportError:
11
+ print("Error: openai package not found. Install with: pip install openai")
12
+ exit(1)
13
+
14
+ from env.environment import CodeGuardianEnv
15
+ from env.models import Action
16
+
17
+ # Setup logging
18
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
19
+ logger = logging.getLogger(__name__)
20
+
21
+ def setup_openai_client() -> OpenAI:
22
+ """Initialize OpenAI client from environment variables."""
23
+ api_base_url = os.getenv('API_BASE_URL')
24
+ model_name = os.getenv('MODEL_NAME', 'gpt-3.5-turbo')
25
+
26
+ # Try HF_TOKEN first, then API_KEY
27
+ api_key = os.getenv('HF_TOKEN') or os.getenv('API_KEY')
28
+
29
+ if not api_key:
30
+ raise ValueError("Neither HF_TOKEN nor API_KEY environment variables are set")
31
+
32
+ client_kwargs = {'api_key': api_key}
33
+ if api_base_url:
34
+ client_kwargs['base_url'] = api_base_url
35
+
36
+ return OpenAI(**client_kwargs), model_name
37
+
38
+ def create_prompt(observation: Dict[str, Any], step_type: str) -> str:
39
+ """Create structured prompt for the LLM based on observation and current step."""
40
+ step_names = {
41
+ 'detect': 'Issue Detection',
42
+ 'risk': 'Risk Assessment',
43
+ 'fix': 'Fix Suggestion'
44
+ }
45
+
46
+ step_instructions = {
47
+ 'detect': 'list all issues you find in the code',
48
+ 'risk': 'output the risk level (low/medium/high)',
49
+ 'fix': 'provide corrected code that fixes the identified issues'
50
+ }
51
+
52
+ prompt = f"""You are a professional code reviewer.
53
+
54
+ Code:
55
+ {observation.code}
56
+
57
+ Intent:
58
+ {observation.intent}
59
+
60
+ Constraints:
61
+ {chr(10).join(f"- {constraint}" for constraint in observation.constraints)}
62
+
63
+ Step: {step_names[step_type]}
64
+
65
+ Instructions:
66
+ * {step_instructions[step_type]}
67
+
68
+ Respond ONLY in JSON format:
69
+ {{
70
+ "detected_issues": [...],
71
+ "risk_level": "...",
72
+ "suggested_fix": "..."
73
+ }}"""
74
+
75
+ return prompt
76
+
77
+ def parse_llm_response(response_text: str) -> Dict[str, Any]:
78
+ """Parse LLM response safely with fallback."""
79
+ try:
80
+ # Clean up response text
81
+ response_text = response_text.strip()
82
+ if response_text.startswith('```json'):
83
+ response_text = response_text[7:]
84
+ if response_text.endswith('```'):
85
+ response_text = response_text[:-3]
86
+ response_text = response_text.strip()
87
+
88
+ parsed = json.loads(response_text)
89
+
90
+ # Ensure all required fields exist
91
+ return {
92
+ 'detected_issues': parsed.get('detected_issues', []),
93
+ 'risk_level': parsed.get('risk_level', 'low'),
94
+ 'suggested_fix': parsed.get('suggested_fix', '')
95
+ }
96
+ except (json.JSONDecodeError, AttributeError) as e:
97
+ logger.warning(f"Failed to parse LLM response: {e}")
98
+ logger.warning(f"Raw response: {response_text[:200]}...")
99
+
100
+ # Return fallback response
101
+ return {
102
+ 'detected_issues': [],
103
+ 'risk_level': 'low',
104
+ 'suggested_fix': ''
105
+ }
106
+
107
+ def create_action_from_response(step_type: str, parsed_response: Dict[str, Any]) -> Action:
108
+ """Create Action model from parsed LLM response."""
109
+ return Action(
110
+ action_type=step_type,
111
+ detected_issues=parsed_response['detected_issues'] if step_type == 'detect' else None,
112
+ risk_level=parsed_response['risk_level'] if step_type == 'risk' else None,
113
+ suggested_fix=parsed_response['suggested_fix'] if step_type == 'fix' else None
114
+ )
115
+
116
+ def run_llm_inference(client: OpenAI, model_name: str, prompt: str) -> str:
117
+ """Run LLM inference with the given prompt."""
118
+ try:
119
+ response = client.chat.completions.create(
120
+ model=model_name,
121
+ messages=[
122
+ {"role": "system", "content": "You are a professional code reviewer. Always respond in valid JSON format."},
123
+ {"role": "user", "content": prompt}
124
+ ],
125
+ temperature=0, # Deterministic
126
+ max_tokens=300 # Reasonable limit
127
+ )
128
+ return response.choices[0].message.content
129
+ except Exception as e:
130
+ logger.error(f"LLM inference failed: {e}")
131
+ raise
132
+
133
+ def main():
134
+ """Main inference loop."""
135
+ try:
136
+ # Setup
137
+ client, model_name = setup_openai_client()
138
+ env = CodeGuardianEnv()
139
+ obs = env.reset()
140
+
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:
149
+ current_step = env.state()['current_step']
150
+ step_types = ['detect', 'risk', 'fix']
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)
158
+
159
+ # Run LLM inference
160
+ try:
161
+ response_text = run_llm_inference(client, model_name, prompt)
162
+ logger.info(f"LLM response: {response_text[:100]}...")
163
+ except Exception as e:
164
+ logger.error(f"LLM inference failed: {e}")
165
+ # Use fallback action
166
+ parsed_response = {'detected_issues': [], 'risk_level': 'low', 'suggested_fix': ''}
167
+ else:
168
+ # Parse response
169
+ parsed_response = parse_llm_response(response_text)
170
+
171
+ # Create action
172
+ action = create_action_from_response(step_type, parsed_response)
173
+
174
+ # Execute step
175
+ next_obs, reward, done, info = env.step(action)
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
187
+
188
+ # Safety check
189
+ if step_count > 50: # Prevent infinite loops
190
+ logger.warning("Maximum steps reached, stopping inference")
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}")
201
+ return 1
202
+
203
+ return 0
204
+
205
+ if __name__ == "__main__":
206
+ exit(main())
test_env.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from env.environment import CodeGuardianEnv
2
+ from env.models import Action
3
+
4
+ env = CodeGuardianEnv()
5
+ obs = env.reset()
6
+
7
+ done = False
8
+
9
+ step_cycle = ["detect", "risk", "fix"]
10
+ step_index = 0
11
+
12
+ while not done:
13
+ print("\n--- NEW STEP ---")
14
+ print("Code:", obs.code)
15
+
16
+ current_action = step_cycle[step_index]
17
+
18
+ if current_action == "detect":
19
+ action = Action(
20
+ action_type="detect",
21
+ detected_issues=["wrong operator"],
22
+ risk_level=None,
23
+ suggested_fix=None
24
+ )
25
+
26
+ elif current_action == "risk":
27
+ action = Action(
28
+ action_type="risk",
29
+ detected_issues=None,
30
+ risk_level="low",
31
+ suggested_fix=None
32
+ )
33
+
34
+ elif current_action == "fix":
35
+ action = Action(
36
+ action_type="fix",
37
+ detected_issues=None,
38
+ risk_level=None,
39
+ suggested_fix="return a + b"
40
+ )
41
+
42
+ obs, reward, done, info = env.step(action)
43
+
44
+ print("Action:", current_action)
45
+ print("Reward:", reward)
46
+ print("Info:", info)
47
+
48
+ step_index = (step_index + 1) % 3