Skip to content

Commit 9927b28

Browse files
authored
Merge pull request #1808 from xvalovic/feat/ais-48-html-report
feat(gooddata-eval): generate eval datasets from insights, plus a readable HTML report
2 parents 1ab844c + b58ed4e commit 9927b28

34 files changed

Lines changed: 3844 additions & 60 deletions

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,6 @@ packages/gooddata-sdk/tests/export/exports/default/
4444
/packages/gooddata-eval/datasets/
4545
# MCP tool logs, written to a relative path by whatever is started from the repo root
4646
/logs/
47+
48+
# editor swap files
49+
*.swp

‎packages/gooddata-eval/AGENTS.md‎

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ experiment. The newest and most actively developed package in the repo.
99

1010
## Owns
1111

12-
- The `gd-eval` CLI (`gd-eval run`, `gd-eval models`)
12+
- The `gd-eval` CLI (`generate`, `run`, `report`, `models`)
1313
- Dataset loading and the evaluation run loop
1414
- Per-capability evaluators and their scoring
1515
- Result reporting, and pushing experiments, scores and trace links to Langfuse
@@ -27,7 +27,7 @@ experiment. The newest and most actively developed package in the repo.
2727
| `core/agentic/` | multi-turn agentic evaluation per capability, **plus** all Langfuse trace polling and linking (`_langfuse.py`, `_trace_linker.py`) |
2828
| `core/chat/` | SSE client for the agent's streaming chat endpoint |
2929
| `core/summary/` | HTTP client for the dedicated dashboard-summary endpoint — a single-shot chat backend, not reporting |
30-
| `core/dataset/` | dataset format and loading |
30+
| `core/dataset/` | dataset format, loading, and `from_insights.py` — dataset generation from a workspace's real insights |
3131
| `core/evaluators/` | single-shot evaluators and their registry |
3232
| `core/langfuse/` | the whole Langfuse v4 client: `_env` (base URL + credentials), `otlp` (OTLP/JSON encoding), `experiment` (root-span construction, score targets), `observations` (trace reads), `client` (httpx calls), `sink` (single-shot results as experiments) |
3333
| `core/reporting/` | console and JSON output rendering |
@@ -61,6 +61,106 @@ its own shape. `test_kind` on the item is what labels the result, not the evalua
6161
which is why `knowledge_question` can reuse `GeneralQuestionEvaluator` verbatim.
6262
`dashboard_summary` items additionally need `summary_input`.
6363

