File size: 9,806 Bytes
d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 c4a80a5 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 2e31ab2 d970572 |
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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
import os
import re
from google import genai
from google.genai import types as genai_types
import logging
from .gemini import base_prompt_instructions
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
FALLBACK_SYSTEM_PROMPT = """You are an expert Manim programmer specializing in fixing broken Manim code and creating visually striking 60-second animations, strictly following Manim Community v0.19.0 standards.
CRITICAL TIMING REQUIREMENTS:
- **Total Duration:** Exactly 60 seconds (1 minute)
- **Narration:** Exactly 150-160 words (average speaking pace: 2.5 words per second)
- **Animation Structure:** Use this timing framework:
* Introduction: 8-10 seconds
* Main content: 40-45 seconds (3-4 major segments)
* Conclusion/summary: 7-10 seconds
- **Synchronization:** Each narration sentence should correspond to 3-5 seconds of animation
Core Requirements:
- **API Version:** Use only Manim Community v0.19.0 API
- **Vectors & Math:** Use 3D vectors (np.array([x, y, 0])) and ensure correct math operations
- **Matrix Visualization:** Use MathTex for matrices: r'\\begin{bmatrix} a & b \\\\ c & d \\end{bmatrix}'
- **Star Usage:** Use Star(n=5, ...) not n_points
- **Error Prevention:** Always validate Scene class exists; avoid 3D scenes
- **Visual Style:** Create vibrant, dynamic animations with smooth transitions
IMPORTANT: Your response must be formatted with clear delimiters:
- Start Manim code with: ### MANIM CODE:
- Start narration with: ### NARRATION:
- End response after narration (no additional text)
"""
def fix_manim_code(faulty_code: str, error_message: str, original_context: str):
"""
Enhanced fallback function with Google Search integration.
"""
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
logging.error("GEMINI_API_KEY not found in environment variables for fallback.")
return None, None
client = genai.Client(api_key=api_key)
# Enhanced fallback prompt with better structure and error analysis
fix_prompt_text = f"""
TASK: Fix the broken Manim code that failed with a specific error.
### ORIGINAL REQUEST:
{original_context}
### BROKEN MANIM CODE:
```python
{faulty_code}
```
### ERROR ENCOUNTERED:
```
{error_message}
```
### ANALYSIS INSTRUCTIONS:
1. **Error Analysis**: Examine the error message carefully. Common issues include:
- Import errors (missing 'from manim import *' or 'import numpy as np')
- Scene class not found (class must inherit from Scene)
- Invalid Manim methods or syntax
- Vector dimension mismatches (use np.array([x, y, 0]))
- Animation object validation errors
- Timing issues (ensure total duration = 60 seconds)
2. **Google Search**: Use Google Search to find:
- Recent Manim Community v0.19.0 API changes
- Specific error message solutions
- Updated method signatures or deprecated features
- Working examples of similar animations
3. **Code Fixing Strategy**:
- Keep the original animation concept intact
- Fix only what's necessary to resolve the error
- Maintain 60-second duration and 120-150 word narration
- Ensure all imports are present
- Validate Scene class exists and is properly named
- Use only verified Manim methods from the allowed list
4. **Quality Checks**:
- Verify vector operations use 3D format: np.array([x, y, 0])
- Check all self.play() calls have valid animation objects
- Ensure run_time and self.wait() sum to exactly 60 seconds
- Count narration words (must be 120-150)
### OUTPUT FORMAT:
Provide your response in exactly this format:
### MANIM CODE:
[Insert the complete, fixed Manim code here - include all imports and Scene class]
### NARRATION:
[Insert the narration script here - exactly 120-150 words, synchronized with animations]
### REQUIREMENTS TO FOLLOW:
{base_prompt_instructions}
"""
contents = [fix_prompt_text]
logging.info("Attempting to fix Manim code via fallback...")
try:
grounding_tool = genai_types.Tool(google_search=genai_types.GoogleSearch())
generation_config = genai_types.GenerateContentConfig(
tools=[grounding_tool],
temperature=0.4, # lower coz grounding
system_instruction=FALLBACK_SYSTEM_PROMPT,
)
response = client.models.generate_content(
model="gemini-2.5-pro",
contents=contents, # type: ignore
config=generation_config,
)
if response:
# print(response)
try:
content = response.text
logging.info("Received response from fallback attempt.")
if "### NARRATION:" in content: # type: ignore
manim_code, narration = content.split("### NARRATION:", 1) # type: ignore
manim_code = (
re.sub(r"```python", "", manim_code).replace("```", "").strip()
)
narration = narration.strip()
if "from manim import *" not in manim_code:
logging.warning(
"Adding missing 'from manim import *' (fallback fix)."
)
manim_code = (
"from manim import *\nimport numpy as np\n" + manim_code
)
elif "import numpy as np" not in manim_code:
logging.warning(
"Adding missing 'import numpy as np' (fallback fix)."
)
lines = manim_code.splitlines()
for i, line in enumerate(lines):
if "from manim import *" in line:
lines.insert(i + 1, "import numpy as np")
manim_code = "\n".join(lines)
break
logging.info(
"Successfully parsed fixed code and narration from fallback."
)
return {
"manim_code": manim_code,
"output_file": "output.mp4",
}, narration
else:
logging.warning(
"Delimiter '### NARRATION:' not found in fallback response. Attempting fallback extraction."
)
code_match = re.search(r"```python(.*?)```", content, re.DOTALL) # type: ignore
if code_match:
manim_code = code_match.group(1).strip()
narration_part = content.split("```", 2)[-1].strip()
narration = narration_part if len(narration_part) > 20 else ""
if not narration:
logging.warning(
"Fallback narration extraction resulted in empty or very short text (fallback fix)."
)
else:
logging.info(
"Successfully parsed code and narration using fallback regex (fallback fix)."
)
if "from manim import *" not in manim_code:
logging.warning(
"Adding missing 'from manim import *' (fallback fix, regex path)."
)
manim_code = (
"from manim import *\nimport numpy as np\n" + manim_code
)
elif "import numpy as np" not in manim_code:
logging.warning(
"Adding missing 'import numpy as np' (fallback fix, regex path)."
)
lines = manim_code.splitlines()
for i, line in enumerate(lines):
if "from manim import *" in line:
lines.insert(i + 1, "import numpy as np")
manim_code = "\n".join(lines)
break
logging.info(
"Successfully parsed fixed code using fallback extraction."
)
return {
"manim_code": manim_code,
"output_file": "output.mp4",
}, narration
else:
logging.error(
"Fallback extraction failed: No Python code block found in fallback response."
)
logging.debug(
f"Fallback content without code block:\n{content}"
)
return None, None
except ValueError:
logging.error("Could not extract text from the fallback response.")
if response.prompt_feedback and response.prompt_feedback.block_reason:
logging.error(
f"Fallback content generation blocked. Reason: {response.prompt_feedback.block_reason.name}"
)
return None, None
except Exception as e:
logging.exception(f"Error processing fallback response: {e}")
return None, None
else:
logging.error("No response received from Gemini during fallback attempt.")
return None, None
except Exception as e:
logging.exception(f"Error calling Gemini API during fallback: {e}")
return None, None
|