Spaces:
Sleeping
Sleeping
| import groq, json, time, os, re | |
| from utils.prompts import get_fixer_prompt, get_simplified_prompt | |
| class FixerAgent: | |
| def __init__(self): | |
| self.client = groq.Groq(api_key=os.environ.get("GROQ_API_KEY")) | |
| self.model = "llama-3.1-8b-instant" | |
| def fix_code(self, buggy_code: str, error_type: str, | |
| description: str, test_cases: list, | |
| test_results: dict = None, | |
| previous_explanation: str = None, | |
| iteration: int = 1) -> dict: | |
| """ | |
| 3-tier fallback: | |
| Tier 1: Full LLM with JSON output | |
| Tier 2: Simplified LLM prompt | |
| Tier 3: Heuristic rule-based fix | |
| Returns: { | |
| "fixed_code": str, | |
| "explanation": str, | |
| "confidence": float, | |
| "method": "llm"/"simplified"/"heuristic", | |
| "elapsed_seconds": float | |
| } | |
| """ | |
| start = time.time() | |
| # Tier 1: Full LLM with JSON output | |
| try: | |
| prompt = get_fixer_prompt( | |
| buggy_code, error_type, description, test_cases, | |
| test_results, previous_explanation, iteration | |
| ) | |
| response_text = self._call_groq(prompt) | |
| parsed = self._parse_json_response(response_text) | |
| if parsed and "fixed_code" in parsed: | |
| parsed["method"] = "llm" | |
| parsed["confidence"] = 0.9 | |
| parsed["elapsed_seconds"] = time.time() - start | |
| return parsed | |
| except Exception as e: | |
| print(f"[FixerAgent] Tier 1 failed: {e}") | |
| # Tier 2: Simplified LLM prompt | |
| try: | |
| simple_prompt = get_simplified_prompt(buggy_code, error_type) | |
| response_text = self._call_groq(simple_prompt) | |
| # extract python code block from response | |
| match = re.search(r"```(?:python)?\n?(.*?)\n?```", response_text, re.DOTALL) | |
| fixed_code = match.group(1).strip() if match else response_text.strip() | |
| return { | |
| "fixed_code": fixed_code, | |
| "explanation": "Simplified prompt fallback", | |
| "confidence": 0.6, | |
| "method": "simplified", | |
| "elapsed_seconds": time.time() - start | |
| } | |
| except Exception as e: | |
| print(f"[FixerAgent] Tier 2 failed: {e}") | |
| # Tier 3: Heuristic rule-based fix | |
| fixed = self._heuristic_fix(buggy_code, error_type) | |
| return { | |
| "fixed_code": fixed, | |
| "explanation": "Heuristic fallback rule", | |
| "confidence": 0.3, | |
| "method": "heuristic", | |
| "elapsed_seconds": time.time() - start | |
| } | |
| def _call_groq(self, prompt: str) -> str: | |
| """Call Groq API, return response text""" | |
| response = self.client.chat.completions.create( | |
| model=self.model, | |
| messages=[{"role": "user", "content": prompt}], | |
| max_tokens=1024, | |
| temperature=0.2 | |
| ) | |
| return response.choices[0].message.content | |
| def _parse_json_response(self, response: str) -> dict: | |
| """ | |
| Parse JSON from LLM response. | |
| Try: json.loads(response) | |
| If fails: find { } block with regex and try again | |
| If fails: return None | |
| """ | |
| def try_parse(s): | |
| try: | |
| res = json.loads(s) | |
| if isinstance(res, dict) and "fixed_code" in res: | |
| return res | |
| except Exception: | |
| pass | |
| return None | |
| # 1. Direct parse | |
| res = try_parse(response.strip()) | |
| if res: return res | |
| # 2. Extract JSON block inside markdown | |
| match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", response, re.DOTALL) | |
| if match: | |
| res = try_parse(match.group(1)) | |
| if res: return res | |
| # 3. Raw { } block fallback | |
| match = re.search(r"(\{.*\})", response, re.DOTALL) | |
| if match: | |
| res = try_parse(match.group(1)) | |
| if res: return res | |
| return None | |
| def _heuristic_fix(self, buggy_code: str, error_type: str) -> str: | |
| """ | |
| Rule-based fixes for common patterns: | |
| IndexError: find lst[len(lst)] → change to lst[len(lst)-1] | |
| Missing return: add "return result" before last line | |
| Wrong operator: common >= vs > substitutions | |
| Returns modified code or original if no rule matches | |
| """ | |
| if error_type == "IndexError": | |
| # Heuristic for off-by-one errors | |
| if "len(lst)]" in buggy_code: | |
| return buggy_code.replace("len(lst)]", "len(lst)-1]") | |
| # Missing return check | |
| if "return " not in buggy_code: | |
| lines = buggy_code.splitlines() | |
| for i in range(len(lines)-1, -1, -1): | |
| if lines[i].strip(): | |
| indent = len(lines[i]) - len(lines[i].lstrip()) | |
| lines[i] = " " * indent + "return " + lines[i].lstrip() | |
| return "\n".join(lines) | |
| # Common operator mistakes | |
| if ">" in buggy_code and ">=" not in buggy_code: | |
| return buggy_code.replace(">", ">=") | |
| return buggy_code | |