manim-mcp / manim_mcp /tools /code_generation.py
bhaveshgoel07's picture
Complete NeuroAnim HF Spaces deployment - all source files
0805c5b
raw
history blame
9.2 kB
"""
Code Generation Tools for Manim MCP Server
This module provides tools for generating and refining Manim animation code.
"""
import logging
from typing import Any, Dict, Optional
from mcp.types import CallToolResult, TextContent
from utils.hf_wrapper import HFInferenceWrapper, ModelConfig
logger = logging.getLogger(__name__)
async def generate_manim_code(
hf_wrapper: HFInferenceWrapper, arguments: Dict[str, Any]
) -> CallToolResult:
"""
Generate Manim Python code for an animation concept.
Uses a code LLM to generate complete, runnable Manim code based on:
- A concept description
- Scene details
- Desired visual elements
- Optional error feedback for retries
Args:
hf_wrapper: HuggingFace inference wrapper instance
arguments: Dictionary containing:
- concept (str): The animation concept
- scene_description (str): Detailed scene description
- visual_elements (list, optional): List of visual elements to include
- model (str, optional): Hugging Face model to use
- previous_code (str, optional): Previous code attempt (for retries)
- error_message (str, optional): Error from previous attempt (for retries)
Returns:
CallToolResult with the generated Manim code
"""
concept = arguments["concept"]
scene_description = arguments["scene_description"]
visual_elements = arguments.get("visual_elements", [])
model = arguments.get("model")
previous_code = arguments.get("previous_code")
error_message = arguments.get("error_message")
try:
model_config = ModelConfig()
selected_model = model or model_config.code_models[0]
# Build prompt based on whether this is a retry
if previous_code and error_message:
prompt = f"""
You are an expert animation engineer using Manim Community Edition (v0.18.0+).
The previous code attempt had an error. Your task is to FIX the code.
PREVIOUS CODE:
```python
{previous_code}
```
ERROR ENCOUNTERED:
{error_message}
TASK: Fix the error in the code above. Pay special attention to:
- Closing all parentheses, brackets, and braces
- Completing all function calls
- Proper indentation
- Valid Python syntax
Concept: {concept}
Scene Description: {scene_description}
Visual Elements: {", ".join(visual_elements)}
STRICT CODE REQUIREMENTS:
1. Header: MUST start with `from manim import *`
2. Class Structure: Define a class inheriting from `MovingCameraScene` (use this instead of `Scene` to enable camera zoom/pan with `self.camera.frame`)
3. Method: All logic must be inside the `def construct(self):` method
4. SYNTAX: Ensure ALL parentheses, brackets, and function calls are properly closed
5. Colors: Use ONLY valid Manim colors (WHITE, BLACK, RED, GREEN, BLUE, YELLOW, ORANGE, PINK, PURPLE, TEAL, GOLD, etc.)
6. Text: Use `Text()` objects for strings
7. Positioning: Use `.next_to()`, `.move_to()`, or `.shift()`
8. Animations: Use Write(), Create(), FadeIn(), FadeOut(), Transform(), Flash(), Indicate() - capitalize properly!
9. Pacing: Include `self.wait(1)` between animations
OUTPUT FORMAT:
Provide ONLY the complete, corrected Python code. No markdown blocks. No explanations.
"""
else:
prompt = f"""
You are an expert animation engineer using Manim Community Edition (v0.18.0+).
Generate a complete, runnable Python script for the following request.
Concept: {concept}
Scene Description: {scene_description}
Visual Elements: {", ".join(visual_elements)}
STRICT CODE REQUIREMENTS:
1. Header: MUST start with `from manim import *`
2. Class Structure: Define a class inheriting from `MovingCameraScene` (e.g., `class GenScene(MovingCameraScene):`) - this enables camera operations like zoom/pan via `self.camera.frame`
3. Method: All logic must be inside the `def construct(self):` method
4. SYNTAX: Ensure ALL parentheses, brackets, and function calls are properly closed
5. Colors: Use ONLY these valid Manim color constants:
- Basic: WHITE, BLACK, GRAY, GREY, LIGHT_GRAY, DARK_GRAY
- Primary: RED, GREEN, BLUE, YELLOW, ORANGE, PINK, PURPLE, TEAL, GOLD, MAROON
- Variants: RED_A, RED_B, RED_C, RED_D, RED_E, GREEN_A, GREEN_B, GREEN_C, GREEN_D, GREEN_E,
BLUE_A, BLUE_B, BLUE_C, BLUE_D, BLUE_E, YELLOW_A, YELLOW_B, YELLOW_C, YELLOW_D, YELLOW_E
- NEVER use: DARK_GREEN, LIGHT_GREEN, DARK_BLUE, LIGHT_BLUE, DARK_RED, LIGHT_RED (these don't exist!)
6. Text: Use `Text()` objects for strings. Avoid `Tex()` or `MathTex()` unless necessary
7. Positioning: Use `.next_to()`, `.move_to()`, or `.shift()` to arrange elements
8. Animations: Use ONLY these valid animations:
- Write(), Create(), FadeIn(), FadeOut(), GrowFromCenter(), ShrinkToCenter()
- Transform(), ReplacementTransform(), MoveToTarget(), ApplyMethod()
- Rotate(), Indicate(), Flash(), ShowCreation() - DO NOT use lowercase like 'flash'
- For custom effects use .animate.method() (e.g., obj.animate.scale(2), obj.animate.shift(UP))
9. Pacing: Include `self.wait(1)` between major animation groups
OUTPUT FORMAT:
Provide ONLY the raw Python code. Do not wrap in markdown blocks (no ```python). Do not include conversational text.
"""
response = await hf_wrapper.text_generation(
model=selected_model,
prompt=prompt,
max_new_tokens=2048,
temperature=0.3,
)
logger.info(f"Successfully generated Manim code for concept: {concept}")
return CallToolResult(
content=[
TextContent(
type="text",
text=f"Generated Manim Code:\n\n```python\n{response}\n```",
)
]
)
except Exception as e:
logger.error(f"Code generation failed: {str(e)}")
return CallToolResult(
content=[
TextContent(type="text", text=f"Code generation failed: {str(e)}")
],
isError=True,
)
async def refine_animation(
hf_wrapper: HFInferenceWrapper, arguments: Dict[str, Any]
) -> CallToolResult:
"""
Refine animation code based on feedback.
Uses a code LLM to improve existing Manim code based on:
- User feedback or error messages
- Specific improvement goals
- Visual or educational quality issues
Args:
hf_wrapper: HuggingFace inference wrapper instance
arguments: Dictionary containing:
- original_code (str): The original Manim code to refine
- feedback (str): Feedback or error message about the code
- improvement_goals (list, optional): List of specific improvement goals
- model (str, optional): Hugging Face model to use
Returns:
CallToolResult with the refined Manim code
"""
original_code = arguments["original_code"]
feedback = arguments["feedback"]
improvement_goals = arguments.get("improvement_goals", [])
model = arguments.get("model")
try:
model_config = ModelConfig()
selected_model = model or model_config.code_models[0]
prompt = f"""
You are a Manim Code Repair Agent. Your task is to rewrite the FULL Python script to fix issues or apply improvements.
Previous Code:
{original_code}
User Feedback/Error:
{feedback}
Improvement Goals:
{", ".join(improvement_goals)}
INSTRUCTIONS:
1. Output the COMPLETE corrected script, including `from manim import *`.
2. Do not output diffs or partial snippets.
3. Ensure the class inherits from `MovingCameraScene` and uses `def construct(self):`.
4. Fix logic errors based on the feedback.
5. Animations: Use ONLY valid animations like Write(), FadeIn(), FadeOut(), Create(), Flash(), Transform() - NEVER lowercase!
6. Colors: Use ONLY these valid Manim color constants:
- Basic: WHITE, BLACK, GRAY, GREY, LIGHT_GRAY, DARK_GRAY
- Primary: RED, GREEN, BLUE, YELLOW, ORANGE, PINK, PURPLE, TEAL, GOLD, MAROON
- Variants: RED_A, RED_B, RED_C, RED_D, RED_E, GREEN_A, GREEN_B, GREEN_C, GREEN_D, GREEN_E,
BLUE_A, BLUE_B, BLUE_C, BLUE_D, BLUE_E, YELLOW_A, YELLOW_B, YELLOW_C, YELLOW_D, YELLOW_E
- NEVER use: DARK_GREEN, LIGHT_GREEN, DARK_BLUE, LIGHT_BLUE, DARK_RED, LIGHT_RED (these don't exist!)
- For darker/lighter variants, use the letter suffixes (e.g., GREEN_E for dark green, GREEN_A for light green).
OUTPUT:
Return ONLY the raw Python code. No markdown backticks. No explanation.
"""
response = await hf_wrapper.text_generation(
model=selected_model,
prompt=prompt,
max_new_tokens=2048,
temperature=0.3,
)
logger.info("Successfully refined animation code")
return CallToolResult(
content=[
TextContent(
type="text",
text=f"Refined Manim Code:\n\n```python\n{response}\n```",
)
]
)
except Exception as e:
logger.error(f"Code refinement failed: {str(e)}")
return CallToolResult(
content=[
TextContent(type="text", text=f"Code refinement failed: {str(e)}")
],
isError=True,
)