synth-ai 0.2.0__py3-none-any.whl ā 0.2.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- synth_ai/__init__.py +28 -2
- synth_ai/core/system.py +4 -0
- synth_ai/environments/__init__.py +35 -0
- synth_ai/environments/environment/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/base.py +50 -0
- synth_ai/environments/environment/core.py +22 -0
- synth_ai/environments/environment/db/__init__.py +1 -0
- synth_ai/environments/environment/db/sqlite.py +45 -0
- synth_ai/environments/environment/registry.py +24 -0
- synth_ai/environments/environment/resources/sqlite.py +46 -0
- synth_ai/environments/environment/results.py +1 -0
- synth_ai/environments/environment/rewards/__init__.py +1 -0
- synth_ai/environments/environment/rewards/core.py +28 -0
- synth_ai/environments/environment/shared_engine.py +26 -0
- synth_ai/environments/environment/tools/__init__.py +34 -0
- synth_ai/environments/examples/__init__.py +1 -0
- synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
- synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
- synth_ai/environments/examples/crafter_classic/engine.py +502 -0
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
- synth_ai/environments/examples/crafter_classic/environment.py +255 -0
- synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
- synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
- synth_ai/environments/examples/enron/engine.py +291 -0
- synth_ai/environments/examples/enron/environment.py +165 -0
- synth_ai/environments/examples/enron/taskset.py +112 -0
- synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
- synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
- synth_ai/environments/examples/minigrid/__init__.py +48 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
- synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
- synth_ai/environments/examples/minigrid/engine.py +589 -0
- synth_ai/environments/examples/minigrid/environment.py +274 -0
- synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
- synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
- synth_ai/environments/examples/minigrid/taskset.py +583 -0
- synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
- synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
- synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
- synth_ai/environments/examples/nethack/__init__.py +7 -0
- synth_ai/environments/examples/nethack/achievements.py +337 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
- synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
- synth_ai/environments/examples/nethack/engine.py +738 -0
- synth_ai/environments/examples/nethack/environment.py +255 -0
- synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
- synth_ai/environments/examples/nethack/taskset.py +323 -0
- synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
- synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
- synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
- synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
- synth_ai/environments/examples/red/__init__.py +7 -0
- synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
- synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
- synth_ai/environments/examples/red/config_logging.py +110 -0
- synth_ai/environments/examples/red/engine.py +693 -0
- synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
- synth_ai/environments/examples/red/environment.py +235 -0
- synth_ai/environments/examples/red/taskset.py +77 -0
- synth_ai/environments/examples/red/test_fixes.py +125 -0
- synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
- synth_ai/environments/examples/red/units/__init__.py +1 -0
- synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
- synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
- synth_ai/environments/examples/red/units/test_engine.py +192 -0
- synth_ai/environments/examples/red/units/test_environment.py +455 -0
- synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
- synth_ai/environments/examples/red/units/test_integration.py +217 -0
- synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
- synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
- synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
- synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
- synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
- synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
- synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
- synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
- synth_ai/environments/examples/red/units/test_taskset.py +116 -0
- synth_ai/environments/examples/red/units/test_tree.py +448 -0
- synth_ai/environments/examples/sokoban/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
- synth_ai/environments/examples/sokoban/engine.py +675 -0
- synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
- synth_ai/environments/examples/sokoban/environment.py +228 -0
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
- synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
- synth_ai/environments/examples/sokoban/taskset.py +425 -0
- synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
- synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
- synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
- synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
- synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
- synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
- synth_ai/environments/examples/tictactoe/__init__.py +1 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
- synth_ai/environments/examples/tictactoe/engine.py +368 -0
- synth_ai/environments/examples/tictactoe/environment.py +239 -0
- synth_ai/environments/examples/tictactoe/taskset.py +214 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
- synth_ai/environments/examples/verilog/__init__.py +10 -0
- synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
- synth_ai/environments/examples/verilog/engine.py +328 -0
- synth_ai/environments/examples/verilog/environment.py +349 -0
- synth_ai/environments/examples/verilog/taskset.py +418 -0
- synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
- synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
- synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
- synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
- synth_ai/environments/reproducibility/core.py +42 -0
- synth_ai/environments/reproducibility/tree.py +364 -0
- synth_ai/environments/service/app.py +78 -0
- synth_ai/environments/service/core_routes.py +775 -0
- synth_ai/environments/service/external_registry.py +57 -0
- synth_ai/environments/service/registry.py +9 -0
- synth_ai/environments/stateful/__init__.py +1 -0
- synth_ai/environments/stateful/core.py +28 -0
- synth_ai/environments/stateful/engine.py +21 -0
- synth_ai/environments/stateful/state.py +7 -0
- synth_ai/environments/tasks/api.py +19 -0
- synth_ai/environments/tasks/core.py +78 -0
- synth_ai/environments/tasks/filters.py +39 -0
- synth_ai/environments/tasks/utils.py +89 -0
- synth_ai/environments/v0_observability/history.py +3 -0
- synth_ai/environments/v0_observability/log.py +2 -0
- synth_ai/lm/caching/constants.py +1 -0
- synth_ai/{zyk/lms ā lm}/caching/ephemeral.py +4 -8
- synth_ai/{zyk/lms ā lm}/caching/handler.py +15 -15
- synth_ai/{zyk/lms ā lm}/caching/initialize.py +2 -4
- synth_ai/{zyk/lms ā lm}/caching/persistent.py +4 -10
- synth_ai/{zyk/lms ā lm}/config.py +2 -1
- synth_ai/{zyk/lms ā lm}/constants.py +2 -2
- synth_ai/{zyk/lms ā lm}/core/all.py +10 -10
- synth_ai/{zyk/lms ā lm}/core/main.py +57 -33
- synth_ai/{zyk/lms ā lm}/core/vendor_clients.py +12 -10
- synth_ai/lm/cost/monitor.py +1 -0
- synth_ai/lm/cost/statefulness.py +1 -0
- synth_ai/lm/provider_support/__init__.py +8 -0
- synth_ai/lm/provider_support/anthropic.py +945 -0
- synth_ai/lm/provider_support/openai.py +1115 -0
- synth_ai/lm/provider_support/suppress_logging.py +31 -0
- synth_ai/{zyk/lms ā lm}/structured_outputs/handler.py +58 -80
- synth_ai/{zyk/lms ā lm}/structured_outputs/inject.py +6 -20
- synth_ai/{zyk/lms ā lm}/structured_outputs/rehabilitate.py +6 -12
- synth_ai/{zyk/lms ā lm}/vendors/core/anthropic_api.py +21 -30
- synth_ai/{zyk/lms ā lm}/vendors/core/gemini_api.py +35 -32
- synth_ai/{zyk/lms ā lm}/vendors/core/mistral_api.py +19 -28
- synth_ai/{zyk/lms ā lm}/vendors/core/openai_api.py +26 -36
- synth_ai/{zyk/lms ā lm}/vendors/openai_standard.py +29 -33
- synth_ai/{zyk/lms ā lm}/vendors/retries.py +1 -1
- synth_ai/lm/vendors/supported/__init__.py +0 -0
- synth_ai/{zyk/lms ā lm}/vendors/supported/custom_endpoint.py +131 -118
- synth_ai/{zyk/lms ā lm}/vendors/supported/deepseek.py +4 -8
- synth_ai/{zyk/lms ā lm}/vendors/supported/grok.py +6 -8
- synth_ai/{zyk/lms ā lm}/vendors/supported/groq.py +1 -1
- synth_ai/{zyk/lms ā lm}/vendors/supported/ollama.py +2 -2
- synth_ai/{zyk/lms ā lm}/vendors/supported/openrouter.py +18 -16
- synth_ai/{zyk/lms ā lm}/vendors/supported/together.py +1 -1
- synth_ai/tracing/__init__.py +0 -0
- synth_ai/tracing/abstractions.py +224 -0
- synth_ai/tracing/base_client.py +91 -0
- synth_ai/tracing/client_manager.py +131 -0
- synth_ai/tracing/config.py +140 -0
- synth_ai/tracing/context.py +146 -0
- synth_ai/tracing/decorators.py +679 -0
- synth_ai/tracing/events/__init__.py +0 -0
- synth_ai/tracing/events/manage.py +147 -0
- synth_ai/tracing/events/scope.py +86 -0
- synth_ai/tracing/events/store.py +227 -0
- synth_ai/tracing/immediate_client.py +152 -0
- synth_ai/tracing/local.py +18 -0
- synth_ai/tracing/log_client_base.py +74 -0
- synth_ai/tracing/retry_queue.py +187 -0
- synth_ai/tracing/trackers.py +515 -0
- synth_ai/tracing/upload.py +504 -0
- synth_ai/tracing/utils.py +9 -0
- synth_ai/zyk/__init__.py +28 -2
- synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
- synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
- {synth_ai-0.2.0.dist-info ā synth_ai-0.2.1.dev0.dist-info}/WHEEL +1 -1
- synth_ai/zyk/lms/caching/constants.py +0 -1
- synth_ai/zyk/lms/cost/monitor.py +0 -1
- synth_ai/zyk/lms/cost/statefulness.py +0 -1
- synth_ai-0.2.0.dist-info/METADATA +0 -36
- synth_ai-0.2.0.dist-info/RECORD +0 -50
- /synth_ai/{zyk/lms/__init__.py ā environments/reproducibility/helpers.py} +0 -0
- /synth_ai/{zyk/lms/caching ā lm}/__init__.py +0 -0
- /synth_ai/{zyk/lms/core ā lm/caching}/__init__.py +0 -0
- /synth_ai/{zyk/lms ā lm}/caching/dbs.py +0 -0
- /synth_ai/{zyk/lms/cost ā lm/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms ā lm}/core/exceptions.py +0 -0
- /synth_ai/{zyk/lms/structured_outputs ā lm/cost}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors ā lm/structured_outputs}/__init__.py +0 -0
- /synth_ai/{zyk/lms ā lm}/tools/__init__.py +0 -0
- /synth_ai/{zyk/lms ā lm}/tools/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/core ā lm/vendors}/__init__.py +0 -0
- /synth_ai/{zyk/lms ā lm}/vendors/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/local ā lm/vendors/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors/supported ā lm/vendors/local}/__init__.py +0 -0
- /synth_ai/{zyk/lms ā lm}/vendors/local/ollama.py +0 -0
- {synth_ai-0.2.0.dist-info ā synth_ai-0.2.1.dev0.dist-info/licenses}/LICENSE +0 -0
- {synth_ai-0.2.0.dist-info ā synth_ai-0.2.1.dev0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1412 @@
|
|
1
|
+
"""
|
2
|
+
Full Crafter Evaluation with Traces, Rewards, and Viewer
|
3
|
+
Extends eval_framework.py with SystemTrace capture, viewer, and comprehensive logging.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import asyncio
|
7
|
+
import json
|
8
|
+
import base64
|
9
|
+
import io
|
10
|
+
import os
|
11
|
+
from pathlib import Path
|
12
|
+
from datetime import datetime
|
13
|
+
from typing import Dict, List, Optional, Set, Tuple, Any, Union
|
14
|
+
from dataclasses import dataclass, asdict
|
15
|
+
import uuid
|
16
|
+
|
17
|
+
import numpy as np
|
18
|
+
from PIL import Image
|
19
|
+
import pandas as pd
|
20
|
+
from tqdm import tqdm
|
21
|
+
import time
|
22
|
+
from fastapi import FastAPI, HTTPException
|
23
|
+
from fastapi.staticfiles import StaticFiles
|
24
|
+
from fastapi.responses import HTMLResponse, FileResponse
|
25
|
+
import uvicorn
|
26
|
+
|
27
|
+
# Import synth-sdk trace structures
|
28
|
+
from synth_sdk.tracing.abstractions import (
|
29
|
+
SystemTrace,
|
30
|
+
EventPartitionElement,
|
31
|
+
Event,
|
32
|
+
AgentComputeStep,
|
33
|
+
EnvironmentComputeStep,
|
34
|
+
MessageInputs,
|
35
|
+
MessageOutputs,
|
36
|
+
ArbitraryInputs,
|
37
|
+
ArbitraryOutputs,
|
38
|
+
TrainingQuestion,
|
39
|
+
RewardSignal,
|
40
|
+
Dataset,
|
41
|
+
)
|
42
|
+
|
43
|
+
# Import base evaluation framework
|
44
|
+
from src.synth_env.examples.crafter_classic.agent_demos.crafter_evaluation_framework import (
|
45
|
+
CrafterEvalFramework,
|
46
|
+
TrajectoryResult,
|
47
|
+
AggregateResults,
|
48
|
+
ACHIEVEMENT_CATEGORIES,
|
49
|
+
ALL_ACHIEVEMENTS,
|
50
|
+
TERMINATION_REASONS,
|
51
|
+
crafter_score,
|
52
|
+
balrog_score,
|
53
|
+
)
|
54
|
+
|
55
|
+
# Action names mapping for Crafter
|
56
|
+
ACTION_NAMES = {
|
57
|
+
-1: "initial_state",
|
58
|
+
0: "noop",
|
59
|
+
1: "move_left",
|
60
|
+
2: "move_right",
|
61
|
+
3: "move_up",
|
62
|
+
4: "move_down",
|
63
|
+
5: "do",
|
64
|
+
6: "sleep",
|
65
|
+
7: "place_stone",
|
66
|
+
8: "place_table",
|
67
|
+
9: "place_furnace",
|
68
|
+
10: "place_plant",
|
69
|
+
11: "make_wood_pickaxe",
|
70
|
+
12: "make_stone_pickaxe",
|
71
|
+
13: "make_iron_pickaxe",
|
72
|
+
14: "make_wood_sword",
|
73
|
+
15: "make_stone_sword",
|
74
|
+
16: "make_iron_sword",
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
class FullCrafterEvalFramework(CrafterEvalFramework):
|
79
|
+
"""Extended evaluation framework with trace capture and visualization."""
|
80
|
+
|
81
|
+
def __init__(self, capture_images: bool = True, output_dir: Optional[str] = None):
|
82
|
+
super().__init__()
|
83
|
+
self.capture_images = capture_images
|
84
|
+
|
85
|
+
# Use standardized eval directory structure
|
86
|
+
if output_dir is None:
|
87
|
+
# Create timestamp-based directory under src/evals/
|
88
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
89
|
+
self.output_dir = Path("src/evals") / "crafter" / f"run_{timestamp}"
|
90
|
+
else:
|
91
|
+
self.output_dir = Path(output_dir)
|
92
|
+
|
93
|
+
self.traces_dir = self.output_dir / "traces"
|
94
|
+
self.viewer_dir = self.output_dir / "viewer"
|
95
|
+
|
96
|
+
# Create directories
|
97
|
+
self.traces_dir.mkdir(parents=True, exist_ok=True)
|
98
|
+
self.viewer_dir.mkdir(parents=True, exist_ok=True)
|
99
|
+
|
100
|
+
# Store traces and datasets
|
101
|
+
self.system_traces: Dict[str, SystemTrace] = {}
|
102
|
+
self.datasets: Dict[str, Dataset] = {}
|
103
|
+
|
104
|
+
def _encode_image_to_base64(self, rgb_array: np.ndarray) -> str:
|
105
|
+
"""Convert RGB numpy array to base64 PNG string."""
|
106
|
+
image = Image.fromarray(rgb_array.astype("uint8"), "RGB")
|
107
|
+
buffer = io.BytesIO()
|
108
|
+
image.save(buffer, format="PNG")
|
109
|
+
buffer.seek(0)
|
110
|
+
return base64.b64encode(buffer.read()).decode("utf-8")
|
111
|
+
|
112
|
+
async def run_single_trajectory_with_trace(
|
113
|
+
self,
|
114
|
+
model_name: str,
|
115
|
+
difficulty: str,
|
116
|
+
seed: int,
|
117
|
+
max_turns: int = 30,
|
118
|
+
collect_detailed_data: bool = True,
|
119
|
+
) -> TrajectoryResult:
|
120
|
+
"""Run a single trajectory with comprehensive trace capture."""
|
121
|
+
from src.synth_env.examples.crafter_classic.agent_demos.crafter_react_agent import (
|
122
|
+
ReActAgent,
|
123
|
+
CrafterHistoryObservationCallable,
|
124
|
+
CrafterMove,
|
125
|
+
)
|
126
|
+
from src.synth_env.examples.crafter_classic.environment import (
|
127
|
+
CrafterClassicEnvironment,
|
128
|
+
)
|
129
|
+
from src.synth_env.examples.crafter_classic.taskset import (
|
130
|
+
CrafterTaskInstance,
|
131
|
+
CrafterTaskInstanceMetadata,
|
132
|
+
)
|
133
|
+
from src.synth_env.tasks.core import Impetus, Intent
|
134
|
+
from synth_ai.zyk import LM
|
135
|
+
|
136
|
+
# Create task instance
|
137
|
+
metadata = CrafterTaskInstanceMetadata(
|
138
|
+
difficulty=difficulty,
|
139
|
+
seed=seed,
|
140
|
+
num_trees_radius=0,
|
141
|
+
num_cows_radius=0,
|
142
|
+
num_hostiles_radius=0,
|
143
|
+
)
|
144
|
+
instance = CrafterTaskInstance(
|
145
|
+
id=uuid.uuid4(),
|
146
|
+
impetus=Impetus(
|
147
|
+
instructions=f"Survive and unlock achievements in a {difficulty} environment."
|
148
|
+
),
|
149
|
+
intent=Intent(rubric={}, gold_trajectories=None, gold_state_diff={}),
|
150
|
+
metadata=metadata,
|
151
|
+
is_reproducible=True,
|
152
|
+
initial_engine_snapshot=None,
|
153
|
+
)
|
154
|
+
|
155
|
+
# Setup environment and agent
|
156
|
+
start_time = time.perf_counter()
|
157
|
+
hist_cb = CrafterHistoryObservationCallable(max_history=1)
|
158
|
+
env = CrafterClassicEnvironment(instance, custom_step_obs=hist_cb)
|
159
|
+
|
160
|
+
llm = LM(model_name=model_name, formatting_model_name=model_name, temperature=0.0)
|
161
|
+
agent = ReActAgent(llm, max_turns=max_turns)
|
162
|
+
|
163
|
+
# Initialize tracking
|
164
|
+
trajectory_id = str(uuid.uuid4())
|
165
|
+
achievements_unlocked = set()
|
166
|
+
achievement_turn_unlocked = {}
|
167
|
+
actions_per_turn = []
|
168
|
+
turn_by_turn_data = [] if collect_detailed_data else None
|
169
|
+
|
170
|
+
# Initialize SystemTrace
|
171
|
+
system_trace = SystemTrace(
|
172
|
+
system_name="crafter_evaluation",
|
173
|
+
system_id=f"crafter_{model_name}_{difficulty}",
|
174
|
+
system_instance_id=trajectory_id,
|
175
|
+
partition=[],
|
176
|
+
metadata={
|
177
|
+
"model_name": model_name,
|
178
|
+
"difficulty": difficulty,
|
179
|
+
"seed": seed,
|
180
|
+
"max_turns": max_turns,
|
181
|
+
},
|
182
|
+
instance_metadata={
|
183
|
+
"start_time": datetime.now().isoformat(),
|
184
|
+
"capture_images": self.capture_images,
|
185
|
+
},
|
186
|
+
)
|
187
|
+
|
188
|
+
# Create TrainingQuestion for this trajectory
|
189
|
+
training_question = TrainingQuestion(
|
190
|
+
id=trajectory_id,
|
191
|
+
intent=f"Survive and unlock achievements in a {difficulty} Crafter environment (seed={seed})",
|
192
|
+
criteria="Maximize the number of achievements unlocked and survive as long as possible",
|
193
|
+
)
|
194
|
+
|
195
|
+
# Run episode
|
196
|
+
obs_payload = await env.initialize()
|
197
|
+
turn_count = 0
|
198
|
+
termination_reason = "unknown"
|
199
|
+
partition_index = 0
|
200
|
+
|
201
|
+
# Create progress bar for this trajectory
|
202
|
+
pbar = tqdm(
|
203
|
+
total=max_turns,
|
204
|
+
desc=f"{model_name} ({difficulty}) Seed {seed}",
|
205
|
+
unit="turn",
|
206
|
+
leave=False,
|
207
|
+
ncols=100,
|
208
|
+
)
|
209
|
+
|
210
|
+
try:
|
211
|
+
while turn_count < max_turns:
|
212
|
+
turn_count += 1
|
213
|
+
pbar.update(1)
|
214
|
+
|
215
|
+
# Track achievements
|
216
|
+
easy_count = len(
|
217
|
+
[a for a in achievements_unlocked if a in ACHIEVEMENT_CATEGORIES["easy"]]
|
218
|
+
)
|
219
|
+
medium_count = len(
|
220
|
+
[a for a in achievements_unlocked if a in ACHIEVEMENT_CATEGORIES["medium"]]
|
221
|
+
)
|
222
|
+
hard_count = len(
|
223
|
+
[a for a in achievements_unlocked if a in ACHIEVEMENT_CATEGORIES["hard"]]
|
224
|
+
)
|
225
|
+
total_count = len(achievements_unlocked)
|
226
|
+
|
227
|
+
achievement_display = f"{total_count}({easy_count}/{medium_count}/{hard_count})"
|
228
|
+
|
229
|
+
pbar.set_postfix(
|
230
|
+
{
|
231
|
+
"achievements": achievement_display,
|
232
|
+
"steps": obs_payload.get("public", {}).num_steps_taken
|
233
|
+
if hasattr(obs_payload.get("public", {}), "num_steps_taken")
|
234
|
+
else 0,
|
235
|
+
}
|
236
|
+
)
|
237
|
+
|
238
|
+
current_formatted_obs = obs_payload["formatted_obs"]
|
239
|
+
|
240
|
+
# Track achievements at start of turn
|
241
|
+
current_achievements = set()
|
242
|
+
if "public" in obs_payload and hasattr(
|
243
|
+
obs_payload["public"], "achievements_status"
|
244
|
+
):
|
245
|
+
current_achievements = {
|
246
|
+
ach
|
247
|
+
for ach, status in obs_payload["public"].achievements_status.items()
|
248
|
+
if status
|
249
|
+
}
|
250
|
+
|
251
|
+
# Check for new achievements
|
252
|
+
new_achievements = current_achievements - achievements_unlocked
|
253
|
+
for ach in new_achievements:
|
254
|
+
achievements_unlocked.add(ach)
|
255
|
+
achievement_turn_unlocked[ach] = turn_count
|
256
|
+
agent.current_achievements.add(ach)
|
257
|
+
|
258
|
+
# Create EventPartitionElement for this turn
|
259
|
+
event_partition = EventPartitionElement(partition_index=partition_index, events=[])
|
260
|
+
|
261
|
+
# Capture initial state image before actions
|
262
|
+
initial_image = None
|
263
|
+
if self.capture_images and turn_count == 1:
|
264
|
+
try:
|
265
|
+
if hasattr(env, "engine") and hasattr(env.engine, "env"):
|
266
|
+
crafter_env = env.engine.env
|
267
|
+
if hasattr(crafter_env, "_render_mode"):
|
268
|
+
crafter_env._render_mode = "rgb_array"
|
269
|
+
initial_rgb = crafter_env.render()
|
270
|
+
if initial_rgb is not None:
|
271
|
+
initial_image = self._encode_image_to_base64(initial_rgb)
|
272
|
+
print(f"ā Initial state captured before actions")
|
273
|
+
except Exception as e:
|
274
|
+
print(f"Warning: Failed to capture initial image: {e}")
|
275
|
+
|
276
|
+
# Agent decision phase
|
277
|
+
agent_compute_began = datetime.now()
|
278
|
+
|
279
|
+
# Create proper system and user prompts
|
280
|
+
system_prompt = "You are playing Crafter. Your goal is to survive and unlock as many achievements as possible."
|
281
|
+
|
282
|
+
# Get agent decision with proper message structure
|
283
|
+
agent_decision = await agent.decide(current_formatted_obs, obs_payload)
|
284
|
+
agent_compute_ended = datetime.now()
|
285
|
+
|
286
|
+
if agent_decision == [-1]: # Agent terminated
|
287
|
+
termination_reason = "agent_quit"
|
288
|
+
break
|
289
|
+
|
290
|
+
action_sequence = agent_decision
|
291
|
+
actions_per_turn.append(len(action_sequence))
|
292
|
+
|
293
|
+
# Create proper tool calls for the actions
|
294
|
+
tool_calls = [
|
295
|
+
{
|
296
|
+
"id": f"crafter_action_{turn_count}",
|
297
|
+
"type": "function",
|
298
|
+
"function": {
|
299
|
+
"name": "crafter_interact",
|
300
|
+
"arguments": json.dumps(
|
301
|
+
{
|
302
|
+
"actions": action_sequence,
|
303
|
+
"reasoning": f"Executing {len(action_sequence)} actions: {[ACTION_NAMES.get(act, f'action_{act}') for act in action_sequence]}",
|
304
|
+
}
|
305
|
+
),
|
306
|
+
},
|
307
|
+
}
|
308
|
+
]
|
309
|
+
|
310
|
+
tool_results = [
|
311
|
+
{
|
312
|
+
"tool_call_id": f"crafter_action_{turn_count}",
|
313
|
+
"content": f"Planned actions: {[ACTION_NAMES.get(act, f'action_{act}') for act in action_sequence]}",
|
314
|
+
}
|
315
|
+
]
|
316
|
+
|
317
|
+
# Create input messages
|
318
|
+
input_messages = [
|
319
|
+
{"role": "system", "content": system_prompt},
|
320
|
+
{"role": "user", "content": current_formatted_obs},
|
321
|
+
]
|
322
|
+
|
323
|
+
# Create output messages with tool calls
|
324
|
+
output_messages = [
|
325
|
+
{
|
326
|
+
"role": "assistant",
|
327
|
+
"content": f"I need to execute {len(action_sequence)} actions to progress in the game: {[ACTION_NAMES.get(act, f'action_{act}') for act in action_sequence]}",
|
328
|
+
"tool_calls": tool_calls,
|
329
|
+
}
|
330
|
+
]
|
331
|
+
|
332
|
+
# Add tool results
|
333
|
+
for tool_result in tool_results:
|
334
|
+
output_messages.append(
|
335
|
+
{
|
336
|
+
"role": "tool",
|
337
|
+
"tool_call_id": tool_result["tool_call_id"],
|
338
|
+
"content": tool_result["content"],
|
339
|
+
}
|
340
|
+
)
|
341
|
+
|
342
|
+
# Create AgentComputeStep with proper message structure
|
343
|
+
agent_compute_step = AgentComputeStep(
|
344
|
+
event_order=0,
|
345
|
+
compute_began=agent_compute_began,
|
346
|
+
compute_ended=agent_compute_ended,
|
347
|
+
compute_input=[MessageInputs(messages=input_messages)],
|
348
|
+
compute_output=[MessageOutputs(messages=output_messages)],
|
349
|
+
model_name=model_name,
|
350
|
+
model_params={"temperature": 0.0},
|
351
|
+
should_learn=True,
|
352
|
+
)
|
353
|
+
|
354
|
+
# Collect turn data
|
355
|
+
if collect_detailed_data:
|
356
|
+
turn_data = {
|
357
|
+
"turn": turn_count,
|
358
|
+
"actions_planned": len(action_sequence),
|
359
|
+
"achievements_at_start": list(current_achievements),
|
360
|
+
"new_achievements_this_turn": list(new_achievements),
|
361
|
+
"steps_before_turn": obs_payload.get("public", {}).num_steps_taken
|
362
|
+
if hasattr(obs_payload.get("public", {}), "num_steps_taken")
|
363
|
+
else 0,
|
364
|
+
}
|
365
|
+
turn_by_turn_data.append(turn_data)
|
366
|
+
|
367
|
+
# Execute actions and collect environment steps
|
368
|
+
environment_compute_steps = []
|
369
|
+
|
370
|
+
# Add initial state as first "step" if we have it
|
371
|
+
if initial_image and turn_count == 1:
|
372
|
+
initial_step = EnvironmentComputeStep(
|
373
|
+
event_order=0,
|
374
|
+
compute_began=agent_compute_began,
|
375
|
+
compute_ended=agent_compute_began,
|
376
|
+
compute_input=[ArbitraryInputs(inputs={"action": "initial_state"})],
|
377
|
+
compute_output=[
|
378
|
+
ArbitraryOutputs(
|
379
|
+
outputs={
|
380
|
+
"action_index": -1, # Special index for initial state
|
381
|
+
"action_order": -1,
|
382
|
+
"image_base64": initial_image,
|
383
|
+
"error": None,
|
384
|
+
"terminated": False,
|
385
|
+
"truncated": False,
|
386
|
+
"reward": 0.0,
|
387
|
+
"total_reward": 0.0,
|
388
|
+
"num_steps": 0,
|
389
|
+
}
|
390
|
+
)
|
391
|
+
],
|
392
|
+
)
|
393
|
+
environment_compute_steps.append(initial_step)
|
394
|
+
|
395
|
+
for i, act_idx in enumerate(action_sequence):
|
396
|
+
env_compute_began = datetime.now()
|
397
|
+
|
398
|
+
# Execute action
|
399
|
+
obs_payload = await env.step([[CrafterMove(act_idx)]])
|
400
|
+
env_compute_ended = datetime.now()
|
401
|
+
|
402
|
+
# Capture image after step if enabled
|
403
|
+
post_step_image = None
|
404
|
+
if self.capture_images:
|
405
|
+
try:
|
406
|
+
# Access the underlying crafter environment through the engine
|
407
|
+
if hasattr(env, "engine") and hasattr(env.engine, "env"):
|
408
|
+
# Force render mode to 'rgb_array' if needed
|
409
|
+
crafter_env = env.engine.env
|
410
|
+
if hasattr(crafter_env, "_render_mode"):
|
411
|
+
crafter_env._render_mode = "rgb_array"
|
412
|
+
|
413
|
+
rgb_array = crafter_env.render()
|
414
|
+
if rgb_array is not None:
|
415
|
+
post_step_image = self._encode_image_to_base64(rgb_array)
|
416
|
+
# Debug: check if images are different
|
417
|
+
# if (
|
418
|
+
# turn_count == 1 and i < 3
|
419
|
+
# ): # Debug first turn, first 3 actions
|
420
|
+
# print(
|
421
|
+
# f"ā Action {i} ({act_idx}): Image captured, first pixel: {rgb_array[0, 0]}, shape: {rgb_array.shape}"
|
422
|
+
# )
|
423
|
+
else:
|
424
|
+
print(f"Warning: render() returned None")
|
425
|
+
else:
|
426
|
+
print(f"Warning: Cannot access env.engine.env for rendering")
|
427
|
+
except Exception as e:
|
428
|
+
print(f"Warning: Failed to capture image: {e}")
|
429
|
+
|
430
|
+
# Create EnvironmentComputeStep
|
431
|
+
env_outputs = {
|
432
|
+
"action_index": act_idx,
|
433
|
+
"action_order": i,
|
434
|
+
"error": obs_payload.get("error", None),
|
435
|
+
"terminated": obs_payload.get("private", {}).terminated
|
436
|
+
if hasattr(obs_payload.get("private", {}), "terminated")
|
437
|
+
else False,
|
438
|
+
"truncated": obs_payload.get("private", {}).truncated
|
439
|
+
if hasattr(obs_payload.get("private", {}), "truncated")
|
440
|
+
else False,
|
441
|
+
"reward": obs_payload.get("private", {}).reward
|
442
|
+
if hasattr(obs_payload.get("private", {}), "reward")
|
443
|
+
else 0.0,
|
444
|
+
"total_reward": obs_payload.get("private", {}).total_reward_episode
|
445
|
+
if hasattr(obs_payload.get("private", {}), "total_reward_episode")
|
446
|
+
else 0.0,
|
447
|
+
"num_steps": obs_payload.get("public", {}).num_steps_taken
|
448
|
+
if hasattr(obs_payload.get("public", {}), "num_steps_taken")
|
449
|
+
else 0,
|
450
|
+
}
|
451
|
+
|
452
|
+
# Add image if captured
|
453
|
+
if post_step_image:
|
454
|
+
env_outputs["image_base64"] = post_step_image
|
455
|
+
|
456
|
+
# Add player stats
|
457
|
+
if hasattr(obs_payload.get("private", {}), "player_internal_stats"):
|
458
|
+
stats = obs_payload["private"].player_internal_stats
|
459
|
+
env_outputs["player_stats"] = {
|
460
|
+
"health": stats.get("health"),
|
461
|
+
"food": stats.get("food"),
|
462
|
+
"drink": stats.get("drink"),
|
463
|
+
}
|
464
|
+
|
465
|
+
# Adjust event order if we have initial state
|
466
|
+
event_order = i + 2 if (initial_image and turn_count == 1) else i + 1
|
467
|
+
env_compute_step = EnvironmentComputeStep(
|
468
|
+
event_order=event_order,
|
469
|
+
compute_began=env_compute_began,
|
470
|
+
compute_ended=env_compute_ended,
|
471
|
+
compute_input=[ArbitraryInputs(inputs={"action": act_idx})],
|
472
|
+
compute_output=[ArbitraryOutputs(outputs=env_outputs)],
|
473
|
+
)
|
474
|
+
environment_compute_steps.append(env_compute_step)
|
475
|
+
|
476
|
+
if "error" in obs_payload:
|
477
|
+
termination_reason = "environment_error"
|
478
|
+
break
|
479
|
+
|
480
|
+
if obs_payload["private"].terminated or obs_payload["private"].truncated:
|
481
|
+
termination_reason = (
|
482
|
+
"timeout" if obs_payload["private"].truncated else "death"
|
483
|
+
)
|
484
|
+
break
|
485
|
+
|
486
|
+
# Create Event for this turn
|
487
|
+
event = Event(
|
488
|
+
system_instance_id=trajectory_id,
|
489
|
+
event_type="turn",
|
490
|
+
opened=agent_compute_began,
|
491
|
+
closed=environment_compute_steps[-1].compute_ended
|
492
|
+
if environment_compute_steps
|
493
|
+
else agent_compute_ended,
|
494
|
+
partition_index=partition_index,
|
495
|
+
agent_compute_step=agent_compute_step,
|
496
|
+
environment_compute_steps=environment_compute_steps,
|
497
|
+
event_metadata={
|
498
|
+
"turn_number": turn_count,
|
499
|
+
"new_achievements": list(new_achievements),
|
500
|
+
"total_achievements": len(achievements_unlocked),
|
501
|
+
},
|
502
|
+
)
|
503
|
+
|
504
|
+
event_partition.events.append(event)
|
505
|
+
system_trace.partition.append(event_partition)
|
506
|
+
partition_index += 1
|
507
|
+
|
508
|
+
if termination_reason in ["environment_error", "timeout", "death"]:
|
509
|
+
break
|
510
|
+
|
511
|
+
# Final metrics
|
512
|
+
if termination_reason == "unknown":
|
513
|
+
termination_reason = "timeout"
|
514
|
+
|
515
|
+
final_private = obs_payload.get("private")
|
516
|
+
final_public = obs_payload.get("public")
|
517
|
+
|
518
|
+
total_steps = (
|
519
|
+
final_public.num_steps_taken if hasattr(final_public, "num_steps_taken") else 0
|
520
|
+
)
|
521
|
+
total_reward = (
|
522
|
+
final_private.total_reward_episode
|
523
|
+
if hasattr(final_private, "total_reward_episode")
|
524
|
+
else 0.0
|
525
|
+
)
|
526
|
+
|
527
|
+
# Health/survival stats
|
528
|
+
final_health = None
|
529
|
+
final_food = None
|
530
|
+
final_drink = None
|
531
|
+
if hasattr(final_private, "player_internal_stats"):
|
532
|
+
stats = final_private.player_internal_stats
|
533
|
+
final_health = stats.get("health")
|
534
|
+
final_food = stats.get("food")
|
535
|
+
final_drink = stats.get("drink")
|
536
|
+
|
537
|
+
# Success determination
|
538
|
+
success = len(achievements_unlocked) > 0 or (
|
539
|
+
hasattr(final_private, "terminated") and final_private.terminated
|
540
|
+
)
|
541
|
+
|
542
|
+
avg_actions_per_turn = (
|
543
|
+
sum(actions_per_turn) / len(actions_per_turn) if actions_per_turn else 0.0
|
544
|
+
)
|
545
|
+
|
546
|
+
# Create RewardSignal
|
547
|
+
hafner_score_value = crafter_score(
|
548
|
+
[(achievement_turn_unlocked.get(ach, 0) > 0) * 100.0 for ach in ALL_ACHIEVEMENTS]
|
549
|
+
)
|
550
|
+
|
551
|
+
reward_signal = RewardSignal(
|
552
|
+
question_id=trajectory_id,
|
553
|
+
system_instance_id=trajectory_id,
|
554
|
+
reward=hafner_score_value,
|
555
|
+
annotation=f"Termination: {termination_reason}, Achievements: {len(achievements_unlocked)}/22",
|
556
|
+
)
|
557
|
+
|
558
|
+
# Create Dataset
|
559
|
+
dataset = Dataset(questions=[training_question], reward_signals=[reward_signal])
|
560
|
+
|
561
|
+
# Store trace and dataset
|
562
|
+
self.system_traces[trajectory_id] = system_trace
|
563
|
+
self.datasets[trajectory_id] = dataset
|
564
|
+
|
565
|
+
# Save to disk
|
566
|
+
self._save_trace_to_disk(trajectory_id, system_trace, dataset)
|
567
|
+
|
568
|
+
# Total duration
|
569
|
+
total_duration_sec = time.perf_counter() - start_time
|
570
|
+
|
571
|
+
# Create trajectory result
|
572
|
+
result = TrajectoryResult(
|
573
|
+
trajectory_id=trajectory_id,
|
574
|
+
model_name=model_name,
|
575
|
+
difficulty=difficulty,
|
576
|
+
seed=seed,
|
577
|
+
success=success,
|
578
|
+
total_steps=total_steps,
|
579
|
+
total_turns=turn_count,
|
580
|
+
total_reward=total_reward,
|
581
|
+
total_duration_sec=total_duration_sec,
|
582
|
+
achievements_unlocked=achievements_unlocked,
|
583
|
+
achievement_turn_unlocked=achievement_turn_unlocked,
|
584
|
+
actions_per_turn=actions_per_turn,
|
585
|
+
avg_actions_per_turn=avg_actions_per_turn,
|
586
|
+
termination_reason=termination_reason,
|
587
|
+
final_health=final_health,
|
588
|
+
final_food=final_food,
|
589
|
+
final_drink=final_drink,
|
590
|
+
turn_by_turn_data=turn_by_turn_data,
|
591
|
+
)
|
592
|
+
|
593
|
+
return result
|
594
|
+
|
595
|
+
finally:
|
596
|
+
pbar.close()
|
597
|
+
|
598
|
+
def _save_trace_to_disk(self, trajectory_id: str, trace: SystemTrace, dataset: Dataset):
|
599
|
+
"""Save trace and dataset to JSON file."""
|
600
|
+
trace_file = self.traces_dir / f"{trajectory_id}.json"
|
601
|
+
|
602
|
+
trace_data = {
|
603
|
+
"trace": trace.to_dict(),
|
604
|
+
"dataset": dataset.to_dict(),
|
605
|
+
"metadata": {
|
606
|
+
"saved_at": datetime.now().isoformat(),
|
607
|
+
"trajectory_id": trajectory_id,
|
608
|
+
},
|
609
|
+
}
|
610
|
+
|
611
|
+
with open(trace_file, "w") as f:
|
612
|
+
json.dump(trace_data, f, indent=2)
|
613
|
+
|
614
|
+
def _save_evaluation_summary(self, report: Dict[str, Any]):
|
615
|
+
"""Save evaluation summary and metadata."""
|
616
|
+
summary_file = self.output_dir / "evaluation_summary.json"
|
617
|
+
|
618
|
+
# Extract key metrics for summary
|
619
|
+
summary_data = {
|
620
|
+
"evaluation_metadata": {
|
621
|
+
"timestamp": datetime.now().isoformat(),
|
622
|
+
"output_directory": str(self.output_dir),
|
623
|
+
"traces_directory": str(self.traces_dir),
|
624
|
+
"viewer_directory": str(self.viewer_dir),
|
625
|
+
"num_trajectories": len(self.trajectory_results),
|
626
|
+
},
|
627
|
+
"evaluation_summary": report.get("evaluation_summary").to_dict()
|
628
|
+
if hasattr(report.get("evaluation_summary"), "to_dict")
|
629
|
+
else None,
|
630
|
+
"traces_info": report.get("traces"),
|
631
|
+
"models_evaluated": list(set(t.model_name for t in self.trajectory_results)),
|
632
|
+
"difficulties_evaluated": list(set(t.difficulty for t in self.trajectory_results)),
|
633
|
+
}
|
634
|
+
|
635
|
+
with open(summary_file, "w") as f:
|
636
|
+
json.dump(summary_data, f, indent=2)
|
637
|
+
|
638
|
+
# Also save the full report as CSV tables
|
639
|
+
if "evaluation_summary" in report and hasattr(report["evaluation_summary"], "to_csv"):
|
640
|
+
report["evaluation_summary"].to_csv(self.output_dir / "summary_table.csv", index=False)
|
641
|
+
|
642
|
+
if "trajectory_by_trajectory_breakdown" in report and hasattr(
|
643
|
+
report["trajectory_by_trajectory_breakdown"], "to_csv"
|
644
|
+
):
|
645
|
+
report["trajectory_by_trajectory_breakdown"].to_csv(
|
646
|
+
self.output_dir / "trajectories.csv", index=False
|
647
|
+
)
|
648
|
+
|
649
|
+
async def run_evaluation(
|
650
|
+
self,
|
651
|
+
model_names: List[str],
|
652
|
+
difficulties: List[str] = ["easy", "hard"],
|
653
|
+
num_trajectories_per_condition: int = 3,
|
654
|
+
max_turns: int = 30,
|
655
|
+
collect_detailed_data: bool = True,
|
656
|
+
) -> Dict[str, Any]:
|
657
|
+
"""Run comprehensive evaluation with trace capture."""
|
658
|
+
|
659
|
+
print(f"šÆ Starting Full Enchilada Crafter Evaluation")
|
660
|
+
print(f" Models: {model_names}")
|
661
|
+
print(f" Difficulties: {difficulties}")
|
662
|
+
print(f" Trajectories per condition: {num_trajectories_per_condition}")
|
663
|
+
print(f" Max turns per trajectory: {max_turns}")
|
664
|
+
print(f" Output directory: {self.output_dir}")
|
665
|
+
|
666
|
+
all_results = []
|
667
|
+
|
668
|
+
for model_name in model_names:
|
669
|
+
for difficulty in difficulties:
|
670
|
+
print(f"\nš Running {model_name} on {difficulty} difficulty...")
|
671
|
+
|
672
|
+
# Run trajectories for this condition
|
673
|
+
trajectory_tasks = []
|
674
|
+
for i in range(num_trajectories_per_condition):
|
675
|
+
seed = 1000 + i if difficulty == "easy" else 2000 + i
|
676
|
+
trajectory_tasks.append(
|
677
|
+
self.run_single_trajectory_with_trace(
|
678
|
+
model_name=model_name,
|
679
|
+
difficulty=difficulty,
|
680
|
+
seed=seed,
|
681
|
+
max_turns=max_turns,
|
682
|
+
collect_detailed_data=collect_detailed_data,
|
683
|
+
)
|
684
|
+
)
|
685
|
+
|
686
|
+
condition_results = await asyncio.gather(*trajectory_tasks)
|
687
|
+
all_results.extend(condition_results)
|
688
|
+
|
689
|
+
self.trajectory_results = all_results
|
690
|
+
|
691
|
+
# Generate report
|
692
|
+
report = self._generate_comprehensive_report()
|
693
|
+
|
694
|
+
# Create viewer files
|
695
|
+
self._create_viewer_files()
|
696
|
+
|
697
|
+
# Add trace info to report
|
698
|
+
report["traces"] = {
|
699
|
+
"count": len(self.system_traces),
|
700
|
+
"directory": str(self.traces_dir),
|
701
|
+
"viewer_url": f"http://localhost:8999",
|
702
|
+
}
|
703
|
+
|
704
|
+
# Save evaluation summary
|
705
|
+
self._save_evaluation_summary(report)
|
706
|
+
|
707
|
+
return report
|
708
|
+
|
709
|
+
def _create_viewer_files(self):
|
710
|
+
"""Create the viewer HTML/JS/CSS files."""
|
711
|
+
|
712
|
+
# Create index.html
|
713
|
+
html_content = """<!DOCTYPE html>
|
714
|
+
<html lang="en">
|
715
|
+
<head>
|
716
|
+
<meta charset="UTF-8">
|
717
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
718
|
+
<title>Crafter Evaluation Viewer</title>
|
719
|
+
<link rel="stylesheet" href="style.css">
|
720
|
+
</head>
|
721
|
+
<body>
|
722
|
+
<div id="app">
|
723
|
+
<div class="header">
|
724
|
+
<h1>š® Crafter Evaluation Viewer</h1>
|
725
|
+
<div class="trace-selector">
|
726
|
+
<label for="trace-select">Select Trace:</label>
|
727
|
+
<select id="trace-select"></select>
|
728
|
+
<button id="refresh-btn">š Refresh</button>
|
729
|
+
</div>
|
730
|
+
</div>
|
731
|
+
|
732
|
+
<div class="main-container">
|
733
|
+
<div class="sidebar">
|
734
|
+
<h2>Timeline</h2>
|
735
|
+
<div id="timeline" class="timeline"></div>
|
736
|
+
|
737
|
+
<div class="trace-info">
|
738
|
+
<h3>Trace Info</h3>
|
739
|
+
<div id="trace-metadata"></div>
|
740
|
+
</div>
|
741
|
+
</div>
|
742
|
+
|
743
|
+
<div class="content">
|
744
|
+
<div class="question-reward">
|
745
|
+
<h2>Training Question</h2>
|
746
|
+
<div id="question-display" class="info-box"></div>
|
747
|
+
|
748
|
+
<h2>Reward Signal</h2>
|
749
|
+
<div id="reward-display" class="info-box"></div>
|
750
|
+
</div>
|
751
|
+
|
752
|
+
<div class="turn-details">
|
753
|
+
<h2>Turn Details</h2>
|
754
|
+
<div id="turn-content">
|
755
|
+
<p class="placeholder">Select a turn from the timeline</p>
|
756
|
+
</div>
|
757
|
+
</div>
|
758
|
+
</div>
|
759
|
+
</div>
|
760
|
+
</div>
|
761
|
+
|
762
|
+
<script src="viewer.js"></script>
|
763
|
+
</body>
|
764
|
+
</html>"""
|
765
|
+
|
766
|
+
# Create style.css
|
767
|
+
css_content = """* {
|
768
|
+
margin: 0;
|
769
|
+
padding: 0;
|
770
|
+
box-sizing: border-box;
|
771
|
+
}
|
772
|
+
|
773
|
+
body {
|
774
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
775
|
+
background-color: #f5f5f5;
|
776
|
+
color: #333;
|
777
|
+
}
|
778
|
+
|
779
|
+
.header {
|
780
|
+
background-color: #2c3e50;
|
781
|
+
color: white;
|
782
|
+
padding: 1rem 2rem;
|
783
|
+
display: flex;
|
784
|
+
justify-content: space-between;
|
785
|
+
align-items: center;
|
786
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
787
|
+
}
|
788
|
+
|
789
|
+
.header h1 {
|
790
|
+
font-size: 1.5rem;
|
791
|
+
}
|
792
|
+
|
793
|
+
.trace-selector {
|
794
|
+
display: flex;
|
795
|
+
align-items: center;
|
796
|
+
gap: 1rem;
|
797
|
+
}
|
798
|
+
|
799
|
+
.trace-selector select {
|
800
|
+
padding: 0.5rem;
|
801
|
+
border-radius: 4px;
|
802
|
+
border: 1px solid #34495e;
|
803
|
+
background-color: white;
|
804
|
+
min-width: 200px;
|
805
|
+
}
|
806
|
+
|
807
|
+
.trace-selector button {
|
808
|
+
padding: 0.5rem 1rem;
|
809
|
+
background-color: #3498db;
|
810
|
+
color: white;
|
811
|
+
border: none;
|
812
|
+
border-radius: 4px;
|
813
|
+
cursor: pointer;
|
814
|
+
transition: background-color 0.2s;
|
815
|
+
}
|
816
|
+
|
817
|
+
.trace-selector button:hover {
|
818
|
+
background-color: #2980b9;
|
819
|
+
}
|
820
|
+
|
821
|
+
.main-container {
|
822
|
+
display: flex;
|
823
|
+
height: calc(100vh - 60px);
|
824
|
+
}
|
825
|
+
|
826
|
+
.sidebar {
|
827
|
+
width: 300px;
|
828
|
+
background-color: white;
|
829
|
+
border-right: 1px solid #ddd;
|
830
|
+
overflow-y: auto;
|
831
|
+
padding: 1rem;
|
832
|
+
}
|
833
|
+
|
834
|
+
.timeline {
|
835
|
+
margin-bottom: 2rem;
|
836
|
+
}
|
837
|
+
|
838
|
+
.timeline-item {
|
839
|
+
padding: 0.75rem;
|
840
|
+
margin-bottom: 0.5rem;
|
841
|
+
background-color: #f8f9fa;
|
842
|
+
border-radius: 4px;
|
843
|
+
cursor: pointer;
|
844
|
+
transition: all 0.2s;
|
845
|
+
border: 2px solid transparent;
|
846
|
+
}
|
847
|
+
|
848
|
+
.timeline-item:hover {
|
849
|
+
background-color: #e9ecef;
|
850
|
+
}
|
851
|
+
|
852
|
+
.timeline-item.active {
|
853
|
+
background-color: #3498db;
|
854
|
+
color: white;
|
855
|
+
border-color: #2980b9;
|
856
|
+
}
|
857
|
+
|
858
|
+
.timeline-item .turn-number {
|
859
|
+
font-weight: bold;
|
860
|
+
margin-bottom: 0.25rem;
|
861
|
+
}
|
862
|
+
|
863
|
+
.timeline-item .turn-stats {
|
864
|
+
font-size: 0.85rem;
|
865
|
+
opacity: 0.8;
|
866
|
+
}
|
867
|
+
|
868
|
+
.trace-info {
|
869
|
+
padding: 1rem;
|
870
|
+
background-color: #f8f9fa;
|
871
|
+
border-radius: 4px;
|
872
|
+
}
|
873
|
+
|
874
|
+
.trace-info h3 {
|
875
|
+
margin-bottom: 0.5rem;
|
876
|
+
color: #2c3e50;
|
877
|
+
}
|
878
|
+
|
879
|
+
.content {
|
880
|
+
flex: 1;
|
881
|
+
padding: 2rem;
|
882
|
+
overflow-y: auto;
|
883
|
+
}
|
884
|
+
|
885
|
+
.question-reward {
|
886
|
+
display: grid;
|
887
|
+
grid-template-columns: 1fr 1fr;
|
888
|
+
gap: 2rem;
|
889
|
+
margin-bottom: 2rem;
|
890
|
+
}
|
891
|
+
|
892
|
+
.info-box {
|
893
|
+
padding: 1rem;
|
894
|
+
background-color: white;
|
895
|
+
border-radius: 4px;
|
896
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
897
|
+
}
|
898
|
+
|
899
|
+
.turn-details {
|
900
|
+
background-color: white;
|
901
|
+
border-radius: 4px;
|
902
|
+
padding: 2rem;
|
903
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
904
|
+
}
|
905
|
+
|
906
|
+
.turn-details h2 {
|
907
|
+
margin-bottom: 1rem;
|
908
|
+
color: #2c3e50;
|
909
|
+
}
|
910
|
+
|
911
|
+
.placeholder {
|
912
|
+
color: #999;
|
913
|
+
text-align: center;
|
914
|
+
padding: 3rem;
|
915
|
+
}
|
916
|
+
|
917
|
+
.agent-section, .environment-section {
|
918
|
+
margin-bottom: 2rem;
|
919
|
+
}
|
920
|
+
|
921
|
+
.agent-section h3, .environment-section h3 {
|
922
|
+
color: #2c3e50;
|
923
|
+
margin-bottom: 1rem;
|
924
|
+
}
|
925
|
+
|
926
|
+
.message-box {
|
927
|
+
background-color: #f8f9fa;
|
928
|
+
padding: 1rem;
|
929
|
+
border-radius: 4px;
|
930
|
+
margin-bottom: 1rem;
|
931
|
+
}
|
932
|
+
|
933
|
+
.message-box .role {
|
934
|
+
font-weight: bold;
|
935
|
+
color: #2c3e50;
|
936
|
+
margin-bottom: 0.5rem;
|
937
|
+
}
|
938
|
+
|
939
|
+
.actions-box {
|
940
|
+
background-color: #e3f2fd;
|
941
|
+
padding: 1rem;
|
942
|
+
border-radius: 4px;
|
943
|
+
margin-bottom: 1rem;
|
944
|
+
font-family: monospace;
|
945
|
+
}
|
946
|
+
|
947
|
+
.env-step {
|
948
|
+
background-color: #f8f9fa;
|
949
|
+
padding: 1rem;
|
950
|
+
border-radius: 4px;
|
951
|
+
margin-bottom: 1rem;
|
952
|
+
border-left: 4px solid #3498db;
|
953
|
+
}
|
954
|
+
|
955
|
+
.env-step h4 {
|
956
|
+
color: #2c3e50;
|
957
|
+
margin-bottom: 0.5rem;
|
958
|
+
}
|
959
|
+
|
960
|
+
.env-image {
|
961
|
+
width: 256px;
|
962
|
+
height: 256px;
|
963
|
+
border-radius: 4px;
|
964
|
+
margin-top: 1rem;
|
965
|
+
image-rendering: pixelated;
|
966
|
+
border: 2px solid #ddd;
|
967
|
+
}
|
968
|
+
|
969
|
+
.stats-grid {
|
970
|
+
display: inline-flex;
|
971
|
+
gap: 1rem;
|
972
|
+
margin-top: 0.5rem;
|
973
|
+
margin-bottom: 0.5rem;
|
974
|
+
}
|
975
|
+
|
976
|
+
.stat-item {
|
977
|
+
background-color: white;
|
978
|
+
padding: 0.25rem 0.5rem;
|
979
|
+
border-radius: 3px;
|
980
|
+
text-align: center;
|
981
|
+
border: 1px solid #e0e0e0;
|
982
|
+
font-size: 0.75rem;
|
983
|
+
}
|
984
|
+
|
985
|
+
.stat-label {
|
986
|
+
font-size: 0.7rem;
|
987
|
+
color: #666;
|
988
|
+
margin-right: 0.25rem;
|
989
|
+
}
|
990
|
+
|
991
|
+
.stat-value {
|
992
|
+
font-size: 0.8rem;
|
993
|
+
font-weight: bold;
|
994
|
+
color: #2c3e50;
|
995
|
+
display: inline;
|
996
|
+
}
|
997
|
+
|
998
|
+
.metadata-item {
|
999
|
+
margin-bottom: 0.5rem;
|
1000
|
+
}
|
1001
|
+
|
1002
|
+
.metadata-label {
|
1003
|
+
font-weight: bold;
|
1004
|
+
color: #666;
|
1005
|
+
}
|
1006
|
+
|
1007
|
+
.achievement-badge {
|
1008
|
+
display: inline-block;
|
1009
|
+
background-color: #27ae60;
|
1010
|
+
color: white;
|
1011
|
+
padding: 0.25rem 0.5rem;
|
1012
|
+
border-radius: 3px;
|
1013
|
+
font-size: 0.85rem;
|
1014
|
+
margin-right: 0.5rem;
|
1015
|
+
margin-bottom: 0.5rem;
|
1016
|
+
}
|
1017
|
+
|
1018
|
+
.images-row {
|
1019
|
+
display: flex;
|
1020
|
+
gap: 1rem;
|
1021
|
+
overflow-x: auto;
|
1022
|
+
padding: 1rem 0;
|
1023
|
+
}
|
1024
|
+
|
1025
|
+
.image-container {
|
1026
|
+
text-align: center;
|
1027
|
+
flex-shrink: 0;
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
.image-caption {
|
1031
|
+
margin-top: 0.5rem;
|
1032
|
+
font-size: 0.85rem;
|
1033
|
+
color: #666;
|
1034
|
+
font-family: monospace;
|
1035
|
+
}
|
1036
|
+
|
1037
|
+
@media (max-width: 768px) {
|
1038
|
+
.main-container {
|
1039
|
+
flex-direction: column;
|
1040
|
+
}
|
1041
|
+
|
1042
|
+
.sidebar {
|
1043
|
+
width: 100%;
|
1044
|
+
height: 200px;
|
1045
|
+
border-right: none;
|
1046
|
+
border-bottom: 1px solid #ddd;
|
1047
|
+
}
|
1048
|
+
|
1049
|
+
.question-reward {
|
1050
|
+
grid-template-columns: 1fr;
|
1051
|
+
}
|
1052
|
+
}"""
|
1053
|
+
|
1054
|
+
# Create viewer.js
|
1055
|
+
js_content = """let currentTrace = null;
|
1056
|
+
let currentTurnIndex = null;
|
1057
|
+
|
1058
|
+
// Action mapping
|
1059
|
+
const ACTION_NAMES = {
|
1060
|
+
-1: 'initial_state',
|
1061
|
+
0: 'noop',
|
1062
|
+
1: 'move_left',
|
1063
|
+
2: 'move_right',
|
1064
|
+
3: 'move_up',
|
1065
|
+
4: 'move_down',
|
1066
|
+
5: 'do',
|
1067
|
+
6: 'sleep',
|
1068
|
+
7: 'place_stone',
|
1069
|
+
8: 'place_table',
|
1070
|
+
9: 'place_furnace',
|
1071
|
+
10: 'place_plant',
|
1072
|
+
11: 'make_wood_pickaxe',
|
1073
|
+
12: 'make_stone_pickaxe',
|
1074
|
+
13: 'make_iron_pickaxe',
|
1075
|
+
14: 'make_wood_sword',
|
1076
|
+
15: 'make_stone_sword',
|
1077
|
+
16: 'make_iron_sword'
|
1078
|
+
};
|
1079
|
+
|
1080
|
+
// Load available traces
|
1081
|
+
async function loadTraceList() {
|
1082
|
+
try {
|
1083
|
+
const response = await fetch('/api/traces');
|
1084
|
+
const traces = await response.json();
|
1085
|
+
|
1086
|
+
const select = document.getElementById('trace-select');
|
1087
|
+
select.innerHTML = '';
|
1088
|
+
|
1089
|
+
traces.forEach(trace => {
|
1090
|
+
const option = document.createElement('option');
|
1091
|
+
option.value = trace.id;
|
1092
|
+
option.textContent = `${trace.model_name} - ${trace.difficulty} - ${trace.id.substring(0, 8)}`;
|
1093
|
+
select.appendChild(option);
|
1094
|
+
});
|
1095
|
+
|
1096
|
+
if (traces.length > 0) {
|
1097
|
+
loadTrace(traces[0].id);
|
1098
|
+
}
|
1099
|
+
} catch (error) {
|
1100
|
+
console.error('Failed to load traces:', error);
|
1101
|
+
}
|
1102
|
+
}
|
1103
|
+
|
1104
|
+
// Load specific trace
|
1105
|
+
async function loadTrace(traceId) {
|
1106
|
+
try {
|
1107
|
+
const response = await fetch(`/api/trace/${traceId}`);
|
1108
|
+
const data = await response.json();
|
1109
|
+
|
1110
|
+
currentTrace = data;
|
1111
|
+
currentTurnIndex = null;
|
1112
|
+
|
1113
|
+
displayTraceInfo();
|
1114
|
+
displayTimeline();
|
1115
|
+
displayQuestionAndReward();
|
1116
|
+
clearTurnDetails();
|
1117
|
+
} catch (error) {
|
1118
|
+
console.error('Failed to load trace:', error);
|
1119
|
+
}
|
1120
|
+
}
|
1121
|
+
|
1122
|
+
// Display trace metadata
|
1123
|
+
function displayTraceInfo() {
|
1124
|
+
const metadataDiv = document.getElementById('trace-metadata');
|
1125
|
+
const metadata = currentTrace.trace.metadata;
|
1126
|
+
|
1127
|
+
metadataDiv.innerHTML = `
|
1128
|
+
<div class="metadata-item">
|
1129
|
+
<span class="metadata-label">Model:</span> ${metadata.model_name}
|
1130
|
+
</div>
|
1131
|
+
<div class="metadata-item">
|
1132
|
+
<span class="metadata-label">Difficulty:</span> ${metadata.difficulty}
|
1133
|
+
</div>
|
1134
|
+
<div class="metadata-item">
|
1135
|
+
<span class="metadata-label">Seed:</span> ${metadata.seed}
|
1136
|
+
</div>
|
1137
|
+
<div class="metadata-item">
|
1138
|
+
<span class="metadata-label">Max Turns:</span> ${metadata.max_turns}
|
1139
|
+
</div>
|
1140
|
+
`;
|
1141
|
+
}
|
1142
|
+
|
1143
|
+
// Display timeline
|
1144
|
+
function displayTimeline() {
|
1145
|
+
const timeline = document.getElementById('timeline');
|
1146
|
+
timeline.innerHTML = '';
|
1147
|
+
|
1148
|
+
currentTrace.trace.partition.forEach((partition, index) => {
|
1149
|
+
const event = partition.events[0];
|
1150
|
+
const metadata = event.event_metadata;
|
1151
|
+
|
1152
|
+
const item = document.createElement('div');
|
1153
|
+
item.className = 'timeline-item';
|
1154
|
+
item.innerHTML = `
|
1155
|
+
<div class="turn-number">Turn ${metadata.turn_number}</div>
|
1156
|
+
<div class="turn-stats">
|
1157
|
+
Actions: ${event.environment_compute_steps.length} |
|
1158
|
+
Achievements: ${metadata.total_achievements}
|
1159
|
+
${metadata.new_achievements.length > 0 ? ' (+' + metadata.new_achievements.length + ')' : ''}
|
1160
|
+
</div>
|
1161
|
+
`;
|
1162
|
+
|
1163
|
+
item.addEventListener('click', () => selectTurn(index));
|
1164
|
+
timeline.appendChild(item);
|
1165
|
+
});
|
1166
|
+
}
|
1167
|
+
|
1168
|
+
// Display question and reward
|
1169
|
+
function displayQuestionAndReward() {
|
1170
|
+
const question = currentTrace.dataset.questions[0];
|
1171
|
+
const reward = currentTrace.dataset.reward_signals[0];
|
1172
|
+
|
1173
|
+
document.getElementById('question-display').innerHTML = `
|
1174
|
+
<p><strong>Intent:</strong> ${question.intent}</p>
|
1175
|
+
<p><strong>Criteria:</strong> ${question.criteria}</p>
|
1176
|
+
`;
|
1177
|
+
|
1178
|
+
document.getElementById('reward-display').innerHTML = `
|
1179
|
+
<p><strong>Hafner Score:</strong> ${reward.reward.toFixed(2)}%</p>
|
1180
|
+
<p><strong>Annotation:</strong> ${reward.annotation}</p>
|
1181
|
+
`;
|
1182
|
+
}
|
1183
|
+
|
1184
|
+
// Select turn
|
1185
|
+
function selectTurn(index) {
|
1186
|
+
currentTurnIndex = index;
|
1187
|
+
|
1188
|
+
// Update timeline selection
|
1189
|
+
document.querySelectorAll('.timeline-item').forEach((item, i) => {
|
1190
|
+
item.classList.toggle('active', i === index);
|
1191
|
+
});
|
1192
|
+
|
1193
|
+
// Display turn details
|
1194
|
+
displayTurnDetails();
|
1195
|
+
}
|
1196
|
+
|
1197
|
+
// Display turn details - SIMPLIFIED VERSION
|
1198
|
+
function displayTurnDetails() {
|
1199
|
+
if (currentTurnIndex === null) return;
|
1200
|
+
|
1201
|
+
const partition = currentTrace.trace.partition[currentTurnIndex];
|
1202
|
+
const event = partition.events[0];
|
1203
|
+
const agentStep = event.agent_compute_step;
|
1204
|
+
const envSteps = event.environment_compute_steps;
|
1205
|
+
|
1206
|
+
let html = '';
|
1207
|
+
|
1208
|
+
// Display actions planned
|
1209
|
+
if (agentStep.compute_output[0] && agentStep.compute_output[0].outputs) {
|
1210
|
+
const outputs = agentStep.compute_output[0].outputs;
|
1211
|
+
const actionNames = outputs.actions.map(idx => `${ACTION_NAMES[idx] || 'unknown'}`);
|
1212
|
+
html += `
|
1213
|
+
<div class="actions-box" style="margin-bottom: 1.5rem;">
|
1214
|
+
<strong>Turn ${event.event_metadata.turn_number} Actions:</strong> ${actionNames.join(' ā ')}
|
1215
|
+
</div>
|
1216
|
+
`;
|
1217
|
+
}
|
1218
|
+
|
1219
|
+
// Display all images in a row
|
1220
|
+
html += '<div class="images-row">';
|
1221
|
+
envSteps.forEach((step, i) => {
|
1222
|
+
const outputs = step.compute_output[0].outputs;
|
1223
|
+
const actionName = ACTION_NAMES[outputs.action_index] || 'unknown';
|
1224
|
+
|
1225
|
+
if (outputs.image_base64) {
|
1226
|
+
// For initial state, show "0. initial state", otherwise show action number
|
1227
|
+
const stepNumber = outputs.action_index === -1 ? 0 : i;
|
1228
|
+
html += `
|
1229
|
+
<div class="image-container">
|
1230
|
+
<img src="data:image/png;base64,${outputs.image_base64}" class="env-image" alt="Game state">
|
1231
|
+
<div class="image-caption">${stepNumber}. ${actionName}</div>
|
1232
|
+
</div>
|
1233
|
+
`;
|
1234
|
+
}
|
1235
|
+
});
|
1236
|
+
html += '</div>';
|
1237
|
+
|
1238
|
+
// New achievements
|
1239
|
+
if (event.event_metadata.new_achievements.length > 0) {
|
1240
|
+
html += '<div class="achievements-section" style="margin-top: 1rem;">';
|
1241
|
+
html += '<strong>New achievements: </strong>';
|
1242
|
+
event.event_metadata.new_achievements.forEach(ach => {
|
1243
|
+
html += `<span class="achievement-badge">${ach}</span>`;
|
1244
|
+
});
|
1245
|
+
html += '</div>';
|
1246
|
+
}
|
1247
|
+
|
1248
|
+
document.getElementById('turn-content').innerHTML = html;
|
1249
|
+
}
|
1250
|
+
|
1251
|
+
// Clear turn details
|
1252
|
+
function clearTurnDetails() {
|
1253
|
+
document.getElementById('turn-content').innerHTML = '<p class="placeholder">Select a turn from the timeline</p>';
|
1254
|
+
}
|
1255
|
+
|
1256
|
+
// Event listeners
|
1257
|
+
document.getElementById('trace-select').addEventListener('change', (e) => {
|
1258
|
+
if (e.target.value) {
|
1259
|
+
loadTrace(e.target.value);
|
1260
|
+
}
|
1261
|
+
});
|
1262
|
+
|
1263
|
+
document.getElementById('refresh-btn').addEventListener('click', () => {
|
1264
|
+
loadTraceList();
|
1265
|
+
});
|
1266
|
+
|
1267
|
+
// Initial load
|
1268
|
+
loadTraceList();"""
|
1269
|
+
|
1270
|
+
# Save files
|
1271
|
+
with open(self.viewer_dir / "index.html", "w") as f:
|
1272
|
+
f.write(html_content)
|
1273
|
+
|
1274
|
+
with open(self.viewer_dir / "style.css", "w") as f:
|
1275
|
+
f.write(css_content)
|
1276
|
+
|
1277
|
+
with open(self.viewer_dir / "viewer.js", "w") as f:
|
1278
|
+
f.write(js_content)
|
1279
|
+
|
1280
|
+
|
1281
|
+
# Global variable to store current eval directory
|
1282
|
+
_current_eval_dir = None
|
1283
|
+
|
1284
|
+
|
1285
|
+
def set_current_eval_dir(eval_dir: Path):
|
1286
|
+
"""Set the current evaluation directory for the viewer."""
|
1287
|
+
global _current_eval_dir
|
1288
|
+
_current_eval_dir = eval_dir
|
1289
|
+
|
1290
|
+
|
1291
|
+
# FastAPI app for viewer
|
1292
|
+
app = FastAPI()
|
1293
|
+
|
1294
|
+
|
1295
|
+
@app.get("/api/traces")
|
1296
|
+
async def get_traces():
|
1297
|
+
"""Get list of available traces."""
|
1298
|
+
global _current_eval_dir
|
1299
|
+
if _current_eval_dir is None:
|
1300
|
+
return []
|
1301
|
+
|
1302
|
+
traces_dir = _current_eval_dir / "traces"
|
1303
|
+
if not traces_dir.exists():
|
1304
|
+
return []
|
1305
|
+
|
1306
|
+
traces = []
|
1307
|
+
for trace_file in traces_dir.glob("*.json"):
|
1308
|
+
try:
|
1309
|
+
with open(trace_file, "r") as f:
|
1310
|
+
data = json.load(f)
|
1311
|
+
trace_meta = data["trace"]["metadata"]
|
1312
|
+
traces.append(
|
1313
|
+
{
|
1314
|
+
"id": trace_file.stem,
|
1315
|
+
"model_name": trace_meta["model_name"],
|
1316
|
+
"difficulty": trace_meta["difficulty"],
|
1317
|
+
"seed": trace_meta["seed"],
|
1318
|
+
}
|
1319
|
+
)
|
1320
|
+
except Exception as e:
|
1321
|
+
print(f"Error loading trace {trace_file}: {e}")
|
1322
|
+
|
1323
|
+
return sorted(traces, key=lambda x: x["id"])
|
1324
|
+
|
1325
|
+
|
1326
|
+
@app.get("/api/trace/{trace_id}")
|
1327
|
+
async def get_trace(trace_id: str):
|
1328
|
+
"""Get specific trace data."""
|
1329
|
+
global _current_eval_dir
|
1330
|
+
if _current_eval_dir is None:
|
1331
|
+
raise HTTPException(status_code=404, detail="No evaluation directory set")
|
1332
|
+
|
1333
|
+
trace_file = _current_eval_dir / "traces" / f"{trace_id}.json"
|
1334
|
+
if not trace_file.exists():
|
1335
|
+
raise HTTPException(status_code=404, detail="Trace not found")
|
1336
|
+
|
1337
|
+
with open(trace_file, "r") as f:
|
1338
|
+
return json.load(f)
|
1339
|
+
|
1340
|
+
|
1341
|
+
@app.get("/api/eval_info")
|
1342
|
+
async def get_eval_info():
|
1343
|
+
"""Get evaluation metadata."""
|
1344
|
+
global _current_eval_dir
|
1345
|
+
if _current_eval_dir is None:
|
1346
|
+
return {"error": "No evaluation directory set"}
|
1347
|
+
|
1348
|
+
summary_file = _current_eval_dir / "evaluation_summary.json"
|
1349
|
+
if summary_file.exists():
|
1350
|
+
with open(summary_file, "r") as f:
|
1351
|
+
return json.load(f)
|
1352
|
+
return {"error": "No evaluation summary found"}
|
1353
|
+
|
1354
|
+
|
1355
|
+
# Convenience function for running evaluation
|
1356
|
+
async def run_full_crafter_eval(
|
1357
|
+
model_names: List[str],
|
1358
|
+
difficulties: List[str] = ["easy", "hard"],
|
1359
|
+
num_trajectories: int = 3,
|
1360
|
+
max_turns: int = 30,
|
1361
|
+
capture_images: bool = True,
|
1362
|
+
launch_viewer: bool = True,
|
1363
|
+
output_dir: Optional[str] = None,
|
1364
|
+
) -> Dict[str, Any]:
|
1365
|
+
"""Run full Crafter evaluation with traces and viewer."""
|
1366
|
+
|
1367
|
+
framework = FullCrafterEvalFramework(capture_images=capture_images, output_dir=output_dir)
|
1368
|
+
report = await framework.run_evaluation(
|
1369
|
+
model_names=model_names,
|
1370
|
+
difficulties=difficulties,
|
1371
|
+
num_trajectories_per_condition=num_trajectories,
|
1372
|
+
max_turns=max_turns,
|
1373
|
+
)
|
1374
|
+
|
1375
|
+
framework.print_report(report)
|
1376
|
+
|
1377
|
+
if launch_viewer:
|
1378
|
+
print(f"\nš Evaluation saved to: {framework.output_dir}")
|
1379
|
+
print("š Launching viewer at http://localhost:8999")
|
1380
|
+
print(" Press Ctrl+C to stop the viewer")
|
1381
|
+
|
1382
|
+
# Set the current eval directory for the viewer
|
1383
|
+
set_current_eval_dir(framework.output_dir)
|
1384
|
+
|
1385
|
+
# Mount static files from the viewer directory
|
1386
|
+
app.mount(
|
1387
|
+
"/",
|
1388
|
+
StaticFiles(directory=str(framework.viewer_dir), html=True),
|
1389
|
+
name="viewer",
|
1390
|
+
)
|
1391
|
+
|
1392
|
+
# Run viewer
|
1393
|
+
config = uvicorn.Config(app, host="0.0.0.0", port=8999, log_level="error")
|
1394
|
+
server = uvicorn.Server(config)
|
1395
|
+
await server.serve()
|
1396
|
+
|
1397
|
+
return report
|
1398
|
+
|
1399
|
+
|
1400
|
+
if __name__ == "__main__":
|
1401
|
+
# Example usage
|
1402
|
+
async def main():
|
1403
|
+
await run_full_crafter_eval(
|
1404
|
+
model_names=["gpt-4.1-mini"],
|
1405
|
+
difficulties=["easy"],
|
1406
|
+
num_trajectories=10,
|
1407
|
+
max_turns=50,
|
1408
|
+
capture_images=True,
|
1409
|
+
launch_viewer=True,
|
1410
|
+
)
|
1411
|
+
|
1412
|
+
asyncio.run(main())
|