Skip to content

Commit 9dd2b28

Browse files
Tomkessclaude
andcommitted
Merge branch 'master' into feat/agentic-best-run-latency
#1849 added two lines to the visualization assertion message (Executed, Stated Value Matches) at the same place this branch moved the raise behind the shared outcome tail. Both land: the new lines close the message, then the tail's keyword arguments follow. 1565 passed, ruff clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 parents c41f542 + 0f69236 commit 9dd2b28

31 files changed

Lines changed: 2855 additions & 78 deletions

‎packages/gooddata-eval/AGENTS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ Run this before guessing a `--model` string.
156156
| `GD_EVAL_AGENT_ID` | which agent to drive, same as `--agent-id` |
157157
| `LANGFUSE_HOST`, `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` | `--langfuse`, `--langfuse-dataset` |
158158
| `GOODDATA_EVAL_CHAT_*` | SSE retry, backoff and timeout knobs |
159+
| `GOODDATA_EVAL_TRACE_LABELS` | `key=value,...` labels stamped on every observation of a chat conversation via W3C baggage; `model_version` also sets the observation `version`, so set it only for a one-model run: every `--model` in one process gets the same labels. Keys must match `[A-Za-z0-9_-]+`; others are skipped with a warning. Dashboard-summary runs are not labelled |
159160
| `GD_EVAL_TIMERS` | same as `--timers` |
160161

161162
A gitignored `.env` at the repo root is the normal place for these; load it with

‎packages/gooddata-eval/README.md‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,55 @@ The `expected_output` rubric:
574574
Each criterion is scored independently by the LLM judge, so `quality_score`
575575
is the fraction of satisfied criteria.
576576

577+
### Items asked in a dashboard context (`user_context`)
578+
579+
An item can be asked the way a user asks from an open dashboard or an attached widget.
580+
Its `user_context` is sent verbatim as `userContext` on every chat message of the item,
581+
follow-up and clarification messages included — the server treats a message without one
582+
as a cleared context. Requires the `enableAiContextSetup` feature flag on the target
583+
organization — without it the server ignores the dashboard view. Applies to chat items
584+
only; `dashboard_summary` items carry their scope in `summary_input` instead. No
585+
`userContext` key is sent at all when neither the item nor, for `agentic_conversation`,
586+
the current turn sets one.
587+
588+
```json
589+
{
590+
"id": "ctx-001",
591+
"dataset_name": "my_dataset_ctx",
592+
"test_kind": "general_question",
593+
"question": "Which dashboard am I looking at, and what does it cover?",
594+
"user_context": {
595+
"view": {
596+
"dashboard": {
597+
"id": "sales_overview",
598+
"title": "Sales Overview",
599+
"widgets": [
600+
{"widgetType": "insight", "widgetId": "w1", "title": "Revenue by Month", "visualizationId": "revenue_by_month"}
601+
]
602+
}
603+
}
604+
},
605+
"expected_output": "Names the Sales Overview dashboard and summarizes its charts."
606+
}
607+
```
608+
609+
The schema belongs to the AI chat API (`UserContext`: `view.dashboard`,
610+
`referencedObjects`, `activeObject`), so it is not validated here beyond being an object.
611+
In a Langfuse dataset, put it in the item `metadata` (or in the `input` object) under
612+
`user_context`. An object in `input` wins and `metadata` is then not read; a missing or
613+
`null` value in `input` falls back to `metadata`. The value used must be an object or `null`;
614+
anything else fails the dataset load.
615+
616+
`agentic_conversation` items take the item's `user_context` as the context of the first
617+
turn. A turn in `expected_output.turns` can set its own `user_context`, which applies
618+
from that turn on until another turn changes it, as the attached context does in the UI:
619+
620+
| Turn | Context sent |
621+
|---|---|
622+
| no `user_context` key | the current one, unchanged |
623+
| `"user_context": {...}` | this one, from this turn on |
624+
| `"user_context": null` | none, from this turn on |
625+
577626
## Supported test kinds
578627

579628
| test_kind | What the agent must produce | Extra required |

‎packages/gooddata-eval/src/gooddata_eval/cli/agentic_runner.py‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
from gooddata_eval.core.agentic.guardrail import evaluate_agentic_guardrail
2020
from gooddata_eval.core.agentic.kda_skill import evaluate_agentic_kda_skill
2121
from gooddata_eval.core.agentic.metric_skill import evaluate_agentic_metric_skill
22+
from gooddata_eval.core.agentic.report_skill import evaluate_agentic_report_skill
2223
from gooddata_eval.core.agentic.search_tool import evaluate_agentic_search_tool
2324
from gooddata_eval.core.agentic.visualization import evaluate_agentic_visualization
2425
from gooddata_eval.core.agentic.what_if import evaluate_agentic_what_if
2526
from gooddata_eval.core.config import ReasoningEffort
27+
from gooddata_eval.core.evaluators.visualization import requires_execution_of
2628
from gooddata_eval.core.models import AgenticEvalOutcome, CreatedVisualization, DatasetItem
2729
from gooddata_eval.core.runner import EvalReport, ItemReport
2830

