Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Wikipedia Speedrun CLI - Play the Wikipedia game with any agent. | |
| Usage: | |
| python scripts/play.py --start "Python (programming language)" --target "Philosophy" | |
| python scripts/play.py --start "Cat" --target "Dog" --agent precomputed | |
| python scripts/play.py --start "Albert Einstein" --target "Pizza" --agent human --visualize | |
| python scripts/play.py --start "Random" --target "Random" --agent live | |
| python scripts/play.py --start "Potato" --target "Barack Obama" --agent llm --model google/gemini-2.0-flash-exp:free | |
| Agents: | |
| human - You play! Pick links manually (requires --visualize) | |
| random - Random baseline | |
| precomputed - Greedy using pre-computed title embeddings | |
| live - Greedy using on-the-fly sentence-transformers | |
| hybrid - Pre-computed filter + live re-rank | |
| oracle - BFS optimal path (uses pre-computed graph) | |
| live-oracle - BFS optimal path (live Wikipedia scraping, slow but complete) | |
| llm - LLM via OpenRouter (use --model to specify) | |
| LLM Models: | |
| Use any OpenRouter model ID directly (e.g., "anthropic/claude-haiku-4.5"). | |
| Full model list with pricing: data/openrouter_models.json | |
| Analyze models: python scripts/analyze_models.py | |
| Examples: | |
| google/gemini-2.0-flash-exp:free - FREE Google model (default) | |
| meta-llama/llama-3.3-70b-instruct:free - FREE Llama model | |
| openai/gpt-4o-mini - Budget ($0.15/1M input) | |
| anthropic/claude-haiku-4.5 - Fast ($0.80/1M input) | |
| deepseek/deepseek-chat - Value ($0.14/1M input) | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import logging | |
| import os | |
| import random | |
| import sys | |
| import warnings | |
| from pathlib import Path | |
| # Suppress TensorFlow and protobuf warnings before any imports | |
| os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" # Suppress TF logging | |
| os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0" # Suppress oneDNN messages | |
| warnings.filterwarnings("ignore", category=UserWarning, module="google.protobuf") | |
| warnings.filterwarnings("ignore", category=DeprecationWarning) | |
| # Fix Windows console encoding | |
| if sys.platform == "win32": | |
| sys.stdout.reconfigure(encoding="utf-8", errors="replace") | |
| # Add project root to path | |
| project_root = Path(__file__).parent.parent | |
| sys.path.insert(0, str(project_root)) | |
| from src.agents import get_agent # noqa: E402 | |
| from src.game import GameEngine # noqa: E402 | |
| def get_random_article() -> str: | |
| """Get a random traversable article from the dataset.""" | |
| from src.data.loader import wiki_data | |
| # Get a random article that has outgoing links | |
| while True: | |
| idx = random.randint(0, wiki_data.article_count() - 1) | |
| title = wiki_data.get_title(idx) | |
| if wiki_data.is_traversable(title): | |
| return title | |
| def parse_args() -> argparse.Namespace: | |
| """Parse command line arguments.""" | |
| parser = argparse.ArgumentParser( | |
| description="Play the Wikipedia Speedrun game", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=__doc__, | |
| ) | |
| parser.add_argument( | |
| "--start", | |
| type=str, | |
| required=True, | |
| help="Starting article title (or 'Random' for random)", | |
| ) | |
| parser.add_argument( | |
| "--target", | |
| type=str, | |
| required=True, | |
| help="Target article title (or 'Random' for random)", | |
| ) | |
| parser.add_argument( | |
| "--agent", | |
| type=str, | |
| default="precomputed", | |
| choices=["human", "random", "precomputed", "live", "hybrid", "oracle", "live-oracle", "llm"], | |
| help="Agent to use (default: precomputed)", | |
| ) | |
| parser.add_argument( | |
| "--model", | |
| type=str, | |
| default="gemini-2.0-flash-exp:free", | |
| help="LLM model for --agent llm (default: gemini-2.0-flash-exp:free)", | |
| ) | |
| parser.add_argument( | |
| "--prefilter", | |
| type=int, | |
| default=None, | |
| help="Pre-filter links to top N using embeddings (default: no filter)", | |
| ) | |
| parser.add_argument( | |
| "--visualize", | |
| action="store_true", | |
| help="Show browser visualization with Playwright", | |
| ) | |
| parser.add_argument( | |
| "--max-steps", | |
| type=int, | |
| default=50, | |
| help="Maximum steps before game is lost (default: 50)", | |
| ) | |
| parser.add_argument( | |
| "--slow-mo", | |
| type=int, | |
| default=0, | |
| help="Slow down visualization by this many ms per action", | |
| ) | |
| parser.add_argument( | |
| "--verbose", | |
| "-v", | |
| action="store_true", | |
| help="Enable verbose logging", | |
| ) | |
| return parser.parse_args() | |
| def main() -> int: | |
| """Main entry point.""" | |
| args = parse_args() | |
| # Set up logging | |
| log_level = logging.DEBUG if args.verbose else logging.INFO | |
| logging.basicConfig( | |
| level=log_level, | |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", | |
| datefmt="%H:%M:%S", | |
| ) | |
| # Handle random article selection | |
| start = args.start | |
| target = args.target | |
| if start.lower() == "random": | |
| print("Selecting random start article...") | |
| start = get_random_article() | |
| print(f" Start: {start}") | |
| if target.lower() == "random": | |
| print("Selecting random target article...") | |
| target = get_random_article() | |
| # Make sure it's different from start | |
| while target == start: | |
| target = get_random_article() | |
| print(f" Target: {target}") | |
| # Get agent | |
| try: | |
| agent_kwargs = {} | |
| if args.agent == "llm": | |
| agent_kwargs["model"] = args.model | |
| if args.prefilter: | |
| agent_kwargs["prefilter"] = args.prefilter | |
| agent = get_agent(args.agent, **agent_kwargs) | |
| except ValueError as e: | |
| print(f"Error: {e}", file=sys.stderr) | |
| return 1 | |
| # Pre-warm the agent (load models, data) before starting the game timer | |
| print("Warming up agent...") | |
| if hasattr(agent, "_ensure_loaded"): | |
| agent._ensure_loaded() | |
| print("Ready!") | |
| print("\n" + "=" * 60) | |
| print("Wikipedia Speedrun") | |
| print("=" * 60) | |
| print(f" Start: {start}") | |
| print(f" Target: {target}") | |
| print(f" Agent: {agent.name} - {agent.description}") | |
| print(f" Visualize: {args.visualize or agent.requires_visualization}") | |
| print("=" * 60 + "\n") | |
| # Create engine and run game | |
| visualize = args.visualize or agent.requires_visualization | |
| with GameEngine(visualize=visualize, slow_mo=args.slow_mo) as engine: | |
| try: | |
| result = engine.run( | |
| agent=agent, | |
| start=start, | |
| target=target, | |
| max_steps=args.max_steps, | |
| ) | |
| except KeyboardInterrupt: | |
| print("\n\nGame interrupted by user") | |
| return 130 # Standard exit code for Ctrl+C | |
| # Print results | |
| print("\n" + "=" * 60) | |
| if result.won: | |
| print(f"Victory! Reached '{result.target_title}' in {result.total_clicks} clicks") | |
| else: | |
| print(f"Game Over. Did not reach '{result.target_title}'") | |
| print("=" * 60) | |
| print("\nPath taken:") | |
| for i, title in enumerate(result.path): | |
| marker = " (START)" if i == 0 else " (TARGET)" if title == result.target_title else "" | |
| print(f" {i}. {title}{marker}") | |
| print(f"\nTotal time: {result.total_time_ms / 1000:.2f} seconds") | |
| print(f"Average decision time: {sum(s.decision_time_ms for s in result.steps) / len(result.steps):.0f}ms" if result.steps else "") | |
| # Print LLM stats if applicable | |
| if hasattr(agent, "get_stats"): | |
| stats = agent.get_stats() | |
| if stats.get("total_requests", 0) > 0: | |
| print(f"\nLLM Stats:") | |
| print(f" Model: {stats['model']}") | |
| print(f" Requests: {stats['total_requests']}") | |
| print(f" Total tokens: {stats['total_tokens']}") | |
| print(f" API time: {stats['total_time_seconds']}s") | |
| return 0 if result.won else 1 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |