Spaces:
Running
Running
File size: 6,853 Bytes
1645305 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
"""
Utilities for cleaning and validating Manim code generated by LLMs.
"""
import re
import logging
import json
logger = logging.getLogger(__name__)
def clean_manim_code(raw_code):
"""
Clean Manim code from LLM responses by removing markdown formatting
and ensuring proper structure.
Args:
raw_code (str): The raw code from the LLM response
Returns:
str: Cleaned, executable Python code
"""
# Start with the raw code
code = raw_code
# Extract code from markdown code blocks if present
if "```python" in code:
parts = code.split("```python")
if len(parts) > 1:
code = parts[1]
if "```" in code:
code = code.split("```")[0]
elif "```" in code:
parts = code.split("```")
if len(parts) > 1:
code = parts[1]
if "```" in parts[1]:
code = code.split("```")[0]
# Remove any remaining backticks
code = code.replace('```', '')
# Ensure code begins with the necessary import
if not code.strip().startswith('from manim import'):
code = 'from manim import *\n\n' + code
# Verify the code contains a Scene class
if 'class' not in code or 'Scene' not in code:
logger.warning("Generated code does not contain a proper Scene class")
# Add a basic scene structure if missing
if 'class ManimScene(Scene):' not in code:
code = 'from manim import *\n\nclass ManimScene(Scene):\n def construct(self):\n ' + code
# Verify the code has a construct method
if 'def construct(self)' not in code:
logger.warning("Generated code does not contain a construct method")
# Try to find where the class is defined and add construct method
class_match = re.search(r'class\s+\w+\s*\(\s*Scene\s*\)\s*:', code)
if class_match:
insert_pos = class_match.end()
code = code[:insert_pos] + '\n def construct(self):\n pass\n' + code[insert_pos:]
# Ensure there's a wait at the end if not present
if 'self.wait(' not in code.split('def construct')[-1]:
# Find the end of the construct method to add wait
construct_body_match = re.search(r'def\s+construct\s*\(\s*self\s*\)\s*:', code)
if construct_body_match:
# Check if the method has content
method_content = code[construct_body_match.end():]
indentation = ' ' # Default indentation
# Try to determine indentation from code
indent_match = re.search(r'\n(\s+)', method_content)
if indent_match:
indentation = indent_match.group(1)
# Find a good place to insert the wait
if '}' in method_content.splitlines()[-1]: # If last line closes something
code = code.rstrip() + f'\n{indentation}self.wait(1)\n'
else:
code = code.rstrip() + f'\n{indentation}self.wait(1)\n'
return code.strip()
def parse_scenario_from_llm_response(content):
"""
Extract structured scenario information from an LLM response.
Args:
content (str): The LLM response text
Returns:
dict: Extracted scenario dictionary
"""
try:
# Try to find and extract a JSON object
json_match = re.search(r'\{.*\}', content, re.DOTALL)
if json_match:
json_str = json_match.group(0)
scenario_dict = json.loads(json_str)
return scenario_dict
except Exception as e:
logger.error(f"Error parsing scenario JSON: {e}")
# Manual parsing fallback
scenario = {
"title": "",
"objects": [],
"transformations": [],
"equations": []
}
# Simple pattern matching to extract information
title_match = re.search(r'title["\s:]+([^"]+)', content, re.IGNORECASE)
if title_match:
scenario["title"] = title_match.group(1).strip()
# Extract lists with various possible formats
objects_pattern = r'objects[":\s\[]+([^\]]+)'
objects_match = re.search(objects_pattern, content, re.IGNORECASE | re.DOTALL)
if objects_match:
objects_text = objects_match.group(1)
# Handle both comma-separated and quote-wrapped items
objects = re.findall(r'"([^"]+)"', objects_text)
if not objects:
objects = [item.strip() for item in objects_text.split(',')]
scenario["objects"] = objects
# Similar extraction for transformations
trans_pattern = r'transformations[":\s\[]+([^\]]+)'
trans_match = re.search(trans_pattern, content, re.IGNORECASE | re.DOTALL)
if trans_match:
trans_text = trans_match.group(1)
transformations = re.findall(r'"([^"]+)"', trans_text)
if not transformations:
transformations = [item.strip() for item in trans_text.split(',')]
scenario["transformations"] = transformations
# Extract equations if present
equations_pattern = r'equations[":\s\[]+([^\]]+)'
equations_match = re.search(equations_pattern, content, re.IGNORECASE | re.DOTALL)
if equations_match:
equations_text = equations_match.group(1)
if equations_text.lower().strip() in ['null', 'none']:
scenario["equations"] = None
else:
equations = re.findall(r'"([^"]+)"', equations_text)
if not equations:
equations = [item.strip() for item in equations_text.split(',')]
scenario["equations"] = equations
return scenario
def validate_manim_code(code):
"""
Perform basic validation on Manim code to catch common issues.
Args:
code (str): The Manim code to validate
Returns:
tuple: (is_valid, error_message)
"""
# Check for basic Python syntax errors
try:
compile(code, '<string>', 'exec')
except SyntaxError as e:
return False, f"Syntax error: {str(e)}"
# Check for necessary components
if 'from manim import' not in code:
return False, "Missing Manim import"
if 'class' not in code or 'Scene' not in code:
return False, "No Scene class defined"
if 'def construct(self)' not in code:
return False, "No construct method defined"
# Check for common Manim issues
if 'self.play(' not in code and 'self.add(' not in code:
return False, "No objects added to scene (missing self.play or self.add calls)"
# All checks passed
return True, "Code appears valid"
|