--- title: Viraltest β€” Creator Optimization Agent emoji: πŸ“Š colorFrom: yellow colorTo: indigo sdk: docker pinned: false app_port: 8000 base_path: /web tags: - openenv --- # Viraltest β€” RL-Based Creator Optimization Environment An [OpenEnv](https://github.com/meta-pytorch/OpenEnv) environment that simulates a social media creator’s weekly posting lifecycle. An AI agent learns **when to post**, **what format**, **which tags**, and **how to differentiate from competitors** β€” maximizing engagement while managing burnout and sleep. ## Submission requirements β€” how this repo maps Use this table to confirm Phase 1 (automated) gates before you submit. | Requirement | Status in this repo | Where to verify | |---------------|---------------------|-----------------| | Real-world task (not a toy/game) | **Met** β€” creator scheduling, energy, trends, competitors | `server/viraltest_environment.py`, `DESIGN.md` | | Full OpenEnv spec: `openenv.yaml`, typed models, HTTP API | **Met** | `openenv.yaml`, `models.py`, `server/app.py` (`create_app`) | | `step()` / `reset()` / `state()` | **Met** β€” standard OpenEnv HTTP endpoints | Run `openenv validate` | | β‰₯3 tasks with graders (easy β†’ hard), scores in **0.0–1.0** | **Met** β€” `weekly_engage`, `weekly_strategic`, `weekly_competitive` | `_run_grader()` in `server/viraltest_environment.py` | | Meaningful reward + partial progress | **Met** β€” per-step `_compute_reward()` | `_compute_reward()` | | Baseline inference script, reproducible | **Met** β€” root `inference.py` | See **Baseline inference** below | | `Dockerfile` builds | **Expected** β€” root `Dockerfile` | `docker build -t viraltest .` (run locally) | | HF Space deploys; `POST /reset` returns **200** | **You must configure** | See **Hugging Face Spaces** β€” ping **Space root**, not only `/web` | | `openenv validate` passes | **Met** in dev (`.venv/bin/openenv validate`) | CI / local | | Env vars: `API_BASE_URL`, `MODEL_NAME`, `HF_TOKEN` | **Documented** β€” `inference.py` reads them (see **Environment variables**) | HF Space **Settings β†’ Secrets** | | `inference.py` at repo root; OpenAI client for LLM calls | **Met** | `inference.py` | | Structured stdout: `[START]`, `[STEP]`, `[END]` | **Met** β€” match field order in `log_*` helpers | `inference.py` | | Inference under 20 minutes; 2 vCPU / 8 GB | **Check** β€” 3 tasks Γ— up to 168 steps each = many LLM calls; use a fast endpoint and sensible `MAX_TOKENS` | `inference.py` | ### Minor items to double-check before judging 1. **`[STEP]` `error=` field** β€” The spec asks for the raw `last_action_error` or `null`. This repo logs errors with spaces replaced by underscores so each line stays a single token after `error=`. If the organizer’s parser expects literal spaces inside unquoted messages, align with their sample; otherwise this is fine for one-line logs. 2. **Default `API_BASE_URL` in `inference.py`** β€” Defaults are for local dev. On Hugging Face, set **`API_BASE_URL`** (e.g. `https://router.huggingface.co/v1`) and **`MODEL_NAME`** in Secrets so evaluation matches your setup. 3. **Space URL for the validator** β€” The official script POSTs to `{your_space_url}/reset` with body `{}`. That must be the **root** of the Space (e.g. `https://YOURNAME-spacename.hf.space`), not the Gradio path under `base_path: /web`. Confirm with curl (see **Pre-submission validation**). --- ## Why this matters Many creators burn out while optimizing posting times and formats. This environment turns that tradeoff into a reproducible simulation so agents can be trained and compared on the same weekly horizon (**168** hourly steps). --- ## Quick Start (Python) The HTTP client is **async** (same pattern as root `inference.py`): ```python import asyncio from viraltest import ViraltestAction, ViraltestEnv async def main(): env = ViraltestEnv(base_url="http://localhost:8000") try: result = await env.reset(task="weekly_engage") action = ViraltestAction( action_type="post", content_type="reel", topic="AI trends", tags=["ai", "coding", "devtools"], ) result = await env.step(action) print(result.observation.engagement_rate, result.observation.creator_energy) finally: await env.close() asyncio.run(main()) ``` --- ## Action space | Field | Type | Description | |-------|------|-------------| | `action_type` | `"post" \| "rest" \| "create_content"` | What the agent does this hour | | `content_type` | `"reel" \| "story" \| "carousel" \| "text_post"` | Required when posting | | `topic` | `str` (≀200 chars) | Post topic | | `tags` | `list[str]` (≀5) | Tags from the environment tag pool | --- ## Observation space (high level) | Field | Description | |-------|-------------| | `current_hour`, `day_of_week`, `days_elapsed` | Simulated calendar | | `creator_energy`, `hours_since_sleep`, `sleep_debt` | Burnout and sleep | | `follower_count`, `engagement_rate` | Growth and rolling engagement | | `trending_topics`, `trending_tags`, `tag_performance` | Trends and learned tag quality | | `competitor_recent_posts`, `competitor_avg_engagement`, `niche_saturation` | Competition | | `error`, `reward`, `done`, `metadata` | Errors, shaping reward, termination, **`metadata["grader_score"]` at episode end** | Full schema: `GET /schema` when the server is running. --- ## Tasks and graders (168 steps each) | Task | Difficulty | Grader focus | |------|------------|--------------| | `weekly_engage` | Easier | Total engagement vs theoretical max; burnout penalty | | `weekly_strategic` | Medium | Engagement + tag discovery/exploitation + energy + consistency | | `weekly_competitive` | Hard | Adds growth vs competitors, differentiation, diversity constraints | Episode ends after **168** steps or if **energy ≀ 0**. Final normalized score is in **`observation.metadata["grader_score"]`** in **\[0, 1\]**. --- ## Reward shaping Per-step reward in **`[0, 1]`** combines engagement, energy change, posting consistency, tags, and competitor differentiation (`_compute_reward` in `server/viraltest_environment.py`). It is dense enough for learning signals before the terminal grader runs. --- ## Local development ```bash git clone cd viral-posts-env # or your fork name # Install (uv recommended; pip works too) uv sync # source .venv/bin/activate # optional # Terminal 1 β€” API server uvicorn viraltest.server.app:app --host 0.0.0.0 --port 8000 # Terminal 2 β€” optional UI # Open http://localhost:8000/dashboard (see server routes in server/app.py) ``` Validate the OpenEnv layout: ```bash .venv/bin/openenv validate # Expect: [OK] ... Ready for multi-mode deployment ``` --- ## Docker From the repository root (same directory as `Dockerfile`): ```bash docker build -t viraltest-env:latest . docker run --rm -p 8000:8000 viraltest-env:latest ``` Smoke test: ```bash curl -s -o /dev/null -w "%{http_code}" -X POST -H "Content-Type: application/json" -d '{}' http://localhost:8000/reset # Expect: 200 ``` --- ## Hugging Face Spaces β€” deploy 1. **Create a Space** with **Docker** SDK (this repo’s README frontmatter uses `sdk: docker`). 2. **Push this repository** (or connect GitHub) so the Space builds from the root `Dockerfile`. 3. **Settings β†’ Variables and secrets** β€” add at least: - **`HF_TOKEN`** β€” Hugging Face API token for inference (and Space pull if private). - **`API_BASE_URL`** β€” OpenAI-compatible base URL (e.g. `https://router.huggingface.co/v1`). - **`MODEL_NAME`** β€” Model id for that router (e.g. `Qwen/Qwen2.5-72B-Instruct`). 4. **App port** β€” `8000` (see frontmatter `app_port: 8000`). 5. **`base_path: /web`** β€” Used for the bundled web UI; the **REST** endpoints (`/reset`, `/step`, `/state`) remain on the **Space root host** as required by the submission validator. **Always test** `https://.hf.space/reset` (not only `/web/...`). Optional CLI (if you use OpenEnv’s tooling): ```bash pip install openenv-core openenv push # follow OpenEnv docs for auth and target Space ``` --- ## Baseline inference (`inference.py`) **Location:** repository root β€” **`inference.py`** (required by the hackathon). **LLM client:** OpenAI-compatible client (`from openai import OpenAI`) using: | Variable | Role | |----------|------| | `API_BASE_URL` | OpenAI-compatible API base | | `MODEL_NAME` | Model name for `chat.completions` | | `HF_TOKEN` | Preferred API key (fallbacks: `OPENAI_API_KEY`, `API_KEY`) | | `IMAGE_NAME` / `LOCAL_IMAGE_NAME` | If using `ViraltestEnv.from_docker_image(...)` instead of HTTP | | `ENV_BASE_URL` | HTTP server URL (default `http://localhost:8000`) | **Stdout format (must not change field names or order):** ```text [START] task= env= model= [STEP] step= action= reward=<0.00> done= error= [END] success= steps= score=<0.00> rewards= ``` Run locally (server on port 8000): ```bash export HF_TOKEN=hf_... export API_BASE_URL=https://router.huggingface.co/v1 export MODEL_NAME=Qwen/Qwen2.5-72B-Instruct uv sync && .venv/bin/python inference.py ``` **Short episodes for debugging** β€” `ALLOW_SHORT_EPISODE=1` and `MAX_STEPS` can shorten runs; full weekly tasks still use **168** steps unless you override (see comments in `inference.py`). --- ## Pre-submission validation Use the provided script (same checks as the official template: ping Space, Docker build, `openenv validate`): ```bash chmod +x validate-submission.sh ./validate-submission.sh https://YOUR-SPACE.hf.space /path/to/viral-posts-env ``` Or download the organizer’s script from their repo and pass your Space URL. **Manual ping (required to pass automated gate):** ```bash curl -s -o /dev/null -w "%{http_code}\n" -X POST \ -H "Content-Type: application/json" -d '{}' \ https://YOUR-SPACE.hf.space/reset # Must print: 200 ``` --- ## Baseline scores (reference) Deterministic dashboard agents (not the LLM) β€” see `README` tables in-repo history / `DESIGN.md` for methodology. Your **`inference.py`** scores will vary by model and endpoint; keep runs under the **20-minute** inference budget. --- ## Project structure ``` . β”œβ”€β”€ inference.py # Hackathon-required baseline (LLM + [START]/[STEP]/[END]) β”œβ”€β”€ openenv.yaml # OpenEnv manifest β”œβ”€β”€ models.py # ViraltestAction, ViraltestObservation β”œβ”€β”€ client.py # ViraltestEnv client β”œβ”€β”€ Dockerfile β”œβ”€β”€ validate-submission.sh # Local preflight β”œβ”€β”€ test_scenarios.py # Offline env tests β”œβ”€β”€ DESIGN.md # Deep design / research notes └── server/ β”œβ”€β”€ app.py # FastAPI + create_app β”œβ”€β”€ viraltest_environment.py └── dashboard.html ``` --- ## License See `LICENSE` in the repository root (BSD-style per upstream OpenEnv examples).