Spaces:
Sleeping
Sleeping
| """ | |
| Debug Agent for CoDA. | |
| Executes generated code, diagnoses errors, and applies fixes | |
| to produce working visualizations. | |
| """ | |
| import logging | |
| import os | |
| import subprocess | |
| import sys | |
| import tempfile | |
| from pathlib import Path | |
| from typing import Optional | |
| from pydantic import BaseModel, Field | |
| from coda.core.base_agent import AgentContext, BaseAgent | |
| from coda.core.llm import LLMProvider | |
| from coda.core.memory import SharedMemory | |
| logger = logging.getLogger(__name__) | |
| class ExecutionResult(BaseModel): | |
| """Structured output from the Debug Agent.""" | |
| success: bool = Field( | |
| description="Whether execution succeeded" | |
| ) | |
| output_file: Optional[str] = Field( | |
| default=None, | |
| description="Path to the generated visualization" | |
| ) | |
| stdout: str = Field( | |
| default="", | |
| description="Standard output from execution" | |
| ) | |
| stderr: str = Field( | |
| default="", | |
| description="Error output from execution" | |
| ) | |
| error_diagnosis: Optional[str] = Field( | |
| default=None, | |
| description="Diagnosis of any errors" | |
| ) | |
| corrected_code: Optional[str] = Field( | |
| default=None, | |
| description="Fixed code if errors occurred" | |
| ) | |
| fix_applied: bool = Field( | |
| default=False, | |
| description="Whether a fix was applied" | |
| ) | |
| execution_time_seconds: float = Field( | |
| default=0.0, | |
| description="Time taken to execute" | |
| ) | |
| class DebugAgent(BaseAgent[ExecutionResult]): | |
| """ | |
| Executes generated code and handles errors. | |
| Runs the visualization code in a subprocess with timeout, | |
| diagnoses errors, and attempts automatic fixes. | |
| """ | |
| MEMORY_KEY = "execution_result" | |
| def __init__( | |
| self, | |
| llm: LLMProvider, | |
| memory: SharedMemory, | |
| timeout_seconds: int = 60, | |
| output_directory: str = "outputs", | |
| name: Optional[str] = None, | |
| ) -> None: | |
| super().__init__(llm, memory, name or "DebugAgent") | |
| self._timeout = timeout_seconds | |
| self._output_dir = Path(output_directory) | |
| self._output_dir.mkdir(parents=True, exist_ok=True) | |
| def execute(self, context: AgentContext) -> ExecutionResult: | |
| """Execute the generated code and handle errors.""" | |
| logger.info(f"[{self._name}] Starting code execution") | |
| generated_code = self._get_from_memory("generated_code") | |
| if not generated_code: | |
| return ExecutionResult( | |
| success=False, | |
| stderr="No generated code found in memory", | |
| ) | |
| code = generated_code.get("code", "") | |
| output_filename = generated_code.get("output_filename", "output.png") | |
| code = self._prepare_code(code, output_filename) | |
| result = self._execute_code(code) | |
| if not result.success and result.stderr: | |
| logger.warning(f"[{self._name}] Code execution failed: {result.stderr[:500]}") | |
| logger.info(f"[{self._name}] Attempting to fix errors") | |
| fixed_result = self._attempt_fix(code, result.stderr, context) | |
| if fixed_result.success: | |
| self._store_result(fixed_result) | |
| logger.info(f"[{self._name}] Fix successful!") | |
| return fixed_result | |
| logger.warning(f"[{self._name}] Fix attempt failed") | |
| result.error_diagnosis = fixed_result.error_diagnosis | |
| result.corrected_code = fixed_result.corrected_code | |
| self._store_result(result) | |
| logger.info(f"[{self._name}] Execution complete: success={result.success}") | |
| return result | |
| def _prepare_code(self, code: str, output_filename: str) -> str: | |
| """Prepare code for execution by setting up paths.""" | |
| output_path = self._output_dir / output_filename | |
| code = code.replace( | |
| f"'{output_filename}'", | |
| f"r'{output_path}'" | |
| ) | |
| code = code.replace( | |
| f'"{output_filename}"', | |
| f"r'{output_path}'" | |
| ) | |
| if "plt.savefig" not in code and "fig.savefig" not in code: | |
| code += f"\nplt.savefig(r'{output_path}', dpi=150, bbox_inches='tight')\n" | |
| return code | |
| def _execute_code(self, code: str) -> ExecutionResult: | |
| """Execute Python code in a subprocess.""" | |
| import time | |
| start_time = time.time() | |
| with tempfile.NamedTemporaryFile( | |
| mode="w", | |
| suffix=".py", | |
| delete=False, | |
| encoding="utf-8" | |
| ) as f: | |
| f.write(code) | |
| temp_file = f.name | |
| try: | |
| result = subprocess.run( | |
| [sys.executable, temp_file], | |
| capture_output=True, | |
| text=True, | |
| timeout=self._timeout, | |
| cwd=str(self._output_dir.parent), | |
| ) | |
| execution_time = time.time() - start_time | |
| output_files = list(self._output_dir.glob("*.png")) | |
| output_file = str(output_files[-1]) if output_files else None | |
| return ExecutionResult( | |
| success=result.returncode == 0, | |
| output_file=output_file, | |
| stdout=result.stdout, | |
| stderr=result.stderr, | |
| execution_time_seconds=execution_time, | |
| ) | |
| except subprocess.TimeoutExpired: | |
| return ExecutionResult( | |
| success=False, | |
| stderr=f"Execution timed out after {self._timeout} seconds", | |
| ) | |
| except Exception as e: | |
| return ExecutionResult( | |
| success=False, | |
| stderr=str(e), | |
| ) | |
| finally: | |
| try: | |
| os.unlink(temp_file) | |
| except OSError: | |
| pass | |
| def _attempt_fix( | |
| self, | |
| original_code: str, | |
| error_message: str, | |
| context: AgentContext, | |
| ) -> ExecutionResult: | |
| """Attempt to fix code errors using the LLM.""" | |
| fix_prompt = f"""The following Python visualization code produced an error. Please fix it. | |
| Original Code: | |
| ```python | |
| {original_code} | |
| ``` | |
| Error Message: | |
| {error_message} | |
| Provide a JSON response with: | |
| - diagnosis: What caused the error | |
| - corrected_code: The fixed Python code | |
| IMPORTANT: Return ONLY valid JSON. Do not include markdown formatting or explanations outside the JSON. | |
| Safe to assume standard libraries (matplotlib, seaborn, pandas, numpy) are available. | |
| JSON Response:""" | |
| response = self._llm.complete( | |
| prompt=fix_prompt, | |
| system_prompt="You are an expert Python debugger. Fix the code error and provide corrected code.", | |
| ) | |
| try: | |
| data = self._extract_json(response.content) | |
| diagnosis = data.get("diagnosis", "Unknown error") | |
| corrected_code = data.get("corrected_code", "") | |
| if corrected_code: | |
| output_filename = "output.png" | |
| corrected_code = self._prepare_code(corrected_code, output_filename) | |
| result = self._execute_code(corrected_code) | |
| result.error_diagnosis = diagnosis | |
| result.corrected_code = corrected_code | |
| result.fix_applied = result.success | |
| return result | |
| except Exception as e: | |
| logger.error(f"Failed to parse fix response: {e}") | |
| return ExecutionResult( | |
| success=False, | |
| stderr=error_message, | |
| error_diagnosis="Failed to automatically fix the error", | |
| ) | |
| def _build_prompt(self, context: AgentContext) -> str: | |
| return "" | |
| def _get_system_prompt(self) -> str: | |
| return "" | |
| def _parse_response(self, response: str) -> ExecutionResult: | |
| return ExecutionResult(success=False) | |
| def _get_output_key(self) -> str: | |
| return self.MEMORY_KEY | |