GUI-World Evaluation

July 28, 2026 · View on GitHub

This package runs GUI-World benchmark inference and LLM-based judging. It supports only the provider adapters implemented in evaluation/providers: Azure OpenAI, Anthropic, Gemini, and Qwen for benchmark inference; OpenAI and Together for judging. Mixtral, VideoChat2, and ChatUniVi are not evaluation backends in this package.

Run every command below from the repository root.

Installation

Python 3.9 or newer is required. A separate virtual environment keeps these dependencies isolated from the Python 3.9-pinned GUI-Vid environment.

python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r evaluation/requirements.txt

The requirements include the supported provider SDKs, OpenCV, Pillow, NumPy, and pytest. Provider SDKs are imported lazily, so importing the package or using offline utilities does not require credentials for every provider.

Download the dataset

The canonical Hugging Face dataset is ONE-Lab/GUI-World. The optional Hugging Face CLI can download it into a local directory:

python -m pip install huggingface_hub
hf download ONE-Lab/GUI-World \
  --repo-type dataset \
  --local-dir GUI-World-data

The full dataset contains all six video collections and can require substantial download time and disk space. To download only the iOS benchmark annotation and videos, repeat --include for each glob:

hf download ONE-Lab/GUI-World \
  --repo-type dataset \
  --include 'Annotation/benchmark/IOS.jsonl' \
  --include 'IOS/**' \
  --local-dir GUI-World-data

Benchmark annotations are located at:

GUI-World-data/Annotation/benchmark/android.jsonl
GUI-World-data/Annotation/benchmark/IOS.jsonl
GUI-World-data/Annotation/benchmark/XR.jsonl
GUI-World-data/Annotation/benchmark/multi.jsonl
GUI-World-data/Annotation/benchmark/software.jsonl
GUI-World-data/Annotation/benchmark/website.jsonl

Pass the download directory itself as --video-root GUI-World-data. Relative video_path values in the JSONL are resolved below that directory; there is no separate keyframe or video root.

Providers and configuration

The operation matrix is intentionally explicit:

ProviderCanonical CLI nameBenchmarkJudge
Azure OpenAIazure-openaiYesNo
Anthropic ClaudeanthropicYesNo
Google GeminigeminiYesNo
Alibaba QwenqwenYesNo
OpenAIopenaiNoYes
TogethertogetherNoYes

Set the variables for only the provider you select:

ProviderRequired variablesOptional variable
Azure OpenAIAZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_VERSION, AZURE_OPENAI_DEPLOYMENT
AnthropicANTHROPIC_API_KEY, ANTHROPIC_MODEL
GeminiGEMINI_API_KEY, GEMINI_MODEL
QwenDASHSCOPE_API_KEY, DASHSCOPE_MODELDASHSCOPE_BASE_URL
OpenAIOPENAI_API_KEY, OPENAI_MODEL
TogetherTOGETHER_API_KEY, TOGETHER_MODEL

Every API key remains required. Passing --model-id MODEL replaces the provider's model variable for that command, so the corresponding *_MODEL variable—or AZURE_OPENAI_DEPLOYMENT—may be omitted. It does not replace endpoint, API version, or key variables.

For example:

export ANTHROPIC_API_KEY="..."
export ANTHROPIC_MODEL="..."

No model identifier is hard-coded because available model names and deployments vary by account.

Run benchmark inference

Inspect all available options:

python -m evaluation.benchmark --help

Linspace sampling on Android

linspace selects up to ten evenly spaced decoded frames, including the first and last frames. Android benchmark records do not include annotated keyframes, so use linspace for Android:

python -m evaluation.benchmark \
  --input GUI-World-data/Annotation/benchmark/android.jsonl \
  --video-root GUI-World-data \
  --provider azure-openai \
  --scenario android \
  --strategy linspace

Annotated sampling on iOS

annotated uses the JSONL keyframes[].frame values in annotation order, removes duplicates, and selects at most ten frames:

python -m evaluation.benchmark \
  --input GUI-World-data/Annotation/benchmark/IOS.jsonl \
  --video-root GUI-World-data \
  --provider anthropic \
  --scenario ios \
  --strategy annotated

Use --attempts and --base-delay to tune bounded provider retries. To process only part of a file, add an inclusive zero-based --start and an exclusive --end:

python -m evaluation.benchmark \
  --input GUI-World-data/Annotation/benchmark/IOS.jsonl \
  --video-root GUI-World-data \
  --provider anthropic \
  --scenario ios \
  --strategy annotated \
  --start 0 \
  --end 10

When --output is omitted or set to auto, the result is written beside the input as:

