File size: 8,537 Bytes
f145e2d
 
d742a8e
f145e2d
d742a8e
 
 
f145e2d
 
 
 
d742a8e
f145e2d
d742a8e
 
 
f145e2d
 
d742a8e
f145e2d
 
 
 
 
 
 
 
 
 
 
d742a8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f145e2d
 
d742a8e
 
 
 
 
 
 
 
 
 
 
f145e2d
d742a8e
 
 
 
 
 
 
 
 
 
f145e2d
 
d742a8e
f145e2d
 
 
 
 
 
 
d742a8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f145e2d
 
d742a8e
 
 
 
 
f145e2d
 
 
 
 
d742a8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f145e2d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d742a8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f145e2d
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#!/usr/bin/env python3
"""
Main entry point for the agent server and evaluation.

This script provides multiple commands:
- server: Creates an agent implementation and starts the RPC server
- eval: Runs local evaluation of the agent with visual rendering
"""

import argparse
import logging
import subprocess
import sys
import threading
import time
import webbrowser

from agent import RLAgent
from evaluation import AgentEvaluator


def setup_logging(level=logging.INFO):
    """Configure logging."""
    logging.basicConfig(
        level=level,
        format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
        handlers=[logging.StreamHandler(sys.stdout)],
    )


def launch_tensorboard(log_dir, port=6006):
    """Launch TensorBoard in a separate thread."""

    def run_tensorboard():
        try:
            # Wait a moment for initial logs to be written
            time.sleep(2)

            # Launch TensorBoard
            subprocess.run(
                [
                    "tensorboard",
                    "--logdir",
                    log_dir,
                    "--port",
                    str(port),
                    "--host",
                    "localhost",
                    "--reload_interval",
                    "1",
                ],
                check=True,
                capture_output=True,
            )
        except subprocess.CalledProcessError:
            # TensorBoard failed to start, but don't crash the evaluation
            pass
        except FileNotFoundError:
            # TensorBoard not installed
            pass

    # Start TensorBoard in background thread
    tb_thread = threading.Thread(target=run_tensorboard, daemon=True)
    tb_thread.start()

    # Give TensorBoard a moment to start
    time.sleep(3)

    # Try to open browser
    try:
        webbrowser.open(f"http://localhost:{port}")
    except Exception:
        # Browser opening failed, but that's okay
        pass

    return f"http://localhost:{port}"


