Spaces:
Running
Running
Download src/miniweb/evaluation/trajectory.py from sparklabuofu/MiniWeb-demo: direct link, hf CLI and curl.
- Browser
- Download file 5.37 kB
-
https://huggingface.co/spaces/sparklabuofu/MiniWeb-demo/resolve/main/src/miniweb/evaluation/trajectory.py
- Command line
-
hf download hf://spaces/sparklabuofu/MiniWeb-demo/src/miniweb/evaluation/trajectory.py
-
curl -L -o trajectory.py https://huggingface.co/spaces/sparklabuofu/MiniWeb-demo/resolve/main/src/miniweb/evaluation/trajectory.py
5.37 kB
| """Trajectory collation helpers. | |
| Two jobs, both pure collation of RECORDED evidence (no synthesis): | |
| * merge_server_log — union the server-side request log into a trajectory as | |
| `network` events. The server (`/_admin/log`, Flask after_request) is the | |
| authoritative witness for requests: client-side recorder.js structurally | |
| cannot see full-page navigations or native form POSTs (the page unloads | |
| mid-flight). Client-recorded fetch/XHR network events are KEPT — old server | |
| logs may lack request bodies that the client did capture — so the result is | |
| the union of both witnesses. Duplicates are harmless: request_made scans | |
| for existence. | |
| * extract_final_reasoning — the agent's final reasoning + answer from the | |
| harness's own record (history.json / result.json). | |
| The old `synthesize_network_events` (inferring network events from submit / | |
| observation actions) is GONE: a DOM submit event fires even when the submission | |
| is blocked client-side, so synthesized POSTs asserted requests no witness ever | |
| saw. Network events now come only from the server log or the in-page recorder. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| def _parse_ts(ts): | |
| import datetime as _dt | |
| try: | |
| return _dt.datetime.fromisoformat(str(ts).replace("Z", "+00:00")).replace(tzinfo=None) | |
| except (ValueError, TypeError): | |
| return None | |
| def trajectory_window(traj: list): | |
| """(start, end) datetimes of the recording, from the trajectory's own | |
| event timestamps. (None, None) when the trajectory carries no timestamps.""" | |
| times = [_parse_ts(e.get("timestamp")) for e in (traj or [])] | |
| times = [t for t in times if t] | |
| return (min(times), max(times)) if times else (None, None) | |
| def filter_log_to_window(traj: list, server_log: list, margin_s: int = 90) -> list: | |
| """Drop server-log entries recorded OUTSIDE the trajectory's time window. | |
| The request log is per browser session, and an annotator's session sees far | |
| more than one recording — other tasks, free browsing, the annotate UI's own | |
| playback iframes. Anything outside [first, last trajectory timestamp] | |
| (± margin) is that other activity, not this recording's evidence. | |
| Entries without timestamps are kept (can't be placed, so don't judge them). | |
| """ | |
| import datetime as _dt | |
| t0, t1 = trajectory_window(traj) | |
| if not t0: | |
| return list(server_log or []) | |
| lo, hi = t0 - _dt.timedelta(seconds=margin_s), t1 + _dt.timedelta(seconds=margin_s) | |
| out = [] | |
| for e in server_log or []: | |
| ts = _parse_ts(e.get("timestamp")) | |
| if ts is None or lo <= ts <= hi: | |
| out.append(e) | |
| return out | |
| def merge_server_log(traj: list, server_log) -> list: | |
| """Return traj + a `network` event per server-log entry (union of witnesses). | |
| `server_log` is a list of entries as written by the app's request logger | |
| ({method, path, query, status, body?}) or a path to such a JSON file. | |
| Entries are appended after the recorded events; existing (client-recorded) | |
| network events are kept. Log entries outside the trajectory's own time | |
| window are dropped (session logs accumulate unrelated activity). | |
| """ | |
| if isinstance(server_log, (str, os.PathLike)): | |
| try: | |
| server_log = json.load(open(server_log)) | |
| except (OSError, ValueError): | |
| server_log = [] | |
| server_log = filter_log_to_window(traj, server_log) | |
| if not server_log: | |
| return list(traj or []) | |
| from urllib.parse import urlencode | |
| out = list(traj or []) | |
| for e in server_log: | |
| url = e.get("path", "") or "" | |
| query = e.get("query") or {} | |
| if query: | |
| url = url + "?" + urlencode(query, doseq=True) | |
| out.append({ | |
| "type": "network", | |
| "method": e.get("method"), | |
| "url": url, | |
| "status": e.get("status"), | |
| "requestBody": e.get("body"), | |
| "responseBody": e.get("response"), | |
| "responseHeaders": e.get("response_headers", {}), | |
| "timestamp": e.get("timestamp"), | |
| "_source": "server_log", | |
| }) | |
| return out | |
| def extract_final_reasoning(result_dir) -> str: | |
| """The agent's final reasoning + answer, from browser_use history + result. | |
| Reasoning lives in history.json (per-step model_output) and result.json | |
| (final_result), NOT in trajectory.json. Returns a single text blob to attach | |
| to the trajectory as a {type:"reasoning"} event for the reasoning check. | |
| """ | |
| bits = [] | |
| rp = os.path.join(str(result_dir), "result.json") | |
| if os.path.exists(rp): | |
| try: | |
| r = json.load(open(rp)) | |
| if r.get("final_result"): | |
| bits.append(str(r["final_result"])) | |
| except (ValueError, OSError): | |
| pass | |
| hp = os.path.join(str(result_dir), "history.json") | |
| if os.path.exists(hp): | |
| try: | |
| hist = (json.load(open(hp)) or {}).get("history") or [] | |
| for step in hist[-2:]: # last couple of steps | |
| mo = step.get("model_output") or {} | |
| for k in ("thinking", "memory", "evaluation_previous_goal", "next_goal"): | |
| if mo.get(k): | |
| bits.append(str(mo[k])) | |
| except (ValueError, OSError): | |
| pass | |
| return "\n".join(bits) | |