gage-eval

June 2, 2026 · View on GitHub

English | 中文

gage-eval is an extensible evaluation framework for large language models. It is built on Step-chain orchestration and the RoleAdapter glue layer, and uses a single PipelineConfig YAML to describe data loading, preprocessing, inference, judging, scoring, and reporting.

Status: internal validation. Docs follow OSS conventions, but the ground truth is the latest implementation in gage-eval-main/.

0. Docs

1. Project Overview

1.1 What you can do

  • Benchmark evaluation: multiple choice, QA, math, code (MMLU, PIQA, GPQA)
  • Multimodal evaluation: VQA, DocVQA, reasoning (MMMU, DocVQA, MathVista)
  • LLM-as-judge: score model outputs with a judge model
  • SWE-bench Pro: offline judging with context injection and Docker
  • Game Arena: GameKit board, table, and frame-game evaluation with Arena Visual browser control

1.2 Key features

FeatureDescriptionCode
Step-chain orchestrationCompose support -> inference -> judge -> auto_eval by YAML ordersrc/gage_eval/evaluation/sample_loop.py, src/gage_eval/evaluation/task_planner.py
RoleAdapter glue layerSteps only reference adapter_id; adapters assemble inputs and call backendssrc/gage_eval/role/adapters/, src/gage_eval/role/role_manager.py
Registry extensionsRegister datasets, backends, roles, metrics via @registry.asset and auto-discoverysrc/gage_eval/registry/, src/gage_eval/__init__.py
High-throughput concurrencyBounded-buffer SampleLoop with backpressure (prefetch_factor/max_inflight)src/gage_eval/evaluation/sample_loop.py
Unified artifactsevents.jsonl, samples.jsonl, summary.jsonsrc/gage_eval/observability/trace.py, src/gage_eval/evaluation/cache.py, src/gage_eval/pipeline/steps/report.py
SWE-bench dockcontext_provider + judge_extend for reproducible offline judgingsrc/gage_eval/role/adapters/context_provider.py, src/gage_eval/role/adapters/judge_extend.py
Agent evaluationDUTAgent + Toolchain + Sandbox execution for tool-using agentssrc/gage_eval/role/adapters/dut_agent.py, src/gage_eval/role/toolchain/, src/gage_eval/sandbox/
Game ArenaGameKit game evaluation with structured arena output and Arena Visual browser controlsrc/gage_eval/role/arena/, src/gage_eval/role/adapters/arena.py

1.3 End-to-end flow

End-to-end flow

1.4 Step view

From the pipeline perspective, there are two hidden but fixed stages:

  • Fixed start: preprocessing after data loading (raw record to standardized Sample)
  • Fixed end: ReportStep aggregates and outputs summary.json (no explicit step needed)

The diagram below shows how each step binds to a RoleAdapter via adapter_id. Common step and role combinations are listed in the table.

Step view

Common step and role combinations:

step_typecommon role_typepurpose
supportcontext_provider, toolchain, modal_processor, helper_modelcontext injection and multimodal prep
inferencedut_model, dut_agent, helper_modelmodel inference or agent execution
judgejudge_model, judge_extendLLM judge or strict offline judge
auto_evalbuiltinper-sample metrics and cache
reportbuiltinaggregated report and summary

1.4.1 Basic evaluation example

A basic evaluation includes only inference and metrics (inference -> auto_eval). preprocess and report are fixed stages.

Example config: config/custom/global_piqa/global_piqa_chat.yaml

flowchart LR
  classDef fixed fill:#F5F5F5,stroke:#9E9E9E,stroke-dasharray: 4 2,color:#666
  classDef step fill:#E3F2FD,stroke:#1565C0,stroke-width:2px,color:#000
  classDef role fill:#FFF8E1,stroke:#FF8F00,stroke-width:2px,color:#000
  classDef impl fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000
  classDef out fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000

  Pre[Preprocess]:::fixed --> Inf[Inference]:::step --> AE[AutoEval]:::step --> Rep[Report]:::fixed

  Inf -. adapter_id .-> Dut[Role DutModel]:::role --> BD[Backend]:::impl
  AE --> Cache[EvalCache]:::impl
  Cache --> Rep

  Cache --> Samp[samples jsonl]:::out
  Rep --> Sum[summary json]:::out

Execution highlights:

  • inference: call dut_model, write model_output to sample.predict_result
  • auto_eval: compute per-sample metrics and write samples.jsonl
  • report: aggregate metrics and output summary.json

1.4.2 LLM judge example

LLM judge evaluation adds a judge step: inference -> judge -> auto_eval.

Example config: config/custom/examples/single_task_local_judge_qwen.yaml

flowchart LR
  classDef fixed fill:#F5F5F5,stroke:#9E9E9E,stroke-dasharray: 4 2,color:#666
  classDef step fill:#E3F2FD,stroke:#1565C0,stroke-width:2px,color:#000
  classDef role fill:#FFF8E1,stroke:#FF8F00,stroke-width:2px,color:#000
  classDef impl fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000
  classDef out fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000

  Pre[Preprocess]:::fixed --> Inf[Inference]:::step --> Jdg[Judge]:::step --> AE[AutoEval]:::step --> Rep[Report]:::fixed

  Inf -. adapter_id .-> Dut[Role DutModel]:::role --> BD1[Backend Dut]:::impl
  Jdg -. adapter_id .-> JRole[Role JudgeModel]:::role --> BD2[Backend Judge]:::impl

  AE --> Cache[EvalCache]:::impl
  Cache --> Rep

  Cache --> Samp[samples jsonl]:::out
  Rep --> Sum[summary json]:::out

