mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
b0ddcf31e4
* ci: add path-gated AI agent integration tests workflow Runs integration_tests/ai_agent_tests against real LLM providers (Anthropic/OpenAI/Google) only when AI-agent backend code or the tests change, since runs make paid LLM calls. Adds a conftest fixture that skips provider-parametrized cases whose API keys are absent, so CI exercises only the providers it has secrets for. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: add path-gated ai_evals global-mode smoke workflow Runs the global AI chat eval (global-test1) across one cheap model per provider (Anthropic/OpenAI/Google/DeepSeek) only when the eval harness or copilot chat code change, since runs make paid LLM calls. Builds Windmill CE from source as the AI proxy; global tools/drafts run in the Vitest bridge. Gates on the deterministic draft pipeline (run succeeded + produced a draft + used write_script), not the variable LLM judge score. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: run AI smokes on PR ready-for-review instead of every push Switch the pull_request trigger from `synchronize` (every commit) to `ready_for_review`, with a job guard skipping draft PRs, so the paid LLM runs only fire when a PR is marked ready to merge (plus push-to-main and manual dispatch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai_evals): lazily load cli mode so non-cli evals skip the cli toolchain The entrypoint eagerly imported modes/cli, which pulls the wmill CLI guidance modules and their JSR deps (@cliffy/*). Global/flow/script/app runs then crashed with "Cannot find module '@cliffy/ansi/colors'" when the cli workspace deps were not installed. Import createCliModeRunner dynamically inside runCliBenchmark instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai_agent): raise low max_completion_tokens to OpenAI's 16 minimum OpenAI's /v1/responses rejects max_output_tokens < 16 with a 400, failing test_low_max_tokens for openai. 16 still exercises a truncated response. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: run ai_evals workflow on Node 22 for the frontend undici 8.x dep The Vitest bridge loads frontend/node_modules/undici@8.x, which requires Node >=22.19; Node 20 failed with "webidl.util.markAsUncloneable is not a function" when loading vitest.config.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai_evals): run frontend evals autonomously + give global-test1 more turns Frontend evals (flow/script/app/global) ran the production chat prompt, which assumes an interactive human — so cheaper models burned their turn budget asking for confirmation, waiting for approval, or presenting a plan, sometimes hitting maxTurns without producing a draft. Append a shared autonomy note in baseEvalRunner (the path all frontend modes share, mirroring cli mode): act directly on clear requests; only ask on genuinely ambiguous ones (preserving the askUserQuestion cases). Also raise global-test1's maxTurns 8 -> 10 so a model that over-explores still converges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(ai_evals): watch draft/prompt deps outside copilot/ The global eval runs production frontend code in-process, so the smoke's behavior depends on files outside frontend/src/lib/components/copilot/**: the draft model (userDraft.svelte.ts, userDraftDbSyncer.svelte.ts), script inference (infer.ts), and the chat system prompts ($system_prompts -> system_prompts/auto-generated). Add them to both push and PR path filters so a change there actually triggers the smoke that gates on draft production. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: skip direct provider tests without credentials * feat: add ai evals skip judge flag * fix: simplify ai evals ci gate * fix: simplify ai evals smoke gate * fix: handle ai eval workflow triggers --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
261 lines
9.8 KiB
Markdown
261 lines
9.8 KiB
Markdown
# AI Evals
|
|
|
|
Small benchmark runner for the Windmill AI generation modes:
|
|
|
|
- `cli`
|
|
- `flow`
|
|
- `script`
|
|
- `app`
|
|
- `global`
|
|
|
|
The benchmark always tests the current production prompts, tools, and guidance in this checkout.
|
|
|
|
Each attempt runs:
|
|
|
|
1. the real production path
|
|
2. deterministic validation
|
|
3. LLM judging
|
|
|
|
## Install
|
|
|
|
```bash
|
|
cd ai_evals
|
|
bun install
|
|
```
|
|
|
|
Frontend modes also require frontend dependencies:
|
|
|
|
```bash
|
|
cd frontend
|
|
bun install
|
|
```
|
|
|
|
## Commands
|
|
|
|
List model aliases:
|
|
|
|
```bash
|
|
cd ai_evals
|
|
bun run cli -- models
|
|
```
|
|
|
|
List cases:
|
|
|
|
```bash
|
|
cd ai_evals
|
|
bun run cli -- cases
|
|
bun run cli -- cases flow
|
|
```
|
|
|
|
Run benchmarks:
|
|
|
|
```bash
|
|
cd ai_evals
|
|
bun run cli -- run flow
|
|
bun run cli -- run flow flow-test4-order-processing-loop --model opus
|
|
bun run cli -- run flow flow-test0-sum-two-numbers --models haiku,opus,4o
|
|
bun run cli -- run flow flow-test0-sum-two-numbers --runs 3 --verbose
|
|
bun run cli -- run flow --record
|
|
GEMINI_API_KEY=... bun run cli -- run app app-test1-counter-create --model gemini-3-flash-preview
|
|
WMILL_AI_EVAL_BACKEND_URL=http://127.0.0.1:8000 bun run cli -- run flow --backend-validation preview
|
|
bun run cli -- run global global-test1-script-create
|
|
bun run cli -- run cli bun-hello-script
|
|
```
|
|
|
|
Public CLI surface:
|
|
|
|
- `models`
|
|
- `cases [mode]`
|
|
- `run <mode> [caseIds...]`
|
|
|
|
`run` options:
|
|
|
|
- `--runs <n>`: repeat each case `n` times
|
|
- `--output <path>`: custom result JSON path
|
|
- `--model <alias>`: choose the model under test
|
|
- `--models <a,b,c>`: run the same cases sequentially against several model aliases
|
|
- `--verbose`: stream assistant output for frontend runs
|
|
- `--skip-judge`: skip LLM judge scoring for the run
|
|
- `--execution-only`: only require the model/proxy/frontend loop to complete; skip validators, tool expectations, backend artifact validation, and judge scoring
|
|
- `--record`: append a compact tracked summary line to `ai_evals/history/<mode>.jsonl` for full-suite runs only
|
|
- `--backend-validation <mode>`: optional backend smoke validation (`off` or `preview`) for `script` and `flow` evals
|
|
|
|
## Models
|
|
|
|
Use `bun run cli -- models` to see the current aliases.
|
|
|
|
Today:
|
|
|
|
- `haiku`
|
|
- `sonnet`
|
|
- `opus`
|
|
- `4o`
|
|
- `gpt-5.5`
|
|
- `gemini-3-flash-preview`
|
|
- `gemini-3.1-pro-preview`
|
|
- `deepseek-v4-flash`
|
|
- `deepseek-v4-pro`
|
|
|
|
Notes:
|
|
|
|
- the command also prints accepted alias spellings such as `gpt-4o`, `gpt-55`, `claude-opus-4.6`, and `claude-haiku-4.5`
|
|
- frontend modes (`flow`, `script`, `app`, `global`) can use Anthropic, OpenAI, Gemini, and DeepSeek-backed aliases
|
|
- `cli` mode always uses the Anthropic agent SDK, so only Anthropic aliases are valid there
|
|
- the judge model is separate and currently defaults to `claude-sonnet-4-6`; use `--skip-judge` for deterministic-only runs
|
|
|
|
## Case Format
|
|
|
|
Cases live in one YAML file per mode under `ai_evals/cases/`.
|
|
|
|
Minimal shape:
|
|
|
|
```yaml
|
|
- id: flow-test0-sum-two-numbers
|
|
prompt: |-
|
|
Create a flow that takes two numbers, `a` and `b`, and returns their sum.
|
|
initial: ai_evals/fixtures/...
|
|
expected: ai_evals/fixtures/...
|
|
```
|
|
|
|
Optional fields:
|
|
|
|
- `initial`: starting state fixture
|
|
- `expected`: expected artifact fixture
|
|
- `validate`: extra deterministic validation rules
|
|
- `runtime.backendPreview`: optional real backend preview config for smoke validation
|
|
|
|
For `flow` mode, `validate` can express requirements such as:
|
|
|
|
- accepted input schema shapes
|
|
- required `results.*` reference validity
|
|
- required module/code/input characteristics
|
|
|
|
For `app` mode, `validate` can express narrow hard requirements such as:
|
|
|
|
- required frontend file paths or backend runnable keys
|
|
- minimum backend runnable counts
|
|
- required backend runnable types
|
|
- minimum datatable / datatable-table counts
|
|
- specific required datatable tables
|
|
|
|
For `global` mode, `validate` can express draft-level requirements such as:
|
|
|
|
- required draft type/path/language
|
|
- required or forbidden snippets in draft values
|
|
- required or forbidden draft counts
|
|
- forbidden draft paths
|
|
|
|
Global initial fixtures can also seed `liveEditorDrafts` with `type`,
|
|
`storagePath`, `effectivePath`, and `value` fields. These drafts emulate the
|
|
currently open script, flow, or raw app editor so cases can test prompts that
|
|
refer to "this" or the "current" item.
|
|
|
|
Global (and flow) initial fixtures can seed `workspace.datatables` so the
|
|
`list_datatables`, `get_datatable_table_schema`, and `exec_datatable_sql` tools
|
|
return seeded data during evals. Each entry is
|
|
`{ datatable_name, schemas: { <schema>: { <table>: { columns, rows? } } } }`.
|
|
SQL runs through a small in-memory engine (`datatableSqlEngine.ts`), not a real
|
|
database. Writes are **stateful within a case**: `CREATE`/`DROP`/`INSERT`/`UPDATE`/
|
|
`DELETE` mutate the seeded datatable in place, so a later `list_datatables`,
|
|
`get_datatable_table_schema`, `SELECT`, or `information_schema` query reflects them
|
|
— this is what stops a model from looping when it re-queries to verify a write.
|
|
The engine is best-effort: `SELECT` returns all rows of the referenced (or first)
|
|
table with no WHERE filtering/projection/joins, `WHERE` on UPDATE/DELETE supports
|
|
`col = value` predicates joined by `AND`, and anything unparseable is a no-op
|
|
success. So validate datatable cases through tool-use and SQL-argument assertions
|
|
(`requiredToolsUsed`, `stringIncludesAnyOf`) — not through exact returned row
|
|
values. An empty/absent `datatables` seed makes `list_datatables` return `[]`,
|
|
which is what the "no datatable configured" blocking cases rely on.
|
|
|
|
Set `WMILL_AI_EVAL_DISABLE_ACTIVE_EDITOR_CONTEXT=1` to run those cases with
|
|
the old behavior where the live editor is only discoverable through
|
|
`list_workspace_items`.
|
|
|
|
App fixtures can also include an optional `datatables.json` file at the fixture root.
|
|
|
|
For `flow` mode, an `initial` fixture can also include a benchmark workspace catalog of
|
|
existing scripts and flows. That lets the real `search_workspace` and
|
|
`get_runnable_details` tools discover reusable workspace runnables during evals.
|
|
|
|
If `--backend-validation preview` is enabled:
|
|
|
|
- `script` evals run a real backend script preview in an isolated temp workspace
|
|
- `flow` evals run a real backend flow preview only for cases that define `runtime.backendPreview`
|
|
- `flow` cases with `initial.workspace` fixtures seed those scripts and flows into the preview workspace before preview
|
|
- when `WMILL_AI_EVAL_BACKEND_WORKSPACE` is set, `ai_evals` creates or reuses that workspace as a dedicated test workspace, clears managed eval assets under `f/evals/*` before each preview run, and then reseeds the current case fixtures
|
|
|
|
Supported backend env vars:
|
|
|
|
- `WMILL_AI_EVAL_BACKEND_VALIDATION=preview`
|
|
- `WMILL_AI_EVAL_BACKEND_URL=http://127.0.0.1:8000`
|
|
- `WMILL_AI_EVAL_BACKEND_EMAIL=admin@windmill.dev`
|
|
- `WMILL_AI_EVAL_BACKEND_PASSWORD=changeme`
|
|
- `WMILL_AI_EVAL_BACKEND_WORKSPACE=integration-tests` to reuse an existing workspace on CE installs with low workspace limits
|
|
|
|
Frontend modes require a reachable Windmill backend and send model requests through the workspace AI proxy at `/api/w/{workspace}/ai/proxy`. At startup, `ai_evals` checks the resolved backend URL and fails early with setup guidance if the backend cannot be reached or login fails.
|
|
|
|
For frontend modes:
|
|
|
|
- `ai_evals` creates a temporary backend workspace, or creates/reuses `WMILL_AI_EVAL_BACKEND_WORKSPACE` when it is set
|
|
- it upserts a provider resource under `f/evals/ai/<provider>`
|
|
- frontend requests go through `/api/w/{workspace}/ai/proxy`
|
|
|
|
## Results And Artifacts
|
|
|
|
Every run writes:
|
|
|
|
- a summary JSON under `ai_evals/results/`
|
|
- generated artifacts in a sibling directory
|
|
|
|
If `--record` is used, the CLI also appends one compact JSON line to:
|
|
|
|
- `ai_evals/history/flow.jsonl`
|
|
- `ai_evals/history/script.jsonl`
|
|
- `ai_evals/history/app.jsonl`
|
|
- `ai_evals/history/global.jsonl`
|
|
- `ai_evals/history/cli.jsonl`
|
|
|
|
Each recorded line contains:
|
|
|
|
- run metadata (`createdAt`, `gitSha`, `mode`, `runModel`, `judgeModel`)
|
|
- suite totals (`caseCount`, `attemptCount`, `passedAttempts`, `passRate`, `averageDurationMs`, `averagePassedDurationMs`, `averageJudgeScore`)
|
|
- average token usage (`averageTokenUsagePerAttempt`, `averageTokenUsagePerPassedAttempt`)
|
|
- per-case metrics under `cases[]` (`averageDurationMs`, `averagePassedDurationMs`, `averageJudgeScore`, `averageTokenUsagePerAttempt`, `averageTokenUsagePerPassedAttempt`, pass rate)
|
|
- `failedCaseIds`
|
|
|
|
The CLI headline duration and token averages use passed attempts only.
|
|
All-attempt averages are still recorded to make failures auditable without
|
|
letting failed attempts skew success cost comparisons.
|
|
|
|
Example:
|
|
|
|
- summary: `ai_evals/results/2026-04-09T09-40-33.051Z__flow.json`
|
|
- artifacts: `ai_evals/results/2026-04-09T09-40-33.051Z__flow/`
|
|
|
|
Typical artifacts by mode:
|
|
|
|
- `flow`: `flow.json`
|
|
- `script`: `script.json` plus the generated script file
|
|
- `app`: `app.json` plus frontend/backend files
|
|
- `global`: `global-drafts.json`
|
|
- `cli`: `assistant-output.txt`, `trace.json`, `wmill-invocations.jsonl`, plus generated workspace files
|
|
- backend-validated attempts also include `backend-preview.json`
|
|
|
|
## Layout
|
|
|
|
- `cases/`: one YAML file per mode
|
|
- `fixtures/`: initial and expected fixtures
|
|
- `core/`: shared loading, model resolution, validation, judging, and result writing
|
|
- `modes/`: one runner per mode
|
|
- `history/`: optional tracked pass-rate history written by `run --record`, one JSONL file per mode
|
|
- `results/`: local benchmark output and artifacts
|
|
|
|
## Notes
|
|
|
|
- Frontend modes reuse the production frontend chat code through the Vitest bridge.
|
|
- Global mode evaluates the production global AI tools and validates the resulting AI draft store.
|
|
- CLI mode creates an isolated workspace, writes the current checkout guidance into it, and benchmarks the real skills / `AGENTS.md` flow.
|
|
- CLI mode now also records a structured trace of invoked skills, tool calls, proposed `wmill` commands, and any attempted `wmill` executions.
|
|
- Frontend progress streams live while the benchmark is running.
|
|
- Deterministic validators should stay focused on real correctness constraints, not one exact implementation shape.
|