Scaffold a new LLM evaluation suite package with Playwright config, evaluate fixture, and package files. Use when creating a new eval suite, adding an evals package for a plugin, or setting up the boilerplate for offline LLM evaluations.
Resources
1Install
npx skillscat add elastic/kibana/evals-create-suite Install via the SkillsCat registry.
Create an Eval Suite
Overview
Eval suites live in dedicated kbn-evals-suite-<name> packages. Each suite is a self-contained Playwright project that uses the evaluate fixture from @kbn/evals to run LLM experiments with datasets, tasks, and evaluators.
Inputs to Collect
- Suite name (kebab-case, e.g.
my-feature) - Parent directory under
x-pack/(e.g.x-pack/platform/packages/shared/ai-infra/orx-pack/solutions/security/test/) - Owner GitHub team handle (e.g.
@elastic/search-ml-ux) - Group (
platform,security,observability,search) - Visibility (
sharedorprivate) - Whether custom fixtures are needed (chat client, esArchiver, supertest, etc.)
Do NOT Use node scripts/scout generate
Eval suites are not standard Scout test configs. The Scout generator creates test/scout/ directories that are picked up by Scout's CI discovery glob -- this will break because evals configs use createPlaywrightEvalsConfig (not createPlaywrightConfig) and contain non-JS files (like .text prompt files) that Playwright cannot parse.
The Scout team has explicitly asked that eval configs live outside test/scout/ directories. All eval suites place their playwright.config.ts in the package root.
Directory Layout
kbn-evals-suite-<name>/
├── evals/
│ └── <name>.spec.ts # evaluation spec(s)
├── src/
│ └── evaluate.ts # re-export or extend the base evaluate fixture
├── playwright.config.ts # MUST be in package root, NOT under test/scout/
├── package.json
├── kibana.jsonc
└── tsconfig.jsonFile Templates
kibana.jsonc
{
"type": "functional-tests",
"id": "@kbn/evals-suite-<name>",
"owner": "@elastic/<team>",
"group": "<platform|security|observability|search>",
"visibility": "<shared|private>"
}type must be "functional-tests" -- not "shared-common" or "plugin".
package.json
{
"name": "@kbn/evals-suite-<name>",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}tsconfig.json
{
"extends": "@kbn/tsconfig-base/tsconfig.json",
"compilerOptions": {
"outDir": "target/types",
"types": ["jest", "node"]
},
"include": ["**/*.ts"],
"exclude": ["target/**/*"],
"kbn_references": [
"@kbn/evals",
"@kbn/scout"
]
}Add any additional package refs your suite imports to kbn_references (e.g. @kbn/inference-common, @kbn/es-archiver).
playwright.config.ts
import Path from 'path';
import { createPlaywrightEvalsConfig } from '@kbn/evals';
export default createPlaywrightEvalsConfig({
testDir: Path.resolve(__dirname, './evals'),
timeout: 30 * 60_000,
});Options:
testDir(required) -- directory containing.spec.tsfilestimeout(optional, default5 * 60_000) -- per-test timeout in msrepetitions(optional, default1) -- overridable viaEVAL_REPETITIONSenv var
src/evaluate.ts
Simple (no custom fixtures):
import { evaluate } from '@kbn/evals';
export { evaluate };Extended (with custom fixtures):
import { evaluate as base } from '@kbn/evals';
import { MyChatClient } from './chat_client';
export const evaluate = base.extend<
{},
{ chatClient: MyChatClient }
>({
chatClient: [
async ({ fetch, log, connector }, use) => {
await use(new MyChatClient(fetch, log, connector.id));
},
{ scope: 'worker' },
],
});When to Extend evaluate
Use the base evaluate directly when your task calls Kibana APIs through the built-in fetch, inferenceClient, or executorClient fixtures.
Extend when you need:
- A chat client that wraps a specific Kibana API endpoint (e.g.
/api/agent_builder/converse) - An
evaluateDatasethelper that encapsulates therunExperiment+ evaluator wiring for a consistent pattern across specs esArchiverfor loading/unloading ES archives in setup/teardownsupertestfor direct HTTP assertions against Kibana- Domain-specific API clients (e.g.
QuickstartClient)
Real examples
| Suite | Approach | Why |
|---|---|---|
llm-tasks |
Base evaluate directly |
Calls task functions in-process; custom CODE evaluators inline |
agent-builder |
Extended with chatClient + Phoenix executor |
Needs HTTP chat client and external Phoenix executor |
security-solution-evals |
Extended with chatClient, esArchiver, supertest, quickApiClient |
Domain-heavy setup: loads ES archives, uses generated API client |
Suite Registration
Add an entry to .buildkite/pipelines/evals/evals.suites.json:
{
"id": "<name>",
"name": "<Human Readable Name>",
"configPath": "<repo-relative path to playwright.config.ts>",
"tags": ["<group>", "<name>"],
"ciLabels": ["evals:<name>"]
}Registration is optional for local dev (suites are auto-discovered from createPlaywrightEvalsConfig imports), but required for CI labeling and node scripts/evals list.
Post-Scaffold Steps
- Run
yarn kbn bootstrapto register the new package. - Verify the suite appears:
node scripts/evals list. - Create your first spec file under
evals/(see theevals-write-specskill). - Run locally:
node scripts/evals start --model <connector-id> --judge <connector-id>.
Common Mistakes
- Placing configs under
test/scout/-- Scout's CI discovery will find them and crash. Keepplaywright.config.tsin the package root. - Using
node scripts/scout generate-- this creates Scout test scaffolds, not eval suites. Scaffold manually using the templates above. - Setting
typeto anything other than"functional-tests"inkibana.jsonc. - Forgetting
@kbn/evalsinkbn_references-- causes TS resolution failures. - Using
Path.joininstead ofPath.resolvefortestDir-- Playwright needs an absolute path. - Creating
evals/specs that import from@kbn/evalsbut the suite'ssrc/evaluate.tsre-exports a different fixture -- always importevaluatefrom the suite's ownsrc/evaluatewhen extending. - Forgetting to run
yarn kbn bootstrapafter creating the package.