Execution highlights:

  • judge: consume sample and model_output, write to sample.eval_result, can be used by metrics
  • auto_eval: compute metrics from model output or judge output via prediction_field

1.4.3 SWE-bench example

SWE-bench is a representative static evaluation: support -> inference -> judge -> auto_eval.

Example config: config/custom/swebench_pro/swebench_pro_smoke_agent.yaml

flowchart LR
  classDef fixed fill:#F5F5F5,stroke:#9E9E9E,stroke-dasharray: 4 2,color:#666
  classDef step fill:#E3F2FD,stroke:#1565C0,stroke-width:2px,color:#000
  classDef role fill:#FFF8E1,stroke:#FF8F00,stroke-width:2px,color:#000
  classDef impl fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000
  classDef out fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000

  Pre[Preprocess]:::fixed --> Sup[Support]:::step --> Inf[Inference]:::step --> Jdg[Judge]:::step --> AE[AutoEval]:::step --> Rep[Report]:::fixed

  Sup -. adapter_id .-> Ctx[Role ContextProvider]:::role --> CImpl[context_impls]:::impl
  Inf -. adapter_id .-> Dut[Role DutModel]:::role --> BD[Backend]:::impl
  Jdg -. adapter_id .-> JExt[Role JudgeExtend]:::role --> JImpl[judge_impls]:::impl --> Docker[Docker Runner]:::impl

  AE --> Cache[EvalCache]:::impl
  Cache --> Rep

  Cache --> Samp[samples jsonl]:::out
  Rep --> Sum[summary json]:::out

Execution highlights:

  • support: inject repo structure and key files into sample.support_outputs, may also mutate sample.inputs
  • judge: offline judge via judge_extend, write resolved and failure_reason, logs under runs/<run_id>/logs
  • auto_eval: aggregate resolved results and failure stats

1.4.4 Game Arena example

Game Arena runs GameKit matches through the first-class arena step and can attach the unified arena_visual browser control page for live or replay viewing.

Example config: config/custom/gomoku/gomoku_llm_visual_gamekit.yaml

flowchart LR
  classDef fixed fill:#F5F5F5,stroke:#9E9E9E,stroke-dasharray: 4 2,color:#666
  classDef step fill:#E3F2FD,stroke:#1565C0,stroke-width:2px,color:#000
  classDef role fill:#FFF8E1,stroke:#FF8F00,stroke-width:2px,color:#000
  classDef impl fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000
  classDef out fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000

  Pre[Preprocess]:::fixed --> Sup[Support]:::step --> Arena[Arena]:::step --> AE[AutoEval]:::step --> Rep[Report]:::fixed

  Sup -. adapter_id .-> Ctx[Role ContextProvider]:::role --> CImpl[context_impls]:::impl
  Arena -. adapter_id .-> ARole[Role Arena]:::role --> GameKit[GameKit runtime]:::impl
  Arena --> Ply[Players]:::impl
  Arena --> Viz[Arena Visual]:::impl

  AE --> Cache[EvalCache]:::impl
  Cache --> Rep

  Cache --> Samp[samples jsonl]:::out
  Rep --> Sum[summary json]:::out

Execution highlights:

  • support: inject game rules and the initial board via context_provider
  • arena: run the GameKit loop across runtime, players, scheduler, and parser; write arena_trace, game_arena, and visual session artifacts when enabled
  • auto_eval: compute metrics from model_output and write cache

1.4.5 Agent evaluation example

Agent evaluation runs support -> inference -> judge -> auto_eval, where dut_agent executes AgentLoop, Toolchain injects tools, and Sandbox isolates side effects.

Example config: config/custom/appworld/appworld_official_jsonl.yaml

flowchart LR
  classDef fixed fill:#F5F5F5,stroke:#9E9E9E,stroke-dasharray: 4 2,color:#666
  classDef step fill:#E3F2FD,stroke:#1565C0,stroke-width:2px,color:#000
  classDef role fill:#FFF8E1,stroke:#FF8F00,stroke-width:2px,color:#000
  classDef impl fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000
  classDef out fill:#E8F5E9,stroke:#2E7D32,stroke-width:1px,color:#000

  Pre[Preprocess]:::fixed --> Sup[Support]:::step --> Inf[Inference]:::step --> Jdg[Judge]:::step --> AE[AutoEval]:::step --> Rep[Report]:::fixed

  Sup -. adapter_id .-> Toolchain[Role Toolchain]:::role --> Tools[MCP Tools]:::impl
  Inf -. adapter_id .-> Dut[Role DutAgent]:::role --> Loop[AgentLoop]:::impl --> Backend[AgentBackend]:::impl
  Loop --> Sandbox[Sandbox]:::impl
  Jdg -. adapter_id .-> JExt[Role JudgeExtend]:::role --> Eval[AppWorld Evaluate]:::impl

  AE --> Cache[EvalCache]:::impl
  Cache --> Rep

  Cache --> Samp[samples jsonl]:::out
  Rep --> Sum[summary json]:::out

2. Quickstart

2.1 Requirements

  • Python 3.10+
  • Linux + CUDA recommended for local vLLM
  • CPU is fine for HTTP backends or dummy demos

requirements.txt currently pins the real-machine Linux GPU baseline used for vLLM validation. CPU-only environments that only run HTTP backends or dummy demos may need a split or adjusted dependency file.

2.2 Install

From the mono-repo root:

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

Path note: example commands assume you run from the repo root.

2.3 Run the minimal demo

This demo uses the dummy backend and does not require a real model.

python run.py \
  --config config/run_configs/demo_echo_run_1.yaml \
  --output-dir runs \
  --run-id demo_echo

2.4 Output structure

A run writes to runs/<run_id>/:

