Spaces:
Running
Running
| """Python SDK / Module Client for Math Agent integration.""" | |
| from __future__ import annotations | |
| import asyncio | |
| import logging | |
| from typing import Any | |
| from uuid import UUID | |
| import httpx | |
| from shared.schemas.math_agent import ( | |
| MathRenderRequest, | |
| MathRenderResponse, | |
| VisualizationSpec, | |
| ) | |
| logger = logging.getLogger(__name__) | |
| class ManimModule: | |
| """High-level client for Math Agent to invoke the Manim video generation service.""" | |
| def __init__( | |
| self, | |
| base_url: str = "http://localhost:8000", | |
| internal_token: str | None = None, | |
| bearer_token: str | None = None, | |
| timeout: float = 60.0, | |
| ) -> None: | |
| self.base_url = base_url.rstrip("/") | |
| self.headers: dict[str, str] = {} | |
| if internal_token: | |
| self.headers["X-Internal-Token"] = internal_token | |
| if bearer_token: | |
| self.headers["Authorization"] = f"Bearer {bearer_token}" | |
| self.timeout = timeout | |
| async def generate( | |
| self, | |
| spec: VisualizationSpec | dict[str, Any], | |
| callback_url: str | None = None, | |
| ) -> MathRenderResponse: | |
| """Submit a VisualizationSpec to start asynchronous video generation.""" | |
| parsed_spec = spec if isinstance(spec, VisualizationSpec) else VisualizationSpec.model_validate(spec) | |
| req = MathRenderRequest(spec=parsed_spec, callback_url=callback_url) | |
| async with httpx.AsyncClient(base_url=self.base_url, headers=self.headers, timeout=self.timeout) as client: | |
| resp = await client.post("/v1/math/generate", json=req.model_dump(mode="json")) | |
| resp.raise_for_status() | |
| return MathRenderResponse.model_validate(resp.json()) | |
| async def get_status(self, job_id: UUID | str) -> MathRenderResponse: | |
| """Query current status, progress, or completed video URL.""" | |
| async with httpx.AsyncClient(base_url=self.base_url, headers=self.headers, timeout=self.timeout) as client: | |
| resp = await client.get(f"/v1/math/jobs/{job_id}") | |
| resp.raise_for_status() | |
| return MathRenderResponse.model_validate(resp.json()) | |
| async def wait_for_completion( | |
| self, | |
| job_id: UUID | str, | |
| poll_interval: float = 3.0, | |
| timeout: float = 600.0, | |
| ) -> MathRenderResponse: | |
| """Poll until the video rendering is completed or failed.""" | |
| elapsed = 0.0 | |
| while elapsed < timeout: | |
| status_resp = await self.get_status(job_id) | |
| if status_resp.status in {"completed", "failed"}: | |
| return status_resp | |
| await asyncio.sleep(poll_interval) | |
| elapsed += poll_interval | |
| raise TimeoutError(f"Rendering job {job_id} did not finish within {timeout} seconds") | |