Spaces:
Runtime error
Runtime error
Download code-review-env/env/reward_engine.py from DeepParmar/code-review: direct link, hf CLI and curl.
- Browser
- Download file 15.6 kB
-
https://huggingface.co/spaces/DeepParmar/code-review/resolve/main/code-review-env/env/reward_engine.py
- Command line
-
hf download hf://spaces/DeepParmar/code-review/code-review-env/env/reward_engine.py
-
curl -L -o reward_engine.py https://huggingface.co/spaces/DeepParmar/code-review/resolve/main/code-review-env/env/reward_engine.py
15.6 kB
| """Reward engine for CodeReviewEnv. | |
| Implements non-sparse, shaped rewards according to the master spec. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import List, Optional, Tuple | |
| from env.graders.grader_easy import grade as grade_easy | |
| from env.graders.grader_hard import grade as grade_hard | |
| from env.graders.grader_medium import grade as grade_medium | |
| from env.models import CodeReviewAction, GroundTruthBug, ReviewComment | |
| class RewardOutcome: | |
| """Outcome details from reward computation.""" | |
| reward: float | |
| reason: str | |
| correctly_identified_bug_line: Optional[int] | |
| is_false_positive: bool | |
| is_red_herring_flag: bool | |
| is_duplicate: bool | |
| final_score: Optional[float] | |
| confidence_modifier: float = 0.0 | |
| explanation_depth: Optional[str] = None | |
| class RewardEngine: | |
| """Compute shaped rewards and final scores for a task episode.""" | |
| def __init__(self, *, task_id: str, ground_truth: List[GroundTruthBug], max_steps: int) -> None: | |
| """Initialize the reward engine for a task.""" | |
| self._task_id = task_id | |
| self._ground_truth = ground_truth | |
| self._max_steps = max_steps | |
| def _match_bug(self, line_number: int, filename: Optional[str] = None) -> Optional[GroundTruthBug]: | |
| """Find the closest ground-truth bug within +/-5 lines, preferring exact matches. | |
| Args: | |
| line_number: The line number to match against. | |
| filename: Optional filename for multi-file matching (Upgrade 4). | |
| """ | |
| candidates: List[Tuple[int, GroundTruthBug]] = [] | |
| for b in self._ground_truth: | |
| # If filename provided, only match bugs in that file | |
| if filename is not None and b.source_file is not None and b.source_file != filename: | |
| continue | |
| dist = abs(b.line_number - line_number) | |
| if dist <= 5: | |
| candidates.append((dist, b)) | |
| if not candidates: | |
| # If filename was specified but no match, try all files (backward compatible) | |
| if filename is not None: | |
| return self._match_bug(line_number, filename=None) | |
| return None | |
| candidates.sort(key=lambda x: (x[0], x[1].line_number)) | |
| return candidates[0][1] | |
| def _grade(self, comments: List[ReviewComment]) -> float: | |
| """Run the deterministic grader for this task.""" | |
| if self._task_id == "easy": | |
| return grade_easy(comments, self._ground_truth) | |
| if self._task_id == "medium": | |
| return grade_medium(comments, self._ground_truth) | |
| if self._task_id == "hard": | |
| return grade_hard(comments, self._ground_truth) | |
| return 0.0 | |
| def _evaluate_explanation_tiers(self, bug: GroundTruthBug, message: str) -> Tuple[bool, float, str]: | |
| """Upgrade 2: Evaluate explanation quality against tiered keywords. | |
| Args: | |
| bug: The matched ground-truth bug. | |
| message: The agent's comment message. | |
| Returns: | |
| Tuple of (should_register, reward_modifier, explanation_depth). | |
| """ | |
| if bug.explanation_tiers is None: | |
| # Fall back to existing required_keywords logic | |
| return self._evaluate_required_keywords(bug, message) | |
| msg_lower = message.lower() | |
| tiers = bug.explanation_tiers | |
| tier3_keywords = tiers.get("tier3", []) | |
| tier2_keywords = tiers.get("tier2", []) | |
| tier1_keywords = tiers.get("tier1", []) | |
| has_tier3 = any(kw.lower() in msg_lower for kw in tier3_keywords) if tier3_keywords else False | |
| has_tier2 = any(kw.lower() in msg_lower for kw in tier2_keywords) if tier2_keywords else False | |
| has_tier1 = any(kw.lower() in msg_lower for kw in tier1_keywords) if tier1_keywords else False | |
| if has_tier3: | |
| # Deep explanation β full credit + bonus | |
| return True, 0.05, "deep" | |
| elif has_tier2: | |
| # Technical explanation β full credit, no bonus | |
| return True, 0.0, "technical" | |
| elif has_tier1: | |
| # Shallow mention β registered but with penalty | |
| return True, -0.05, "shallow" | |
| else: | |
| # No match at all β not registered, penalty | |
| return False, -0.10, "missing" | |
| def _evaluate_required_keywords(self, bug: GroundTruthBug, message: str) -> Tuple[bool, float, str]: | |
| """Original required_keywords logic for backward compatibility. | |
| Returns: | |
| Tuple of (should_register, reward_modifier, explanation_depth). | |
| """ | |
| if not bug.required_keywords or not message: | |
| return True, 0.0, "technical" | |
| msg_lower = message.lower() | |
| has_keyword = any(kw.lower() in msg_lower for kw in bug.required_keywords) | |
| if has_keyword: | |
| return True, 0.0, "technical" | |
| else: | |
| return False, -0.10, "missing" | |
| def _compute_confidence_modifier( | |
| self, | |
| confidence: Optional[int], | |
| is_correct: bool, | |
| is_false_positive: bool, | |
| is_red_herring: bool, | |
| ) -> float: | |
| """Upgrade 1: Compute calibration modifier based on confidence level. | |
| Args: | |
| confidence: Agent's confidence value (0-100) or None. | |
| is_correct: Whether the bug was correctly matched. | |
| is_false_positive: Whether this was a false positive. | |
| is_red_herring: Whether this hit a red herring. | |
| Returns: | |
| Modifier to add to the base reward. | |
| """ | |
| if confidence is None: | |
| return 0.0 | |
| if 80 <= confidence <= 100: | |
| if is_correct and not is_false_positive and not is_red_herring: | |
| return 0.05 # High confidence + correct β bonus | |
| elif is_false_positive: | |
| return -0.10 # High confidence + false positive β extra penalty | |
| elif is_red_herring: | |
| return -0.10 # High confidence + red herring β extra penalty | |
| elif 50 <= confidence <= 79: | |
| return 0.0 # Medium confidence β no modifier | |
| elif 0 <= confidence <= 49: | |
| if is_correct and not is_false_positive and not is_red_herring: | |
| return -0.02 # Low confidence + correct β should know when it knows | |
| return 0.0 | |
| def compute( | |
| self, | |
| action: CodeReviewAction, | |
| *, | |
| comments_so_far: List[ReviewComment], | |
| correctly_identified_bug_lines: set[int], | |
| step_number: int, | |
| steps_used_after_this: int, | |
| ) -> RewardOutcome: | |
| """Compute reward for an action. | |
| Args: | |
| action: Agent action. | |
| comments_so_far: Existing comments before applying this action. | |
| correctly_identified_bug_lines: Bug line numbers already credited. | |
| step_number: Current step number (1-indexed). | |
| steps_used_after_this: Step count used after applying this step (for efficiency bonus). | |
| Returns: | |
| RewardOutcome with reward and metadata. | |
| """ | |
| # Handle inspect_file and inspect_lines actions | |
| if action.operation == "inspect_file": | |
| return RewardOutcome( | |
| reward=0.0, | |
| reason="Inspected file", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| ) | |
| if action.operation == "inspect_lines": | |
| # Check if the inspected range contains a real bug line | |
| if action.start_line is not None and action.end_line is not None: | |
| for b in self._ground_truth: | |
| if not b.is_red_herring and action.start_line <= b.line_number <= action.end_line: | |
| if action.filename is None or b.source_file is None or action.filename == b.source_file: | |
| return RewardOutcome( | |
| reward=0.02, | |
| reason="Inspected range contains a real bug", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| ) | |
| return RewardOutcome( | |
| reward=0.0, | |
| reason="Inspected range contains no bugs", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| ) | |
| if action.operation == "add_comment": | |
| if action.line_number is None: | |
| return RewardOutcome( | |
| reward=-0.05, | |
| reason="Invalid add_comment: missing line_number", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=True, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| ) | |
| matched = self._match_bug(action.line_number, filename=action.filename) | |
| if matched is None: | |
| # False positive | |
| conf_mod = self._compute_confidence_modifier( | |
| action.confidence, is_correct=False, | |
| is_false_positive=True, is_red_herring=False, | |
| ) | |
| base_reward = -0.10 + conf_mod | |
| return RewardOutcome( | |
| reward=base_reward, | |
| reason="False positive: no ground-truth bug near commented line", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=True, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| confidence_modifier=conf_mod, | |
| ) | |
| if matched.is_red_herring: | |
| conf_mod = self._compute_confidence_modifier( | |
| action.confidence, is_correct=False, | |
| is_false_positive=False, is_red_herring=True, | |
| ) | |
| base_reward = -0.20 + conf_mod | |
| return RewardOutcome( | |
| reward=base_reward, | |
| reason="Flagged red herring", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=True, | |
| is_duplicate=False, | |
| final_score=None, | |
| confidence_modifier=conf_mod, | |
| ) | |
| if matched.line_number in correctly_identified_bug_lines: | |
| return RewardOutcome( | |
| reward=-0.05, | |
| reason="Duplicate comment on already-identified bug", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=True, | |
| final_score=None, | |
| ) | |
| base = 0.15 | |
| sev_bonus = 0.05 if action.severity == matched.severity else 0.0 | |
| cat_bonus = 0.05 if action.category == matched.category else 0.0 | |
| # Use tiered evaluation if explanation_tiers is present | |
| should_register, semantic_modifier, explanation_depth = self._evaluate_explanation_tiers( | |
| matched, action.message or "" | |
| ) | |
| reward = min(0.25, base + sev_bonus + cat_bonus) + semantic_modifier | |
| registered_line = matched.line_number if should_register else None | |
| # Apply confidence modifier AFTER all existing logic | |
| is_correct = registered_line is not None | |
| conf_mod = self._compute_confidence_modifier( | |
| action.confidence, is_correct=is_correct, | |
| is_false_positive=False, is_red_herring=False, | |
| ) | |
| reward += conf_mod | |
| return RewardOutcome( | |
| reward=reward, | |
| reason="Correct proximity but missed semantic 'why'" if not should_register else "Correct bug proximity match", | |
| correctly_identified_bug_line=registered_line, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| confidence_modifier=conf_mod, | |
| explanation_depth=explanation_depth, | |
| ) | |
| if action.operation == "approve": | |
| remaining_critical_or_major = [ | |
| b | |
| for b in self._ground_truth | |
| if (not b.is_red_herring) and b.severity in {"critical", "major"} and b.line_number not in correctly_identified_bug_lines | |
| ] | |
| if remaining_critical_or_major: | |
| return RewardOutcome( | |
| reward=-0.50, | |
| reason="Approved while critical/major bugs remain unfound", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| ) | |
| return RewardOutcome( | |
| reward=0.10, | |
| reason="Approved with no critical/major bugs remaining", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| ) | |
| if action.operation == "request_changes": | |
| if len(correctly_identified_bug_lines) > 0: | |
| return RewardOutcome( | |
| reward=0.05, | |
| reason="Requested changes with evidence", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| ) | |
| return RewardOutcome( | |
| reward=-0.05, | |
| reason="Requested changes without evidence", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| ) | |
| if action.operation == "done": | |
| final_score = self._grade(comments_so_far) | |
| reward = float(final_score) | |
| if steps_used_after_this < int(0.6 * self._max_steps) and final_score > 0.8: | |
| reward += 0.10 | |
| return RewardOutcome( | |
| reward=reward, | |
| reason="Final grading score", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=False, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=final_score, | |
| ) | |
| return RewardOutcome( | |
| reward=-0.05, | |
| reason="Unknown operation", | |
| correctly_identified_bug_line=None, | |
| is_false_positive=True, | |
| is_red_herring_flag=False, | |
| is_duplicate=False, | |
| final_score=None, | |
| ) | |