64+
## Running the pipeline
65+
66+
Four subcommands, in the order you use them. Everything runs through `uv`; never a bare
67+
`python`. There is no build step -- `uv run` syncs the environment from `uv.lock` on first
68+
use, so a fresh clone needs nothing but:
69+
70+
```bash
71+
uv run --package gooddata-eval gd-eval <subcommand> --help
72+
```
73+
74+
`openai` is an optional extra (`llm-judge`) so the published package stays installable
75+
without it, but the `dev` dependency group pulls it in, which is why a plain `uv run` here
76+
has the phrasing step and the LLM judge. Installing `gooddata-eval` from PyPI does not --
77+
there the extra is explicit, and every `openai` import site is guarded or deferred.
78+
79+
Connection is the same for every subcommand that talks to the platform: `--host` +
80+
`--token`, or `GOODDATA_TOKEN` in the environment, or `--profile <name>` reading
81+
`~/.gooddata/profiles.yaml`. Precedence is flags > env > profile.
82+
83+
### 1. `generate` — build a dataset from a workspace
84+
85+
Reverse-engineers `visualization` items out of the charts a workspace already has, so the
86+
expected output is copied from a live object rather than invented. Needs `OPENAI_API_KEY`
87+
for the phrasing step, or `--no-phrase` to emit mechanical `Show <title>` questions.
88+
89+
```bash
90+
uv run --package gooddata-eval gd-eval generate \
91+
--host "$GOODDATA_HOST" --workspace "$WORKSPACE_ID" \
92+
--dataset-name ecommerce --out ./datasets/ecommerce \
93+
--snapshot-out /tmp/ws.json \
94+
--phrase-model gpt-4o --skip-ambiguous
95+
```
96+
97+
Iterate offline instead of re-fetching: `--snapshot-out` writes everything the generator
98+
read as one JSON file, and `--snapshot-in` replays it with no host, token or network. Add
99+
`--dry-run` to print the shape counts and each spec's brief without writing anything —
100+
the fastest way to see what a workspace yields.
101+
102+
Quality gates fail the command (exit 1) below `--min-questions` (15) or `--min-filtered` (1). Lower them for a smoke test; do not lower them to ship a dataset.
103+
`--langfuse-out` additionally writes a Langfuse-importable file, and `--id-prefix` rewrites
104+
ids on that export only, because Langfuse item ids are unique per project.
105+
106+
Insights the AAC spec cannot express without guessing are skipped with a printed reason
107+
(`SKIP <id>: derived measure (previousPeriodMeasure)`). Read those — they are the
108+
generator telling you what it refused to invent, not noise.
109+
110+
### 2. `run` — evaluate
111+
112+
```bash
113+
uv run --package gooddata-eval gd-eval run \
114+
--host "$GOODDATA_HOST" --workspace "$WORKSPACE_ID" \
115+
--dataset ./datasets/ecommerce --kind visualization \
116+
--model gpt-5.2 --model ProviderName/gpt-4o \
117+
--runs 3 --gate power --concurrency 4 \
118+
--json ./results/run.json --html ./results/run.html
119+
```
120+
121+
`--dataset` reads a local folder; `--langfuse-dataset` pulls one by name instead. `--kind`
122+
only supplies a default for items that do not carry their own `test_kind`. Repeat
123+
`--model` to compare models in one run. `--runs` with `--gate power` measures stability
124+
(every run must pass) rather than pass@K. `--langfuse` pushes the run as a scored
125+
experiment, needing `LANGFUSE_HOST`, `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY`.
126+
127+
`--concurrency` is capped for you where it matters: kinds that create workspace objects
128+
run one at a time regardless, see the parallel-safety gotcha below.
129+
130+
### 3. `report` — compare runs
131+
132+
```bash
133+
uv run --package gooddata-eval gd-eval report \
134+
./results/*.json -o ./results/comparison.html --title "luna vs 4o" --redact
135+
```
136+
137+
Several JSON reports become side-by-side columns keyed by file name. `--redact` is the
138+
customer-safe form: conversation ids, response ids and raw reasoning dropped, model names
139+
replaced with "Model A", "Model B".
140+
141+
### 4. `models` — what the org has configured
142+
143+
```bash
144+
uv run --package gooddata-eval gd-eval models --host "$GOODDATA_HOST"
145+
```
146+
147+
Run this before guessing a `--model` string.
148+
149+
### Environment
150+
151+
| Variable | Used by |
152+
|---|---|
153+
| `GOODDATA_TOKEN` | every platform-facing subcommand |
154+
| `OPENAI_API_KEY` | `generate` phrasing, and the LLM-as-judge evaluators |
155+
| `GD_EVAL_JUDGE_MODEL` | judge model, same as `--judge-model` |
156+
| `GD_EVAL_AGENT_ID` | which agent to drive, same as `--agent-id` |
157+
| `LANGFUSE_HOST`, `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` | `--langfuse`, `--langfuse-dataset` |
158+
| `GOODDATA_EVAL_CHAT_*` | SSE retry, backoff and timeout knobs |
159+
| `GD_EVAL_TIMERS` | same as `--timers` |
160+
161+
A gitignored `.env` at the repo root is the normal place for these; load it with
162+
`set -a && . ./.env && set +a` before the command.
163+
64164
## Gotchas
65165

66166
**Adding an evaluator is a registry change, not a naming convention.** Single-shot kinds go
@@ -89,6 +189,23 @@ ingestion has no pass/fail signal and inflates or misattributes per-item latency
89189
(`run_trace_link_inline` is the synchronous alternative). Do not "fix" a slow item by
90190
making trace scoring synchronous again.
91191

192+
**A generated item's `expected_output` is copied, never invented — keep it that way.**
193+
`core/dataset/from_insights.py` converts each insight with the platform's own
194+
`declarative_visualization_to_aac()` (from `gooddata-code-convertors`, via `gooddata-sdk`),
195+
so the mapping is not ours to get wrong. What is ours is deciding what the evaluator cannot
196+
yet score — derived measures and measure-level filters convert fine and then compare wrong,
197+
so they are skipped with a printed reason — and stripping the no-op filters AD saves for an
198+
"All" selection, which would otherwise let a question claim a filter its chart lacks. Teach
199+
the comparator about a construct and the matching skip can go; do not make one convert by
200+
hand. Chart type names are the convertor's, which are also the agent's — do not rename them. One
201+
granularity is patched in `CONVERTOR_GRANULARITY_FIXES`: `week_us` → `WEEK_US` is a convertor
202+
bug, the platform enum is `WEEK`.
203+
204+
**The snapshot is a plain-JSON contract.** `--snapshot-in`/`--snapshot-out` is what makes
205+
the generator testable offline and iterable without re-fetching, and it is why the
206+
generator reads the declarative analytics model rather than `sdk.visualizations`. Anything
207+
that changes the fetch shape invalidates every saved snapshot.
208+
92209
**Scoring weights do not sum to 1.** `quality_score` is the fraction of boolean-valued keys
93210
in `best_detail` that are true, falling back to `pass_at_k` when there are none (text
94211
evaluators). `value_score` is `0.6 * quality + 0.2 * speed` — the 0.8 total is what the

0 commit comments

Comments
 (0)