Spaces:
Running
Running
| """Cross-service contract for the Math Agent integration layer.""" | |
| from __future__ import annotations | |
| from datetime import datetime | |
| from typing import Any, Literal | |
| from uuid import UUID | |
| from pydantic import BaseModel, ConfigDict, Field | |
| from shared.schemas.render_job import RenderQuality | |
| class GeometryObject(BaseModel): | |
| """A single geometric entity the Math Agent wants visualized.""" | |
| model_config = ConfigDict(extra="forbid") | |
| type: str = Field(description="Geometry type: circle, triangle, line, angle, point, polygon, function_plot, etc.") | |
| label: str | None = Field(default=None, description="Optional identifier or label: A, B, C, C_1, etc.") | |
| properties: dict[str, Any] = Field( | |
| default_factory=dict, | |
| description="Coordinates, dimensions, colors, or constraints (e.g. {'center': [0, 0], 'radius': 3})", | |
| ) | |
| class AnimationDirective(BaseModel): | |
| """One animation beat requested by the Math Agent.""" | |
| model_config = ConfigDict(extra="forbid") | |
| action: str = Field(description="Animation action: draw, highlight, transform, fade_in, fade_out, write, etc.") | |
| targets: list[str] = Field(default_factory=list, description="Labels or names of geometry objects involved") | |
| narration: str | None = Field(default=None, max_length=2_000, description="Voiceover/explanation for this beat") | |
| duration_hint: float | None = Field(default=None, ge=0.1, le=60.0, description="Estimated duration in seconds") | |
| class OutputConfig(BaseModel): | |
| """Rendering and output configuration for the generated animation.""" | |
| model_config = ConfigDict(extra="forbid") | |
| quality: RenderQuality = "720p" | |
| format: Literal["mp4", "gif"] = "mp4" | |
| language: str = Field(default="vi", min_length=2, max_length=16, description="Language code: vi, en, ...") | |
| class VisualizationSpec(BaseModel): | |
| """The standardized schema that Math Agent sends to the Manim Module.""" | |
| model_config = ConfigDict(extra="forbid") | |
| problem: str = Field( | |
| min_length=1, | |
| max_length=20_000, | |
| description="Math problem description, theorem statement, or topic to visualize", | |
| ) | |
| solution_steps: list[str] = Field( | |
| min_length=1, | |
| max_length=50, | |
| description="Ordered list of reasoning or solution steps", | |
| ) | |
| geometry: list[GeometryObject] = Field( | |
| default_factory=list, | |
| description="List of geometric or mathematical entities to construct", | |
| ) | |
| animations: list[AnimationDirective] = Field( | |
| default_factory=list, | |
| description="Optional animation script and narration beats", | |
| ) | |
| output_config: OutputConfig = Field(default_factory=OutputConfig) | |
| def to_prompt(self) -> str: | |
| """Serialize the spec into a rich natural language prompt for the AI pipeline.""" | |
| parts = [f"Chủ đề / Đề bài: {self.problem}"] | |
| if self.solution_steps: | |
| parts.append("Các bước giải thích / chứng minh chi tiết:") | |
| for idx, step in enumerate(self.solution_steps, 1): | |
| parts.append(f" {idx}. {step}") | |
| if self.geometry: | |
| parts.append("Các đối tượng hình học / toán học cần trực quan hóa:") | |
| for obj in self.geometry: | |
| label_str = f" ({obj.label})" if obj.label else "" | |
| props_str = f" - thuộc tính: {obj.properties}" if obj.properties else "" | |
| parts.append(f" - {obj.type}{label_str}{props_str}") | |
| if self.animations: | |
| parts.append("Chỉ dẫn hoạt họa và thuyết minh (animation beats):") | |
| for idx, anim in enumerate(self.animations, 1): | |
| targets_str = f" [đối tượng: {', '.join(anim.targets)}]" if anim.targets else "" | |
| narr_str = f" | Lời thoại: '{anim.narration}'" if anim.narration else "" | |
| parts.append(f" - Beat {idx}: {anim.action}{targets_str}{narr_str}") | |
| return "\n".join(parts) | |
| class MathRenderRequest(BaseModel): | |
| """Request payload for POST /v1/math/generate.""" | |
| model_config = ConfigDict(extra="forbid") | |
| spec: VisualizationSpec | |
| callback_url: str | None = Field( | |
| default=None, | |
| max_length=2_048, | |
| description="Optional webhook URL to receive notification when video generation finishes", | |
| ) | |
| class MathRenderResponse(BaseModel): | |
| """Status or completion response for Math Agent.""" | |
| model_config = ConfigDict(extra="forbid") | |
| job_id: UUID = Field(description="Identifier for tracking the generation & rendering job") | |
| project_id: UUID = Field(description="Corresponding Manim project ID") | |
| status: Literal["queued", "generating", "rendering", "completed", "failed"] | |
| video_url: str | None = Field(default=None, description="Signed or direct URL to the final rendered video") | |
| duration: float | None = Field(default=None, description="Total video duration in seconds") | |
| error: str | None = Field(default=None, description="Error message if generation or rendering failed") | |
| created_at: datetime = Field(default_factory=datetime.now) | |
| completed_at: datetime | None = None | |