CoolFace
Apppublic

LixinDu/math-ai-solver

sourceHugging Faceupdated 7d agoView on Hugging Face
0likes
App README

Math AI Solver (execution backend)

A small, authenticated HTTP service that validates and executes Python programs written for the math_for_AI_assistant project, using SymPy, NumPy, and Plotly.

This Space is the execution backend only. It renders no UI and holds no user accounts. The browser interface, login, and model calls live in the main application.

What this service does

ComponentResponsibility
Cloudflare WorkerLogin, LLM calls, forwarding execution requests
This SpaceValidate Python, execute it under limits, collect output and figures
BrowserRender Plotly JSON returned by the application

Endpoints

POST /solve

Request:

json
{"code": "import sympy as sp\nprint(sp.factor(12))"}

Response (matching src/executor's ExecutionResult fields):

json
{
  "passed": true,
  "status": "completed",
  "timed_out": false,
  "stdout": "12\n",
  "stderr_summary": null,
  "plotly_json": null
}

plotly_json, when present, is a JSON object (the figure), bounded to 512 KiB. Execution failures retain their structured status and error details rather than collapsing into a generic error.

GET /healthz

Returns {"status": "ok"}. Requires no authentication so the platform's health probe can reach it. It exposes no information beyond liveness.

GET /

Describes the service: name and available endpoints. Requires no authentication and exposes no configuration.

This exists so the Space's embedded App view does not display "Not Found". Without a root route, GET / correctly returns 404, which makes the Space page look broken even when the service is healthy. The interactive API docs (/docs, /openapi.json) remain disabled deliberately — the schema would advertise the request shape to unauthenticated callers.

Authentication

POST /solve requires a bearer token:

Authorization: Bearer <SOLVER_TOKEN>

SOLVER_TOKEN is a dedicated random value. It is stored as a Space secret (HF Settings → Secrets) and as a Cloudflare Worker secret. It never appears in browser code, request bodies, or logs.

The token is compared with hmac.compare_digest (constant time). Authentication runs before the request body is read or parsed, so an unauthenticated caller cannot make the service buffer and parse a payload.

Distinct HTTP statuses are used so callers can tell the cases apart:

CaseStatus
Missing/incorrect token401
Malformed Content-Length400
Request body over the byte ceiling413
Malformed request body422
Validated code rejected (AST/security)200, passed: false
Execution failure (error, timeout)200, passed: false with structured status
Service overloaded429
Unexpected internal failure500, JSON body

Note the deliberate split: authentication failures, size limits, overload, and internal errors are HTTP errors, while code rejection and execution failure are normal responses carrying structured detail. Callers should not treat a failed computation as a transport error.

An internal failure returns a JSON body ({"detail": ...}) and logs the traceback to the Space logs. The response never echoes exception text, paths, or stack frames.

Telling two different failures apart. Hugging Face's proxy answers with its own opaque page — historically the bare text Internal Server Error — when a request never reaches this application (for example, an unauthenticated request to a private Space). Our handler cannot change that, because we never see the request. What it does change is the other case: a failure inside the service now returns JSON, so a caller can distinguish "the solver broke" from "the edge broke" without reading the logs.

Execution limits

Limits are enforced inside the sandbox per process, not by the host:

LimitDefaultEnv var
Wall-clock timeout (parent)5 sMATH_AI_SOLVER_TIMEOUT_SECONDS
CPU seconds (child, RLIMIT_CPU)10 sMATH_AI_SANDBOX_CPU_SECONDS
Virtual memory (child, RLIMIT_AS)2048 MiBMATH_AI_SANDBOX_MEMORY_MB
Request body (before parsing)64 KiB
code field12 000 chars
Stdout (worker cap)120 000 chars
Plotly artifact512 KiB

A larger host does not raise these caps. RLIMIT_AS bounds virtual address space, not RSS — Python plus NumPy, Plotly, and SymPy map significant virtual memory, so the default is sized for the import stack while still catching runaway allocations.

Concurrency starts at 1. Measure before raising it. Simultaneous heavy requests on a 2 vCPU instance will starve each other and produce spurious CPU/OOM failures that look like bugs in the submitted code.

Isolation: read this before exposing the service

The AST allowlist and the subprocess are useful controls, but they do not establish complete filesystem, network, or secret isolation. Blocking write_html does not prove that all file access is blocked. The allowlist is a denylist of import names and call attributes, not a sandbox boundary.

Bearer authentication controls who may call the service. It does not make submitted code trustworthy.

Treat this service as untrusted-code-adjacent: keep it free of other secrets, do not co-locate data with it, and do not assume a submitted program cannot escape its intended bounds.

Vendored code and upstream pinning

This Space is self-contained. It carries a versioned copy of:

  • src/validation/code_security.pymath_ai_solver/validation/code_security.py
  • src/executor/sandbox.pymath_ai_solver/executor/sandbox.py

Upstream: math_for_AI_assistant

  • Commit: ebab6dc4702c8ae8fce9d069faac259a9f77723f
  • Branch: public/teachers
  • Date: 2026-09-14

UPSTREAM_SHA in math_ai_solver/__init__.py records this, and tests/test_upstream_sync.py verifies the copies still match the recorded content hashes. A security fix in the upstream validator must be ported here. Until a shared package exists, the copies can drift, and a fix that lands only upstream would leave this service more permissive than the application it serves.

Maintainers: the maintainer-side rationale, drift procedure, and re-vendor steps are recorded privately in the source repository (outside this Space, in deploy/docs/). This folder is uploaded verbatim, so internal notes are deliberately kept out of it.

To update: copy the modules, re-record UPSTREAM_SHA and the hashes, and update tests/test_upstream_sync.py. Make it an explicit, reviewed commit.

Local development

bash
cd deploy/hf-space
python -m venv .venv && source .venv/bin/activate
python -m pip install -r requirements.txt -r requirements-dev.txt
python -m pytest tests -q

The suite also runs from the repository root:

bash
python -m pytest deploy/hf-space/tests -q

The package is named math_ai_solver rather than app deliberately: this repository already has a top-level app.py (the Streamlit interface), and a package called app shadows it during pytest collection, which breaks the main project's tests.

Run locally:

bash
SOLVER_TOKEN=local-test-token python -m uvicorn math_ai_solver.main:app --port 7860
bash
curl -s -X POST http://127.0.0.1:7860/solve \
  -H 'Authorization: Bearer local-test-token' \
  -H 'Content-Type: application/json' \
  -d '{"code": "import sympy as sp\nprint(sp.factor(12))"}'

Deployment

The Space repository is flat at its root: README.md, Dockerfile, requirements.txt, and math_ai_solver/ all sit at the top level. This folder is the staging area for those contents.

Set the secret before going live:

  • HF Space → Settings → SecretsSOLVER_TOKEN
  • Cloudflare Worker → SecretsSOLVER_TOKEN (same value)