Implement a custom model for evaluation
maingenerate function that accepts a prompt and returns generated outputs. The required interface can be found in action_prediction/metric.py at line 328.repository·main·Indexed 21 days ago
https://github.com/osu-nlp-group/mind2webA dataset and framework for training and evaluating generalist web agents. Mind2Web provides real-world web tasks with complex action sequences, HTML snapshots, and traces. It includes tools for fine-tuning and evaluating candidate generation models (using DeBERTa) and action prediction models (using T5 or LLMs like GPT-3.5-turbo), along with utilities for DOM tree representation and multi-choice dataset formatting.
generate function that accepts a prompt and returns generated outputs. The required interface can be found in action_prediction/metric.py at line 328.The dataset is organized into training and various test splits (Cross Task, Cross Website, and Cross Domain). After cloning the training data and unzipping the test data, the directory structure should follow this pattern:
Mind2Web
├── data
│ ├── train
│ │ └── train_*.json
│ ├── test_task
│ │ └── test_task_*.json
│ ├── test_website
│ │ └── test_website_*.json
│ └── test_domain
│ └── test_domain_*.json
└── ...To evaluate using Large Language Models (LLMs), you need the dataset, candidate generation results, and an OPENAI_API_KEY. The evaluation uses a multi-choice QA formulation where the model selects the target element from a list of candidates.
export OPENAI_API_KEY={YOUR_API_KEY}src/action_prediction/llm_prompt.json.Run the evaluation script using the following command:
export OPENAI_API_KEY={YOUR_API_KEY}
python action_prediction/evaluate_llm.py \
+output_path={OUTPUT_DIR} \
+llm_prompt=action_prediction/llm_prompt.json \
+llm=gpt-3.5-turbo \
+llm_rate_limit=60 \
+top_k=50To evaluate trained action prediction models (such as flan-t5-base, flan-t5-large, or flan-t5-xl), you must first configure the parameters in action_prediction/conf/config.yaml. The project uses Hydra for configuration management.
In action_prediction/conf/config.yaml, ensure the following are set:
data.data_path: The directory where you cloned the Mind2Web dataset.data.score_file: The path to the downloaded pickle file containing candidate generation model outputs.hydra.run.dir: The working directory for log files.Run the evaluation script using the following command structure:
python action_prediction/evaluate.py \
+model_path={MODEL_PATH_OR_NAME} \
model=flan-t5-large \
+output_path={OUTPUT_PATH} \
+top_k=50Mind2Web provides a training set available directly on Huggingface and a protected test set to prevent data contamination.
mind2web.Important: Do not redistribute unzipped data files online. The dataset contains canary GUIDs to detect unauthorized use in training corpora.
git clone git@hf.co:datasets/osunlp/Mind2WebFine-tuning is performed using a seq2seq T5 model implementation based on Huggingface Transformers's Seq2SeqTrainer. Configuration is managed via action_prediction/conf/config.yaml. Checkpoints are saved in the workdir defined by Hydra.
Use torchrun for distributed training. Example for 4 GPUs:
torchrun --nproc-per-node 4 --master_port=8004\
action_prediction/train.py\
model=flan-t5-large\
train.per_device_train_batch_size=8\
train.gradient_accumulation_steps=1\
train.fsdp=True\
train.num_gpus=4\
train.epoch=5\
run_id="full"The raw dump contains high-fidelity data including Playwright trace files, network traffic (.har), video recordings, and various page snapshots (.mhtml).
Due to its size, the raw dump is shared via Globus. You can access the collection via the following link:
https://app.globus.org/file-manager?origin_id=32e6b738-a0b0-47f8-b475-26bf1c5ebf19
Key files in the raw dump structure:
trace.zip: Playwright trace file (inspect via trace viewer).session.har.zip: Network traffic for replaying.videos/: WebM recordings of the annotation process.dom_content.json: DOM snapshots extracted via DOMSnapshot.captureSnapshot.screenshot.json: Page screenshots stored as base64 strings.{action_id}_before/after.mhtml: Standalone MHTML snapshots of the page state.Because different annotators might select slightly different nodes for the same logical action, the project provides heuristics to normalize the selection:
move_up_annotation: Finds the closest clickable ancestor that contains the target interaction position.move_down_annotation: Finds all descendants within the target bounding box.set_alternative: Sets data_pw_testid_buckeye_alt on the normalized ancestor and data_pw_testid_buckeye_alt_fuzzy on the candidate descendants to allow for flexible matching.The project uses a build_dom_tree function to reconstruct a full HTML tree from a DOMSnapshot.NodeTreeSnapshot object. This is necessary because the raw data is stored in a compressed/indexed format (nodes, layout, and strings) rather than raw HTML.
Key steps in reconstruction:
str_mapping.nodes to create lxml.etree.Element objects.bounding_box_rect, text_value, input_value, and is_clickable.contentDocumentIndex.parentIndex.The processing pipeline includes logic to prune the DOM tree of nodes that do not contribute to user interaction or visibility.
is_visible: Checks if a node's bounding_box_rect is valid (non-negative coordinates and positive dimensions).is_empty: Determines if a node has no text, no meaningful attributes (from salient_attributes), and no children.a, button, select, option, and input are treated as visible if they contain text, even if their bounding box suggests otherwise.clean_empty_node: Iterates through the tree in reverse to safely remove nodes identified as empty or invisible.The src/action_prediction/evaluate.py script serves as a CLI entrypoint for evaluating action prediction models using the Hydra configuration framework. It supports both seq2seq and lm (causal language model) architectures and can perform evaluation in either multichoice or generation modes.
To run the evaluation, you must provide a Hydra configuration file (located in conf/config.yaml by default) that defines the model path, data paths, and evaluation parameters. The script uses AutoModel from the Transformers library to load models and ActionEvaluator classes to compute metrics.
Key configuration requirements:
model_path: Path to the model weights.model.arch: Either seq2seq or lm.model.mode: Either multichoice or generation mode.data.data_path: Path to the Mind2Web dataset.data.test_split_files: A dictionary mapping test keys to their respective split files.lm_template: (Required for lm architecture) A JSON file containing the language model template.top_k: The number of top candidates to consider during evaluation.python src/action_prediction/evaluate.py --config-name your_config_nameTo prepare high-quality training data, you can use get_candidates_for_actions to transform raw annotations into a structured format containing positive and negative candidates, along with their HTML representations.
This process involves:
clean_tree(): Removes non-salient attributes (keeping only alt, aria_label, role, etc.) and cleans text/SVG icon classes.prune_tree(): For each candidate, it keeps only the candidate node, its ancestors, a limited number of descendants (controlled by max_depth and max_children), and immediate siblings. This reduces noise and token count.ancestor_repr (the pruned tree up to the node) and subtree_repr (the node's own subtree) using get_tree_repr().Output Format: The function returns a list of dictionaries for each action, containing pos_candidates (those matching the action_uid via fuzzy matching) and neg_candidates (others).
def get_candidates_for_actions(x):
results = []
action_seq = [a["action_repr"] for a in x[1]]
for idx, a in enumerate(x[1]):
target_uid = a["uid"]
dom_tree = etree.fromstring(a["raw"])
candidates = get_candidates(dom_tree, target_uid)
all_candidate_ids = [c["attributes"]["backend_node_id"] for c in candidates]
dom_tree = clean_tree(dom_tree, all_candidate_ids)
for c in candidates:
node_tree = prune_tree(dom_tree, [c["attributes"]["backend_node_id"]])
c_node = node_tree.xpath("//*[@backend_node_id]")[0]
if c_node.getparent() is not None:
c_node.getparent().remove(c_node)
c["ancestor_repr"] = get_tree_repr(node_tree)
else:
c["ancestor_repr"] = ""
c["subtree_repr"] = get_tree_repr(c_node)
pos_candidates = [c for c in candidates if c["attributes"].get("data_pw_testid_buckeye_alt_fuzzy", "") == target_uid]
neg_candidates = [c for c in candidates if c["attributes"].get("data_pw_testid_buckeye_alt_fuzzy", "") != target_uid]
results.append({
"original_task": x[0]["task"],
"confirmed_task": x[0]["confirmed_task"],
"action_uid": a["uid"],
"annotation_id": x[0]["annotation_id"],
"annotator_id": x[0]["annotator_id"],
"session_id": x[0]["session_id"],
"website": x[0]["website"],
"pos_candidates": pos_candidates,
"neg_candidates": neg_candidates,
"raw": a["raw"].decode("utf8"),
"cleaned_html": etree.tostring(dom_tree).decode("utf8"),
"previous_actions": action_seq[:idx],
"current_action": action_seq[idx]
})
return (x[0], results)