Spaces:
Sleeping
Sleeping
| """ | |
| FastAPI application for the Shopify Store Audit & Remediation Environment. | |
| Endpoints: | |
| POST /reset - Reset the environment (accepts task selection) | |
| POST /step - Execute an action | |
| GET /state - Get current environment state | |
| GET /schema - Get action/observation schemas | |
| WS /ws - WebSocket for persistent sessions | |
| """ | |
| try: | |
| from openenv.core.env_server.http_server import create_app | |
| except Exception as e: | |
| raise ImportError( | |
| "openenv is required. Install with: pip install openenv-core[core]" | |
| ) from e | |
| try: | |
| from ..models import ShopifyStoreAuditAction, ShopifyStoreAuditObservation | |
| from .shopify_store_audit_environment import ShopifyStoreAuditEnvironment | |
| except ImportError: | |
| from models import ShopifyStoreAuditAction, ShopifyStoreAuditObservation | |
| from server.shopify_store_audit_environment import ShopifyStoreAuditEnvironment | |
| from fastapi.responses import HTMLResponse, JSONResponse | |
| try: | |
| from .tasks import ALL_TASKS | |
| except ImportError: | |
| from server.tasks import ALL_TASKS | |
| try: | |
| from .graders import grade_product_listing_qa, grade_seo_collection_optimization, grade_full_store_audit | |
| except ImportError: | |
| from server.graders import grade_product_listing_qa, grade_seo_collection_optimization, grade_full_store_audit | |
| GRADER_MAP = { | |
| "product_listing_qa": grade_product_listing_qa, | |
| "seo_collection_optimization": grade_seo_collection_optimization, | |
| "full_store_audit": grade_full_store_audit, | |
| } | |
| app = create_app( | |
| ShopifyStoreAuditEnvironment, | |
| ShopifyStoreAuditAction, | |
| ShopifyStoreAuditObservation, | |
| env_name="shopify_store_audit", | |
| max_concurrent_envs=2, | |
| ) | |
| LANDING_HTML = """<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>Shopify Store Audit - OpenEnv</title> | |
| <style> | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; | |
| background:#0a0a0a;color:#e5e5e5;min-height:100vh;display:flex;align-items:center;justify-content:center} | |
| .container{max-width:720px;padding:48px 32px;text-align:center} | |
| h1{font-size:2.2rem;margin-bottom:8px;background:linear-gradient(135deg,#34d399,#3b82f6); | |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent} | |
| .subtitle{color:#a3a3a3;font-size:1.05rem;margin-bottom:36px} | |
| .cards{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:36px} | |
| .card{background:#171717;border:1px solid #262626;border-radius:12px;padding:20px 16px;text-align:left} | |
| .card h3{font-size:.95rem;margin-bottom:4px;color:#f5f5f5} | |
| .card .tag{display:inline-block;font-size:.7rem;padding:2px 8px;border-radius:99px; | |
| margin-bottom:10px;font-weight:600;text-transform:uppercase} | |
| .easy .tag{background:#064e3b;color:#6ee7b7}.med .tag{background:#78350f;color:#fcd34d} | |
| .hard .tag{background:#7f1d1d;color:#fca5a5} | |
| .card p{font-size:.82rem;color:#a3a3a3;line-height:1.45} | |
| .endpoints{background:#171717;border:1px solid #262626;border-radius:12px; | |
| padding:24px;text-align:left;margin-bottom:28px} | |
| .endpoints h2{font-size:1rem;margin-bottom:14px;color:#d4d4d4} | |
| .ep{display:flex;align-items:center;gap:10px;margin-bottom:8px;font-size:.85rem} | |
| .method{font-weight:700;font-size:.75rem;padding:2px 8px;border-radius:4px;min-width:48px;text-align:center} | |
| .get{background:#1e3a5f;color:#93c5fd}.post{background:#3b1f0b;color:#fdba74} | |
| .ws{background:#312e81;color:#c4b5fd} | |
| code{color:#a78bfa;font-size:.82rem} | |
| .try-it{margin-top:24px;font-size:.82rem;color:#737373} | |
| .try-it code{background:#262626;padding:3px 8px;border-radius:4px;font-size:.78rem} | |
| .footer{color:#525252;font-size:.78rem;margin-top:8px} | |
| .footer a{color:#60a5fa;text-decoration:none} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🛒 Shopify Store Audit</h1> | |
| <p class="subtitle">RL environment with real Shopify product data — 45 products, 184 discoverable issues, shaped rewards</p> | |
| <div class="cards"> | |
| <div class="card easy"><span class="tag">Easy · Full hints</span> | |
| <h3>Product Listing QA</h3> | |
| <p>8 issues · 25 steps<br>Issues listed with suggested commands. Agent fills in params.</p></div> | |
| <div class="card med"><span class="tag">Medium · Descriptions only</span> | |
| <h3>SEO & Collections</h3> | |
| <p>12 issues · 35 steps<br>Issue descriptions shown. Agent picks commands & params.</p></div> | |
| <div class="card hard"><span class="tag">Hard · Explore</span> | |
| <h3>Full Store Audit</h3> | |
| <p>20 issues · 50 steps<br>Only category counts. Agent must discover issues itself.</p></div> | |
| </div> | |
| <div class="endpoints"> | |
| <h2>API Endpoints</h2> | |
| <div class="ep"><span class="method get">GET</span><code>/health</code> — health check</div> | |
| <div class="ep"><span class="method get">GET</span><code>/tasks</code> — enumerate tasks & graders</div> | |
| <div class="ep"><span class="method post">POST</span><code>/reset</code> — reset (body: <code>{}</code>)</div> | |
| <div class="ep"><span class="method post">POST</span><code>/step</code> — action (body: <code>{"action":{"command":"...","params":{}}}</code>)</div> | |
| <div class="ep"><span class="method get">GET</span><code>/state</code> — current state</div> | |
| <div class="ep"><span class="method get">GET</span><code>/schema</code> — action/observation schemas</div> | |
| <div class="ep"><span class="method post">POST</span><code>/grade</code> — run grader on sample</div> | |
| <div class="ep"><span class="method ws">WS</span><code>/ws</code> — persistent WebSocket session</div> | |
| </div> | |
| <p class="footer">Real CSV data · 18 Shopify GraphQL commands · Shaped rewards · Regression penalties · Randomised episodes<br> | |
| <a href="https://huggingface.co/spaces/devaatmik/shopify-store-audit/blob/main/README.md">Full docs</a> · | |
| Built for <a href="https://huggingface.co/blog/openenv">OpenEnv</a></p> | |
| </div> | |
| </body> | |
| </html>""" | |
| async def list_tasks(): | |
| """Enumerate all tasks with their grader info. Used by competition validators.""" | |
| tasks = [] | |
| for task_id, cfg in ALL_TASKS.items(): | |
| tasks.append({ | |
| "id": cfg.task_id, | |
| "title": cfg.title, | |
| "description": cfg.description, | |
| "difficulty": cfg.difficulty, | |
| "max_steps": cfg.max_steps, | |
| "num_issues": cfg.num_issues, | |
| "hint_level": cfg.hint_level, | |
| "has_grader": task_id in GRADER_MAP, | |
| "grader": f"server.graders.grade_{task_id}", | |
| }) | |
| return {"tasks": tasks, "count": len(tasks)} | |
| async def grade_task(body: dict): | |
| """Run a grader on the provided observation/sample. Used by competition validators.""" | |
| task_id = body.get("task_id", body.get("task", "")) | |
| sample = body.get("sample", body.get("observation", body)) | |
| grader = GRADER_MAP.get(task_id) | |
| if grader is None: | |
| return JSONResponse( | |
| status_code=400, | |
| content={"error": f"Unknown task '{task_id}'. Available: {list(GRADER_MAP.keys())}"}, | |
| ) | |
| try: | |
| score = grader(sample) | |
| return {"task_id": task_id, "score": score, "valid": 0.0 <= score <= 1.0} | |
| except Exception as e: | |
| return JSONResponse(status_code=500, content={"error": str(e)}) | |
| async def root(): | |
| return LANDING_HTML | |
| def main(host: str = "0.0.0.0", port: int = 8000): | |
| import uvicorn | |
| uvicorn.run(app, host=host, port=port) | |
| if __name__ == "__main__": | |
| main() | |