salmankhanpm/mobile-actions-env
mobile-actions-env
An OpenEnv-compatible reinforcement-learning environment for LLM tool-calling on mobile devices — built on google/mobile-actions.
Description & Motivation
Modern LLMs are increasingly deployed as mobile assistants that must select and invoke the right API (calendar, maps, email, music) from natural-language user requests. However, there is no standardised RL training loop for this setting.
mobile-actions-env closes that gap: it wraps the Google Mobile-Actions dataset inside an OpenEnv-compliant HTTP server so any agent framework can interact through the canonical reset() / step() / state() loop, receiving dense graded rewards per tool call.
Key design goals:
- Graded, dense rewards — not just binary success/fail; partial credit for partially correct tool calls encourages learning even from imperfect actions.
- Multiple task types — five distinct mobile-action categories, each with different tool schemas.
- Multi-turn support — the
multi_turntask requires chaining two sequential tool calls correctly. - Zero external dependencies at runtime — all episodes are embedded in the server; no HF download required to start.
Action Space
Every action is a JSON object with exactly two fields:
{ "name": "<tool_name>", "arguments": { "<key>": "<value>", ... } }Available tools depend on the active episode and are returned in available_tools on each reset() / step().
Example tools across tasks:
Observation Space
Each reset() and step() response includes an ObservationResponse:
{
"messages": [
{ "role": "developer", "content": "You are a mobile assistant." },
{ "role": "user", "content": "Book a dentist appointment for Friday 23rd at 10am." }
],
"available_tools": [
{
"type": "function",
"function": {
"name": "create_calendar_event",
"description": "Creates a calendar event.",
"parameters": {
"type": "OBJECT",
"properties": {
"title": { "type": "STRING" },
"datetime": { "type": "STRING" }
},
"required": ["title", "datetime"]
}
}
}
],
"turn_index": 0,
"metadata": "calendar",
"episode_id": 1
}Reward Semantics
Rewards are issued per step in the range [-0.5, 1.0]:
Tasks
1. calendar_scheduling — 🟢 Easy
Agent receives a natural-language scheduling request and must call create_calendar_event with correct title and datetime fields. Max steps: 5 · Episodes: 2
2. map_navigation — 🟢 Easy
Agent receives a location-lookup request and must call show_map with a relevant query. Max steps: 5 · Episodes: 2
3. email_communication — 🟡 Medium
Agent must call send_email with correct recipient (to) and subject. Body is optional but checked for value matching. Max steps: 5 · Episodes: 2
4. media_control — 🟡 Medium
Agent must choose between play_music (needs query) or set_alarm (needs time) based on the user request. Max steps: 5 · Episodes: 2
5. multi_turn — 🔴 Hard
Agent handles a two-turn conversation requiring two sequential tool calls in order: create_calendar_event then send_email. Both turns must be correct. Max steps: 10 · Episodes: 1
Baseline Scores
Measured using python inference.py --dry-run (random agent) and HF_TOKEN=... python inference.py (Qwen2.5-72B-Instruct via HF Inference Providers):
Score formula per episode:
score = clamp((mean_step_reward - (-0.5)) / (1.0 - (-0.5)), 0, 1)Project Structure
.
├── openenv.yaml # OpenEnv spec manifest
├── Dockerfile # Container definition (docker build + run)
├── requirements.txt # Python dependencies
├── server.py # FastAPI: /reset /step /state /health /tasks
├── inference.py # Agent inference script (OpenAI client)
├── test_server.py # Integration test suite (26 tests, all pass)
├── validate-submission.sh # Pre-submission validator
└── graders/
├── __init__.py # Shared utilities (_norm, name_match, arg_present)
├── calendar_grader.py
├── maps_grader.py
├── email_grader.py
├── media_grader.py
└── multiturn_grader.pySetup & Usage
Local (no Docker)
pip install -r requirements.txt
# Start the environment server
uvicorn server:app --host 0.0.0.0 --port 7860 --reload
# Verify it's healthy
curl http://localhost:7860/healthDocker
# Build
docker build -t openenv-mobile-actions .
# Run
docker run -p 7860:7860 openenv-mobile-actions
# Run with LLM credentials
docker run -p 7860:7860 \
-e HF_TOKEN=$HF_TOKEN \
-e MODEL_NAME=Qwen/Qwen2.5-72B-Instruct \
openenv-mobile-actionsRun the test suite
# Server must be running on :7860
pytest test_server.py -v # 26 testsRun the inference agent
# Dry-run (random agent, no API key needed)
python inference.py --dry-run
# Real LLM — all tasks
export HF_TOKEN=hf_...
export MODEL_NAME=Qwen/Qwen2.5-72B-Instruct
python inference.py
# Single task, 3 episodes
python inference.py --task calendar_scheduling --episodes 3Pre-submission validation
# Requires your deployed HF Space URL
./validate-submission.sh https://your-space.hf.space .API Reference
POST /reset
Start a new episode. Optionally filter by task or pin to a specific episode.
Request:
{ "task": "calendar_scheduling", "seed": 42, "episode_index": null }Response: ObservationResponse (see Observation Space above)
POST /step
Submit a tool-call action. Returns the next observation, reward, done flag, and debug info.
Request:
{ "action": { "name": "create_calendar_event", "arguments": { "title": "Meeting", "datetime": "2024-08-20T14:00:00" } } }Response:
{
"observation": { "messages": [...], "available_tools": [...], "turn_index": 1, ... },
"reward": 0.6,
"done": true,
"info": { "reason": "args_present_partial_values", "pred_name": "create_calendar_event", "gt_name": "create_calendar_event", "value_fraction": 0.5, "cumulative_reward": 0.6 }
}GET /state
Read-only snapshot of the current episode state.
Response:
{
"episode_id": 1, "task": "calendar_scheduling", "turn_index": 0,
"max_turns": 10, "done": false, "cumulative_reward": 0.0,
"available_tools": ["create_calendar_event"], "pending_gt_calls": 1,
"metadata": "calendar", "elapsed_ms": 12.3
}GET /health
Returns {"status": "ok", "version": "1.0.0"}.
POST /reset/{task}
Shorthand to reset directly to a specific task: POST /reset/map_navigation
GET /tasks
Returns all task IDs with episode counts.
