Agentic-zork / app.py
nathanael-fijalkow's picture
Fix columns vs rows in submission dataset
059c848
"""
Gradio App - Text Adventure AI Agent Assignment
A simple interface for the text adventure AI agent assignment.
"""
import gradio as gr
from huggingface_hub import HfApi
from datetime import datetime
import json
TITLE = "Playing Zork has never been so boring"
DESCRIPTION = """
Build AI agents to play classic text adventure games (Zork, Colossal Cave, Enchanter, etc.) using the Model Context Protocol (MCP) and HuggingFace models.
This project provides:
- **MCP Server** - Exposes text adventure games as MCP tools using FastMCP
- **ReAct Agent** - An agent that uses MCP tools to play games with reasoning
- **Submission Template** - Starter code for students to implement their own solutions
- **Evaluation System** - Deterministic evaluation with seeded runs
- **57 Games** - Zork trilogy, Infocom classics, and many more Z-machine games
"""
CLONE_INSTRUCTIONS = """
## Getting Started
### 0. Clone this space
```bash
git clone https://huggingface.co/spaces/LLM-course/Agentic-zork
```
This includes:
- run_agent.py: Script to run agents on text adventure games
- evaluation/: Evaluation scripts and utilities
- games/: Text adventure game environments
- submission_template/: Template for your agent submission
### 1. Fork the template space
Fork the template space on Hugging Face:
```
https://huggingface.co/spaces/LLM-course/text-adventure-template
```
### 2. Clone your fork locally
```bash
git clone https://huggingface.co/spaces/YOUR_USERNAME/text-adventure-agent
```
### 3. Implement your agent
Edit these files:
- `agent.py` - Your ReAct agent implementation (implement the `StudentAgent` class)
- `mcp_server.py` - Your MCP server implementation (add tools like `play_action`, `memory`, etc.)
### 4. Test locally
```bash
# Test MCP server interactively
fastmcp dev mcp_server.py
# Run your agent
python run_agent.py --agent . --game lostpig -v -n 20
```
### 5. Push to your space
### 6. Submit your space URL
"""
DATASET_REPO = "LLM-course/zork-submission"
SUBMISSIONS_FILE_JSONL = "submissions.jsonl"
SUBMISSIONS_FILE_JSON = "submissions.json"
def submit_space(space_url: str, profile: gr.OAuthProfile | None) -> str:
"""Submit a space URL to the dataset."""
if profile is None:
return "Please log in with your HuggingFace account first (button above)."
if not space_url or not space_url.strip():
return "Please enter your Space URL."
space_url = space_url.strip()
# Validate URL format
if not ("huggingface.co/spaces/" in space_url or "hf.co/spaces/" in space_url):
return "Invalid Space URL. It should look like: https://huggingface.co/spaces/username/space-name"
username = profile.username
try:
api = HfApi()
# Try to load existing submissions from JSONL first
try:
submissions_path = api.hf_hub_download(
repo_id=DATASET_REPO,
filename=SUBMISSIONS_FILE_JSONL,
repo_type="dataset",
)
with open(submissions_path, "r") as f:
submissions = [
json.loads(line)
for line in f
if line.strip()
]
except Exception:
# Fallback for backward compatibility with old JSON format
try:
submissions_path = api.hf_hub_download(
repo_id=DATASET_REPO,
filename=SUBMISSIONS_FILE_JSON,
repo_type="dataset",
)
with open(submissions_path, "r") as f:
submissions = json.load(f)
except Exception:
submissions = []
# Backward compatibility: migrate old dict format to
# row-based list format
if isinstance(submissions, dict):
submissions = [
{
"username": user,
"space_url": data.get("space_url", ""),
"submitted_at": data.get("submitted_at", ""),
}
for user, data in submissions.items()
]
elif not isinstance(submissions, list):
submissions = []
# Update with new submission (overwrites previous for same user)
new_submission = {
"username": username,
"space_url": space_url,
"submitted_at": datetime.now().isoformat(),
}
existing_index = next(
(
index
for index, entry in enumerate(submissions)
if entry.get("username") == username
),
None,
)
if existing_index is not None:
submissions[existing_index] = new_submission
else:
submissions.append(new_submission)
# Save back to dataset as JSONL (one JSON object per line)
submissions_jsonl = "\n".join(
json.dumps(entry, ensure_ascii=False)
for entry in submissions
) + "\n"
api.upload_file(
path_or_fileobj=submissions_jsonl.encode(),
path_in_repo=SUBMISSIONS_FILE_JSONL,
repo_id=DATASET_REPO,
repo_type="dataset",
commit_message=f"Update submission for {username}",
)
return f"Submission successful! Space URL '{space_url}' recorded for user '{username}'."
except Exception as e:
return f"Error submitting: {str(e)}"
demo = gr.Blocks(title=TITLE)
with demo:
gr.Markdown(f"# {TITLE}")
gr.Markdown(DESCRIPTION)
gr.Markdown("---")
gr.Markdown(CLONE_INSTRUCTIONS)
# Submission section
gr.LoginButton()
space_input = gr.Textbox(
label="Your Space URL",
placeholder="https://huggingface.co/spaces/your-username/text-adventure-agent",
)
submit_btn = gr.Button("Click HERE to Submit", variant="primary")
result_text = gr.Textbox(label="Status", interactive=False)
submit_btn.click(
fn=submit_space,
inputs=[space_input],
outputs=[result_text],
)
if __name__ == "__main__":
demo.launch()