Chapter 2. Evaluate LLMs with EvalHub
Use EvalHub to evaluate your large language models (LLMs) against standardized benchmarks, track results with MLflow, and manage evaluation workflows across multiple tenants.
2.1. Understanding EvalHub Copy linkLink copied to clipboard!
EvalHub is an evaluation orchestration service for large language models (LLMs) on Red Hat OpenShift AI. EvalHub provides a versioned REST API for submitting evaluation jobs, managing benchmark providers, and tracking results through MLflow experiment tracking.
Each evaluation runs as an isolated Job, enabling parallel execution and horizontal scalability across namespaces and tenants.
EvalHub consists of three components:
- EvalHub Server — A REST API service that handles evaluation workflows, job orchestration, and provider management, with PostgreSQL storage.
-
EvalHub SDK and CLI — A Python client library and command-line tool for submitting evaluations and building framework adapters. The CLI provides the
evalhubcommand for interacting with EvalHub from the terminal. - Providers — Evaluation framework adapters packaged as container images. Each provider translates EvalHub job requests into evaluation framework-specific commands and reports results back to the server.
2.1.1. Core concepts Copy linkLink copied to clipboard!
The following concepts are central to EvalHub.
- Providers
-
A provider represents an evaluation framework, such as
lm_evaluation_harness,garak,guidellm, orlighteval. Each provider includes a set of benchmarks. EvalHub includes built-in providers that are read-only. - Benchmarks
-
A benchmark is a specific evaluation task within a provider. For example, the
lm_evaluation_harnessprovider includes benchmarks such asmmlu,hellaswag,arc_challenge, andgsm8k. Each benchmark has a category such asmath,reasoning,safety, orcode, along with metrics and optional pass criteria. - Collections
-
A collection groups benchmarks from one or more providers into a reusable evaluation suite. For example, a
safety-and-fairness-v1collection might combine safety benchmarks fromlm_evaluation_harnesswith vulnerability scans fromgarak. - Pass criteria and thresholds
Pass criteria define the minimum score that a benchmark or job must achieve to pass. Thresholds can be set at three levels, from most to least specific:
- Benchmark level — You set a benchmark-level threshold per benchmark in a job submission or collection definition. This overrides all other thresholds.
- Collection level — A collection-level threshold applies to all benchmarks in the collection that do not have their own threshold.
Provider level — A provider-level threshold is the default threshold defined in the provider’s benchmark configuration.
Each benchmark declares a primary score metric, such as
acc_normortoxicity_score, and optionally alower_is_betterflag. Whenlower_is_betterisfalse(the default), the benchmark passes if the score is greater than or equal to the threshold. Whenlower_is_betteristrue, it passes if the score is less than or equal to the threshold.Each benchmark in a collection or job can be assigned a weight that controls its relative importance in the overall score. At the job level, EvalHub computes a weighted average of all benchmark primary scores and compares it against the job-level threshold to determine an overall pass or fail result.
- Evaluation jobs
-
An evaluation job represents a single evaluation run against a model. A job references either a list of benchmarks or a collection, a model endpoint, and optional MLflow experiment configuration. Jobs progress through states:
pending,running,completed,failed,cancelled, orpartially_failed. Evaluation jobs can optionally use Red Hat build of Kueue for workload queuing and resource governance on clusters where the Red Hat build of Kueue Operator is installed. For more information, see Kueue integration overview. - Adapters
-
An adapter wraps an evaluation framework, such as
lm_evaluation_harness, and implements theFrameworkAdapterinterface so that EvalHub can orchestrate the evaluation. Adapters are packaged as Red Hat Universal Base Image 9 (UBI9) container images.
2.2. EvalHub architecture overview Copy linkLink copied to clipboard!
In OpenShift AI, the Evalhub evaluates large language models (LLMs). Understand its core components and data flow to effectively manage, monitor, and optimize your AI model evaluation processes.
When you submit an evaluation job, EvalHub follows this workflow:
- The client submits a job through the REST API, SDK, or CLI.
-
The server validates the request, resolves benchmarks, and persists the job with a status of
pending. The runtime creates a Kubernetes Job for each benchmark. Each Job pod contains two containers:
- The adapter container runs the evaluation framework. Adapters are provider-specific container images that implement a standard interface, translating the job specification into the evaluation framework-specific invocations and returning structured results.
-
The sidecar proxy container authenticates to the EvalHub server using a
ServiceAccounttoken and forwards status events and results from the adapter. The sidecar also proxies authenticated requests to MLflow and OCI registries when configured. This design keeps credentials out of the adapter container, which can run custom user-provided code.
- The adapter runs the evaluation and reports status events back to EvalHub through the sidecar.
- The server aggregates and stores the results. If MLflow integration is enabled, the server also logs the results to MLflow.
In local development mode, EvalHub runs subprocesses instead of Jobs.
2.3. Deploy EvalHub with the TrustyAI Operator Copy linkLink copied to clipboard!
Deploy EvalHub through the TrustyAI Operator as part of the OpenShift AI.
Prerequisites
- You have cluster administrator privileges for your OpenShift cluster.
-
You have installed the OpenShift CLI (
oc) version 4.12 or later. -
You have the TrustyAI component in your OpenShift AI
DataScienceClusterset toManaged. -
You have configured KServe to use
RawDeploymentmode.
Procedure
Create a Secret containing the PostgreSQL connection string. The Secret must contain a
db-urlkey with a valid PostgreSQL connection URI:apiVersion: v1 kind: Secret metadata: name: evalhub-db-credentials type: Opaque stringData: db-url: "postgres://evalhub:changeme@postgresql.evalhub.svc.cluster.local:5432/evalhub"NoteReplace the hostname, credentials including the
changemeplaceholder, and database name to match your PostgreSQL deployment.Apply the created
evalhub-db-credentials.yaml:$ oc apply -f evalhub-db-credentials.yaml -n <namespace>Create an EvalHub custom resource to deploy the service, such as
evalhub_cr.yaml:apiVersion: trustyai.opendatahub.io/v1alpha1 kind: EvalHub metadata: name: evalhub spec: replicas: 1 database: type: postgresql secret: evalhub-db-credentials providers: - lm-evaluation-harness - garak - guidellm collections: - safety-and-fairness-v1 env: - name: MLFLOW_TRACKING_URI value: "http://mlflow.mlflow.svc.cluster.local:5000"where:
replicasdefines the number of EvalHub pods to create.database.typedefines the storage backend. Set topostgresqlfor PostgreSQL.database.secretdefines the name of a Secret containing the PostgreSQL connection string.providersdefines the list of evaluation provider configurations to load at startup.collectionsdefines the list of benchmark collections to load at startup.oteldefines the OpenTelemetry exporter configuration for traces and metrics (optional).envdefines the environment variables to set in the EvalHub deployment containers.Apply the custom resource to the cluster:
$ oc apply -f evalhub_cr.yaml -n <namespace>NoteUse a dedicated namespace for EvalHub rather than
redhat-ods-applications. Theredhat-ods-applicationsnamespace has NetworkPolicies that restrict cross-namespace traffic, which requires additional labeling on tenant namespaces. For more information, see Section 2.26, “Set up a tenant namespace”.The TrustyAI Operator automatically reconciles the EvalHub custom resource in your namespace.
Verification
Confirm that the EvalHub pod is running:
$ oc get pods -l app=eval-hub -n <namespace>NAME READY STATUS RESTARTS AGE evalhub-7b9f4c6d88-x2k4p 1/1 Running 0 2mQuery the health endpoint:
$ export EVALHUB_URL=https://$(oc get routes evalhub -o jsonpath='{.spec.host}' -n <namespace>) $ curl $EVALHUB_URL/api/v1/health | jq .{ "status": "healthy", "timestamp": "2026-04-13T10:00:00Z", "version": "0.3.0", "uptime": 3600000000000, }
2.4. Install the EvalHub SDK and CLI Copy linkLink copied to clipboard!
Install the EvalHub Python SDK and command-line interface (CLI) to interact with EvalHub from your local environment or workbench. The SDK provides a Python client library for programmatic access, while the CLI provides the evalhub command for terminal-based workflows.
Prerequisites
- You have Python 3.11 or later installed.
- You have deployed EvalHub. For more information, see Section 2.3, “Deploy EvalHub with the TrustyAI Operator”.
- You have network access to install Python packages from PyPI.
Procedure
Install the EvalHub SDK with CLI support:
$ pip install "eval-hub-sdk[cli]"To install only the Python SDK without the CLI, run:
$ pip install "eval-hub-sdk[client]"Configure the CLI to connect to your EvalHub server:
$ evalhub config set base_url https://<evalhub_route> $ evalhub config set tenant <namespace>where:
-
base_urldefines the URL of your EvalHub server route. -
tenantdefines the namespace where your evaluation jobs will run.
-
Set your authentication token:
$ export TOKEN=$(oc create token <serviceaccount> -n <namespace>) $ evalhub config set token $TOKENReplace
<serviceaccount>with the name of a ServiceAccount that has EvalHub access. For more information about granting access, see Section 2.27, “Grant access to EvalHub”.
Verification
Verify the CLI can connect to EvalHub:
$ evalhub healthExample output:
{ "status": "healthy", "timestamp": "2026-06-03T10:00:00Z", "version": "0.3.0" }List available evaluation providers:
$ evalhub providers list
2.5. EvalHub multi-tenancy Copy linkLink copied to clipboard!
EvalHub is a multi-tenant service. All API requests, except requests to /api/v1/health, must include the X-Tenant header, which identifies the target namespace. Resources such as jobs, providers, and collections are scoped to the tenant specified in this header.
When using curl, include the -H "X-Tenant: <namespace>" header in each request.
When using the Python SDK, set the tenant at client initialization:
from evalhub import SyncEvalHubClient
client = SyncEvalHubClient(
base_url="https://evalhub.example.com",
tenant="my-namespace"
)
When using the CLI, configure the tenant in your connection profile. The CLI stores connection settings in named profiles at ~/.config/evalhub/config.yaml. Settings are persistent across commands. Use --profile <name> to override the active profile at runtime.
$ evalhub config set tenant my-namespace
All API requests must also include an Authorization: Bearer $TOKEN header. The curl examples in this guide assume you have stored the EvalHub route URL in the EVALHUB_URL environment variable and a valid bearer token in the TOKEN environment variable.
2.6. Route evaluation jobs through Kueue Copy linkLink copied to clipboard!
To improve scheduling and resource management, route EvalHub evaluation jobs through Kueue queues in Red Hat build of Kueue. This integration enables your evaluation workloads to participate in cluster-wide quota governance alongside training and inference workloads.
2.6.1. EvalHub Kueue integration overview Copy linkLink copied to clipboard!
When the Red Hat build of Kueue Operator is installed on your cluster, EvalHub can routes evaluation jobs through Kueue’s workload queuing and quota management system. Understand this integration to ensure your evaluation workloads participate in cluster resource governance policies alongside training and inference workloads.
EvalHub Kueue integration is a Technology Preview feature only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs) and might not be functionally complete. Red Hat does not recommend using them in production. These features provide early access to upcoming product features, enabling customers to test functionality and provide feedback during the development process.
2.6.1.1. How EvalHub integrates with Kueue Copy linkLink copied to clipboard!
When you submit an evaluation job and specify a Kueue LocalQueue name, EvalHub sets the kueue.x-k8s.io/queue-name label on the Kubernetes Job resource. The Kueue Operator watches for this label and routes the Job through the specified queue for admission control and scheduling.
The TrustyAI Operator detects whether the Kueue Workload API (kueue.x-k8s.io/v1beta1) is available on your cluster at startup. If Kueue is not installed, the Kueue workload monitoring components are disabled, and EvalHub uses the default Kubernetes scheduler.
2.6.1.2. EvalHub behavior scenarios with configured Kueue Copy linkLink copied to clipboard!
- Kueue present, valid queue
-
When you specify a LocalQueue that exists and is backed by an active ClusterQueue, EvalHub submits the Job through that queue. The job transitions to a
pendingstate while waiting for Kueue to admit it. Once Kueue reserves quota, the job progresses torunning. This ensures your evaluation workload respects the quota and fair-sharing policies defined in the ClusterQueue. - Kueue present, invalid queue
-
If you specify a LocalQueue that does not exist in the namespace, or if the backing ClusterQueue is stopped or unavailable, the job transitions to
failedwith aqueue_errormessage code. Check the queue configuration and namespace labels in Configure Kueue for evaluation jobs. - Kueue absent
-
If the Kueue Operator is not installed or the Workload API is not available, the
kueue.x-k8s.io/queue-namelabel is ignored by the default Kubernetes scheduler. The evaluation job runs immediately with default scheduling, and no error occurs. This allows clusters to transition to Kueue management without requiring changes to job submission logic.
2.6.1.3. Namespace labels for Kueue integration Copy linkLink copied to clipboard!
To use Kueue with EvalHub, both of the following labels must be present on the tenant namespace:
evalhub.trustyai.opendatahub.io/tenant=- This label registers the namespace as an EvalHub tenant. The TrustyAI Operator watches for this label and provisions the necessary roles and service accounts.
kueue.openshift.io/managed=true- This label marks the namespace as managed by Kueue. The Kueue Operator watches for this label and enables workload admission control in the namespace.
Both labels are required for Kueue integration to function.
Additional resources
2.6.2. Configure Kueue for evaluation jobs Copy linkLink copied to clipboard!
To manage workload queuing and quotas for EvalHub evaluation jobs, configure Kueue in Red Hat build of Kueue. Install the Kueue Operator, create queue resources, and label tenant namespaces to complete this integration.
EvalHub Kueue integration is a Technology Preview feature only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs) and might not be functionally complete. Red Hat does not recommend using them in production. These features provide early access to upcoming product features, enabling customers to test functionality and provide feedback during the development process.
Prerequisites
- You have cluster administrator privileges.
- You have a running EvalHub instance. For details, see Deploy EvalHub with the TrustyAI Operator.
- You have already configured at least one EvalHub tenant namespace. See Set up a tenant namespace.
Procedure
Install the Red Hat build of Kueue Operator from OperatorHub.
Follow the instructions in Red Hat build of Kueue on OpenShift to install the operator on your cluster.
Create a ClusterQueue that defines the resource pool for evaluation workloads.
Refer to the Kueue documentation for detailed configuration of ClusterQueue resources, including resource flavors, nominal quota, and borrowing policies.
Use the following as a basic example:
apiVersion: kueue.x-k8s.io/v1beta1 kind: ClusterQueue metadata: name: evaluation-workloads spec: namespaceSelector: {} resources: - name: cpu flavors: - name: default resources: - name: cpu nominalQuota: "10" - name: memory flavors: - name: default resources: - name: memory nominalQuota: "40Gi" - name: gpu flavors: - name: default resources: - name: nvidia.com/gpu nominalQuota: "4"Create a LocalQueue in each EvalHub tenant namespace, pointing to the ClusterQueue.
Replace
<namespace>with your tenant namespace name andevaluation-workloadswith your ClusterQueue name if different.apiVersion: kueue.x-k8s.io/v1beta1 kind: LocalQueue metadata: name: evaluation-queue namespace: <namespace> spec: clusterQueue: evaluation-workloadsLabel the EvalHub tenant namespace for Kueue management:
$ oc label namespace <namespace> kueue.openshift.io/managed=trueThis label is in addition to the EvalHub tenant label (
evalhub.trustyai.opendatahub.io/tenant=) that was applied when you created the tenant namespace. Both labels are required for Kueue integration.
Verification
Confirm that both labels are set on the namespace:
$ oc get namespace <namespace> --show-labels | grep -E "kueue.openshift.io|evalhub"The output should include both
kueue.openshift.io/managed=trueandevalhub.trustyai.opendatahub.io/tenant=.Confirm that the LocalQueue is created and active:
$ oc get localqueue -n <namespace>The LocalQueue must be listed with status
Active.Submit a test evaluation job and verify that it transitions through the expected states.
When you submit a job with a Kueue queue specified, the job must enter the
pendingstate while waiting for Kueue to admit the workload. Once resources are available in the queue, the job transitions torunning.See Submit an evaluation job for instructions on submitting jobs with a queue specification.
Additional resources
2.7. List EvalHub providers and benchmarks Copy linkLink copied to clipboard!
List the evaluation providers and benchmarks registered in EvalHub to see which evaluation frameworks and tasks are available for your jobs. You can list providers by using the REST API, Python SDK, or CLI.
Prerequisites
- You have a running EvalHub instance.
Procedure
List all registered providers:
$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" $EVALHUB_URL/api/v1/evaluations/providers | jq .{ "items": [ { "resource": { "id": "lm_evaluation_harness", "owner": "system" }, "name": "lm_evaluation_harness", "title": "LM Evaluation Harness", "benchmarks": [ ... ] }, { "resource": { "id": "garak", "owner": "system" }, "name": "garak", "title": "Garak", "benchmarks": [ ... ] } ] }Get a specific provider with its benchmarks:
To get a specific provider by using the REST API, run:
$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" $EVALHUB_URL/api/v1/evaluations/providers/lm_evaluation_harness | jq .{ "resource": { "id": "lm_evaluation_harness", "owner": "system" }, "name": "lm_evaluation_harness", "title": "LM Evaluation Harness", "benchmarks": [ { "id": "mmlu", "name": "MMLU", "category": "reasoning" }, { "id": "hellaswag", "name": "HellaSwag", "category": "reasoning" }, { "id": "arc_challenge", "name": "ARC Challenge", "category": "reasoning" }, ... ] }To get a specific provider by using the Python SDK, run:
from evalhub.client import SyncEvalHubClient client = SyncEvalHubClient( base_url="https://evalhub.example.com", tenant="my-namespace" ) for provider in client.providers.list(): print(f"{provider.resource.id}: {provider.name}") benchmarks = client.benchmarks.list(provider_id="lm_evaluation_harness") for b in benchmarks: print(f" {b.id}: {b.name}")lm_evaluation_harness: LM Evaluation Harness garak: Garak guidellm: GuideLLM mmlu: Massive Multitask Language Understanding hellaswag: HellaSwag gsm8k: Grade School Math 8K ...To get a specific provider by using the CLI, run:
$ evalhub providers listID NAME DESCRIPTION BENCHMARKS lm_evaluation_harness LM Evaluation Harness EleutherAI language model evaluation 167 garak Garak LLM vulnerability and safety scanner 12 guidellm GuideLLM Performance benchmarking 4
Optional: Get more details information about a specific provider. For example, for details about
lm_evaluation_harness, run:$ evalhub providers describe lm_evaluation_harnessProvider: LM Evaluation Harness ID: lm_evaluation_harness Description: EleutherAI language model evaluation framework Benchmarks (167): ID NAME CATEGORY METRICS mmlu Massive Multitask Language Und… knowledge acc, acc_norm hellaswag HellaSwag reasoning acc, acc_norm gsm8k Grade School Math 8K math exact_match arc_easy ARC Easy reasoning acc, acc_norm ...
Verification
- Confirm that the provider list is not empty and includes the built-in providers enabled in your EvalHub deployment.
2.8. Submit an evaluation job Copy linkLink copied to clipboard!
Submit an evaluation job in EvalHub by specifying a model endpoint and one or more benchmarks. EvalHub runs the benchmarks against the model and returns a job ID that you can use to track results.
Prerequisites
- You have a running EvalHub instance.
- You have a model endpoint accessible from within the cluster.
- You know which providers and benchmarks are available. See List providers and benchmarks.
- Optional: If you want to use Kueue scheduling, a cluster administrator has configured Kueue for the tenant namespace. See Configure Kueue for evaluation jobs.
Procedure
Submit a job by specifying the model endpoint and one or more benchmarks:
To use the REST API, run:
$ curl -X POST $EVALHUB_URL/api/v1/evaluations/jobs \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "X-Tenant: <namespace>" \ -d { "name": "my-eval", "model": { "url": "http://my-model.my-namespace.svc.cluster.local:8080/v1", "name": "my-model" }, "benchmarks": [ { "provider_id": "lm_evaluation_harness", "benchmark_id": "mmlu" }, { "provider_id": "lm_evaluation_harness", "benchmark_id": "hellaswag" } ] }NoteMost providers expect the model URL to point to an OpenAI-compatible inference endpoint. The required URL format might vary depending on the provider. Check the provider documentation for specific requirements.
The server returns a
202 Acceptedresponse with the job resource, including a job ID for tracking.To use the Python SDK, enter the following command:
from evalhub.client import SyncEvalHubClient from evalhub.models import JobSubmissionRequest, ModelConfig, BenchmarkConfig client = SyncEvalHubClient( base_url="https://evalhub.example.com", tenant="my-namespace" ) job = client.jobs.create(JobSubmissionRequest( name="my-eval", model=ModelConfig( url="http://my-model.my-namespace.svc.cluster.local:8080/v1", name="my-model" ), benchmarks=[ BenchmarkConfig(provider_id="lm_evaluation_harness", benchmark_id="mmlu"), BenchmarkConfig(provider_id="lm_evaluation_harness", benchmark_id="hellaswag"), ] )) print(f"Job ID: {job.resource.id}")To use the CLI, run the following command:
$ evalhub eval run \ --name my-eval \ --model-url http://my-model.my-namespace.svc.cluster.local:8080/v1 \ --model-name my-model \ --provider lm_evaluation_harness \ -b mmlu -b hellaswagTo use a YAML config file, run:
$ evalhub eval run --config evaljob.yaml
Optional: To route the job through a Kueue queue, add the
queueparameter when submitting:To submit with a Kueue queue using the
curlutility:$ curl -X POST $EVALHUB_URL/api/v1/evaluations/jobs \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "X-Tenant: `<namespace>`" \ -d { "model": { "url": "http://my-model.my-namespace.svc.cluster.local:8080/v1", "name": "my-model" }, "benchmarks": [ { "provider_id": "lm_evaluation_harness", "benchmark_id": "mmlu" } ], "queue": { "kind": "kueue", "name": "<queue_name>" } }Replace
<queue_name>with the name of the Kueue LocalQueue in your tenant namespace.To submit with a Kueue queue using the Python SDK:
from evalhub.client import SyncEvalHubClient from evalhub.models import JobSubmissionRequest, ModelConfig, BenchmarkConfig, QueueConfig client = SyncEvalHubClient( base_url="https://evalhub.example.com", tenant="my-namespace" ) job = client.jobs.create(JobSubmissionRequest( model=ModelConfig( url="http://my-model.my-namespace.svc.cluster.local:8080/v1", name="my-model" ), benchmarks=[ BenchmarkConfig(provider_id="lm_evaluation_harness", benchmark_id="mmlu"), ], queue=QueueConfig(kind="kueue", name="<queue_name>") )) print(f"Job ID: {job.resource.id}")To submit with a Kueue queue using the CLI:
$ evalhub eval run \ --name my-eval \ --model-url http://my-model.my-namespace.svc.cluster.local:8080/v1 \ --model-name my-model \ --queue kueue:`<queue_name>` \ --provider lm_evaluation_harness \ -b mmluThe
queueparameter is optional. When omitted, the job runs with the default Kubernetes scheduler.For details on how Kueue scheduling affects job status and behavior, see Section 2.6.1, “EvalHub Kueue integration overview”.
Verification
Confirm the job is registered and check its status:
$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/jobs/<job_id> | jq .status.stateThe job status transitions from
pendingtorunningtocompleted.Alternatively, use the CLI:
$ evalhub eval status <job_id>Alternatively, use the Python SDK:
job = client.jobs.get(job_id) print(job.state)
2.9. Submit an evaluation job using the OpenShift AI dashboard Copy linkLink copied to clipboard!
Submit evaluation jobs for your models directly from the OpenShift AI dashboard. You can configure evaluation parameters, select benchmarks or benchmark suites, and optionally set pass or fail thresholds to validate model performance against expected criteria.
EvalHub logs evaluation results to MLflow for tracking and comparison.
Prerequisites
- You have logged in to Red Hat OpenShift AI.
- You have enabled the TrustyAI component, as described in Enabling the TrustyAI component.
- You have configured an MLflow experiment tracking server, as described in Configure MLflow experiment tracking.
- You have a deployed model with an inference endpoint.
You have role-based permissions that allow you to do the following:
- Get, list, create, update, delete EvalHub resources.
- Create and get MLflow experiments.
-
The Evaluations page is enabled in the OpenShift AI dashboard. If it is not visible, set
disableEvalHubto false in theOdhDashboardConfigcustom resource (CR).
Procedure
-
In the OpenShift AI dashboard, click Develop & train
Evaluations. - Optional: From the Project dropdown menu, select the project namespace with the model you deployed. This filters the evaluations list to show only evaluations from the selected project.
Click the Start evaluation run button and select the benchmarks to run:
- Benchmark: Select an individual benchmark to evaluate a specific capability or task.
Benchmark suite: Select a predefined collection of related benchmarks to evaluate multiple aspects of model performance. Benchmark suites in the dashboard correspond to collections as described in EvalHub built-in collections.
You can select multiple benchmarks or benchmark suites. Each selection appears as a tag below the field.
Configure the basic evaluation parameters:
- Evaluation name: Enter a unique name for the evaluation job. By default, the name is the evaluation timestamp. This name identifies the job in the evaluations list and in MLflow experiments.
- MLflow experiment: Select an existing MLflow experiment from the dropdown menu. Evaluation results are logged as runs within the selected experiment.
- Source: Select the source to evaluate from the dropdown menu. You can choose a model, an agent, or pre-recorded responses as a source.
- Enter additional parameters, such as the source name, Endpoint URL, or Dataset URL, depending on the selected source.
Optional: Change the evaluation threshold values by dragging the slider to select a value between
0and100. This value is equivalent to the0.0-1.0threshold used in the API.Alternatively, click the numeric input field to the right of the slider and type a value.
Thresholds define pass or fail criteria for evaluation results. If you set a threshold, the evaluation results include a pass or fail status based on whether the overall score meets the specified value.
Click Benchmark parameters to expand the section and configure benchmark-specific parameters. For example, if you evaluate with the Basic science Q&A benchmark, add the following parameters:
{ "num_examples": 10, "limit": 5, "tokenizer": "google/flan-t5-small" }Click Evaluate.
The evaluation job is submitted and the page returns to the evaluations list.
Verification
- On the Evaluations page, locate your evaluation job in the list.
- Verify that the Status column shows Running while the job executes. The job progresses to Completed when all benchmarks finish successfully, or Failed if an error occurs.
- When the job completes, click the evaluation name to view detailed results, including benchmark scores and pass or fail status if you configured a threshold.
2.10. Track evaluation jobs and results Copy linkLink copied to clipboard!
Track the status of running evaluation jobs and retrieve results after completion. You can check individual jobs, list all jobs, and filter by status.
Prerequisites
- You have submitted an evaluation job to EvalHub.
- You have the job ID returned from the submission.
Procedure
Check the status of a specific job:
$ curl -s \ -H "Authorization: Bearer $TOKEN" \ -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/jobs/<job_id> | jq .Example response for a completed job:
{ "resource": { "id": "<job_id>", "tenant": "<namespace>", "created_at": "2026-04-22T10:00:00Z" }, "status": { "state": "completed", "benchmarks": [ { "id": "mmlu", "provider_id": "lm_evaluation_harness", "status": "completed" }, { "id": "hellaswag", "provider_id": "lm_evaluation_harness", "status": "completed" } ] }, "results": { "benchmarks": [ { "id": "mmlu", "provider_id": "lm_evaluation_harness", "metrics": { "acc": 0.65, "acc_norm": 0.68 } }, { "id": "hellaswag", "provider_id": "lm_evaluation_harness", "metrics": { "acc": 0.72, "acc_norm": 0.75 } } ] }, "name": "my-eval", "model": { "url": "http://my-model:8080/v1", "name": "my-model" }, ... }After the job completes, retrieve the benchmark results:
$ curl -s \ -H "Authorization: Bearer $TOKEN" \ -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/jobs/<job_id> | jq .resultsThe
resultsobject contains benchmark scores, metrics, and pass/fail outcomes. If pass criteria are configured, the results include atestfield with the overall score, threshold, and pass/fail status.List all jobs, optionally filtered by status:
To use the REST API, run:
$ curl -s \ -H "Authorization: Bearer $TOKEN" \ -H "X-Tenant: <namespace>" \ "$EVALHUB_URL/api/v1/evaluations/jobs?status=completed&limit=10" | jq .Expand Table 2.1. Job query parameters Parameter Default Description limit50Maximum number of results to return. The maximum allowed value is 100.
offset0Number of results to skip for pagination.
status—
Filter by job state:
pending,running,completed,failed,cancelled,partially_failed.name—
Filter by job name. Uses exact, case-sensitive matching.
tags—
Filter by a single tag. Returns jobs that contain the specified tag in their tags list.
owner—
Filter by the authenticated username of the job owner, for example
system:serviceaccount:<namespace>:<name>for aServiceAccountor the OpenShift username.experiment_id—
Filter by MLflow experiment ID.
To use the CLI and to watch a job’s status in real time, use the
--watchflag. The CLI polls the job at regular intervals and displays benchmark progress until the job reaches a terminal state:$ evalhub eval status --watch <job_id>To retrieve formatted results after a job completes:
$ evalhub eval results <job_id> --format tableBENCHMARK PROVIDER METRIC VALUE mmlu lm_evaluation_harness acc 0.65 mmlu lm_evaluation_harness acc_norm 0.68 hellaswag lm_evaluation_harness acc 0.72 hellaswag lm_evaluation_harness acc_norm 0.75The
--formatflag supportstable,json,yaml, andcsv.To use the Python SDK and to check the status of a specific job, run:
job = client.jobs.get(job_id) print(f"State: {job.state}")To wait for a job to complete:
result = client.jobs.wait_for_completion(job_id, timeout=3600, poll_interval=5.0) for b in result.results.benchmarks: print(f"{b.id}: {b.metrics}")To list jobs filtered by status:
from evalhub.models import JobStatus completed_jobs = client.jobs.list(status=JobStatus.COMPLETED, limit=10) for job in completed_jobs: print(f"{job.id}: {job.state}")
2.10.1. Kueue-specific job status behavior Copy linkLink copied to clipboard!
When an evaluation job is submitted with a Kueue queue, the job status transitions behave differently from jobs without Kueue.
The pending state indicates that the job is waiting for Kueue to admit the workload. The job remains in pending until sufficient quota is available in the specified LocalQueue. Once Kueue reserves quota, the job transitions to running.
If a job fails with a queue_error message code, the specified Kueue queue could not be resolved. This typically means the LocalQueue does not exist in the namespace, or the backing ClusterQueue is stopped or unavailable. Check the queue configuration and namespace labels as described in Configure Kueue for evaluation jobs.
When Kueue preempts or evicts a job to make room for higher-priority work, the job status in the EvalHub API remains running. To detect preemption, check the Kubernetes Workload resource conditions. For more information, see Understanding preemption in evaluation jobs.
2.11. Cancel and delete jobs Copy linkLink copied to clipboard!
Cancel a running evaluation job or permanently delete a job record from the database by using the REST API, the CLI, or the Pyton SDK.
Prerequisites
- You have submitted an evaluation job to EvalHub.
- You have the job ID of the job to cancel or delete.
-
You have
deletepermissions on theevaluationsvirtual resource in the tenant namespace. For more information, see Section 2.27, “Grant access to EvalHub”.
Procedure
Cancel or permanently delete the job by using the REST API:
To cancel a running job with a soft delete, where the job is marked as
cancelledbut the record is preserved for auditing, run the following command:$ curl -X DELETE -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" $EVALHUB_URL/api/v1/evaluations/jobs/<job_id>To permanently delete a job record from the database, run the following command with the
hard_deletequery parameter:WarningThe
hard_deleteoperation permanently removes the job record from the database. This action cannot be undone, and the job results will no longer be available for auditing.$ curl -X DELETE -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" "$EVALHUB_URL/api/v1/evaluations/jobs/<job_id>?hard_delete=true"For both soft and hard deletes, EvalHub cleans up associated
JobandConfigMapKubernetes resources in the tenant namespace before updating or removing the record. The server returns204 No Contenton success.
Cancel or permanently delete the job by using the CLI:
To cancel a running job with a soft delete:
$ evalhub eval cancel <job_id>To permanently delete a job with a hard delete:
$ evalhub eval cancel <job_id> --hard
Cancel or permanently delete the job by using the Python SDK:
To cancel a running job with a soft delete:
client.jobs.cancel(job_id)To permanently delete a job with a hard delete:
client.jobs.cancel(job_id, hard_delete=True)
Verification
For a soft delete, verify the job status is
cancelled:$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/jobs/<job_id> | jq .status.stateAlternatively, use the CLI:
$ evalhub eval status <job_id>Alternatively, use the Python SDK:
job = client.jobs.get(job_id) print(job.state)For a hard delete, verify the job returns
404 Not Found:$ curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: Bearer $TOKEN" \ -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/jobs/<job_id>The CLI and Python SDK raise an error when retrieving a hard-deleted job, confirming that the record has been removed.
2.12. EvalHub built-in collections Copy linkLink copied to clipboard!
EvalHub includes several built-in collections that group benchmarks from one or more providers into reusable evaluation suites. Each benchmark in a collection can have its own weight, primary score metric, and pass criteria threshold.
Note that the built-in collections correspond to benchmark suites in the OpenShift AI dashboard.
| Collection | Category | Description | Benchmarks |
|---|---|---|---|
|
| general | Open LLM Leaderboard v2. Comprehensive evaluation suite for general-purpose language models. |
|
|
| safety | Evaluates model safety, bias, and fairness across diverse scenarios. |
|
|
| safety | End-to-end safety assessment covering toxic content generation, tendency to produce false or misleading information, and alignment with ethical principles. |
|
Each built-in collection defines per-benchmark weights and thresholds. For example, the safety-and-fairness-v1 collection assigns higher weights to toxigen and ethics_cm (weight 3) than to winogender and crows_pairs_english (weight 1), which gives these benchmarks greater influence on the overall safety score.
2.13. Create a custom collection in EvalHub Copy linkLink copied to clipboard!
Create a custom collection that groups benchmarks from one or more providers into a reusable evaluation job.
Prerequisites
- You have a running EvalHub instance.
Procedure
Create a collection:
By using the REST API:
$ curl -X POST $EVALHUB_URL/api/v1/evaluations/collections \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "X-Tenant: <namespace>" \ -d '{ "name": "my-safety-suite", "category": "safety", "benchmarks": [ {"provider_id": "lm_evaluation_harness", "benchmark_id": "truthfulqa_mc2"}, {"provider_id": "garak", "benchmark_id": "owasp_llm_top_10"} ] }'Example response:
{ "resource": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "tenant": "<namespace>", "created_at": "2026-04-22T10:00:00Z", "owner": "<user_name>" }, "name": "my-safety-suite", "category": "safety", "benchmarks": [ {"provider_id": "lm_evaluation_harness", "id": "truthfulqa_mc2"}, {"provider_id": "garak", "id": "owasp_llm_top_10"} ] }By using the CLI with a YAML spec file:
my-safety-suite.yamlname: my-safety-suite category: safety benchmarks: - provider_id: lm_evaluation_harness benchmark_id: truthfulqa_mc2 - provider_id: garak benchmark_id: owasp_llm_top_10$ evalhub collections create --file my-safety-suite.yamlBy using the Python SDK:
collection = client.collections.create({ "name": "my-safety-suite", "category": "safety", "benchmarks": [ {"provider_id": "lm_evaluation_harness", "benchmark_id": "truthfulqa_mc2"}, {"provider_id": "garak", "benchmark_id": "owasp_llm_top_10"} ] })
Optional: After creating a collection, you can submit evaluation jobs that reference it. The following example shows a job submission by using the created collection:
$ curl -X POST $EVALHUB_URL/api/v1/evaluations/jobs \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "X-Tenant: <namespace>" \ -d '{ "name": "my-eval", "model": { "url": "http://my-model.my-namespace.svc.cluster.local:8080/v1", "name": "my-model" }, "collection": { "id": "<collection_id>" } }'
Verification
Confirm the collection was created:
$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/collections/<collection_id> | jq .Alternatively, use the CLI:
$ evalhub collections describe <collection_id>Alternatively, use the Python SDK:
collection = client.collections.get(collection_id)
2.14. Configure API key authentication for model endpoints Copy linkLink copied to clipboard!
Configure EvalHub to authenticate to a model endpoint by using an API key stored as a Kubernetes Secret.
Prerequisites
- You have the model endpoint URL.
- You have the API key for your model endpoint.
Procedure
Create a Secret containing your API key in the
model-auth.yamlfile:apiVersion: v1 kind: Secret metadata: name: model-auth type: Opaque stringData: api-key: "<api_key>"Apply the Secret to the tenant namespace:
$ oc apply -f model-auth.yaml -n <namespace>When you submit an evaluation job, include an
authfield in themodelobject to reference the Secret:Example model configuration with API key authentication:
"model": { "url": "http://my-model.my-namespace.svc.cluster.local:8080/v1", "name": "my-model", "auth": { "secret_ref": "model-auth" } }where
secret_refspecifies the name of the Secret that has the API key. For details, see Submit an evaluation job.
Verification
Confirm that the Secret creation succeeded and has the expected
api-keykey:$ oc get secret model-auth -n <namespace> -o jsonpath='{.data}' | jq 'keys'The output should include
<api_key>.
2.15. Authenticate models with a ServiceAccount token Copy linkLink copied to clipboard!
For models served with KServe and protected by kube-rbac-proxy, EvalHub can use automatic ServiceAccount token injection.
Procedure
Create a RoleBinding granting the job
ServiceAccountaccess to the model’s InferenceService.For more information about creating a
ServiceAccountand RoleBinding for model authentication, see Making authenticated inference requests in Deploying models with distributed inference.
2.16. Use custom data from S3 for EvalHub evaluations Copy linkLink copied to clipboard!
You can load external test datasets from S3-compatible storage, such as MinIO or Amazon S3, before an evaluation runs. When configured, EvalHub schedules an init container that downloads the data to /test_data inside the Job pod. The adapter can then read the files from that path.
This feature only applies when EvalHub runs benchmarks as Jobs. It does not apply to local-only evaluation runs.
Prerequisites
- You have an S3-compatible storage endpoint with your test data set already uploaded to a bucket.
- You have the S3 credentials for your storage endpoint.
Procedure
Create a Secret containing your S3 credentials in the
my-s3-credentials.yamlfile:apiVersion: v1 kind: Secret metadata: name: my-s3-credentials namespace: <namespace> type: Opaque stringData: AWS_ACCESS_KEY_ID: "<your_access_key>" AWS_SECRET_ACCESS_KEY: "<your_secret_key>" AWS_DEFAULT_REGION: "<your_region>" AWS_S3_ENDPOINT: "<your_s3_endpoint>"where:
-
AWS_DEFAULT_REGIONdefines the region for your S3-compatible storage, for exampleus-east-1. -
AWS_S3_ENDPOINTdefines the endpoint URL for your S3-compatible storage, for examplehttps://minio.example.com:9000for MinIO. For Amazon S3, you can omit this field or use the default AWS endpoint.
-
Apply the Secret:
$ oc apply -f my-s3-credentials.yamlWhen you submit an evaluation job, add a
test_data_refblock to each benchmark that requires external data:Example S3 test data configuration in a job submission:
"benchmarks": [ { "provider_id": "lm_evaluation_harness", "benchmark_id": "mmlu", "test_data_ref": { "s3": { "bucket": "my-eval-data", "key": "datasets/mmlu", "secret_ref": "my-s3-credentials" } } } ]where:
-
s3.bucketdefines the S3 bucket name. -
s3.keydefines the S3 key prefix for the data set files. s3.secret_refdefines the name of theSecretcontaining the S3 credentials.For the full job submission request, see Section 2.8, “Submit an evaluation job”.
The init container downloads all objects under the specified S3 prefix to
/test_data, preserving the relative directory structure. Thesecret_refmust reference aSecretin the tenant namespace.NoteThe expected file format and directory structure of the test data depend on the adapter and benchmark. See the adapter documentation for the required data layout.
Alternatively, use the CLI:
$ evalhub eval run \ --name s3-data-eval \ --model-url http://my-model.my-namespace.svc.cluster.local:8080/v1 \ --model-name my-model \ --provider lm_evaluation_harness \ --benchmark mmlu \ --test-data-s3-bucket my-eval-data \ --test-data-s3-key datasets/mmlu \ --test-data-s3-secret my-s3-credentialsAlternatively, use the Python SDK:
from evalhub.models import ( JobSubmissionRequest, ModelConfig, BenchmarkConfig, TestDataRef, S3TestDataRef ) job = client.jobs.submit(JobSubmissionRequest( name="s3-data-eval", model=ModelConfig( url="http://my-model.my-namespace.svc.cluster.local:8080/v1", name="my-model" ), benchmarks=[ BenchmarkConfig( id="mmlu", provider_id="lm_evaluation_harness", test_data_ref=TestDataRef( s3=S3TestDataRef( bucket="my-eval-data", key="datasets/mmlu", secret_ref="my-s3-credentials", ) ), ) ], ))Collections also support
test_data_refon individual benchmarks, allowing you to define custom data sources as part of a reusable evaluation suite.
-
Verification
Confirm that the job completes successfully. If the init container fails to download data from S3, the job transitions to the
failedstate.$ curl -s \ -H "Authorization: Bearer $TOKEN" \ -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/jobs/<job_id> | jq .status.stateIf the job fails, check the init container logs for download errors:
$ oc logs <pod_name> -c init -n <namespace>
2.17. Export evaluation results to an OCI registry Copy linkLink copied to clipboard!
EvalHub can export evaluation artifacts, such as logs, metrics, and outputs, by pushing artifacts to an Open Container Initiative (OCI) compatible registry for long-term storage and traceability.
Prerequisites
- You have access to an OCI-compatible container registry such as Quay.io.
- You have registry credentials for the OCI registry.
Procedure
Create a
kubernetes.io/dockerconfigjsonSecret with your registry credentials:$ oc create secret docker-registry oci-registry-credentials \ --docker-server=quay.io \ --docker-username=<user_name> \ --docker-password=<password> \ -n <namespace>When you submit an evaluation job, include an
exportsblock in the job submission body:Example OCI export configuration in a job submission:
"benchmarks": [ { "provider_id": "lm_evaluation_harness", "benchmark_id": "mmlu" } ], "exports": { "oci": { "coordinates": { "oci_host": "quay.io", "oci_repository": "my-org/eval-results" }, "k8s": { "connection": "oci-registry-credentials" } } }where:
-
oci.coordinates.oci_hostdefines the OCI registry hostname. -
oci.coordinates.oci_repositorydefines the repository path within the registry. oci.k8s.connectiondefines the name of the Secret containing the registry credentials.For the full job submission request, see Submit an evaluation job.
Results artifact from the evaluation frameworks are stored as OCI artifacts with separate layers, allowing selective access to specific outputs.
-
Verification
After the job completes, retrieve the OCI artifact reference from the job results:
$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/jobs/<job_id> | jq '.results.benchmarks[0].artifacts'Verify the artifact exists in the registry by using
skopeo:$ skopeo inspect --creds <user_name>:<password> docker://quay.io/my-org/eval-results:<tag>The tag is in the format
evalhub-<hash>, where the hash is derived from the job ID, provider, and benchmark. You can find the full OCI reference, including the tag, in the job results.
2.18. Configure MLflow experiment tracking for evaluation jobs Copy linkLink copied to clipboard!
When MLflow is configured for EvalHub, you can associate evaluation jobs with designated MLflow experiments. EvalHub automatically logs benchmark metrics as MLflow runs within the experiment.
Prerequisites
- You have a running MLflow instance accessible from the EvalHub deployment.
- You have configured the MLflow tracking URI in the EvalHub configuration. See Section 2.24, “EvalHub configuration reference” for details.
Procedure
When you submit an evaluation job by using REST API, include an
experimentblock in the job submission body:Example experiment configuration in a job submission:
"benchmarks": [ { "provider_id": "lm_evaluation_harness", "benchmark_id": "mmlu" } ], "experiment": { "name": "my-model-v2-eval" }For the full job submission request, see Section 2.8, “Submit an evaluation job”.
When using the CLI, include the
experimentfield in your YAML config file:Example experiment fragment in a YAML config file:
experiment: name: my-model-v2-eval$ evalhub eval run --config eval-with-mlflow.yamlFor the full YAML config file structure, see Section 2.8, “Submit an evaluation job”.
When using the Python SDK, pass an
ExperimentConfigto theJobSubmissionRequest:from evalhub.models import ExperimentConfig experiment=ExperimentConfig(name="my-model-v2-eval")For the full
JobSubmissionRequest, see Section 2.8, “Submit an evaluation job”.
Verification
When the job completes, the
resultssection includes anmlflow_experiment_urllinking to the experiment in the MLflow UI:$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/jobs/<job_id> | jq .results.mlflow_experiment_urlExample output:
"https://mlflow.example.com/#/experiments/42"Alternatively, use the CLI. The
evalhub eval resultscommand automatically displays the MLflow experiment URL when available:$ evalhub eval results <job_id>Alternatively, use the Python SDK:
job = client.jobs.get(job_id) print(job.results.mlflow_experiment_url)
2.19. Add a custom provider by using the API Copy linkLink copied to clipboard!
Register a custom provider by using the REST API. A provider definition includes a name, a container image for the adapter runtime, and a list of benchmarks. For more information about adapters, see Section 2.1, “Understanding EvalHub”.
Prerequisites
- You have a running EvalHub instance.
- You have a container image for your custom adapter packaged as a UBI9 image.
Procedure
Register the custom provider:
$ curl -X POST $EVALHUB_URL/api/v1/evaluations/providers \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "X-Tenant: <namespace>" \ -d '{ "name": "my-custom-provider", "title": "My Custom Provider", "description": "Custom evaluation framework for domain-specific benchmarks.", "benchmarks": [ { "id": "domain_accuracy", "name": "Domain Accuracy", "category": "general", "metrics": ["accuracy", "f1"], "primary_score": { "metric": "accuracy", "lower_is_better": false }, "pass_criteria": { "threshold": 0.8 } } ], "runtime": { "k8s": { "image": "quay.io/my-org/my-adapter:latest", "cpu_request": "500m", "memory_request": "512Mi", "cpu_limit": "2000m", "memory_limit": "4Gi" } } }'Example response:
{ "resource": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "tenant": "<namespace>", "created_at": "2026-04-22T10:00:00Z", "owner": "<user_name>" }, "name": "my-custom-provider", "title": "My Custom Provider", "description": "Custom evaluation framework for domain-specific benchmarks.", "benchmarks": [ { "id": "domain_accuracy", "name": "Domain Accuracy", "category": "general", "metrics": ["accuracy", "f1"], "primary_score": { "metric": "accuracy", "lower_is_better": false }, "pass_criteria": { "threshold": 0.8 } } ], "runtime": { "k8s": { "image": "quay.io/my-org/my-adapter:latest", "cpu_request": "500m", "memory_request": "512Mi", "cpu_limit": "2000m", "memory_limit": "4Gi" } } }The
runtime.k8ssection specifies the container image and resource requests for the adapter pod. Each benchmark must declare anid,name, andcategory. The optionalprimary_scoreandpass_criteriafields set default thresholds for the benchmark.User-created providers can be updated and deleted through the API. Built-in providers with
owner: systemare read-only.NoteThe Python SDK and CLI do not support creating providers. Use the REST API to register custom providers.
Verification
Confirm the provider was registered by retrieving it with the ID from the response:
$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/providers/<provider_id> | jq .nameThe output should return
"my-custom-provider".Alternatively, use the CLI:
$ evalhub providers describe <provider_id>Alternatively, use the Python SDK:
provider = client.providers.get(provider_id) print(provider.name)
2.20. Add a custom provider by using a ConfigMap Copy linkLink copied to clipboard!
Add providers at the Operator level by creating a ConfigMap in the Operator namespace with the appropriate labels. The TrustyAI Operator discovers the ConfigMap by its labels and then mounts the ConfigMap into the EvalHub deployment automatically.
Providers registered this way are system-owned, read-only, and available to all tenants. To register a tenant-scoped provider that can be updated or deleted, use the REST API instead. See Section 2.19, “Add a custom provider by using the API”.
Prerequisites
- You have a running EvalHub deployment.
- You have a container image for your custom adapter. See Section 2.22, “Write a custom evaluation adapter by using Python SDK”.
-
You have cluster administrator privileges or permissions to create
ConfigMapresources in the operator namespace. - You have permissions to edit the EvalHub custom resource.
Procedure
Create a
ConfigMapin the EvalHub custom resource namespace with the provider definition:evalhub-provider-my-custom-provider.yamlapiVersion: v1 kind: ConfigMap metadata: name: evalhub-provider-my-custom-provider namespace: <evalhub_namespace> labels: trustyai.opendatahub.io/evalhub-provider-type: system trustyai.opendatahub.io/evalhub-provider-name: my-custom-provider data: my-custom-provider.yaml: | id: my-custom-provider name: My Custom Provider description: Custom evaluation framework for domain-specific benchmarks. runtime: k8s: image: quay.io/my-org/my-adapter:latest cpu_request: "500m" memory_request: "512Mi" cpu_limit: "2000m" memory_limit: "4Gi" benchmarks: - id: domain_accuracy name: Domain Accuracy category: general metrics: - accuracy - f1 primary_score: metric: accuracy lower_is_better: false pass_criteria: threshold: 0.8Apply the created
ConfigMap:$ oc apply -f evalhub-provider-my-custom-provider.yamlReference the provider name in your EvalHub custom resource by adding it to the
spec.providerslist:Example
spec.providersfragment:spec: providers: - lm-evaluation-harness - garak - my-custom-providerFor the full EvalHub custom resource structure, see Section 2.3, “Deploy EvalHub with the TrustyAI Operator”.
The operator copies the
ConfigMapto the instance namespace and mounts it as a projected volume at/etc/evalhub/config/providers. The EvalHub server loads all provider YAML files from this directory at startup.
Verification
Confirm that the
ConfigMapwas created:$ oc get configmap evalhub-provider-my-custom-provider -n <evalhub_namespace>Check that the EvalHub deployment has restarted and is ready:
$ oc get pods -l app=eval-hub -n <evalhub_namespace>Confirm the custom provider is loaded:
$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/providers/my-custom-provider | jq .nameThe output should return
"My Custom Provider".
2.21. Add a collection by using a ConfigMap Copy linkLink copied to clipboard!
Add providers at the Operator level by creating a ConfigMap in the Operator namespace with the appropriate labels. The TrustyAI Operator discovers the ConfigMap by its labels and then mounts the ConfigMap into the EvalHub deployment automatically.
Collections registered this way are system-owned, read-only, and available to all tenants. To create a tenant-scoped collection that can be updated or deleted, use the REST API instead. See Section 2.13, “Create a custom collection in EvalHub”.
Prerequisites
- You have a running EvalHub deployment.
-
You have cluster administrator privileges or permissions to create
ConfigMapresources in the operator namespace. - You have permissions to edit the EvalHub custom resource.
- You know which provider-benchmark pairs you want to include in the collection. See Section 2.7, “List EvalHub providers and benchmarks”.
Procedure
Create a
ConfigMapin the EvalHub custom resource namespace with the collection definition:evalhub-collection-my-eval-suite.yamlapiVersion: v1 kind: ConfigMap metadata: name: evalhub-collection-my-eval-suite namespace: <evalhub_namespace> labels: trustyai.opendatahub.io/evalhub-collection-type: system trustyai.opendatahub.io/evalhub-collection-name: my-eval-suite data: my-eval-suite.yaml: | id: my-eval-suite name: My Evaluation Suite category: general description: Custom evaluation suite for internal model validation. pass_criteria: threshold: 0.7 benchmarks: - id: mmlu provider_id: lm_evaluation_harness weight: 2 primary_score: metric: acc_norm lower_is_better: false pass_criteria: threshold: 0.6 - id: hellaswag provider_id: lm_evaluation_harness weight: 1 primary_score: metric: acc_norm lower_is_better: false pass_criteria: threshold: 0.7Apply the
evalhub-collection-my-eval-suite.yaml:$ oc apply -f evalhub-collection-my-eval-suite.yamlReference the collection in your EvalHub custom resource by adding the collection name to the
spec.collectionslist:Example
spec.collectionsfragment:spec: collections: - leaderboard-v2 - safety-and-fairness-v1 - my-eval-suiteFor the full EvalHub custom resource structure, see Section 2.3, “Deploy EvalHub with the TrustyAI Operator”.
The operator mounts collection
ConfigMap(s) at/etc/evalhub/config/collections.
Verification
Confirm that the
ConfigMapwas created:$ oc get configmap evalhub-collection-my-eval-suite -n <evalhub_namespace>Check that the EvalHub deployment has restarted and is ready:
$ oc get pods -l app=eval-hub -n <evalhub_namespace>List collections and confirm the custom collection is present:
$ curl -s -H "Authorization: Bearer $TOKEN" -H "X-Tenant: <namespace>" \ $EVALHUB_URL/api/v1/evaluations/collections/my-eval-suite | jq .nameThe output should return
"My Evaluation Suite".
2.22. Write a custom evaluation adapter by using Python SDK Copy linkLink copied to clipboard!
An adapter translates EvalHub job requests into evaluation framework-specific commands. To write a custom adapter, install the EvalHub SDK with adapter dependencies and implement a single method.
Prerequisites
- You have Python 3.11 or later installed.
- You have an evaluation framework that you want to integrate with EvalHub.
-
You have
podmanor another container build tool installed to package the adapter as a container image.
Procedure
Install the EvalHub SDK with the adapter extra:
$ pip install "eval-hub-sdk[adapter]"Create a class that extends
FrameworkAdapterand implementsrun_benchmark_job:from evalhub.adapter import FrameworkAdapter from evalhub.models import JobSpec, JobCallbacks, JobResults, JobStatusUpdate, JobPhase class MyAdapter(FrameworkAdapter): def run_benchmark_job(self, config: JobSpec, callbacks: JobCallbacks) -> JobResults: callbacks.report_status(JobStatusUpdate( phase=JobPhase.RUNNING_EVALUATION, message="Running evaluation" )) # Replace with your framework's evaluation function scores = run_my_framework( model_url=config.model.url, benchmark=config.benchmark_id, parameters=config.parameters ) return JobResults( id=config.id, benchmark_id=config.benchmark_id, benchmark_index=config.benchmark_index, model_name=config.model.name, results=scores, num_examples_evaluated=len(scores), duration_seconds=self._get_duration() # Implement to return elapsed seconds )The framework handles loading the job specification from the mounted
ConfigMap, authenticating with the sidecar proxy container that communicates with the EvalHub server, and reporting results. Your adapter only needs to run the evaluation and return the results. For more information about the adapter and sidecar architecture, see Section 2.2, “EvalHub architecture overview”.Package your adapter as a Red Hat Universal Base Image 9 (UBI9) container image:
Create a
Containerfilein your adapter directory:ContainerfileFROM registry.access.redhat.com/ubi9/python-312 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY main.py /app/main.py ENTRYPOINT ["python", "main.py"]Build the image:
$ podman build -t quay.io/my-org/my-adapter:latest .Push the image to a container registry:
$ podman push quay.io/my-org/my-adapter:latest
Reference the image in the provider’s
runtime.k8s.imagefield when registering the provider. See Section 2.19, “Add a custom provider by using the API”.The following tables describe the
JobSpecandJobCallbacksinterfaces available to your adapter.Expand Table 2.3. JobSpec fields Field Description idUnique job identifier.
provider_idIdentifier of the provider that the benchmark belongs to.
benchmark_idIdentifier of the benchmark to evaluate.
benchmark_indexIndex of this benchmark within the job.
modelModel configuration, including
urlandname.parametersBenchmark-specific parameters, for example
num_fewshotorlimit.num_examplesThe number of examples to evaluate. When set to
None, the adapter evaluates all examples.exportsOptional OCI artifact export specification.
Expand Table 2.4. JobCallbacks methods Method Purpose report_status(update)Sends progress updates including the phase, message, and completed/total steps.
create_oci_artifact(spec)Pushes evaluation artifacts to an OCI registry.
report_results(results)Reports the final results to the EvalHub server. This method is called automatically if you return
JobResults.
2.23. EvalHub API endpoints reference Copy linkLink copied to clipboard!
All endpoints use the path prefix /api/v1. The OpenAPI 3.1.0 specification is available at /openapi.yaml and interactive documentation is available at /docs.
2.23.1. Evaluation job endpoints Copy linkLink copied to clipboard!
| Endpoint | Method | Description |
|---|---|---|
|
| POST |
Create and submit an evaluation job. Returns |
|
| GET | List evaluation jobs with pagination and filtering. |
|
| GET | Get a specific evaluation job with current status and results. |
|
| DELETE |
Cancel or hard-delete a job. Use |
|
| POST | Submit job status events from the adapter runtime. |
| State | Description |
|---|---|
|
|
The job is created and awaiting execution. When a Kueue queue is specified, |
|
| The evaluation is actively running. |
|
| All benchmarks completed successfully. |
|
|
The evaluation encountered a fatal error. A |
|
| The user canceled the job. |
|
| Some benchmarks succeed and others failed. |
2.23.2. Provider endpoints Copy linkLink copied to clipboard!
| Endpoint | Method | Description |
|---|---|---|
|
| POST | Create a custom provider. |
|
| GET |
List providers. Use |
|
| GET | Get a provider with all its benchmarks. |
|
| PUT | Replace a provider. |
|
| PATCH | Patch a provider with JSON Patch operations. |
|
| DELETE | Delete a provider. |
| Provider | Benchmarks | Description |
|---|---|---|
|
| 167 | General-purpose LLM evaluation: MMLU, HellaSwag, ARC, TruthfulQA, GSM8K, and more across 12 categories. |
|
| 8 | Security vulnerability scanning: OWASP LLM Top 10, AVID taxonomy, CWE. |
|
| 7 | Guidance language model evaluation. |
|
| 24 | Lightweight evaluation framework. |
2.23.3. Collection endpoints Copy linkLink copied to clipboard!
| Endpoint | Method | Description |
|---|---|---|
|
| POST | Create a benchmark collection. |
|
| GET | List collections with filtering. |
|
| GET | Get a collection with all benchmark references. |
|
| PUT | Replace a collection. |
|
| PATCH | Patch a collection with JSON Patch operations. |
|
| DELETE | Delete a collection. |
2.23.4. Health and observability endpoints Copy linkLink copied to clipboard!
| Endpoint | Method | Description |
|---|---|---|
|
| GET | Health check with status, timestamp, and build information. |
|
| GET | Prometheus metrics endpoint when enabled. |
|
| GET | OpenAPI 3.1.0 specification in YAML or JSON based on Accept header. |
|
| GET | Interactive Swagger UI documentation. |
2.23.5. Job submission fields Copy linkLink copied to clipboard!
Review the fields available when submitting an evaluation job via the POST /api/v1/evaluations/jobs endpoint:
| Field | Type | Required | Description |
|---|---|---|---|
|
| Object | Yes |
The model endpoint configuration. Contains |
|
| Array | Yes (if no collection) |
Array of benchmark objects to evaluate. Each object contains |
|
| String | No |
Reference to a pre-configured benchmark collection. Use either |
|
| String | No | A descriptive name for the evaluation job. |
|
| Array | No | String tags for organizing and filtering jobs. |
|
| Object | No |
Kueue queue configuration. Contains parameters such as |
|
| Object | No | Pass criteria for benchmarks in this job, overriding provider or collection defaults. |
|
| Object | No | MLflow experiment tracking configuration. |
2.23.6. Job failure message codes Copy linkLink copied to clipboard!
Review the message codes that appear in failed job responses:
| Message Code | Description |
|---|---|
|
| The specified Kueue queue could not be resolved. The LocalQueue does not exist in the namespace, or the backing ClusterQueue is stopped or unavailable. |
|
| The evaluation runtime encountered an error during benchmark execution. |
2.24. EvalHub configuration reference Copy linkLink copied to clipboard!
Configuration applies to the EvalHub server component. You configure EvalHub by using config/config.yaml and environment variables. Environment variables take precedence over config/config.yaml.
When deploying EvalHub with the TrustyAI Operator, the operator generates the config.yaml automatically from the EvalHub custom resource and environment variables defined in the spec.env field. You do not need to create or edit config.yaml directly. For information about configuring the EvalHub custom resource, see Section 2.3, “Deploy EvalHub with the TrustyAI Operator”.
2.24.1. Service configuration Copy linkLink copied to clipboard!
| Parameter | Environment variable | Default | Description |
|---|---|---|---|
|
|
|
| The port that the API server listens on. |
|
|
|
| The address that the API server binds to. |
|
|
| — | Path to the TLS certificate file. |
|
|
| — | Path to the TLS private key file. |
|
| — |
|
Disables authentication and authorization. Setting this to |
2.24.2. Database configuration Copy linkLink copied to clipboard!
When deploying EvalHub with the TrustyAI Operator, you must set spec.database.type in the EvalHub custom resource to either postgresql or sqlite. The operator generates the corresponding configuration automatically. The postgresql option sets the driver to pgx and injects the connection URL from a Kubernetes Secret. The sqlite option sets the driver to sqlite with an in-memory database. Data is not persisted across restarts with sqlite. Use postgresql for production deployments.
The following table describes the parameters available in the EvalHub config/config.yaml configuration file.
| Parameter | Environment variable | Default | Description |
|---|---|---|---|
|
| — |
|
The storage driver. Supported values: |
|
|
|
|
The database connection string. The default value is a SQLite in-memory URI, which stores all data in memory and does not persist across restarts. For PostgreSQL, use the format |
2.24.3. MLflow configuration Copy linkLink copied to clipboard!
| Parameter | Environment variable | Default | Description |
|---|---|---|---|
|
|
| — | The URL of the MLflow tracking server. Setting this parameter enables MLflow integration. When set, evaluation results are logged to MLflow. Without this parameter, MLflow tracking is disabled. |
|
|
| — | The path to a TLS CA certificate file for verifying the MLflow server’s certificate. |
|
|
|
|
If |
|
|
| — |
The path to a file containing an authentication token for the MLflow server. The token is sent as a Bearer token in the |
|
|
| — | The MLflow workspace or experiment namespace. |
2.24.4. OpenTelemetry configuration Copy linkLink copied to clipboard!
When deploying with the TrustyAI Operator, include the otel field in the EvalHub custom resource to enable OpenTelemetry. The presence of the otel field in the CR enables OpenTelemetry automatically.
| CR field | Default | Description |
|---|---|---|
|
|
|
The exporter type. Supported values: |
|
| — |
The endpoint for the OTLP exporter, for example |
|
|
|
If |
|
|
|
Trace sampling ratio as a value between |
2.25. EvalHub multi-tenancy and RBAC Copy linkLink copied to clipboard!
EvalHub supports namespace-based multi-tenancy, where each Kubernetes namespace represents a tenant. EvalHub enforces isolation at multiple layers, including authentication, authorization, data access, and job execution.
EvalHub enforces isolation at the following layers:
-
Authentication — EvalHub uses the Kubernetes
TokenReviewAPI to validate bearer tokens in incoming requests. -
Authorization —
SubjectAccessReview(SAR) checks verify that the caller has permission to perform the requested operation on EvalHub virtual resources in the target namespace. Virtual resources are logical resource names that EvalHub defines for RBAC purposes under thetrustyai.opendatahub.ioAPI group. They do not correspond to Kubernetes custom resource definitions. The virtual resources areevaluations,collections,providers, andstatus-events. -
Data isolation — EvalHub scopes all database queries by
tenant_idto prevent cross-tenant data access. - Job execution — EvalHub creates Job resources in the tenant’s namespace.
The X-Tenant request header determines the target tenant namespace. The X-User header identifies the authenticated user.
2.26. Set up a tenant namespace Copy linkLink copied to clipboard!
Register a namespace as an EvalHub tenant so that users, programmatic clients, and agents can submit evaluation jobs in that namespace.
Prerequisites
- You have cluster administrator privileges.
- You have a running EvalHub instance.
- You have a namespace to use as a tenant.
Procedure
Add the tenant label to the namespace:
$ oc label namespace <namespace> evalhub.trustyai.opendatahub.io/tenant=The label value is intentionally empty. The TrustyAI Operator checks for the presence of the label, not its value.
NoteUse a dedicated namespace for EvalHub rather than
redhat-ods-applications, as described in Section 2.3, “Deploy EvalHub with the TrustyAI Operator”. Theredhat-ods-applicationsnamespace hasNetworkPolicyresources that restrict cross-namespace traffic, which requires additional labeling on tenant namespaces. If EvalHub is deployed inredhat-ods-applications, label each tenant namespace to allow the evaluationJobsidecar to communicate with the EvalHub server:$ oc label namespace <namespace> opendatahub.io/generated-namespace=trueReview the
NetworkPolicyresources withoc get networkpolicy -n <evalhub-server-namespace>to determine any additional requirements.The TrustyAI Operator watches for this label and automatically provisions the following resources in the labeled namespace:
-
A job
ServiceAccountused by evaluationJobpods as their identity. -
A
RoleandRoleBindinggranting the jobServiceAccountpermission to createstatus-eventsfor reporting job progress. -
A
RoleBindinggranting the EvalHub APIServiceAccountpermission to create and deleteJobresources in the tenant namespace. -
A
RoleBindinggranting the EvalHub APIServiceAccountpermission to manageConfigMapresources used to mount job specifications intoJobpods. -
A
RoleBindinggranting the jobServiceAccountaccess to MLflow resources when MLflow is configured. A service CA
ConfigMapwith the cluster CA bundle injected by OpenShift, so thatJobpods can make HTTPS requests to the EvalHub API.When the tenant label is removed from a namespace, the controller cleans up all provisioned resources automatically.
-
A job
Verification
Confirm that the tenant label is set on the namespace:
$ oc get namespace <namespace> --show-labels | grep evalhubConfirm that the operator provisioned the expected resources in the tenant namespace:
$ oc get serviceaccount,rolebinding,configmap -n <namespace> | grep evalhubThe output should include a
ServiceAccount,RoleBindingresources, and a service CAConfigMapcreated by the operator.
2.27. Grant access to EvalHub Copy linkLink copied to clipboard!
Grant tenant users access to EvalHub by creating a Role and RoleBinding in the tenant namespace. EvalHub supports three types of principals.
Prerequisites
-
You have permissions to create
RoleandRoleBindingresources in the tenant namespace. -
You have impersonation privileges to verify access with
oc auth can-i --as. - You have set up the target namespace as an EvalHub tenant.
- You have identified which virtual resources and verbs to grant. See Section 2.28, “EvalHub roles reference” for available resources.
Procedure
Select the type of principal that matches your use case from the following table:
Expand Table 2.17. Principal types Principal type Token source Use case ServiceAccountMounted pod token or long-lived token
Automation, CI/CD pipelines, agents using Model Context Protocol (MCP)
OpenShift User
oc whoami -tInteractive use
OpenShift Group
User token with group membership
Team-based access
Create a
Rolein the tenant namespace that grants access to the required EvalHub virtual resources:apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: evalhub-evaluator namespace: <namespace> rules: - apiGroups: ["trustyai.opendatahub.io"] resources: ["evaluations", "collections", "providers"] verbs: ["get", "list", "create", "update", "delete"] - apiGroups: ["mlflow.kubeflow.org"] resources: ["experiments"] verbs: ["create", "get"]Apply the
Role:$ oc apply -f evalhub-evaluator-role.yamlCreate a
RoleBindingto bind the principal to theRoledepending on the selected type.To grant access to a ServiceAccount:
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: my-sa-evalhub-access namespace: <namespace> subjects: - kind: ServiceAccount name: my-sa namespace: <namespace> roleRef: kind: Role name: evalhub-evaluator apiGroup: rbac.authorization.k8s.ioApply the
RoleBindingby the command:$ oc apply -f my-sa-evalhub-access.yamlTo obtain a bearer token for a
ServiceAccount, run the following command:$ export TOKEN=$(oc create token my-sa -n <namespace> --duration=1h)To grant access to an OpenShift User:
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: user-evalhub-access namespace: <namespace> subjects: - kind: User name: <user_name> roleRef: kind: Role name: evalhub-evaluator apiGroup: rbac.authorization.k8s.ioApply the user
RoleBinding:$ oc apply -f user-evalhub-access.yamlTo obtain a bearer token for an OpenShift User, log in as the user and run the following command:
$ export TOKEN=$(oc whoami -t)To grant access to an OpenShift Group:
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: team-evalhub-access namespace: <namespace> subjects: - kind: Group name: evalhub-users roleRef: kind: Role name: evalhub-evaluator apiGroup: rbac.authorization.k8s.ioApply the group
RoleBinding:$ oc apply -f team-evalhub-access.yamlTo obtain a bearer token for a Group member, log in as a user who belongs to the group and run the following command:
$ export TOKEN=$(oc whoami -t)
Verification
Verify that the principal has the expected permissions on the EvalHub virtual resources by using
oc auth can-i.For a
ServiceAccount:$ oc auth can-i create evaluations.trustyai.opendatahub.io \ -n <namespace> \ --as=system:serviceaccount:<namespace>:my-saFor an OpenShift User:
$ oc auth can-i create evaluations.trustyai.opendatahub.io \ -n <namespace> \ --as=<user_name>For an OpenShift Group:
$ oc auth can-i create evaluations.trustyai.opendatahub.io \ -n <namespace> \ --as=<user_name> --as-group=evalhub-usersEach command should return
yes.
2.28. EvalHub roles reference Copy linkLink copied to clipboard!
EvalHub uses virtual Kubernetes resources for tenant authorization. These resources do not correspond to actual Kubernetes API resources. EvalHub performs SubjectAccessReview (SAR) checks against these resources in the tenant namespace specified by the X-Tenant header.
To authorize tenant users, create a Role in the tenant namespace granting the required verbs on these virtual resources. For instructions, see Section 2.27, “Grant access to EvalHub”.
| API group | Resource | Verbs | Description |
|---|---|---|---|
|
|
|
| Submit, view, update, and delete evaluation jobs. |
|
|
|
| Create, view, update, and delete benchmark collections. |
|
|
|
| Create, view, update, and delete evaluation providers. |
|
|
|
| Report job progress. Used by operator-provisioned job ServiceAccounts, not by tenant users. |
|
|
|
| Create and access MLflow experiments for result tracking. |
2.29. Additional resources Copy linkLink copied to clipboard!
The following resources provide additional information about EvalHub.