runs/<run_id>/
  events.jsonl          # event stream
  samples.jsonl         # AutoEval cache, one line per sample
  summary.json          # aggregated metrics and timings
  samples/              # optional, per-sample or sharded outputs
  logs/                 # judge logs such as SWE-bench

Artifact sources:

FileWriterNotes
events.jsonlObservabilityTracekey events, local file or HTTP sink
samples.jsonlEvalCacheper-sample snapshot of model and metrics
summary.jsonReportStep + EvalCacheaggregated metrics and summary generators

2.5 Python SDK example

PYTHONPATH=src python - <<'PY'
from pathlib import Path
import yaml

import gage_eval  # noqa: F401
from gage_eval.config import build_default_registry
from gage_eval.config.pipeline_config import PipelineConfig
from gage_eval.evaluation.runtime_builder import build_runtime
from gage_eval.observability.trace import ObservabilityTrace
from gage_eval.role.resource_profile import NodeResource, ResourceProfile

payload = yaml.safe_load(Path("config/custom/global_piqa/global_piqa_chat.yaml").read_text(encoding="utf-8"))
config = PipelineConfig.from_dict(payload)

registry = build_default_registry()
profile = ResourceProfile(nodes=[NodeResource(node_id="local", gpus=1, cpus=8)])
trace = ObservabilityTrace(run_id="sdk_demo")

runtime = build_runtime(config=config, registry=registry, resource_profile=profile, trace=trace)
runtime.run()
PY

3. Core Concepts

3.1 Step chain

3.1.1 Step list and responsibilities

step_typefrequencytypical role_typepurpose
support0 to N per samplecontext_providercontext injection and tooling
inference0 or 1 per sampledut_modelmodel inference
judge0 or 1 per samplejudge_model or judge_extendscoring or strict offline judge
auto_eval0 or 1 per samplebuiltincompute metrics and cache
reportonce per runbuiltinwrite summary.json

3.1.2 Step contract

StepInput payload fieldsOutput target
supportsample, stepappend to sample.support_outputs or mutate sample
inferencesampleappend to sample.predict_result
judgesample, model_output, tracemerge into sample.eval_result
auto_evalsample, model_output, judge_outputwrite samples.jsonl and events

Execution snippet from src/gage_eval/evaluation/task_planner.py:

# StepExecutionContext.execute_judge
payload = {
    "sample": self.sample,
    "model_output": self._model_output or {},
    "trace": self.trace,
}
self._judge_output = self.judge.execute(payload, self.role_manager, self.trace)
update_eval_result(self.sample, self._judge_output)

3.1.3 Per-sample sequence view

Per-sample sequence

3.2 RoleAdapter glue layer

RoleAdapter decouples step semantics from backend execution:

  • Steps only reference adapter_id.
  • RoleAdapters assemble inputs, prompts, and backends.
  • RoleManager builds RolePools and reuses role instances under concurrency.

3.2.1 Runtime relationship

flowchart TD
  Spec[RoleAdapterSpec] --> Reg[ConfigRegistry]
  Reg --> Adpt[RoleAdapter]
  Adpt --> Pool[RolePool]
  Pool --> Role[Role]
  Role --> Backend[Backend]
  Role --> Hist[History]

3.2.2 Dock style RoleAdapters

Dock style adapters allow composition without rewriting orchestration.

Dock stylerole_type exampleExtension pointTypical configScenario
Implementation selectioncontext_provider, judge_extendchoose impl from registryparams.implementationRAG, offline judge
Backend compositiondut_model, judge_modelswitch backendbackend_id or backendmodel inference
Capability orchestrationtoolchain, dut_agentdeclarative toolingparams, capabilitiestools and agents
3.2.2.1 Implementation selection
# 1) Select an implementation by name
impl_cls = registry.get("context_impls", implementation)
self._impl = impl_cls(**implementation_params)

# 2) Support provide and aprovide, normalize to async
provider = getattr(self._impl, "aprovide", None) or getattr(self._impl, "provide", None)
self._provider = ensure_async(provider)
3.2.2.2 Backend composition

dut_model/helper_model/judge_model treat model execution as backends:

  • Switch backends: change backend.type (e.g., openai_http, vllm, dummy)
  • Reuse: declare backends[].backend_id and reference by RoleAdapter
  • Fast experiments: inline backend inside RoleAdapter for quick tests
3.2.2.3 Capability orchestration

These adapters glue tools, multimodal processing, or agent logic into the step chain.

3.3 Runtime modes

3.3.1 Single task PipelineRuntime

When tasks is empty:

  • The runtime selects one dataset (override with CLI --dataset-id)
  • It executes custom.steps or the builtin flow

3.3.2 Multi task TaskOrchestratorRuntime

When tasks is not empty:

  • Each TaskSpec binds a dataset, a step chain, and metrics
  • Tasks run sequentially, sharing the same run_id and RoleManager
  • summary.json records the tasks list

Config example (from config/custom/examples/multi_task_openai_http_demo.yaml):

metrics:
  - metric_id: multi_choice_acc
    implementation: multi_choice_accuracy
  - metric_id: docvqa_anls
    implementation: docvqa_anls
  - metric_id: latency
    implementation: latency

tasks:
  - task_id: mmlu_business_ethics_http_eval
    dataset_id: mmlu_business_ethics
    steps:
      - step: inference
        adapter_id: dut_text
      - step: auto_eval
    metric_overrides:
      - metric_id: multi_choice_acc
      - metric_id: latency
    max_samples: 5
    reporting:
      sinks:
        - type: console

  - task_id: docvqa_http_eval
    dataset_id: docvqa_val
    steps:
      - step: inference
        adapter_id: dut_docvqa
      - step: auto_eval
    metric_overrides:
      - metric_id: docvqa_anls
      - metric_id: latency
    max_samples: 5