output_<provider>_<scenario>_<strategy>_<start>_<end>.jsonl

For example, the first command produces a name such as output_azure-openai_android_linspace_0_123.jsonl, where 123 is the number of input records. Pass --output path/to/results.jsonl to choose a different location; its parent directory must already exist.

An existing output is never overwritten accidentally. Re-run the same command with --resume to retain tasks whose question, gold answer, and non-empty model answer still match, and retry missing, failed, or stale tasks:

mkdir -p results
python -m evaluation.benchmark \
  --input GUI-World-data/Annotation/benchmark/IOS.jsonl \
  --output results/ios-anthropic.jsonl \
  --video-root GUI-World-data \
  --provider anthropic \
  --scenario ios \
  --strategy annotated \
  --resume

JSONL records do not store the provider, model ID, scenario, frame strategy, or other run metadata, and resume does not verify those settings. The automatic filename is only a label, not configuration validation. Use a new --output path instead of --resume whenever any run setting changes.

Judge benchmark results

The judge reads benchmark result JSONL rather than the source annotations:

python -m evaluation.judge --help

Example with OpenAI:

python -m evaluation.judge \
  --input results/ios-anthropic.jsonl \
  --provider openai \
  --workers 8

OpenAI and Together providers are called concurrently across records. --workers must be positive. --start is inclusive, --end is exclusive, and --resume reuses only judgments whose question, gold answer, model result, and judge schema still match.

Automatic judge output is written beside its input as:

llm_judge_<provider>_<input-stem>_<start>_<end>.jsonl

As with benchmark inference, pass --output for an explicit path. Without --resume, an existing output causes an error.

Judged JSONL records likewise do not store the judge provider, model ID, or other run metadata, and resume does not verify those settings. Changing the judge configuration requires a new output path.

Canonical names and legacy aliases

Names are case-insensitive. New commands should use the canonical names:

KindCanonicalAccepted legacy alias
Inference providerazure-openaiGPT-4V
Inference provideranthropicClaude3-Opus
Inference providergeminiGemini-Pro
Inference providerqwenQwen-VL-Max
Judge provideropenaigpt-4
Judge providertogetherllama3
Scenarioandroidmobile
Frame strategylinspaceRandom
Frame strategyannotatedExtracted

Uppercase scenario spellings such as IOS and XR are accepted. The old option names --model, --setting, and --keyframe remain aliases for --provider, --scenario, and --strategy.

JSONL format and errors

Input and output use strict UTF-8 JSON Lines:

  • blank lines are ignored;
  • every non-blank line must be one JSON object;
  • NaN, Infinity, arrays, scalars, and malformed JSON are rejected;
  • every record must contain id or video_path;
  • record identifiers, converted to strings, must be unique;
  • errors name the file and line number where possible.

Writes use a temporary file, fsync, and atomic replacement. The input and output paths must differ, and the output directory must already exist and permit creation of an atomic temporary file.

A successful benchmark task has this shape:

{
  "id": "record-id",
  "video_path": "IOS/176.mp4",
  "result": {
    "Sequential-QA": {
      "q": "What happened before the menu opened?",
      "a": "The user selected a document.",
      "MLLM_result": {
        "Description": "Optional visual summary.",
        "Analysis": "Optional reasoning.",
        "Answer": "The user selected a document."
      }
    }
  }
}

MLLM_result.Answer is always a string after successful normalization. Description and Analysis are included only when the provider returns them as strings. A failed inference task has no MLLM_result; it receives an error string and is retried by --resume.

Free-form judge tasks add:

{
  "LLM_Judge": {
    "Evaluation": "The response agrees with the gold answer.",
    "Score": 4
  }
}

Evaluation must be non-empty and Score must be an integer from 1 through 5. For MCQA and Reasoning, LLM_Judge is the provider's raw, unambiguous "Yes" or "No" string for compatibility. Invalid judge output leaves LLM_Judge absent and adds judge_error.

Before any judge request, all selected records are validated. A selected record with a judgeable model answer must have a top-level result object and non-empty string q and a values for that task. Structural errors stop the run before provider calls or output writes.

Tests and offline checks

The test suite uses fake clients and local fixtures; it makes no paid API calls and requires no provider credentials:

python -c "import evaluation; import evaluation.benchmark; import evaluation.judge"
python -m evaluation.benchmark --help
python -m evaluation.judge --help
python -m pytest evaluation/tests -q

The case-study test also decodes every annotated frame and verifies it against the retained PNG:

python -m pytest evaluation/tests/test_case_studies.py -q

For the included examples and their normalized annotations, see Case Studies. Return to the project README for dataset and paper links.