@@ -44,6 +46,7 @@ class _LfKw(TypedDict, total=False):
4446
"agentic_metric_skill",
4547
"agentic_alert_skill",
4648
"agentic_dashboard_skill",
49+
"agentic_report_skill",
4750
"agentic_search",
4851
"agentic_general_question",
4952
"agentic_guardrail",
@@ -94,7 +97,8 @@ class _LfKw(TypedDict, total=False):
9497
#
9598
# agentic_dashboard_skill is absent by default rather than by evidence: gen-ai holds the draft and
9699
# any chart it authors in conversation state and writes neither until a user saves from the UI, so
97-
# it is a candidate for the allowlist once the dataset has runs behind it.
100+
# it is a candidate for the allowlist once the dataset has runs behind it. agentic_report_skill is
101+
# absent for the same reason: the report draft stays in conversation state until a user saves it.
98102
WORKSPACE_MUTATING_TEST_KINDS = frozenset(AGENTIC_TEST_KINDS) - PARALLEL_SAFE_TEST_KINDS
99103

100104

@@ -176,9 +180,11 @@ def _dispatch_agentic(
176180
workspace_id=workspace_id,
177181
question=item.question,
178182
expected_outputs=_parse_visualization_expected(eo),
183+
requires_execution=requires_execution_of(eo),
179184
k=k,
180185
gate=gate,
181186
agent_id=agent_id,
187+
user_context=item.user_context,
182188
**lf_kw,
183189
)
184190
elif kind == "agentic_metric_skill":
@@ -191,6 +197,7 @@ def _dispatch_agentic(
191197
k=k,
192198
gate=gate,
193199
agent_id=agent_id,
200+
user_context=item.user_context,
194201
**lf_kw,
195202
)
196203
elif kind == "agentic_dashboard_skill":
@@ -203,6 +210,20 @@ def _dispatch_agentic(
203210
k=k,
204211
gate=gate,
205212
agent_id=agent_id,
213+
user_context=item.user_context,
214+
**lf_kw,
215+
)
216+
elif kind == "agentic_report_skill":
217+
return evaluate_agentic_report_skill(
218+
host=host,
219+
token=token,
220+
workspace_id=workspace_id,
221+
question=item.question,
222+
expected_output=eo,
223+
k=k,
224+
gate=gate,
225+
agent_id=agent_id,
226+
user_context=item.user_context,
206227
**lf_kw,
207228
)
208229
elif kind == "agentic_alert_skill":
@@ -215,6 +236,7 @@ def _dispatch_agentic(
215236
k=k,
216237
gate=gate,
217238
agent_id=agent_id,
239+
user_context=item.user_context,
218240
**lf_kw,
219241
)
220242
elif kind == "agentic_search":
@@ -230,6 +252,7 @@ def _dispatch_agentic(
230252
k=k,
231253
gate=gate,
232254
agent_id=agent_id,
255+
user_context=item.user_context,
233256
**lf_kw,
234257
)
235258
elif kind == "agentic_general_question":
@@ -255,6 +278,7 @@ def _dispatch_agentic(
255278
k=k,
256279
gate=gate,
257280
agent_id=agent_id,
281+
user_context=item.user_context,
258282
**lf_kw,
259283
)
260284
elif kind == "agentic_kda_skill":
@@ -267,6 +291,7 @@ def _dispatch_agentic(
267291
k=k,
268292
gate=gate,
269293
agent_id=agent_id,
294+
user_context=item.user_context,
270295
**lf_kw,
271296
)
272297
elif kind == "agentic_what_if":
@@ -278,6 +303,7 @@ def _dispatch_agentic(
278303
expected_output=eo if isinstance(eo, dict) else {},
279304
k=k,
280305
agent_id=agent_id,
306+
user_context=item.user_context,
281307
**lf_kw,
282308
)
283309
elif kind == "agentic_anomaly_detection":
@@ -290,6 +316,7 @@ def _dispatch_agentic(
290316
k=k,
291317
gate=gate,
292318
agent_id=agent_id,
319+
user_context=item.user_context,
293320
**lf_kw,
294321
)
295322
elif kind == "agentic_conversation":
@@ -300,6 +327,7 @@ def _dispatch_agentic(
300327
workspace_id=workspace_id,
301328
fixture=ConversationFixture.model_validate(fixture_data),
302329
agent_id=agent_id,
330+
user_context=item.user_context,
303331
**lf_kw,
304332
)
305333
else:

