mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 19:02:16 +00:00
Extend the eval framework to compute per-sample hardware efficiency metrics when model hardware parameters are provided in TOML config: - ModelConfig gains param_count_b, active_params_b, gpu_peak_tflops, gpu_peak_bandwidth_gb_s, num_gpus fields - RunConfig gains metadata dict, populated by expand_suite() from model hardware params - EvalRunner._process_one() computes IPW (Intelligence Per Watt), IPJ (Intelligence Per Joule), MFU, and MBU per sample - All telemetry fields (energy, power, GPU util, throughput, MFU, MBU, IPW, IPJ) written to JSONL output and summary JSON - RunSummary includes MetricStats (mean/median/min/max/std) for all telemetry metrics plus total_energy_joules - GLM-4.7-Flash eval config enriched with A100 SXM hardware params - 146 eval tests pass (26 new tests for telemetry, efficiency metrics, metadata flow, MetricStats helpers) - User guide and API docs updated with new fields and output format Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1214 lines
36 KiB
Markdown
1214 lines
36 KiB
Markdown
# Evaluation Framework
|
|
|
|
The `openjarvis-evals` package provides a structured harness for measuring model quality
|
|
across research benchmarks. It is a separate package from the main `openjarvis` library,
|
|
installed from the `evals/` directory, and exposes a CLI (`openjarvis-eval`) plus a
|
|
Python API for programmatic use.
|
|
|
|
The framework is organized around four ABCs — `InferenceBackend`, `DatasetProvider`,
|
|
`Scorer`, and the concrete `EvalRunner` — wired together by `RunConfig`. A TOML-based
|
|
suite configuration system expands a models-by-benchmarks matrix into individual
|
|
`RunConfig` objects so an entire comparison table can be launched from a single file.
|
|
|
|
!!! note "Installation"
|
|
The evaluation framework is a separate package. Install it from the repository root:
|
|
|
|
```bash
|
|
cd evals/
|
|
uv pip install -e ".[dev]"
|
|
# or
|
|
pip install -e ".[dev]"
|
|
```
|
|
|
|
The package requires Python 3.10+, `openjarvis>=1.0.0`, and `datasets>=2.14`.
|
|
|
|
---
|
|
|
|
## Core Types (`evals.core.types`)
|
|
|
|
These dataclasses are the shared vocabulary for every component in the framework.
|
|
|
|
### EvalRecord
|
|
|
|
A single evaluation sample loaded from a dataset.
|
|
|
|
```python
|
|
from evals.core.types import EvalRecord
|
|
```
|
|
|
|
| Field | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `record_id` | `str` | — | Unique identifier for this sample |
|
|
| `problem` | `str` | — | The prompt or question presented to the model |
|
|
| `reference` | `str` | — | Ground-truth answer used for scoring |
|
|
| `category` | `str` | — | Task category: `"chat"`, `"reasoning"`, `"rag"`, or `"agentic"` |
|
|
| `subject` | `str` | `""` | Subject area or sub-topic within the benchmark |
|
|
| `metadata` | `Dict[str, Any]` | `{}` | Benchmark-specific extra fields (options, difficulty, file paths, etc.) |
|
|
|
|
```python
|
|
record = EvalRecord(
|
|
record_id="supergpqa-0",
|
|
problem="What is the capital of France?\nOptions:\nA. Berlin\nB. Paris\nC. Madrid",
|
|
reference="B",
|
|
category="reasoning",
|
|
subject="geography",
|
|
metadata={"difficulty": "easy", "options": ["Berlin", "Paris", "Madrid"]},
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
### EvalResult
|
|
|
|
The result of running inference on a single `EvalRecord`.
|
|
|
|
```python
|
|
from evals.core.types import EvalResult
|
|
```
|
|
|
|
| Field | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `record_id` | `str` | — | Matches the source `EvalRecord.record_id` |
|
|
| `model_answer` | `str` | — | Raw text output from the model |
|
|
| `is_correct` | `Optional[bool]` | `None` | Scoring verdict; `None` if scoring could not be determined |
|
|
| `score` | `Optional[float]` | `None` | Numeric score (typically `1.0` / `0.0`); may be `None` if `is_correct` is `None` |
|
|
| `latency_seconds` | `float` | `0.0` | Wall-clock generation time |
|
|
| `prompt_tokens` | `int` | `0` | Input token count from usage metadata |
|
|
| `completion_tokens` | `int` | `0` | Output token count from usage metadata |
|
|
| `cost_usd` | `float` | `0.0` | Estimated inference cost in USD |
|
|
| `error` | `Optional[str]` | `None` | Exception message if inference or scoring failed |
|
|
| `scoring_metadata` | `Dict[str, Any]` | `{}` | Scorer-specific details (extracted letter, judge output, match type, etc.) |
|
|
| `ttft` | `float` | `0.0` | Time to first token (seconds) |
|
|
| `energy_joules` | `float` | `0.0` | GPU energy consumed (joules) |
|
|
| `power_watts` | `float` | `0.0` | Average GPU power draw (watts) |
|
|
| `gpu_utilization_pct` | `float` | `0.0` | Average GPU utilization (%) |
|
|
| `throughput_tok_per_sec` | `float` | `0.0` | Output token throughput (tokens/sec) |
|
|
| `mfu_pct` | `float` | `0.0` | Model FLOPs Utilization (%) |
|
|
| `mbu_pct` | `float` | `0.0` | Memory Bandwidth Utilization (%) |
|
|
| `ipw` | `float` | `0.0` | Intelligence Per Watt: `accuracy / power_watts` |
|
|
| `ipj` | `float` | `0.0` | Intelligence Per Joule: `accuracy / energy_joules` |
|
|
|
|
!!! tip "Distinguishing errors from wrong answers"
|
|
A non-`None` `error` field means inference itself failed. When `error` is `None` but
|
|
`is_correct` is `None`, scoring was attempted but the scorer could not determine a
|
|
verdict (for example, the judge returned an unparseable response).
|
|
|
|
---
|
|
|
|
### RunConfig
|
|
|
|
Configuration for a single evaluation run (one model on one benchmark).
|
|
|
|
```python
|
|
from evals.core.types import RunConfig
|
|
```
|
|
|
|
| Field | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `benchmark` | `str` | — | Benchmark name: `"supergpqa"`, `"gaia"`, `"frames"`, or `"wildchat"` |
|
|
| `backend` | `str` | — | Backend identifier: `"jarvis-direct"` or `"jarvis-agent"` |
|
|
| `model` | `str` | — | Model identifier passed to the backend (e.g., `"qwen3:8b"`, `"gpt-4o"`) |
|
|
| `max_samples` | `Optional[int]` | `None` | Limit the dataset to this many records; `None` uses the full dataset |
|
|
| `max_workers` | `int` | `4` | Number of parallel threads for inference |
|
|
| `temperature` | `float` | `0.0` | Sampling temperature |
|
|
| `max_tokens` | `int` | `2048` | Maximum output tokens per sample |
|
|
| `judge_model` | `str` | `"gpt-5-mini-2025-08-07"` | Model identifier used by the LLM judge scorer |
|
|
| `engine_key` | `Optional[str]` | `None` | Override the OpenJarvis engine (`"ollama"`, `"vllm"`, `"cloud"`, etc.) |
|
|
| `agent_name` | `Optional[str]` | `None` | Agent name for `jarvis-agent` backend; defaults to `"orchestrator"` |
|
|
| `tools` | `List[str]` | `[]` | Tool names enabled for the agent (e.g., `["calculator", "file_read"]`) |
|
|
| `output_path` | `Optional[str]` | `None` | JSONL output file path; auto-generated from benchmark and model name if `None` |
|
|
| `seed` | `int` | `42` | Random seed for dataset shuffling |
|
|
| `dataset_split` | `Optional[str]` | `None` | Override the dataset split (e.g., `"validation"`, `"test"`) |
|
|
| `telemetry` | `bool` | `False` | Enable GPU telemetry capture |
|
|
| `gpu_metrics` | `bool` | `False` | Enable GPU metric polling via `pynvml` |
|
|
| `metadata` | `Dict[str, Any]` | `{}` | Model hardware metadata for efficiency calculations (populated by `expand_suite()`) |
|
|
|
|
```python
|
|
config = RunConfig(
|
|
benchmark="supergpqa",
|
|
backend="jarvis-direct",
|
|
model="qwen3:8b",
|
|
max_samples=100,
|
|
max_workers=8,
|
|
engine_key="ollama",
|
|
output_path="results/supergpqa_qwen3-8b.jsonl",
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
### RunSummary
|
|
|
|
Aggregate statistics produced by `EvalRunner.run()` at the end of a completed run.
|
|
|
|
```python
|
|
from evals.core.types import RunSummary
|
|
```
|
|
|
|
| Field | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `benchmark` | `str` | — | Benchmark name |
|
|
| `category` | `str` | — | Task category (inferred from records; falls back to `benchmark` name) |
|
|
| `backend` | `str` | — | Backend used |
|
|
| `model` | `str` | — | Model identifier |
|
|
| `total_samples` | `int` | — | Total records processed (including errors) |
|
|
| `scored_samples` | `int` | — | Records where `is_correct` is not `None` |
|
|
| `correct` | `int` | — | Records where `is_correct` is `True` |
|
|
| `accuracy` | `float` | — | `correct / scored_samples`; rounded to 4 decimal places |
|
|
| `errors` | `int` | — | Records where inference or scoring raised an exception |
|
|
| `mean_latency_seconds` | `float` | — | Mean wall-clock latency across all successful inferences |
|
|
| `total_cost_usd` | `float` | — | Sum of `cost_usd` across all records |
|
|
| `per_subject` | `Dict[str, Dict[str, float]]` | `{}` | Per-subject breakdown: `{subject: {accuracy, total, scored, correct}}` |
|
|
| `started_at` | `float` | `0.0` | Unix timestamp at run start |
|
|
| `ended_at` | `float` | `0.0` | Unix timestamp at run end |
|
|
| `accuracy_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for per-sample accuracy (binary 0/1) |
|
|
| `latency_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for inference latency |
|
|
| `ttft_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for time-to-first-token |
|
|
| `energy_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for GPU energy (joules) |
|
|
| `power_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for GPU power (watts) |
|
|
| `gpu_utilization_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for GPU utilization (%) |
|
|
| `throughput_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for token throughput |
|
|
| `mfu_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for Model FLOPs Utilization (%) |
|
|
| `mbu_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for Memory Bandwidth Utilization (%) |
|
|
| `ipw_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for Intelligence Per Watt |
|
|
| `ipj_stats` | `Optional[MetricStats]` | `None` | Descriptive statistics for Intelligence Per Joule |
|
|
| `total_energy_joules` | `float` | `0.0` | Total GPU energy consumed across all samples |
|
|
|
|
The runner also writes a `.summary.json` file alongside the JSONL output, containing
|
|
the serialized `RunSummary`.
|
|
|
|
---
|
|
|
|
### MetricStats
|
|
|
|
Descriptive statistics for a single metric across samples.
|
|
|
|
```python
|
|
from evals.core.types import MetricStats
|
|
```
|
|
|
|
| Field | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `mean` | `float` | `0.0` | Arithmetic mean |
|
|
| `median` | `float` | `0.0` | Median value |
|
|
| `min` | `float` | `0.0` | Minimum value |
|
|
| `max` | `float` | `0.0` | Maximum value |
|
|
| `std` | `float` | `0.0` | Standard deviation (0.0 for single-element lists) |
|
|
|
|
`MetricStats` is computed by `_metric_stats()` in the runner and serialized to
|
|
JSON by `_metric_stats_to_dict()`. Fields in `RunSummary` like `accuracy_stats`,
|
|
`energy_stats`, `mfu_stats`, etc. are `Optional[MetricStats]` — they are `None`
|
|
when no positive values were observed for that metric.
|
|
|
|
---
|
|
|
|
## Suite Config Types (`evals.core.types`)
|
|
|
|
These dataclasses map directly to sections in a TOML eval suite config file.
|
|
They are populated by `load_eval_config()` and consumed by `expand_suite()`.
|
|
|
|
### MetaConfig
|
|
|
|
```python
|
|
@dataclass
|
|
class MetaConfig:
|
|
name: str = ""
|
|
description: str = ""
|
|
```
|
|
|
|
Maps to the `[meta]` TOML section. Both fields are optional and used only for
|
|
display output in the CLI.
|
|
|
|
---
|
|
|
|
### DefaultsConfig
|
|
|
|
```python
|
|
@dataclass
|
|
class DefaultsConfig:
|
|
temperature: float = 0.0
|
|
max_tokens: int = 2048
|
|
```
|
|
|
|
Maps to `[defaults]`. These values are the lowest-priority settings in the merge
|
|
precedence: `benchmark-level > model-level > [defaults] > built-in defaults`.
|
|
|
|
---
|
|
|
|
### JudgeConfig
|
|
|
|
```python
|
|
@dataclass
|
|
class JudgeConfig:
|
|
model: str = "gpt-4o"
|
|
provider: Optional[str] = None
|
|
temperature: float = 0.0
|
|
max_tokens: int = 1024
|
|
```
|
|
|
|
Maps to `[judge]`. The judge model is used by LLM-as-judge scorers (GAIA, FRAMES,
|
|
WildChat, SuperGPQA). The `provider` field is reserved for future routing; currently
|
|
the judge backend is always constructed with `engine_key="cloud"`.
|
|
|
|
---
|
|
|
|
### ExecutionConfig
|
|
|
|
```python
|
|
@dataclass
|
|
class ExecutionConfig:
|
|
max_workers: int = 4
|
|
output_dir: str = "results/"
|
|
seed: int = 42
|
|
telemetry: bool = False
|
|
gpu_metrics: bool = False
|
|
```
|
|
|
|
Maps to `[run]`. `output_dir` is the base directory for all JSONL output files;
|
|
individual filenames are auto-generated as `{benchmark}_{model-slug}.jsonl`.
|
|
When `telemetry` is enabled, the runner captures GPU energy, power, utilization,
|
|
and throughput per sample via `InstrumentedEngine`. When `gpu_metrics` is enabled,
|
|
`GpuMonitor` polls GPU sensors via `pynvml` during inference.
|
|
|
|
---
|
|
|
|
### ModelConfig
|
|
|
|
```python
|
|
@dataclass
|
|
class ModelConfig:
|
|
name: str = ""
|
|
engine: Optional[str] = None
|
|
provider: Optional[str] = None
|
|
temperature: Optional[float] = None
|
|
max_tokens: Optional[int] = None
|
|
param_count_b: float = 0.0
|
|
active_params_b: Optional[float] = None
|
|
gpu_peak_tflops: float = 0.0
|
|
gpu_peak_bandwidth_gb_s: float = 0.0
|
|
num_gpus: int = 1
|
|
```
|
|
|
|
Maps to each `[[models]]` entry. `name` is required. `temperature` and `max_tokens`
|
|
override `[defaults]` for every benchmark this model runs against, unless a
|
|
benchmark-level override also exists. The hardware parameters (`param_count_b`,
|
|
`active_params_b`, `gpu_peak_tflops`, `gpu_peak_bandwidth_gb_s`, `num_gpus`) are
|
|
used to compute MFU (Model FLOPs Utilization) and MBU (Memory Bandwidth Utilization)
|
|
per sample. These are flowed into `RunConfig.metadata` by `expand_suite()`.
|
|
|
|
---
|
|
|
|
### BenchmarkConfig
|
|
|
|
```python
|
|
@dataclass
|
|
class BenchmarkConfig:
|
|
name: str = ""
|
|
backend: str = "jarvis-direct"
|
|
max_samples: Optional[int] = None
|
|
split: Optional[str] = None
|
|
agent: Optional[str] = None
|
|
tools: List[str] = field(default_factory=list)
|
|
judge_model: Optional[str] = None
|
|
temperature: Optional[float] = None
|
|
max_tokens: Optional[int] = None
|
|
```
|
|
|
|
Maps to each `[[benchmarks]]` entry. `name` is required. `backend` must be one of
|
|
`"jarvis-direct"` or `"jarvis-agent"`. `judge_model` overrides `[judge].model` for
|
|
this benchmark only.
|
|
|
|
---
|
|
|
|
### EvalSuiteConfig
|
|
|
|
The top-level config object returned by `load_eval_config()`.
|
|
|
|
```python
|
|
@dataclass
|
|
class EvalSuiteConfig:
|
|
meta: MetaConfig
|
|
defaults: DefaultsConfig
|
|
judge: JudgeConfig
|
|
run: ExecutionConfig
|
|
models: List[ModelConfig]
|
|
benchmarks: List[BenchmarkConfig]
|
|
```
|
|
|
|
`expand_suite(suite)` iterates over `models x benchmarks` to produce one `RunConfig`
|
|
per pair, applying the merge precedence rules documented in `DefaultsConfig`.
|
|
|
|
---
|
|
|
|
## Config Module (`evals.core.config`)
|
|
|
|
```python
|
|
from evals.core.config import load_eval_config, expand_suite, EvalConfigError
|
|
```
|
|
|
|
### EvalConfigError
|
|
|
|
```python
|
|
class EvalConfigError(Exception): ...
|
|
```
|
|
|
|
Raised by `load_eval_config()` for structural validation failures: missing required
|
|
fields, invalid backend names, or empty `[[models]]` / `[[benchmarks]]` lists.
|
|
|
|
---
|
|
|
|
### load_eval_config
|
|
|
|
```python
|
|
def load_eval_config(path: str | Path) -> EvalSuiteConfig
|
|
```
|
|
|
|
Load and validate an eval suite configuration from a TOML file.
|
|
|
|
Uses the standard library `tomllib` on Python 3.11+ and the `tomli` backport on
|
|
Python 3.10.
|
|
|
|
**Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `path` | `str \| Path` | Path to the TOML config file |
|
|
|
|
**Returns:** `EvalSuiteConfig`
|
|
|
|
**Raises:**
|
|
|
|
- `EvalConfigError` — structural validation failures (missing `name`, invalid backend, no models/benchmarks defined)
|
|
- `FileNotFoundError` — if the config file does not exist
|
|
|
|
```python
|
|
from evals.core.config import load_eval_config
|
|
|
|
suite = load_eval_config("evals/configs/full-suite.toml")
|
|
print(f"{len(suite.models)} models, {len(suite.benchmarks)} benchmarks")
|
|
```
|
|
|
|
---
|
|
|
|
### expand_suite
|
|
|
|
```python
|
|
def expand_suite(suite: EvalSuiteConfig) -> List[RunConfig]
|
|
```
|
|
|
|
Expand an `EvalSuiteConfig` into a flat list of `RunConfig` objects, one per
|
|
model-benchmark pair, with all override layers merged.
|
|
|
|
**Merge precedence (highest wins):**
|
|
|
|
1. Benchmark-level (`BenchmarkConfig.temperature`, `.max_tokens`, `.judge_model`)
|
|
2. Model-level (`ModelConfig.temperature`, `.max_tokens`)
|
|
3. Suite defaults (`DefaultsConfig`)
|
|
4. Built-in dataclass defaults
|
|
|
|
Output paths are auto-generated as `{output_dir}/{benchmark}_{model-slug}.jsonl`,
|
|
where `model-slug` replaces `/` and `:` with `-`.
|
|
|
|
**Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `suite` | `EvalSuiteConfig` | Parsed suite configuration |
|
|
|
|
**Returns:** `List[RunConfig]` — one entry per model-benchmark combination.
|
|
|
|
```python
|
|
from evals.core.config import load_eval_config, expand_suite
|
|
|
|
suite = load_eval_config("evals/configs/full-suite.toml")
|
|
run_configs = expand_suite(suite) # e.g., 3 models x 4 benchmarks = 12 RunConfigs
|
|
for rc in run_configs:
|
|
print(f"{rc.benchmark} / {rc.model} -> {rc.output_path}")
|
|
```
|
|
|
|
---
|
|
|
|
## Abstract Base Classes
|
|
|
|
### InferenceBackend (`evals.core.backend`)
|
|
|
|
```python
|
|
from evals.core.backend import InferenceBackend
|
|
```
|
|
|
|
Base class for all inference backends. A backend wraps an engine or agent and
|
|
provides a uniform text-in / text-out interface for the runner.
|
|
|
|
```python
|
|
class InferenceBackend(ABC):
|
|
backend_id: str
|
|
```
|
|
|
|
**Class attribute:**
|
|
|
|
| Attribute | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `backend_id` | `str` | Registry identifier (e.g., `"jarvis-direct"`, `"jarvis-agent"`) |
|
|
|
|
**Abstract methods:**
|
|
|
|
#### generate
|
|
|
|
```python
|
|
@abstractmethod
|
|
def generate(
|
|
self,
|
|
prompt: str,
|
|
*,
|
|
model: str,
|
|
system: str = "",
|
|
temperature: float = 0.0,
|
|
max_tokens: int = 2048,
|
|
) -> str
|
|
```
|
|
|
|
Generate a response and return the text content only.
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `prompt` | `str` | — | User message or formatted problem text |
|
|
| `model` | `str` | — | Model identifier |
|
|
| `system` | `str` | `""` | Optional system prompt |
|
|
| `temperature` | `float` | `0.0` | Sampling temperature |
|
|
| `max_tokens` | `int` | `2048` | Maximum output tokens |
|
|
|
|
**Returns:** `str` — model text output.
|
|
|
|
---
|
|
|
|
#### generate_full
|
|
|
|
```python
|
|
@abstractmethod
|
|
def generate_full(
|
|
self,
|
|
prompt: str,
|
|
*,
|
|
model: str,
|
|
system: str = "",
|
|
temperature: float = 0.0,
|
|
max_tokens: int = 2048,
|
|
) -> Dict[str, Any]
|
|
```
|
|
|
|
Generate a response and return full details including usage and cost metadata.
|
|
|
|
**Returns:** `dict` with keys:
|
|
|
|
| Key | Type | Description |
|
|
|-----|------|-------------|
|
|
| `content` | `str` | Model text output |
|
|
| `usage` | `dict` | Token usage (`prompt_tokens`, `completion_tokens`) |
|
|
| `model` | `str` | Model identifier used |
|
|
| `latency_seconds` | `float` | Wall-clock generation time |
|
|
| `cost_usd` | `float` | Estimated inference cost |
|
|
|
|
---
|
|
|
|
#### close
|
|
|
|
```python
|
|
def close(self) -> None
|
|
```
|
|
|
|
Release resources held by the backend (connections, engine handles, etc.).
|
|
The default implementation is a no-op; subclasses override as needed.
|
|
|
|
---
|
|
|
|
### DatasetProvider (`evals.core.dataset`)
|
|
|
|
```python
|
|
from evals.core.dataset import DatasetProvider
|
|
```
|
|
|
|
Base class for all evaluation dataset providers. Datasets are loaded lazily via
|
|
`load()` and then consumed record-by-record through `iter_records()`.
|
|
|
|
```python
|
|
class DatasetProvider(ABC):
|
|
dataset_id: str
|
|
dataset_name: str
|
|
```
|
|
|
|
**Class attributes:**
|
|
|
|
| Attribute | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `dataset_id` | `str` | Short identifier matching the CLI benchmark name |
|
|
| `dataset_name` | `str` | Human-readable display name |
|
|
|
|
**Abstract methods:**
|
|
|
|
#### load
|
|
|
|
```python
|
|
@abstractmethod
|
|
def load(
|
|
self,
|
|
*,
|
|
max_samples: Optional[int] = None,
|
|
split: Optional[str] = None,
|
|
seed: Optional[int] = None,
|
|
) -> None
|
|
```
|
|
|
|
Load the dataset, optionally downloading from HuggingFace Hub. Must be called
|
|
before `iter_records()`.
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `max_samples` | `Optional[int]` | `None` | Truncate to this many records after shuffling |
|
|
| `split` | `Optional[str]` | `None` | Dataset split override (e.g., `"test"`, `"validation"`) |
|
|
| `seed` | `Optional[int]` | `None` | Shuffle seed; `None` preserves original order |
|
|
|
|
---
|
|
|
|
#### iter_records
|
|
|
|
```python
|
|
@abstractmethod
|
|
def iter_records(self) -> Iterable[EvalRecord]
|
|
```
|
|
|
|
Iterate over the loaded `EvalRecord` objects. Raises if called before `load()`.
|
|
|
|
---
|
|
|
|
#### size
|
|
|
|
```python
|
|
@abstractmethod
|
|
def size(self) -> int
|
|
```
|
|
|
|
Return the count of loaded records.
|
|
|
|
---
|
|
|
|
### Scorer (`evals.core.scorer`)
|
|
|
|
```python
|
|
from evals.core.scorer import Scorer, LLMJudgeScorer
|
|
```
|
|
|
|
Base class for all scorers. A scorer compares a model's answer to the reference
|
|
in an `EvalRecord` and returns a correctness verdict with optional metadata.
|
|
|
|
```python
|
|
class Scorer(ABC):
|
|
scorer_id: str
|
|
```
|
|
|
|
**Class attribute:**
|
|
|
|
| Attribute | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `scorer_id` | `str` | Short identifier matching the benchmark name |
|
|
|
|
**Abstract method:**
|
|
|
|
#### score
|
|
|
|
```python
|
|
@abstractmethod
|
|
def score(
|
|
self,
|
|
record: EvalRecord,
|
|
model_answer: str,
|
|
) -> Tuple[Optional[bool], Dict[str, Any]]
|
|
```
|
|
|
|
Score a model answer against the reference.
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `record` | `EvalRecord` | The source sample including `reference` and `metadata` |
|
|
| `model_answer` | `str` | Raw text output from the model |
|
|
|
|
**Returns:** `(is_correct, metadata)` tuple where:
|
|
|
|
- `is_correct` is `True`, `False`, or `None` (if scoring could not be determined)
|
|
- `metadata` is a `dict` of scorer-specific details stored in `EvalResult.scoring_metadata`
|
|
|
|
---
|
|
|
|
### LLMJudgeScorer
|
|
|
|
```python
|
|
class LLMJudgeScorer(Scorer):
|
|
def __init__(self, judge_backend: InferenceBackend, judge_model: str) -> None
|
|
```
|
|
|
|
Convenience base class for scorers that call an LLM to evaluate answers.
|
|
Exposes `_ask_judge()` to subclasses.
|
|
|
|
```python
|
|
def _ask_judge(
|
|
self,
|
|
prompt: str,
|
|
*,
|
|
system: str = "",
|
|
temperature: float = 0.0,
|
|
max_tokens: int = 1024,
|
|
) -> str
|
|
```
|
|
|
|
Send a prompt to the judge LLM and return the response text. Delegates to
|
|
`judge_backend.generate()`.
|
|
|
|
---
|
|
|
|
## EvalRunner (`evals.core.runner`)
|
|
|
|
```python
|
|
from evals.core.runner import EvalRunner
|
|
```
|
|
|
|
The `EvalRunner` wires together a `RunConfig`, `DatasetProvider`, `InferenceBackend`,
|
|
and `Scorer` and executes the benchmark. Inference is parallelized using a
|
|
`ThreadPoolExecutor`. Results are written to JSONL incrementally so progress is
|
|
not lost if the run is interrupted.
|
|
|
|
### Constructor
|
|
|
|
```python
|
|
class EvalRunner:
|
|
def __init__(
|
|
self,
|
|
config: RunConfig,
|
|
dataset: DatasetProvider,
|
|
backend: InferenceBackend,
|
|
scorer: Scorer,
|
|
) -> None
|
|
```
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `config` | `RunConfig` | Run parameters (model, workers, output path, etc.) |
|
|
| `dataset` | `DatasetProvider` | Dataset to evaluate against |
|
|
| `backend` | `InferenceBackend` | Inference backend for generation |
|
|
| `scorer` | `Scorer` | Scorer for comparing model answers to references |
|
|
|
|
### run
|
|
|
|
```python
|
|
def run(self) -> RunSummary
|
|
```
|
|
|
|
Execute the full evaluation and return aggregate statistics.
|
|
|
|
The method:
|
|
|
|
1. Calls `dataset.load()` with the `RunConfig` sampling parameters
|
|
2. Submits all records to a `ThreadPoolExecutor` with `config.max_workers` threads
|
|
3. For each record, calls `backend.generate_full()` then `scorer.score()`
|
|
4. Writes each `EvalResult` to a JSONL file as it completes
|
|
5. Writes a `.summary.json` alongside the JSONL at the end
|
|
|
|
**Returns:** `RunSummary`
|
|
|
|
```python title="programmatic_eval.py"
|
|
from evals.core.types import RunConfig
|
|
from evals.core.runner import EvalRunner
|
|
from evals.datasets.supergpqa import SuperGPQADataset
|
|
from evals.backends.jarvis_direct import JarvisDirectBackend
|
|
from evals.scorers.supergpqa_mcq import SuperGPQAScorer
|
|
|
|
config = RunConfig(
|
|
benchmark="supergpqa",
|
|
backend="jarvis-direct",
|
|
model="qwen3:8b",
|
|
max_samples=50,
|
|
engine_key="ollama",
|
|
)
|
|
|
|
dataset = SuperGPQADataset()
|
|
backend = JarvisDirectBackend(engine_key="ollama")
|
|
judge_backend = JarvisDirectBackend(engine_key="cloud")
|
|
scorer = SuperGPQAScorer(judge_backend=judge_backend, judge_model="gpt-4o")
|
|
|
|
runner = EvalRunner(config, dataset, backend, scorer)
|
|
summary = runner.run()
|
|
|
|
print(f"Accuracy: {summary.accuracy:.4f} ({summary.correct}/{summary.scored_samples})")
|
|
print(f"Mean latency: {summary.mean_latency_seconds:.2f}s")
|
|
print(f"Total cost: ${summary.total_cost_usd:.4f}")
|
|
|
|
backend.close()
|
|
judge_backend.close()
|
|
```
|
|
|
|
---
|
|
|
|
## Backends
|
|
|
|
### JarvisDirectBackend (`evals.backends.jarvis_direct`)
|
|
|
|
```python
|
|
from evals.backends.jarvis_direct import JarvisDirectBackend
|
|
```
|
|
|
|
Engine-level inference via `SystemBuilder`. Routes directly to the configured
|
|
`InferenceEngine` without an agent loop, making it the fastest backend and
|
|
appropriate for benchmarks that do not require tool use.
|
|
|
|
```python
|
|
class JarvisDirectBackend(InferenceBackend):
|
|
backend_id = "jarvis-direct"
|
|
|
|
def __init__(self, engine_key: Optional[str] = None) -> None
|
|
```
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `engine_key` | `Optional[str]` | `None` | OpenJarvis engine identifier. `None` uses the auto-discovered engine from `~/.openjarvis/config.toml` |
|
|
|
|
Telemetry and traces are disabled for eval runs. The backend calls
|
|
`SystemBuilder().engine(engine_key).telemetry(False).traces(False).build()`.
|
|
|
|
**Compatible benchmarks:** `supergpqa`, `frames`, `wildchat` (any benchmark that
|
|
does not require multi-step tool calling).
|
|
|
|
=== "Local model"
|
|
|
|
```python
|
|
backend = JarvisDirectBackend(engine_key="ollama")
|
|
text = backend.generate("What is 2+2?", model="qwen3:8b")
|
|
```
|
|
|
|
=== "Cloud model"
|
|
|
|
```python
|
|
backend = JarvisDirectBackend(engine_key="cloud")
|
|
text = backend.generate("What is 2+2?", model="gpt-4o")
|
|
```
|
|
|
|
---
|
|
|
|
### JarvisAgentBackend (`evals.backends.jarvis_agent`)
|
|
|
|
```python
|
|
from evals.backends.jarvis_agent import JarvisAgentBackend
|
|
```
|
|
|
|
Agent-level inference via `JarvisSystem.ask()`. Wraps the full OpenJarvis agent
|
|
harness, enabling multi-turn tool-calling loops for agentic benchmarks.
|
|
|
|
```python
|
|
class JarvisAgentBackend(InferenceBackend):
|
|
backend_id = "jarvis-agent"
|
|
|
|
def __init__(
|
|
self,
|
|
engine_key: Optional[str] = None,
|
|
agent_name: str = "orchestrator",
|
|
tools: Optional[List[str]] = None,
|
|
) -> None
|
|
```
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `engine_key` | `Optional[str]` | `None` | OpenJarvis engine identifier |
|
|
| `agent_name` | `str` | `"orchestrator"` | Agent to use (`"orchestrator"`, `"react"`, etc.) |
|
|
| `tools` | `Optional[List[str]]` | `None` | Tool names to enable (e.g., `["calculator", "file_read"]`) |
|
|
|
|
The `generate_full()` return dict includes two additional keys beyond the standard
|
|
`InferenceBackend` contract:
|
|
|
|
| Key | Type | Description |
|
|
|-----|------|-------------|
|
|
| `turns` | `int` | Number of agent turns completed |
|
|
| `tool_results` | `list` | Tool call results from the agent loop |
|
|
|
|
**Compatible benchmarks:** `gaia` (requires file reading and multi-step reasoning).
|
|
|
|
```python
|
|
backend = JarvisAgentBackend(
|
|
engine_key="ollama",
|
|
agent_name="orchestrator",
|
|
tools=["file_read", "calculator"],
|
|
)
|
|
result = backend.generate_full(
|
|
"How many pages is the attached PDF?",
|
|
model="qwen3:8b",
|
|
)
|
|
print(result["content"])
|
|
print(f"Completed in {result['turns']} turn(s)")
|
|
backend.close()
|
|
```
|
|
|
|
---
|
|
|
|
## Dataset Providers
|
|
|
|
### SuperGPQADataset (`evals.datasets.supergpqa`)
|
|
|
|
```python
|
|
from evals.datasets.supergpqa import SuperGPQADataset
|
|
```
|
|
|
|
Loads the SuperGPQA multiple-choice benchmark from HuggingFace (`m-a-p/SuperGPQA`).
|
|
Records have `category="reasoning"` and `subject` set to the discipline subfield.
|
|
|
|
```python
|
|
class SuperGPQADataset(DatasetProvider):
|
|
dataset_id = "supergpqa"
|
|
dataset_name = "SuperGPQA"
|
|
```
|
|
|
|
- **Default split:** `"train"`
|
|
- **HuggingFace path:** `m-a-p/SuperGPQA`
|
|
- Each problem is formatted with lettered options (A, B, C, ...) and the instruction
|
|
"Respond with the correct letter only."
|
|
- `record.reference` is the correct answer letter (e.g., `"B"`).
|
|
|
|
---
|
|
|
|
### GAIADataset (`evals.datasets.gaia`)
|
|
|
|
```python
|
|
from evals.datasets.gaia import GAIADataset
|
|
```
|
|
|
|
Loads the GAIA agentic benchmark from HuggingFace (`gaia-benchmark/GAIA`).
|
|
Records have `category="agentic"` and `subject` set to `level_1`, `level_2`, or
|
|
`level_3`.
|
|
|
|
```python
|
|
class GAIADataset(DatasetProvider):
|
|
dataset_id = "gaia"
|
|
dataset_name = "GAIA"
|
|
|
|
def __init__(self, cache_dir: Optional[str] = None) -> None
|
|
```
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `cache_dir` | `Optional[str]` | `~/.cache/gaia_benchmark` | Local directory for HuggingFace snapshot download |
|
|
|
|
- **Default split:** `"validation"`
|
|
- **Default subset:** `"2023_all"`
|
|
- Downloads the full dataset snapshot including associated files (PDFs, images, CSVs)
|
|
referenced in questions. File paths are embedded in the problem prompt.
|
|
|
|
!!! warning "Dataset access"
|
|
GAIA requires accepting the HuggingFace dataset terms of service and being logged
|
|
in with `huggingface-cli login` before the snapshot download can proceed.
|
|
|
|
---
|
|
|
|
### FRAMESDataset (`evals.datasets.frames`)
|
|
|
|
```python
|
|
from evals.datasets.frames import FRAMESDataset
|
|
```
|
|
|
|
Loads the FRAMES multi-hop factual retrieval benchmark from HuggingFace
|
|
(`google/frames-benchmark`). Records have `category="rag"` and `subject` set to
|
|
the reasoning type(s) (e.g., `"multi-hop, temporal"`).
|
|
|
|
```python
|
|
class FRAMESDataset(DatasetProvider):
|
|
dataset_id = "frames"
|
|
dataset_name = "FRAMES"
|
|
```
|
|
|
|
- **Default split:** `"test"`
|
|
- Wikipedia article links referenced in each question are included in the problem prompt.
|
|
|
|
---
|
|
|
|
### WildChatDataset (`evals.datasets.wildchat`)
|
|
|
|
```python
|
|
from evals.datasets.wildchat import WildChatDataset
|
|
```
|
|
|
|
Loads the WildChat-1M dataset (`allenai/WildChat-1M`) and filters to English
|
|
single-turn conversations for chat quality evaluation. Records have
|
|
`category="chat"` and `subject="conversation"`.
|
|
|
|
```python
|
|
class WildChatDataset(DatasetProvider):
|
|
dataset_id = "wildchat"
|
|
dataset_name = "WildChat"
|
|
```
|
|
|
|
- **Default split:** `"train"`
|
|
- Filters by `language == "english"` and exactly two turns (one user + one assistant).
|
|
- `record.problem` is the user message; `record.reference` is the original assistant
|
|
response used as the quality baseline by the judge scorer.
|
|
|
|
---
|
|
|
|
## Scorers
|
|
|
|
### SuperGPQAScorer (`evals.scorers.supergpqa_mcq`)
|
|
|
|
```python
|
|
from evals.scorers.supergpqa_mcq import SuperGPQAScorer
|
|
```
|
|
|
|
LLM-based letter extraction followed by exact match against the reference letter.
|
|
The judge LLM extracts the final answer letter from potentially verbose model
|
|
responses, then compares it to `record.reference`.
|
|
|
|
```python
|
|
class SuperGPQAScorer(LLMJudgeScorer):
|
|
scorer_id = "supergpqa"
|
|
```
|
|
|
|
**Scoring metadata keys:**
|
|
|
|
| Key | Description |
|
|
|-----|-------------|
|
|
| `reference_letter` | Correct answer letter from the dataset |
|
|
| `candidate_letter` | Letter extracted by the judge LLM |
|
|
| `valid_letters` | Valid answer letters for this question (e.g., `"ABCD"`) |
|
|
| `reason` | Set to `"missing_reference_letter"` or `"no_choice_letter_extracted"` on failure |
|
|
|
|
---
|
|
|
|
### GAIAScorer (`evals.scorers.gaia_exact`)
|
|
|
|
```python
|
|
from evals.scorers.gaia_exact import GAIAScorer, exact_match
|
|
```
|
|
|
|
Normalized exact match with an LLM fallback for semantic comparison. Tries exact
|
|
match first (no API call); falls back to the judge LLM only when exact match fails.
|
|
|
|
```python
|
|
class GAIAScorer(LLMJudgeScorer):
|
|
scorer_id = "gaia"
|
|
```
|
|
|
|
**Normalization rules for exact match:**
|
|
|
|
- Numbers: strips `$`, `%`, `,` then compares as `float`
|
|
- Lists (comma- or semicolon-separated): splits and compares element-by-element
|
|
- Strings: lowercases, strips whitespace and punctuation
|
|
|
|
**Scoring metadata keys:**
|
|
|
|
| Key | Description |
|
|
|-----|-------------|
|
|
| `match_type` | `"exact"` or `"llm_fallback"` |
|
|
| `raw_judge_output` | Full LLM judge response (llm_fallback only) |
|
|
| `extracted_answer` | Answer extracted by the judge (llm_fallback only) |
|
|
|
|
The `exact_match` helper function is also exported and can be used independently:
|
|
|
|
```python
|
|
from evals.scorers.gaia_exact import exact_match
|
|
|
|
assert exact_match("$1,000", "1000") is True
|
|
assert exact_match("paris", "Paris") is True
|
|
assert exact_match("3, 5", "3,5") is True
|
|
```
|
|
|
|
---
|
|
|
|
### FRAMESScorer (`evals.scorers.frames_judge`)
|
|
|
|
```python
|
|
from evals.scorers.frames_judge import FRAMESScorer
|
|
```
|
|
|
|
LLM-as-judge scorer for FRAMES multi-hop factual retrieval. Uses a structured
|
|
grading rubric that focuses on semantic equivalence, ignoring formatting and
|
|
capitalization differences.
|
|
|
|
```python
|
|
class FRAMESScorer(LLMJudgeScorer):
|
|
scorer_id = "frames"
|
|
```
|
|
|
|
**Scoring metadata keys:**
|
|
|
|
| Key | Description |
|
|
|-----|-------------|
|
|
| `raw_judge_output` | Full LLM judge response |
|
|
| `extracted_answer` | Answer extracted by the judge |
|
|
|
|
---
|
|
|
|
### WildChatScorer (`evals.scorers.wildchat_judge`)
|
|
|
|
```python
|
|
from evals.scorers.wildchat_judge import WildChatScorer
|
|
```
|
|
|
|
Dual-comparison LLM-as-judge for chat quality. Runs two comparisons — once with
|
|
the model answer as Assistant A and once as Assistant B — to reduce position bias.
|
|
The model answer is considered correct if it wins or ties in either comparison.
|
|
|
|
```python
|
|
class WildChatScorer(LLMJudgeScorer):
|
|
scorer_id = "wildchat"
|
|
```
|
|
|
|
The judge uses a five-point verdict scale: `[[A>>B]]`, `[[A>B]]`, `[[A=B]]`,
|
|
`[[B>A]]`, `[[B>>A]]`. A tie (`A=B`) is counted as correct.
|
|
|
|
**Scoring metadata keys:**
|
|
|
|
| Key | Description |
|
|
|-----|-------------|
|
|
| `generated_as_a` | `{verdict, response}` from the first comparison pass |
|
|
| `generated_as_b` | `{verdict, response}` from the second comparison pass |
|
|
|
|
---
|
|
|
|
## CLI Reference
|
|
|
|
The evaluation framework ships a `openjarvis-eval` CLI built with Click.
|
|
|
|
### openjarvis-eval run
|
|
|
|
Run a single benchmark or a full suite from a TOML config.
|
|
|
|
```bash title="Single run"
|
|
openjarvis-eval run \
|
|
--benchmark supergpqa \
|
|
--model qwen3:8b \
|
|
--engine ollama \
|
|
--max-samples 100 \
|
|
--max-workers 8 \
|
|
--output results/supergpqa_qwen3-8b.jsonl
|
|
```
|
|
|
|
```bash title="Suite run from TOML"
|
|
openjarvis-eval run --config evals/configs/full-suite.toml
|
|
```
|
|
|
|
| Option | Short | Default | Description |
|
|
|--------|-------|---------|-------------|
|
|
| `--config` | `-c` | `None` | TOML suite config file; enables suite mode |
|
|
| `--benchmark` | `-b` | — | Benchmark name (required in single-run mode) |
|
|
| `--backend` | | `jarvis-direct` | `jarvis-direct` or `jarvis-agent` |
|
|
| `--model` | `-m` | — | Model identifier (required in single-run mode) |
|
|
| `--engine` | `-e` | `None` | Engine key override |
|
|
| `--agent` | | `orchestrator` | Agent name for `jarvis-agent` backend |
|
|
| `--tools` | | `""` | Comma-separated tool names |
|
|
| `--max-samples` | `-n` | `None` | Sample limit |
|
|
| `--max-workers` | `-w` | `4` | Parallel threads |
|
|
| `--judge-model` | | `gpt-4o` | LLM judge model |
|
|
| `--output` | `-o` | auto | JSONL output path |
|
|
| `--seed` | | `42` | Shuffle seed |
|
|
| `--split` | | `None` | Dataset split override |
|
|
| `--temperature` | | `0.0` | Sampling temperature |
|
|
| `--max-tokens` | | `2048` | Maximum output tokens |
|
|
| `--verbose` | `-v` | `False` | Enable debug logging |
|
|
|
|
### openjarvis-eval run-all
|
|
|
|
Run all four benchmarks against a single model.
|
|
|
|
```bash
|
|
openjarvis-eval run-all \
|
|
--model qwen3:8b \
|
|
--engine ollama \
|
|
--max-samples 50 \
|
|
--output-dir results/
|
|
```
|
|
|
|
### openjarvis-eval summarize
|
|
|
|
Recompute summary statistics from an existing JSONL output file.
|
|
|
|
```bash
|
|
openjarvis-eval summarize results/supergpqa_qwen3-8b.jsonl
|
|
```
|
|
|
|
### openjarvis-eval list
|
|
|
|
List all available benchmarks and backends.
|
|
|
|
```bash
|
|
openjarvis-eval list
|
|
```
|
|
|
|
---
|
|
|
|
## TOML Suite Config Format
|
|
|
|
A suite config drives a full `models x benchmarks` comparison matrix with a single
|
|
command. All sections except `[[models]]` and `[[benchmarks]]` are optional.
|
|
|
|
```toml title="evals/configs/full-suite.toml"
|
|
[meta]
|
|
name = "full-suite-v1"
|
|
description = "Evaluate all benchmarks against production models"
|
|
|
|
[defaults]
|
|
temperature = 0.0
|
|
max_tokens = 2048
|
|
|
|
[judge]
|
|
model = "gpt-4o"
|
|
temperature = 0.0
|
|
max_tokens = 1024
|
|
|
|
[run]
|
|
max_workers = 4
|
|
output_dir = "results/"
|
|
seed = 42
|
|
|
|
# One [[models]] entry per model to evaluate
|
|
[[models]]
|
|
name = "qwen3:8b"
|
|
engine = "ollama"
|
|
temperature = 0.3
|
|
|
|
[[models]]
|
|
name = "gpt-4o"
|
|
provider = "openai"
|
|
|
|
# One [[benchmarks]] entry per benchmark
|
|
[[benchmarks]]
|
|
name = "supergpqa"
|
|
backend = "jarvis-direct"
|
|
max_samples = 200
|
|
|
|
[[benchmarks]]
|
|
name = "gaia"
|
|
backend = "jarvis-agent"
|
|
agent = "orchestrator"
|
|
tools = ["file_read", "calculator"]
|
|
max_samples = 50
|
|
judge_model = "claude-sonnet-4-20250514" # override judge for this benchmark
|
|
|
|
[[benchmarks]]
|
|
name = "frames"
|
|
backend = "jarvis-direct"
|
|
max_samples = 100
|
|
|
|
[[benchmarks]]
|
|
name = "wildchat"
|
|
backend = "jarvis-direct"
|
|
max_samples = 150
|
|
temperature = 0.7
|
|
```
|
|
|
|
```bash
|
|
openjarvis-eval run --config evals/configs/full-suite.toml
|
|
# Suite: full-suite-v1
|
|
# 2 model(s) x 4 benchmark(s) = 8 run(s)
|
|
```
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- [Benchmarks Module](bench.md) — `openjarvis.bench` performance benchmarks (latency, throughput) for the inference engine, separate from the eval framework
|
|
- [Telemetry & Traces](telemetry.md) — `openjarvis.telemetry` and `openjarvis.traces` for production monitoring
|
|
- [Python SDK](../user-guide/python-sdk.md) — `Jarvis` class used internally by eval backends
|
|
- [Agents](../user-guide/agents.md) — Agent implementations invoked by `JarvisAgentBackend`
|