Task override fields:

FieldPurpose
stepsoverride step chain
metric_overridesoverride metric set
max_samplesoverride sample limit
concurrency/prefetch_factor/max_inflightoverride scheduling and backpressure
reportingoverride sinks; events still go to runs/*/events.jsonl
flowchart TD
  Orc[TaskOrchestrator] --> T1[TaskEntry]
  Orc --> T2[TaskEntry]
  T1 --> L1[SampleLoop]
  T2 --> L2[SampleLoop]
  Orc --> RM[RoleManager]
  Orc --> Cache[EvalCache]
  Cache --> Report[ReportStep]

3.4 Sample schema

Samples are standardized across data and evaluation. The default validator lives in src/gage_eval/assets/datasets/validation.py.

Recommended minimal fields:

FieldTypeNotes
idstrsample id
messageslistchat messages, supports multimodal content
choicesliststructured options, optional
inputsdictpreprocessed inputs, such as prompt or multi_modal_data
metadatadicttask metadata such as answers or repo info
predict_resultlistinference outputs, appended by runtime
eval_resultdictjudge outputs and metrics

3.4.1 Example: multimodal standardized Sample

{
  "id": "demo_0001",
  "messages": [
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "What does the image say" },
        { "type": "image_url", "image_url": { "url": "https://example.com/demo.png" } }
      ]
    }
  ],
  "choices": [],
  "inputs": {
    "prompt": "What does the image say",
    "multi_modal_data": {
      "image": ["https://example.com/demo.png"]
    }
  },
  "metadata": {
    "label": "hello"
  },
  "predict_result": [
    {
      "index": 0,
      "answer": "hello",
      "message": {
        "role": "assistant",
        "content": [{ "type": "text", "text": "hello" }]
      }
    }
  ],
  "eval_result": {}
}

Common message content types:

typeExampleNotes
text{ "type": "text", "text": "..." }text fragment
image_url{ "type": "image_url", "image_url": { "url": "..." } }image url or data url
audio_url{ "type": "audio_url", "audio_url": { "url": "..." } }audio url or data url
file_url{ "type": "file_url", "file_url": { "url": "..." } }file url

Inference outputs are normalized into message.content segments when writing predict_result.

3.4.2 Multimodal normalization rules

Preprocessing performs two operations:

  • normalize_messages: inject sample.visual/sample.audio into user messages (_inject_modal_fragments)
  • merge_multimodal_inputs: collect media refs into inputs.multi_modal_data (merge_multimodal_inputs)
flowchart LR
  Raw[record] --> Prep[Preprocessor transform]
  Prep --> Msg[messages]
  Prep --> MM[multi modal data]
  Msg --> MM
  Infer[inference] --> Out[predict_result message]

Practical tips:

  • Prefer messages: most backends consume chat messages; multi_modal_data is a media reference table
  • Keep reproducibility: use doc_to_visual to convert local files into data urls

3.4.3 Protocol implementation status

The standardized Sample protocol is already implemented in the codebase:

  • The canonical envelope helpers live in src/gage_eval/evaluation/sample_envelope.py (append_predict_result, update_eval_result, resolve_model_output).
  • The dataclass representation and adapters are in src/gage_eval/assets/datasets/sample.py.
  • Validation is enforced by SampleValidator in src/gage_eval/assets/datasets/validation.py and is configurable via datasets[].schema.

3.5 Game Arena runtime lane

Game Arena is implemented as a first-class arena step, sharing the same Sample envelope and output flow as standard runs.

Key wiring points:

  • RoleAdapter: role_type: arena in role_adapters, implemented by src/gage_eval/role/adapters/arena.py.
  • Runtime objects: GameKit runtime, parser, players, scheduler, and arena_visual are resolved under src/gage_eval/role/arena/.
  • Outputs: the arena loop writes arena_trace, game_arena, and artifacts.visual_session_ref; auto_eval can aggregate arena metrics.

Common configs to start with:

  • config/custom/gomoku/gomoku_llm_visual_gamekit.yaml
  • config/custom/tictactoe/tictactoe_llm_visual_gamekit.yaml
  • config/custom/doudizhu/doudizhu_llm_visual_gamekit.yaml
  • config/custom/pettingzoo/space_invaders_llm_visual_gamekit.yaml

For the full spec and browser control wiring, see docs/guide/game_arena.md and docs/guide/game_arena_topics/game_arena_visual_control.md.

4. Config details

4.1 PipelineConfig structure

A typical config (details omitted):

api_version: gage/v1alpha1
kind: PipelineConfig
metadata:
  name: demo

custom:
  steps:
    - step: inference
    - step: auto_eval

datasets: []
backends: []
role_adapters: []
metrics: []
tasks: []

Fields:

FieldDescriptionCode entry
datasetsdataset definitionssrc/gage_eval/assets/datasets/loaders/
backendsbackend list for rolessrc/gage_eval/role/model/backends/
role_adaptersrole glue layersrc/gage_eval/config/registry.py
custom.stepsdefault step chainsrc/gage_eval/evaluation/pipeline.py
tasksmulti-task entrysrc/gage_eval/evaluation/task_plan.py
metricsmetric setsrc/gage_eval/metrics/
summary_generatorssummary extensionssrc/gage_eval/pipeline/steps/report.py

4.1.1 ID references

gage configs emphasize declaration and reference. Define resources at the top level, then reference by id.

  • dataset: datasets[].dataset_id referenced by tasks[].dataset_id (legacy dataset_ref)
  • role adapter: role_adapters[].adapter_id referenced by steps[].adapter_id (legacy role_ref)
  • backend: backends[].backend_id referenced by role_adapters[].backend_id or inline backend
  • prompt: prompts[].prompt_id referenced by role_adapters[].prompt_id
  • metric: metrics[].metric_id referenced by tasks[].metric_overrides[].metric_id
flowchart TD
  PC[PipelineConfig] --> DS[datasets dataset_id]
  PC --> BE[backends backend_id]
  PC --> PR[prompts prompt_id]
  PC --> RA[role_adapters adapter_id]
  PC --> MT[metrics metric_id]
  PC --> TK[tasks task_id]

  DS -->|dataset_id| TK
  RA -->|adapter_id| ST[steps step_type]
  BE -->|backend_id| RA
  PR -->|prompt_id| RA
  MT -->|metric_overrides| TK

4.2 Datasets

4.2.1 HuggingFace Hub

Example: config/custom/mmmu/mmmu_qwen_vl.yaml

Key fields:

  • hub: huggingface
  • hub_params.hub_id/subset/split
  • loader: hf_hub
  • params.preprocess: preprocessor id from registry
  • params.doc_to_visual: multimodal injection callable

4.2.2 Local JSONL

Example: config/custom/docvqa/docvqa_qwen_vl.yaml

Key fields:

  • loader: jsonl
  • params.path: JSONL path, repo-root relative recommended
  • params.streaming: optional, may be auto-enabled for large files

4.2.3 Preprocess and doc_to_visual

doc_to_visual/doc_to_text/doc_to_audio refer to callables, for example gage_eval.assets.datasets.utils.multimodal:embed_local_message_images.

Implementation notes (src/gage_eval/assets/datasets/loaders/loader_utils.py):

  • inherit same-name arguments from preprocess_kwargs
  • pass extra args via doc_to_visual_kwargs

4.2.4 Builtin preprocess pipeline

Regardless of custom preprocessors, all records are normalized into the standardized Sample. The core template method is BasePreprocessor.transform in src/gage_eval/assets/datasets/preprocessors/base.py:

flowchart TD
  In[Raw record] --> S1[Step1 role cleanup]
  S1 --> S2[Step2 to_sample]
  S2 --> S3[Step3 inputs normalize]
  S3 --> S4[Step4 schema validation]
  S4 --> S5[Step5 doc to hooks]
  S5 --> S6[Step6 multimodal merge]
  S6 --> S7[Step7 chat_template mark]
  S7 --> S8[Step8 normalize_sample]
  S8 --> Out[Standardized Sample]

4.3 Backends

Common backends (backend.type):

backend typeScenarioNotes
vllmLocal inferenceAsyncLLMEngine with text and multimodal support
litellmRemote serviceUnified API over OpenAI, Anthropic, Kimi, Grok
sglangHigh-throughput inferenceSGLang server integration
tgiHigh-throughput inferenceHuggingFace TGI server
openai_httpRemote serviceOpenAI ChatCompletion compatible API
dummySmoke testsecho prompt or cycle responses

4.4 RoleAdapters

4.4.1 Model RoleAdapters

Model RoleAdapters compose backends and switch execution engines per role type:

  • dut_model: target model
  • helper_model: helper model for support or judge
  • judge_model: LLM-as-judge

Base class: src/gage_eval/role/adapters/model_role_adapter.py

Binding options:

  • reference backends[].backend_id (recommended)
  • inline backend for quick experiments

Example:

backends:
  - backend_id: openai_compatible_chat
    type: openai_http
    config:
      base_url: ${OPENAI_API_BASE:-https://api.openai.com/v1}
      api_key: ${OPENAI_API_KEY:?set OPENAI_API_KEY}
      model: ${OPENAI_MODEL:-gpt-5.4}

role_adapters:
  - adapter_id: dut_text
    role_type: dut_model
    backend_id: openai_compatible_chat
    prompt_id: multi_choice_infer_prompt
    capabilities: [chat_completion]

4.4.2 context_provider dock

  • Use for RAG, knowledge injection, repo context
  • Select implementation via params.implementation from context_impls
  • Default example: SWE-bench swebench_repo

4.4.3 judge_extend dock

  • Use for non-LLM or strict judges
  • Select implementation via params.implementation from judge_impls
  • Default example: SWE-bench swebench_docker

4.4.4 Step and Role combinations

step_typecommon role_typeNotes
supportcontext_provider, toolchain, modal_processor, helper_modelcontext injection, tool prep, multimodal prep
inferencedut_model, dut_agentmodel inference or agent flow
judgejudge_model, judge_extendLLM judge or offline judge
auto_evalbuiltinmetric compute and aggregation
reportbuiltinoutput summary.json

4.5 Metrics

Metrics are computed per sample and aggregated globally.

4.5.1 Per-sample computation

  • Runs in auto_eval: AutoEvalStep.execute calls MetricInstance.evaluate
  • Writes samples.jsonl with sample/model_output/judge_output/metrics

4.5.2 Aggregation

  • Aggregators accumulate in MetricInstance.evaluate
  • ReportStep.finalize triggers AutoEvalStep.aggregated_metrics and finalize
  • Writes aggregated metrics to summary.json
flowchart TD
  S[Sample] --> AE[AutoEvalStep]
  AE --> R1[MetricInstance evaluate]
  R1 --> PS[per sample MetricResult]
  PS --> Cache[EvalCache samples jsonl]
  R1 --> Agg[Aggregator add]
  Agg --> Fin[Aggregator finalize]
  Fin --> RP[ReportStep]
  RP --> Sum[summary json]

4.5.3 Metric config forms

metrics supports multiple equivalent formats (see _normalize_metric_entry).

FormYAML exampleWhen to use
String shorthand- exact_matchbuiltin with no params
KV shorthand- exact_match: {case_sensitive: true}simple params and same id
Function shorthand- exact_match(case_sensitive=true, strip_whitespace=false)compact flat params
Full objectsee belowmultiple variants or stable ids

Rules:

  • Only flat k=v params, comma separated; supports true/false/null, numbers, quoted strings
  • aggregation=... is extracted into aggregator config
  • Use full objects for multiple variants of the same implementation

Example:

metrics:
  # 1) String shorthand
  - exact_match

  # 2) KV shorthand
  - regex_match:
      pattern: "^A$"
      ignore_case: true

  # 3) Function shorthand
  - exact_match(case_sensitive=true, strip_whitespace=false)

  # 4) Full object
  - metric_id: exact_match_cs
    implementation: exact_match
    params:
      case_sensitive: true

4.6 Tasks and multi-task

  • tasks empty: single-task runtime
  • tasks non-empty: orchestrator runtime
  • If adapter_id is omitted in inference, it is inferred only when exactly one dut_model exists

4.7 Runtime params, templates, and run configs

4.7.1 run.py CLI params

Common flags (python run.py --help):

FlagPurpose
--configconfig path
--dataset-iddataset for single-task runs
--gpusResourceProfile GPUs
--cpusResourceProfile CPUs
--concurrencyoverride concurrency, sets GAGE_EVAL_THREADS
--max-samplesoverride sample limit, sets GAGE_EVAL_MAX_SAMPLES
--output-diroutput root, sets GAGE_EVAL_SAVE_DIR
--run-idfixed run id for reproducibility
--model-pathoverride vLLM model_path

4.7.2 Common environment variables

Env varPurposeCode
GAGE_EVAL_SAVE_DIRoutput rootsrc/gage_eval/observability/trace.py, src/gage_eval/evaluation/cache.py
GAGE_EVAL_THREADSconcurrencysrc/gage_eval/evaluation/sample_loop.py, src/gage_eval/evaluation/runtime_builder.py
GAGE_EVAL_MAX_SAMPLESmax samplessrc/gage_eval/evaluation/sample_loop.py
GAGE_EVAL_PREFETCH_FACTORprefetch factorsrc/gage_eval/evaluation/sample_loop.py
GAGE_EVAL_MAX_INFLIGHTmax inflightsrc/gage_eval/evaluation/sample_loop.py
GAGE_EVAL_REPORT_HTTP_URLevent HTTP sinksrc/gage_eval/observability/trace.py
GAGE_EVAL_ENABLE_BUFFERED_WRITERbuffered samples writersrc/gage_eval/evaluation/cache.py

4.7.3 BuiltinTemplate

BuiltinTemplate solidifies a stable PipelineConfig template:

  • config/builtin_templates/<name>/vN.yaml

Example: config/builtin_templates/demo_echo/v1.yaml

kind: BuiltinTemplate
metadata:
  name: demo_echo
  version: V1
  digest: sha256:...
definition:
  datasets: [...]
  backends: [...]
  role_adapters: [...]
  custom: {steps: [...]}

4.7.4 RunConfig

RunConfig provides runtime overrides on top of a template:

  • kind: RunConfig
  • base_task: builtin/<name> references a BuiltinTemplate
  • runtime overrides datasets, backends, tasks

Example: config/run_configs/demo_echo_run_1.yaml

Entry modes:

  • --config points to PipelineConfig: run directly
  • --config points to RunConfig: compile then run and validate digest

4.7.5 distill and init

distill: generate a BuiltinTemplate from PipelineConfig:

python run.py \
  --config config/custom/global_piqa/global_piqa_chat.yaml \
  --distill \
  --builtin-name global_piqa_chat

init: generate RunConfig or PipelineConfig from a BuiltinTemplate:

python run.py --init demo_echo --init-mode run-config
python run.py --init demo_echo --init-mode pipeline-config

5. Best practices

Scenario navigation table (run from repo root):

ScenarioLevelHighlightsConfigTypical stepsKey roles
Minimal smokeStarterRunConfig compile, dummy backendconfig/run_configs/demo_echo_run_1.yamlinference -> auto_evaldut_model
Text multiple-choiceStarterstructured choices, auto metricsconfig/custom/global_piqa/global_piqa_chat.yamlinference -> auto_evaldut_model
GPQAAdvancedexpert MCQ, few-shotconfig/custom/gpqa_diamond/async_chat.yamlinference -> auto_evaldut_model
LLM judgeAdvancedpost-inference judgeconfig/custom/examples/single_task_local_judge_qwen.yamlinference -> judge -> auto_evaldut_model, judge_model
Multi-taskAdvancedTaskOrchestrator, overridesconfig/custom/examples/multi_task_openai_http_demo.yamlper-taskdut_model
DocVQAAdvanceddoc_to_visual, image_urlconfig/custom/docvqa/docvqa_qwen_vl.yamlinference -> auto_evaldut_model
MathVistaAdvancedmultimodal, answer extractionconfig/custom/mathvista/chat.yamlinference -> auto_evaldut_model
MMMUAdvancedHF Hub, multimodal preprocessconfig/custom/mmmu/mmmu_qwen_vl.yamlinference -> auto_evaldut_model
SWE-bench ProExpertcontext_provider, judge_extend, Dockerconfig/custom/swebench_pro/swebench_pro_smoke_agent.yamlsupport -> inference -> judge -> auto_evalcontext_provider, dut_model, judge_extend

5.1 Step and Role patterns

ScenariostepsKey roles
Pure auto evaluationinference -> auto_evaldut_model
LLM judgeinference -> judge -> auto_evaldut_model, judge_model
Context enhancedsupport -> inference -> auto_evalcontext_provider, dut_model
Engineering evalsupport -> inference -> judge -> auto_evalcontext_provider, dut_model, judge_extend

5.2 Text examples

5.2.1 Global PIQA multiple-choice

Config: config/custom/global_piqa/global_piqa_chat.yaml

Run:

python run.py \
  --config config/custom/global_piqa/global_piqa_chat.yaml \
  --output-dir runs \
  --run-id piqa_smoke \
  --max-samples 50

5.2.2 Multi-task example

Config: config/custom/examples/multi_task_openai_http_demo.yaml

Notes:

  • Two tasks share the same backend
  • Each task uses metric_overrides to select a subset

5.3 Multimodal examples

5.3.1 DocVQA

Config: config/custom/docvqa/docvqa_qwen_vl.yaml

Notes:

  • JSONL messages contains image_url
  • doc_to_visual converts local images to data urls for remote VLMs

5.3.2 MMMU

Config: config/custom/mmmu/mmmu_qwen_vl.yaml

Notes:

  • HF Hub dataset + preprocess: mmmu_multimodal_inputs
  • TaskSpec concurrency controls SampleLoop

5.4 SWE-bench Pro

SWE-bench is a representative step and role composition:

  • support injects repo context via context_provider
  • inference produces patches
  • judge uses judge_extend and Docker for offline tests
  • auto_eval computes resolve rate and aggregates failure reasons

5.4.1 Dock config notes

Config: config/custom/swebench_pro/swebench_pro_smoke_agent.yaml

Key snippet:

custom:
  steps:
    - step: support
      adapter_id: swebench_context_provider
    - step: inference
      adapter_id: swebench_dut_model
    - step: judge
      adapter_id: swebench_docker_judge
    - step: auto_eval

role_adapters:
  - adapter_id: swebench_context_provider
    role_type: context_provider
    params:
      implementation: swebench_repo
      implementation_params:
        repo_source: docker_image
        repo_root: /app
        topk_files: 5
        block_network: true

  - adapter_id: swebench_docker_judge
    role_type: judge_extend
    params:
      implementation: swebench_docker
      implementation_params:
        scripts_dir: third_party/swebench_pro/run_scripts
        block_network: true
        test_timeout_s: 900

5.4.2 Run command

python run.py \
  --config config/custom/swebench_pro/swebench_pro_smoke_agent.yaml \
  --output-dir runs \
  --run-id swebench_smoke \
  --concurrency 1

5.4.3 Output and troubleshooting

  • Check runs/swebench_smoke/summary.json for swebench_summary
  • Check runs/swebench_smoke/logs/<instance_id>/stdout.log, stderr.log, output.json

5.5 Performance tuning

5.5.1 SampleLoop backpressure

  • GAGE_EVAL_PREFETCH_FACTOR: higher hides data loading latency
  • GAGE_EVAL_MAX_INFLIGHT: limits inflight samples for memory control
flowchart TD
  Producer[Prefetcher] --> Q[Queue]
  Q --> W1[Worker]
  Q --> W2[Worker]
  W1 --> Done[Done]
  W2 --> Done

5.5.2 Large scale output

Enable buffered writer for large runs:

  • GAGE_EVAL_ENABLE_BUFFERED_WRITER=1
  • or GAGE_EVAL_BUFFER_THRESHOLD for auto

6. Extension

6.1 Registry extension mechanism

gage uses a global Registry to manage extensible assets. The goal is to extend without touching core orchestration code.

Entry points:

  • Registry singleton: src/gage_eval/registry/__init__.py
  • Default kinds: DEFAULT_KINDS
  • Auto-discovery: import gage_eval triggers registry.auto_discover
flowchart TD
  classDef stage fill:#E3F2FD,stroke:#1E88E5,color:#111;
  classDef reg fill:#FFF3E0,stroke:#FB8C00,color:#111;
  classDef rt fill:#E8F5E9,stroke:#43A047,color:#111;

  A[Import gage_eval]:::stage --> B[Auto discover]:::stage --> C[Load modules]:::stage --> D[Asset registration]:::reg --> E[Registry table]:::reg

  PC[PipelineConfig]:::stage --> CR[ConfigRegistry]:::stage --> Q[Registry lookup]:::reg --> Obj[Runtime objects]:::rt

  E --> Q

Extensible kinds (from DEFAULT_KINDS):

kindMeaningTypical reference
backendsmodel and service backendsbackends[].type or inline backend.type
rolesrole adaptersrole_adapters[].role_type
metricsmetric calculatorsmetrics[].implementation, tasks[].metric_overrides
context_implscontext providersrole_adapters[].params.implementation
judge_implsjudge extensionsrole_adapters[].params.implementation
dataset_hubsdataset sourcesdatasets[].hub
dataset_loadersdataset loadersdatasets[].loader
dataset_preprocessorsdataset preprocessorsdatasets[].params.preprocess
doc_convertersdoc to sample convertersreserved
promptsprompt templatesprompts[].prompt_id
model_hubsmodel hubsmodels[].hub
templatesconfig templatesreserved
reporting_sinksreporting sinkstasks[].reporting.sinks[].type
summary_generatorssummary generatorssummary_generators[] or env GAGE_EVAL_SUMMARY_GENERATORS
observability_pluginsobservability pluginsobservability config
pipeline_stepspipeline stepsbuiltin step assembly

Only packages included in src/gage_eval/__init__.py auto-discover will be registered on startup; other extensions can be imported explicitly.

Extension approaches:

  • Registry-based: place implementations under auto-discover packages and register via @registry.asset
  • Direct import: reference Python paths in config, runtime imports at execution time

6.1.1 Copy-paste example: register preprocessor and metric in one file

As long as a module is imported, it can register multiple assets. The example below can be copied into any importable module.

from __future__ import annotations

from typing import Any, Dict

from gage_eval.assets.datasets.preprocessors.base import BasePreprocessor
from gage_eval.metrics.base import MetricContext, SimpleMetric
from gage_eval.registry import registry


@registry.asset("dataset_preprocessors", "demo_preprocess", desc="demo preprocessor", tags=("demo",))
class DemoPreprocessor(BasePreprocessor):
    name = "demo_preprocess"

    def to_sample(self, record: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]:
        # Map raw record to standardized Sample fields
        prompt = str(record.get("question") or record.get("prompt") or "")
        label = record.get("answer") or record.get("label")
        return {
            "prompt": prompt,
            "label": "" if label is None else str(label),
            "metadata": {"source": "demo_preprocess"},
        }


@registry.asset("metrics", "demo_length", desc="demo metric length", tags=("demo",))
class DemoLengthMetric(SimpleMetric):
    value_key = "length"

    def compute_value(self, context: MetricContext):
        # Use context.get to read sample or output fields
        text = context.get("model_output.answer", default="") or ""
        text = str(text)
        return float(len(text)), {"preview": text[:50]}

YAML usage:

datasets:
  - dataset_id: demo_dataset
    loader: jsonl
    params:
      path: local-datasets/demo.jsonl
      preprocess: demo_preprocess

metrics:
  - demo_length

Generate a registry manifest:

python scripts/build_registry_manifest.py --out registry_manifest.yaml

6.2 Add a new Metric

  1. Implement a SimpleMetric under src/gage_eval/metrics/
  2. Register via @registry.asset("metrics", ...)
  3. Reference by metrics[].implementation in YAML

Template:

from gage_eval.metrics.base import MetricContext, SimpleMetric
from gage_eval.registry import registry

@registry.asset("metrics", "my_metric", desc="demo metric", tags=("custom",))
class MyMetric(SimpleMetric):
    def compute_value(self, context: MetricContext):
        return 1.0, {"note": "ok"}

6.3 Add context_impls or judge_impls

  • context: implement provide or aprovide, register to context_impls
  • judge: implement invoke or ainvoke, register to judge_impls

6.4 Add a Backend

  1. Implement a backend class and register to backends:
from gage_eval.registry import registry
from gage_eval.role.model.backends.base_backend import Backend

@registry.asset("backends", "my_backend", desc="demo backend", tags=("backend",))
class MyBackend(Backend):
    async def ainvoke(self, payload: dict) -> dict:
        return {"text": "hello", "latency_ms": 1.0}
  1. Declare in YAML and reference by RoleAdapter:
backends:
  - backend_id: my_backend_1
    type: my_backend
    config: {}

role_adapters:
  - adapter_id: dut_demo
    role_type: dut_model
    backend_id: my_backend_1

6.5 Add a DatasetPreprocessor

  1. Implement and register in dataset_preprocessors:
from gage_eval.registry import registry
from gage_eval.assets.datasets.preprocessors.base import DatasetPreprocessor

@registry.asset("dataset_preprocessors", "my_pre", desc="demo preprocessor", tags=("dataset",))
class MyPreprocessor(DatasetPreprocessor):
    def transform(self, sample: dict, **kwargs):
        sample.setdefault("metadata", {})["preprocessed_by"] = "my_pre"
        return sample.get("inputs")
  1. Enable in dataset config:
datasets:
  - dataset_id: my_ds
    loader: jsonl
    params:
      path: local-datasets/my_ds.jsonl
      preprocess: my_pre

6.6 Quick dataset onboarding

flowchart LR
  I[Inspect sample] --> P[Add loader or preprocessor] --> C[Create config] --> R[Validate and run]

Common workflow (from repo root):

# 1) Validate the new config
PYTHONPATH=src python -m gage_eval.tools.config_checker \
  --config config/custom/<topic>/<config>.yaml

# 2) Smoke-run a few samples
PYTHONPATH=src python run.py \
  --config config/custom/<topic>/<config>.yaml \
  --max-samples 5

Before these commands, inspect a few raw records from the target dataset and add the required loader or preprocessor assets under src/gage_eval/assets/datasets/. Keep task-specific tests close to the changed implementation under tests/.

7. Testing and validation

  • Config validation:
    PYTHONPATH=src python -m gage_eval.tools.config_checker \
      --config config/custom/global_piqa/global_piqa_chat.yaml
    
  • See TESTING.md for test guidance.

8. Roadmap

This section records near-term directions and may evolve with internal validation.

8.1 Standardized Sample and data pipeline

  • Consolidate standardized Sample fields and validators across text, multimodal, and engineering tasks
  • Improve data loading and preprocessing so inputs vary but outputs are unified
  • Add diagnostics and debugging tools for samples

8.2 gage-client standalone project

  • User-facing: config initialization, run management, artifact browsing
  • Developer-facing: benchmarking templates and onboarding workflows
  • Decouple from core runtime; run.py remains the minimal entrypoint

8.3 Multi-node inference: RoleType Controller

  • Add controller role adapter to split scheduling and execution
  • Support multi-node inference via controller, worker, and protocol configs
  • Focus on sharding, load balancing, health checks, retries, and timeouts

8.4 Benchmark expansion

  • Add and maintain benchmarks across text, multimodal, LLM judge, and engineering domains
  • Provide dataset prep notes, smoke configs, metric definitions, and troubleshooting