‎packages/gooddata-eval/src/gooddata_eval/core/agentic/__init__.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@
5353
evaluate_agentic_metric_skill,
5454
run_agentic_metric_skill,
5555
)
56+
from gooddata_eval.core.agentic.report_skill import (
57+
AgenticReportSummary,
58+
ReportEvaluation,
59+
ReportRunResult,
60+
ReportSkillAssertionError,
61+
evaluate_agentic_report_skill,
62+
run_agentic_report_skill,
63+
)
5664
from gooddata_eval.core.agentic.search_tool import (
5765
AgenticSearchSummary,
5866
SearchResult,
@@ -75,6 +83,7 @@
7583
"AgenticGuardrailSummary",
7684
"AgenticKdaSummary",
7785
"AgenticMetricSummary",
86+
"AgenticReportSummary",
7887
"AgenticSearchSummary",
7988
"AgenticRunSummary",
8089
"AlertEvaluation",
@@ -95,6 +104,9 @@
95104
"KdaSkillAssertionError",
96105
"MetricRunResult",
97106
"MetricSkillAssertionError",
107+
"ReportEvaluation",
108+
"ReportRunResult",
109+
"ReportSkillAssertionError",
98110
"RunResult",
99111
"SearchResult",
100112
"SearchToolAssertionError",
@@ -108,6 +120,7 @@
108120
"evaluate_agentic_guardrail",
109121
"evaluate_agentic_kda_skill",
110122
"evaluate_agentic_metric_skill",
123+
"evaluate_agentic_report_skill",
111124
"evaluate_agentic_search_tool",
112125
"evaluate_agentic_visualization",
113126
"run_agentic_alert_skill",
@@ -117,6 +130,7 @@
117130
"run_agentic_guardrail",
118131
"run_agentic_kda_skill",
119132
"run_agentic_metric_skill",
133+
"run_agentic_report_skill",
120134
"run_agentic_search_tool",
121135
"run_agentic_visualization",
122136
]

‎packages/gooddata-eval/src/gooddata_eval/core/agentic/alert_skill.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,12 +663,18 @@ def run_agentic_alert_skill(
663663
initial_conversation_id: str | None = None,
664664
reasoning_effort: ReasoningEffort | None = None,
665665
agent_id: str | None = None,
666+
user_context: dict | None = None,
666667
) -> AgenticAlertSummary:
667668
"""Run the alert-skill agentic evaluation K times and return a summary."""
668669
expected = _normalize_expected_output(expected_output)
669670
run_results: list[AlertRunResult] = []
670671
client = ChatClient(
671-
host=host, token=token, workspace_id=workspace_id, reasoning_effort=reasoning_effort, agent_id=agent_id
672+
host=host,
673+
token=token,
674+
workspace_id=workspace_id,
675+
reasoning_effort=reasoning_effort,
676+
agent_id=agent_id,
677+
user_context=user_context,
672678
)
673679
sdk = GoodDataSdk.create(host, token)
674680

@@ -864,6 +870,7 @@ def evaluate_agentic_alert_skill(
864870
run_metadata_extra: dict | None = None,
865871
reasoning_effort: ReasoningEffort | None = None,
866872
submit_trace_link: SubmitTraceLink = run_trace_link_inline,
873+
user_context: dict | None = None,
867874
gate: EvalGate = DEFAULT_GATE,
868875
) -> AgenticEvalOutcome:
869876
"""Run alert-skill evaluation, log to Langfuse, and raise AlertSkillAssertionError on failure.
@@ -887,6 +894,7 @@ def evaluate_agentic_alert_skill(
887894
initial_conversation_id=initial_conversation_id,
888895
reasoning_effort=reasoning_effort,
889896
agent_id=agent_id,
897+
user_context=user_context,
890898
)
891899

892900
if langfuse is not None and dataset_item_id:

‎packages/gooddata-eval/src/gooddata_eval/core/agentic/anomaly_detection.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ def run_agentic_anomaly_detection(
354354
initial_conversation_id: str | None = None,
355355
reasoning_effort: ReasoningEffort | None = None,
356356
agent_id: str | None = None,
357+
user_context: dict | None = None,
357358
) -> AgenticAnomalySummary:
358359
"""Run the anomaly-detection agentic evaluation K times and return a summary.
359360
@@ -366,7 +367,12 @@ def run_agentic_anomaly_detection(
366367
raise ValueError(f"k must be >= 1, got {k}")
367368
run_results: list[AnomalyRunResult] = []
368369
client = ChatClient(
369-
host=host, token=token, workspace_id=workspace_id, reasoning_effort=reasoning_effort, agent_id=agent_id
370+
host=host,
371+
token=token,
372+
workspace_id=workspace_id,
373+
reasoning_effort=reasoning_effort,
374+
agent_id=agent_id,
375+
user_context=user_context,
370376
)
371377

372378
def _run_once(conv_id: str) -> AnomalyRunResult:
@@ -545,6 +551,7 @@ def evaluate_agentic_anomaly_detection(
545551
run_metadata_extra: dict | None = None,
546552
reasoning_effort: ReasoningEffort | None = None,
547553
submit_trace_link: SubmitTraceLink = run_trace_link_inline,
554+
user_context: dict | None = None,
548555
) -> AgenticEvalOutcome:
549556
"""Run anomaly-detection evaluation, log to Langfuse, and raise on failure."""
550557
langfuse, window_start = open_trace_window(langfuse)
@@ -559,6 +566,7 @@ def evaluate_agentic_anomaly_detection(
559566
initial_conversation_id=initial_conversation_id,
560567
reasoning_effort=reasoning_effort,
561568
agent_id=agent_id,
569+
user_context=user_context,
562570
)
563571

564572
if langfuse is not None and dataset_item_id:

0 commit comments

Comments
 (0)