Agent skill#
cuML ships an Agent Skill for users who write cuML code with coding
agents such as Claude Code, Codex or Cursor. It lives in
skills/cuml/ and focuses on what
current models get wrong: APIs removed or renamed in recent releases, enabling and verifying
cuml.accel, large-data UMAP/HDBSCAN settings, and model export. This page explains how to keep
the skill correct and how to measure whether it helps.
Layout#
| Path | Contents |
|---|---|
SKILL.md |
The skill. Keep it short (under about 8 KB); agents load all of it. |
references/parity-and-numerics.md |
How to compare cuML results with scikit-learn; read on demand. |
scripts/removed_apis.json |
Removed APIs whose replacement cannot be found by inspecting the installed cuML. |
scripts/check_removed_apis.py |
Scans user code for the APIs in removed_apis.json. |
evals/evals.json |
Evaluation cases (prompt, expected output, assertions). |
evals/config.yml, evals/environment/Dockerfile |
How the evaluation runs. |
Keeping the skill up to date#
The skill tells agents to look things up in the installed cuML (signatures, docstrings, error
messages, python -m cuml.accel -v) and in the documentation for the installed release, rather
than carrying copies of that information. Keeping cuML’s docstrings, error messages and
docs/source/cuml-accel/compatibility.rst accurate therefore keeps the skill accurate. Fix
mistakes there, not in the skill.
When you deprecate or remove a public API:
Name the replacement in the deprecation warning and docstring.
If, after the removal, an agent could not find the replacement by inspecting the installed cuML (for example a removed module, or a dictionary key that is silently ignored), add an entry to
scripts/removed_apis.json(deprecated_in,removed_in,replacement, and the fields used to verify it). Remove entries once the replacement becomes discoverable.
python/cuml/tests/test_agent_skill.py checks that every API listed in removed_apis.json is
really gone, that every recommended replacement exists, that compatibility.rst has not moved
(the skill links to it), and that evals.json is well formed. Run it with:
pytest python/cuml/tests/test_agent_skill.py
Write SKILL.md for things models get wrong, not as a tutorial. General knowledge that models
already have (what cuML is, KMeans differs numerically from scikit-learn, CuPy interop) costs
context without improving answers.
Evaluation cases#
evals/evals.json uses the agentskills.io evaluation format:
{
"skill_name": "cuml",
"evals": [
{
"id": "p18",
"name": "svc-proba",
"prompt": "Using cuML's SVC directly ...",
"expected_output": "cuML SVC no longer has a `probability` argument ...",
"assertions": ["Does not pass `probability=True` to cuML SVC or LinearSVC", "..."]
}
]
}
expected_output describes a correct answer; each assertion is one checkable statement an LLM
judge evaluates against the agent’s transcript. Extra fields are kept as metadata: track
and, for probe cases, probe_verdicts, the grades models received without the skill
(C correct, P partly wrong, W wrong).
Good cases are ones where agents without the skill fail: removed APIs, silent fallbacks, new
recipes. Cases that every model already passes only check that the skill does no harm. Keep
assertions specific and verifiable, for example “Does not pass data_on_host to
fit_transform” rather than “Gives good UMAP advice”.
All current cases were seen by the skill’s author. A held-out set written by someone else is still needed to measure the skill without overfitting.
Running the evaluation#
The evaluation uses NVIDIA SkillEvaluator Tier 3. It runs each case twice, with and without the skill, with a coding agent inside a Docker container, grades both transcripts with an LLM judge, and reports the difference (”Skill Lift”). The setup below uses NVIDIA Build models for both the agent and the judge. This is easy to get started with, but for serious eval run use https://inference.nvidia.com. The service is more reliable and gives you access to models used in commercial coding agents.
Set up#
You need Docker (with Compose v2), an NVIDIA API key from
build.nvidia.com, and SkillEvaluator in its own Python 3.12 or 3.13
environment. The environment name follows the cuML convention, skilleval-YYYYMMDD:
conda create -n skilleval-20261005 python=3.13
conda activate skilleval-20261005
pip install "skillevaluator[tier3] @ git+https://github.com/NVIDIA/SkillEvaluator.git@6bab56e9d2e28f178bd413ff731b3d27291371e4"
Configure the provider. With NVIDIA Build, one key is used by the judge and by the agent. The
default agent is OpenCode, and agent and judge both default to
nvidia/nemotron-3-super-120b-a12b.
export SKILL_EVAL_LLM_PROVIDER=nv_build
export NVIDIA_API_KEY=nvapi-...
export SKILLEVALUATOR_RESULTS_DIR=~/skilleval-results # keep results out of the repository
Check the setup and the dataset:
skillevaluator doctor --agents opencode --env-mode docker --verify-models
skillevaluator tier3 validate skills/cuml --strict
skillevaluator models --limit 100 # model IDs in the catalog
doctor and models only read the public model catalog, so they pass even with an invalid key.
Check that the key can run inference before starting an evaluation; this should print 200:
curl -s -o /dev/null -w '%{http_code}\n' https://integrate.api.nvidia.com/v1/chat/completions \
-H "Authorization: Bearer $NVIDIA_API_KEY" -H "Content-Type: application/json" \
-d '{"model": "nvidia/nemotron-3-super-120b-a12b", "messages": [{"role": "user", "content": "Say OK"}], "max_tokens": 5}'
Other providers work too: SKILL_EVAL_LLM_PROVIDER=anthropic with ANTHROPIC_API_KEY runs Claude
Code with Claude models, and openai runs Codex. See the SkillEvaluator configuration docs.
Run#
skillevaluator tier3 evaluate skills/cuml --agents opencode --env-mode docker --n-attempts 3
Pick a different agent model with --agent-model opencode=nvidia/<publisher>/<model> (the extra
nvidia/ prefix is OpenCode’s adapter namespace). Claude Code can also run against NVIDIA Build
models through an experimental SkillEvaluator bridge: --agents claude-code --agent-model claude-code=<publisher>/<model>.
--n-attempts 1 is enough for a smoke test; use 3 or more for numbers you report.
SkillEvaluator has no option to select cases. To run a subset, copy the skill and keep only the cases you want:
tmp=$(mktemp -d) && cp -r skills/cuml "$tmp/cuml"
jq '.evals |= map(select(.id == "p18" or .id == "b01"))' skills/cuml/evals/evals.json \
> "$tmp/cuml/evals/evals.json"
skillevaluator tier3 evaluate "$tmp/cuml" --agents opencode --env-mode docker \
--n-attempts 1 --harbor-keep-jobs
--harbor-keep-jobs keeps the agent transcripts; browse them with
skillevaluator harbor-view <run-dir>/_harbor-jobs. Read a few: a run where the agent did
nothing still gets graded.
Results#
skillevaluator view skills/cuml opens the HTML report of the latest run. The report scores
five dimensions (security, correctness, discoverability, effectiveness, efficiency); Skill Lift
is the with-skill score minus the without-skill score.
If any trial errors (an agent timeout, a provider error), SkillEvaluator reports no score for
that arm: the report shows N/A and no lift.json is written. Check execution_status and
execution_errors in the run’s result.json. The trials that did run are still graded, each in
<agent>/<arm>/trials/*/reward.json. This script
summarises them per arm and per case and lists the trials it skipped:
python analyze_results.py ~/skilleval-results/cuml/latest
python analyze_results.py RUN_DIR+FILL_IN_RUN_DIR # merge two runs of the same skill
Use accuracy, goal_accuracy and behavior_check to judge answer quality. skill_execution
and skill_efficiency measure use of the skill itself; the without-skill arm scores near zero on
them by construction, and overall averages them in, so its lift overstates the benefit.
Report how many trials were scored in each arm, and record the image’s package versions
(pip freeze inside the container) next to any results you report.
Using inference.nvidia.com#
To use a model like Opus 5.5 to evaluate the skill you have to use https://inference.nvidia.com. Get an API key and then set the following environment variables:
export SKILL_EVAL_LLM_PROVIDER=anthropic
export ANTHROPIC_API_KEY=<your NV inference API key>
export ANTHROPIC_BASE_URL=https://inference-api.nvidia.com
export SKILL_EVAL_LLM_MODEL=azure/anthropic/claude-sonnet-5-5
export SKILLEVALUATOR_RESULTS_DIR=~/skilleval-results
Evaluation environment#
evals/environment/Dockerfile installs a pinned cuML release and the newest scikit-learn,
umap-learn and hdbscan versions inside that release’s cuml.accel tested window, so rebuilding
the image gives the same versions. SkillEvaluator replaces its FROM line with its own base image
and keeps the rest. When a new cuML release is on PyPI, update all four pins together using that
release’s _CONSTRAINTS in python/cuml/cuml/accel/core.py.
GPU access#
SkillEvaluator does not give containers a GPU, and rejects GPU settings in its configuration
files as sandbox escapes. Without a GPU, cuML still imports and agents can inspect signatures,
but every fit fails with cudaErrorNoDevice, which changes how agents behave. To give the
evaluation containers a GPU, make NVIDIA the default Docker runtime and expose a device in the
image:
Install the NVIDIA Container Toolkit, add
"default-runtime": "nvidia"to/etc/docker/daemon.json, and restart Docker.Add
ENV NVIDIA_VISIBLE_DEVICES=0 NVIDIA_DRIVER_CAPABILITIES=compute,utilitytoevals/environment/Dockerfile, using the index or UUID fromnvidia-smi -L.Set
n_concurrent: 1inevals/config.yml, because trials share the GPU.
This deliberately bypasses a SkillEvaluator safety rule and changes the default runtime for every container on the machine. Only do it on a machine you control, for skills you trust.
Limitations#
The judge sees the agent’s transcript, not files it wrote or code it ran.
Prompts about very large data (out-of-memory at 10M rows) or multiple GPUs cannot be reproduced faithfully in the evaluation container; they are graded on the answer.
There is no held-out case set yet.
Results depend on the agent and model. The
probe_verdictsbaselines inevals.jsoncome from Claude models; other models fail different cases.