def main():
    """Main entry point."""
    parser = argparse.ArgumentParser(
        description="Agent server and evaluation tool",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python main.py server --host localhost --port 8000
  python main.py eval --task reach-v3 --episodes 5
  python main.py eval --task push-v3 --episodes 10 --render-mode rgb_array
  python main.py eval --task reach-v3 --episodes 20 --no-tensorboard
  python main.py eval --task door-open-v3 --log-dir custom_logs/
        """,
    )

    # Add subcommands
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # Server subcommand
    server_parser = subparsers.add_parser("server", help="Start the agent server")
    server_parser.add_argument(
        "--host", type=str, default="0.0.0.0", help="Host to bind the server to"
    )
    server_parser.add_argument(
        "--port", type=int, default=8000, help="Port to bind the server to"
    )
    server_parser.add_argument(
        "--log-level",
        type=str,
        default="INFO",
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
        help="Logging level",
    )

    # Evaluation subcommand
    eval_parser = subparsers.add_parser("eval", help="Run local agent evaluation")
    eval_parser.add_argument(
        "--task",
        type=str,
        default="reach-v3",
        help="MetaWorld task name (default: reach-v3)",
    )
    eval_parser.add_argument(
        "--episodes",
        type=int,
        default=5,
        help="Number of episodes to run (default: 5)",
    )
    eval_parser.add_argument(
        "--steps",
        type=int,
        default=200,
        help="Maximum steps per episode (default: 200)",
    )
    eval_parser.add_argument(
        "--seed",
        type=int,
        default=None,
        help="Random seed for reproducibility",
    )
    eval_parser.add_argument(
        "--render-mode",
        type=str,
        default="human",
        choices=["human", "rgb_array"],
        help="Rendering mode (default: human)",
    )
    eval_parser.add_argument(
        "--log-level",
        type=str,
        default="INFO",
        choices=["DEBUG", "INFO", "WARNING", "ERROR"],
        help="Logging level (default: INFO)",
    )
    eval_parser.add_argument(
        "--list-tasks",
        action="store_true",
        help="List available MetaWorld tasks and exit",
    )
    eval_parser.add_argument(
        "--tensorboard",
        action="store_true",
        default=True,
        help="Enable TensorBoard logging (default: True)",
    )
    eval_parser.add_argument(
        "--no-tensorboard",
        action="store_true",
        help="Disable TensorBoard logging",
    )
    eval_parser.add_argument(
        "--log-dir",
        type=str,
        default=None,
        help="TensorBoard log directory (auto-generated if not specified)",
    )

    args = parser.parse_args()

    # If no command is provided, show help
    if not args.command:
        parser.print_help()
        sys.exit(1)

    # Setup logging
    log_level = getattr(logging, args.log_level)
    setup_logging(log_level)
    logger = logging.getLogger(__name__)

    if args.command == "server":
        run_server(args, logger)
    elif args.command == "eval":
        run_evaluation(args, logger)


def run_server(args, logger):
    """Run the agent server."""
    # Import server functionality only when needed to avoid capnp dependency for eval
    try:
        from agent_server import start_server
    except ImportError as e:
        logger.error(f"Failed to import server functionality: {e}")
        logger.error("Make sure capnp and other server dependencies are installed")
        sys.exit(1)

    logger.info(f"Starting agent server on {args.host}:{args.port}")

    # Create the RLAgent
    agent = RLAgent()

    # Start the server
    try:
        start_server(agent, args.host, args.port)
    except KeyboardInterrupt:
        logger.info("Server stopped by user")
    except Exception as e:
        logger.error(f"Error starting server: {e}", exc_info=True)
        sys.exit(1)


def run_evaluation(args, logger):
    """Run local agent evaluation."""
    logger.info("Running local evaluation")

    # Determine TensorBoard usage
    use_tensorboard = args.tensorboard and not args.no_tensorboard

    # Setup log directory if using TensorBoard
    log_dir = args.log_dir
    if use_tensorboard and not log_dir:
        from datetime import datetime

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        log_dir = f"runs/{args.task}_{timestamp}"

    # Create evaluator
    evaluator = AgentEvaluator(
        task_name=args.task,
        render_mode=args.render_mode,
        max_episodes=args.episodes,
        max_steps_per_episode=args.steps,
        seed=args.seed,
        use_tensorboard=use_tensorboard,
        log_dir=log_dir,
    )

    if args.list_tasks:
        evaluator.list_available_tasks()
        return

    # Launch TensorBoard if enabled
    tensorboard_url = None
    if use_tensorboard and log_dir:
        logger.info("Starting TensorBoard...")
        try:
            tensorboard_url = launch_tensorboard(log_dir)
            logger.info(f"TensorBoard available at: {tensorboard_url}")
            logger.info("TensorBoard will show metrics in real-time during evaluation")
        except Exception as e:
            logger.warning(f"Failed to start TensorBoard: {e}")
            logger.info("Continuing evaluation without TensorBoard...")

    try:
        evaluator.run_evaluation()
        logger.info("Evaluation completed successfully")

        if tensorboard_url:
            logger.info(f"View detailed metrics at: {tensorboard_url}")
            logger.info("TensorBoard will continue running in the background")

        # Optionally save results to file
        # import json
        # with open("evaluation_results.json", "w") as f:
        #     json.dump(results, f, indent=2)
        # logger.info("Results saved to evaluation_results.json")

    except KeyboardInterrupt:
        logger.info("Evaluation stopped by user")
        if tensorboard_url:
            logger.info(f"TensorBoard still available at: {tensorboard_url}")
    except Exception as e:
        logger.error(f"Error during evaluation: {e}", exc_info=True)
        if tensorboard_url:
            logger.info(f"TensorBoard still available at: {tensorboard_url}")
        sys.exit(1)


if __name__ == "__main__":
    main()