|
1 | 1 | """ |
2 | 2 | Logger for RLM iterations. |
3 | 3 |
|
4 | | -Writes RLMIteration data to JSON-lines files for analysis and debugging. |
| 4 | +Captures run metadata and iterations in memory so they can be attached to |
| 5 | +RLMChatCompletion.metadata. Optionally writes the same data to JSON-lines files. |
5 | 6 | """ |
6 | 7 |
|
7 | 8 | import json |
|
13 | 14 |
|
14 | 15 |
|
15 | 16 | class RLMLogger: |
16 | | - """Logger that writes RLMIteration data to a JSON-lines file.""" |
| 17 | + """ |
| 18 | + Captures trajectory (run metadata + iterations) for each completion. |
| 19 | + By default only captures in memory; set log_dir to also save to disk. |
17 | 20 |
|
18 | | - def __init__(self, log_dir: str, file_name: str = "rlm"): |
19 | | - self.log_dir = log_dir |
20 | | - os.makedirs(log_dir, exist_ok=True) |
21 | | - |
22 | | - timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") |
23 | | - run_id = str(uuid.uuid4())[:8] |
24 | | - self.log_file_path = os.path.join(log_dir, f"{file_name}_{timestamp}_{run_id}.jsonl") |
| 21 | + - log_dir=None: trajectory is available via get_trajectory() and can be |
| 22 | + attached to RLMChatCompletion.metadata (no disk write). |
| 23 | + - log_dir="path": same capture plus appends to a JSONL file per run. |
| 24 | + """ |
25 | 25 |
|
| 26 | + def __init__(self, log_dir: str | None = None, file_name: str = "rlm"): |
| 27 | + self._save_to_disk = log_dir is not None |
| 28 | + self.log_dir = log_dir |
| 29 | + self.log_file_path: str | None = None |
| 30 | + if self._save_to_disk and log_dir: |
| 31 | + os.makedirs(log_dir, exist_ok=True) |
| 32 | + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") |
| 33 | + run_id = str(uuid.uuid4())[:8] |
| 34 | + self.log_file_path = os.path.join(log_dir, f"{file_name}_{timestamp}_{run_id}.jsonl") |
| 35 | + |
| 36 | + self._run_metadata: dict | None = None |
| 37 | + self._iterations: list[dict] = [] |
26 | 38 | self._iteration_count = 0 |
27 | 39 | self._metadata_logged = False |
28 | 40 |
|
29 | | - def log_metadata(self, metadata: RLMMetadata): |
30 | | - """Log RLM metadata as the first entry in the file.""" |
| 41 | + def log_metadata(self, metadata: RLMMetadata) -> None: |
| 42 | + """Capture run metadata (and optionally write to file).""" |
31 | 43 | if self._metadata_logged: |
32 | 44 | return |
33 | 45 |
|
34 | | - entry = { |
35 | | - "type": "metadata", |
36 | | - "timestamp": datetime.now().isoformat(), |
37 | | - **metadata.to_dict(), |
38 | | - } |
39 | | - |
40 | | - with open(self.log_file_path, "a") as f: |
41 | | - json.dump(entry, f) |
42 | | - f.write("\n") |
43 | | - |
| 46 | + self._run_metadata = metadata.to_dict() |
44 | 47 | self._metadata_logged = True |
45 | 48 |
|
46 | | - def log(self, iteration: RLMIteration): |
47 | | - """Log an RLMIteration to the file.""" |
| 49 | + if self._save_to_disk and self.log_file_path: |
| 50 | + entry = { |
| 51 | + "type": "metadata", |
| 52 | + "timestamp": datetime.now().isoformat(), |
| 53 | + **self._run_metadata, |
| 54 | + } |
| 55 | + with open(self.log_file_path, "a") as f: |
| 56 | + json.dump(entry, f) |
| 57 | + f.write("\n") |
| 58 | + |
| 59 | + def log(self, iteration: RLMIteration) -> None: |
| 60 | + """Capture one iteration (and optionally append to file).""" |
48 | 61 | self._iteration_count += 1 |
49 | | - |
50 | 62 | entry = { |
51 | 63 | "type": "iteration", |
52 | 64 | "iteration": self._iteration_count, |
53 | 65 | "timestamp": datetime.now().isoformat(), |
54 | 66 | **iteration.to_dict(), |
55 | 67 | } |
| 68 | + self._iterations.append(entry) |
| 69 | + |
| 70 | + if self._save_to_disk and self.log_file_path: |
| 71 | + with open(self.log_file_path, "a") as f: |
| 72 | + json.dump(entry, f) |
| 73 | + f.write("\n") |
56 | 74 |
|
57 | | - with open(self.log_file_path, "a") as f: |
58 | | - json.dump(entry, f) |
59 | | - f.write("\n") |
| 75 | + def clear_iterations(self) -> None: |
| 76 | + """Reset iterations for the next completion (trajectory is per completion).""" |
| 77 | + self._iterations = [] |
| 78 | + self._iteration_count = 0 |
| 79 | + |
| 80 | + def get_trajectory(self) -> dict | None: |
| 81 | + """Return captured run_metadata + iterations for the current completion, or None if no metadata yet.""" |
| 82 | + if self._run_metadata is None: |
| 83 | + return None |
| 84 | + return { |
| 85 | + "run_metadata": self._run_metadata, |
| 86 | + "iterations": list(self._iterations), |
| 87 | + } |
60 | 88 |
|
61 | 89 | @property |
62 | 90 | def iteration_count(self) -> int: |
|
0 commit comments