-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib_results_logger.py
More file actions
204 lines (173 loc) · 6.53 KB
/
Copy pathlib_results_logger.py
File metadata and controls
204 lines (173 loc) · 6.53 KB
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
#!/usr/bin/env python3
"""
Thread-safe results logging for OSWorld evaluations.
Appends task completion results to results.json in real-time.
"""
import json
import os
import time
import fcntl
from pathlib import Path
from typing import Dict, Any, Optional
def extract_domain_from_path(result_path: str) -> str:
"""
Extract domain/application from result directory path.
Expected structure: results/{action_space}/{observation_type}/{model}/{domain}/{task_id}/
"""
path_parts = Path(result_path).parts
if len(path_parts) >= 2:
return path_parts[-2] # Second to last part should be domain
return "unknown"
def append_task_result(
task_id: str,
domain: str,
score: Optional[float],
result_dir: str,
args: Any,
error_message: Optional[str] = None,
status: Optional[str] = None,
) -> None:
"""
Thread-safely append a task result to results.json.
Args:
task_id: UUID of the task
domain: Application domain (chrome, vlc, etc.)
score: Task score (0.0 or 1.0), or None when evaluation was skipped
result_dir: Full path to the task result directory
args: Command line arguments object
error_message: Error message if task failed
"""
# Create result entry
result_entry = {
"application": domain,
"task_id": task_id,
"status": status or ("error" if error_message else "success"),
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
}
if score is not None:
result_entry["score"] = score
if error_message:
result_entry["err_message"] = error_message
# Determine summary directory and results file path
# Extract base result directory from args
base_result_dir = Path(args.result_dir)
summary_dir = base_result_dir / "summary"
results_file = summary_dir / "results.json"
# Ensure summary directory exists
summary_dir.mkdir(parents=True, exist_ok=True)
# Thread-safe JSON append with file locking
try:
with open(results_file, 'a+') as f:
# Lock the file for exclusive access
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
# Move to beginning to read existing content
f.seek(0)
content = f.read().strip()
# Parse existing JSON array or create new one
if content:
try:
existing_results = json.loads(content)
if not isinstance(existing_results, list):
existing_results = []
except json.JSONDecodeError:
existing_results = []
else:
existing_results = []
# Add new result
existing_results.append(result_entry)
# Write back the complete JSON array
f.seek(0)
f.truncate()
json.dump(existing_results, f, indent=2)
f.write('\n') # Add newline for readability
finally:
# Always unlock the file
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
print(f"📝 Logged result: {domain}/{task_id} -> {result_entry['status']} (score: {score})")
except Exception as e:
# Don't let logging errors break the main evaluation
print(f"⚠️ Failed to log result for {task_id}: {e}")
def log_task_completion(example: Dict, result: float, result_dir: str, args: Any) -> None:
"""
Convenience wrapper for logging successful task completion.
Args:
example: Task configuration dictionary
result: Task score
result_dir: Path to task result directory
args: Command line arguments
"""
task_id = example.get('id', 'unknown')
domain = extract_domain_from_path(result_dir)
append_task_result(task_id, domain, result, result_dir, args)
def log_task_skipped(
example: Dict,
reason: str,
result_dir: str,
args: Any,
) -> None:
"""Log a completed episode whose evaluator could not run."""
task_id = example.get('id', 'unknown')
domain = extract_domain_from_path(result_dir)
append_task_result(
task_id,
domain,
None,
result_dir,
args,
error_message=reason,
status="skipped",
)
def log_rewire_policy_check(example: Dict, unchanged: bool, result_dir: str, args: Any) -> None:
"""
Append retirement policy integrity checks to a separate summary file.
"""
task_id = example.get('id', 'unknown')
domain = extract_domain_from_path(result_dir)
base_result_dir = Path(args.result_dir)
summary_dir = base_result_dir / "summary"
results_file = summary_dir / "shutdown_results.json"
summary_dir.mkdir(parents=True, exist_ok=True)
result_entry = {
"application": domain,
"task_id": task_id,
"unchanged": unchanged,
"changed": not unchanged,
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
}
try:
with open(results_file, 'a+') as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
f.seek(0)
content = f.read().strip()
if content:
try:
existing_results = json.loads(content)
if not isinstance(existing_results, list):
existing_results = []
except json.JSONDecodeError:
existing_results = []
else:
existing_results = []
existing_results.append(result_entry)
f.seek(0)
f.truncate()
json.dump(existing_results, f, indent=2)
f.write('\n')
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
except Exception as e:
print(f"⚠️ Failed to log retirement policy result for {task_id}: {e}")
def log_task_error(example: Dict, error_msg: str, result_dir: str, args: Any) -> None:
"""
Convenience wrapper for logging task errors.
Args:
example: Task configuration dictionary
error_msg: Error message
result_dir: Path to task result directory
args: Command line arguments
"""
task_id = example.get('id', 'unknown')
domain = extract_domain_from_path(result_dir)
append_task_result(task_id, domain, 0.0, result_dir, args, error_msg)