gooddata-eval 1.78.0__tar.gz → 1.78.1.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/AGENTS.md +14 -9
- gooddata_eval-1.78.1.dev2/Makefile +6 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/PKG-INFO +123 -6
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/README.md +121 -4
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/pyproject.toml +2 -2
- gooddata_eval-1.78.1.dev2/scripts/calibrate_takeaway_judge.py +75 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/cli/agentic_runner.py +4 -2
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/cli/main.py +17 -11
- gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/_retry.py +71 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_conversation_context.py +59 -10
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/dashboard_skill.py +80 -1
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/visualization.py +132 -22
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/chat/sse_client.py +17 -62
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/from_insights.py +10 -6
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/langfuse_source.py +29 -2
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/__init__.py +5 -2
- gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/evaluators/_rubric.py +94 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/summary.py +12 -64
- gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/evaluators/takeaway.py +245 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/visualization.py +296 -6
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/models.py +61 -4
- gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/number_format.py +179 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/scoring.py +148 -8
- gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/takeaway/evaluate.py +275 -0
- gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/takeaway/http_client.py +104 -0
- gooddata_eval-1.78.1.dev2/tests/__init__.py +1 -0
- gooddata_eval-1.78.1.dev2/tests/fixtures/takeaway_calibration.json +68 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_conversation.py +115 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_dashboard_skill.py +113 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_runner.py +47 -5
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_visualization.py +272 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_cli.py +51 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_from_insights.py +24 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_source.py +96 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_models.py +50 -0
- gooddata_eval-1.78.1.dev2/tests/test_number_format.py +195 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_runner.py +1 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_sse_client.py +23 -0
- gooddata_eval-1.78.1.dev2/tests/test_takeaway_client.py +257 -0
- gooddata_eval-1.78.1.dev2/tests/test_takeaway_evaluate.py +349 -0
- gooddata_eval-1.78.1.dev2/tests/test_takeaway_evaluator.py +425 -0
- gooddata_eval-1.78.1.dev2/tests/test_visualization_evaluator.py +765 -0
- gooddata_eval-1.78.0/Makefile +0 -2
- gooddata_eval-1.78.0/tests/test_visualization_evaluator.py +0 -369
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/.gitignore +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/CLAUDE.md +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/LICENSE.txt +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/scripts/verify_guardrail_refusal_criteria.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/_version.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/cli/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/_output.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_catalog.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_failed_runs.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_gate.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_langfuse.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_trace_linker.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/alert_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/anomaly_detection.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/conversation.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/document_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/general_question.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/guardrail.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/kda_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/metric_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/report_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/search_tool.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/what_if.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/chat/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/chat/render.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/config.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/connection.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/local.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_deep_subset.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_guardrail_criteria.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_llm_judge.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_maql.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_text_utils.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/alert_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/base.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/general_question.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/guardrail.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/metric_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/search_tool.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/granularity.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/interaction.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/_env.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/client.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/experiment.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/item_scope.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/observations.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/otlp.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/sink.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/console.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/html_report.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/json_report.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/report_template.html +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/runner.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/summary/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/summary/http_client.py +0 -0
- {gooddata_eval-1.78.0/tests → gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/takeaway}/__init__.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/timing.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/workspace.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/_fake_langfuse.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/conftest.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/fixtures/sample_dataset/metric_skill_create.json +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/fixtures/sample_dataset/visualization_revenue.json +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/fixtures/sse_visualization_stream.txt +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_alert_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_anomaly_detection.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_document_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_gate.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_general_question.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_guardrail.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_join_trace.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_kda_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_langfuse_trace.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_metric_skill.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_observe_experiment.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_run_context.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_search_tool.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_what_if.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_alert_skill_evaluator.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_chat_render.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_connection.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_deep_subset.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_fake_langfuse.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_guardrail_criteria.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_html_report.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_interaction.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_client.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_e2e_fake_server.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_env.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_experiment.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_item_scope.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_observations.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_otlp.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_sink.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_llm_judge.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_local_loader.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_maql_normalize.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_metric_skill_evaluator.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_reporting.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_scoring.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_search_tool_evaluator.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_summary_client.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_summary_evaluator.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_text_evaluators.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_timing.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_trace_linker.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_workspace.py +0 -0
- {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tox.ini +0 -0
|
@@ -27,6 +27,7 @@ experiment. The newest and most actively developed package in the repo.
|
|
|
27
27
|
| `core/agentic/` | multi-turn agentic evaluation per capability, **plus** all Langfuse trace polling and linking (`_langfuse.py`, `_trace_linker.py`) |
|
|
28
28
|
| `core/chat/` | SSE client for the agent's streaming chat endpoint |
|
|
29
29
|
| `core/summary/` | HTTP client for the dedicated dashboard-summary endpoint — a single-shot chat backend, not reporting |
|
|
30
|
+
| `core/takeaway/` | HTTP client for the AI Publisher takeaway endpoint — a single-shot backend like `core/summary/`, keeping the whole response on `ChatResult.raw_response` — plus `evaluate_document_takeaway`, the K-run, gate and Langfuse entry point the Tavern shim calls |
|
|
30
31
|
| `core/dataset/` | dataset format, loading, and `from_insights.py` — dataset generation from a workspace's real insights |
|
|
31
32
|
| `core/evaluators/` | single-shot evaluators and their registry |
|
|
32
33
|
| `core/langfuse/` | the whole Langfuse v4 client: `_env` (base URL + credentials), `otlp` (OTLP/JSON encoding), `experiment` (root-span construction, score targets), `item_scope` (an item's experiment scope sent to gen-ai as baggage, and which conversations joined), `observations` (trace reads), `client` (httpx calls), `sink` (single-shot results as experiments) |
|
|
@@ -59,7 +60,8 @@ implementation, sometimes under different `test_kind` strings (`search_tool` vs
|
|
|
59
60
|
`expected_output: Any`. `expected_output` is deliberately untyped — each evaluator parses
|
|
60
61
|
its own shape. `test_kind` on the item is what labels the result, not the evaluator class,
|
|
61
62
|
which is why `knowledge_question` can reuse `GeneralQuestionEvaluator` verbatim.
|
|
62
|
-
`dashboard_summary` items additionally need `summary_input
|
|
63
|
+
`dashboard_summary` items additionally need `summary_input`, `document_takeaway` items
|
|
64
|
+
`takeaway_input`.
|
|
63
65
|
|
|
64
66
|
## Running the pipeline
|
|
65
67
|
|
|
@@ -155,8 +157,8 @@ Run this before guessing a `--model` string.
|
|
|
155
157
|
| `GD_EVAL_JUDGE_MODEL` | judge model, same as `--judge-model` |
|
|
156
158
|
| `GD_EVAL_AGENT_ID` | which agent to drive, same as `--agent-id` |
|
|
157
159
|
| `LANGFUSE_HOST`, `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` | `--langfuse`, `--langfuse-dataset` |
|
|
158
|
-
| `GOODDATA_EVAL_CHAT_*` |
|
|
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 |
|
|
160
|
+
| `GOODDATA_EVAL_CHAT_*` | retry and backoff knobs shared by the chat and takeaway clients (`core/_retry.py`), and the SSE turn and item timeouts |
|
|
161
|
+
| `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 and document-takeaway runs are not labelled |
|
|
160
162
|
| `GOODDATA_EVAL_JOIN_GENAI_TRACE` | on unless set to `0`, `false`, `no`, `off` or empty. An agentic item's chat turns ask gen-ai to open them under gd-eval's experiment root, so each item is one Langfuse trace with its scores written once. A gen-ai that drops caller baggage keeps today's separate traces; see the gotcha below |
|
|
161
163
|
| `GD_EVAL_TIMERS` | same as `--timers` |
|
|
162
164
|
|
|
@@ -181,12 +183,15 @@ That default is correct: agent tool calls create real server-side objects (metri
|
|
|
181
183
|
Adding a kind to `PARALLEL_SAFE_TEST_KINDS` is a deliberate assertion that it is read-only,
|
|
182
184
|
which nothing in the package can prove for you.
|
|
183
185
|
|
|
184
|
-
**The SSE client's retry predicate is load-bearing.**
|
|
185
|
-
429/502/503/504
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
**The SSE client's retry predicate is load-bearing.** The backoff loop and the retryable
|
|
187
|
+
status set (429/500/502/503/504) live in `core/_retry.py`, shared by the chat and takeaway
|
|
188
|
+
clients and tuned by the same `GOODDATA_EVAL_CHAT_*` knobs. `core/chat/sse_client.py`
|
|
189
|
+
retries that status set and `httpx.RemoteProtocolError` (a mid-stream disconnect), and
|
|
190
|
+
treats a `METADATA_SYNC_IN_PROGRESS` payload as transient. The `RemoteProtocolError` case
|
|
191
|
+
was added after it was confirmed live to contaminate a small percentage of visualization
|
|
192
|
+
runs with a hard fail and no retry. Narrowing that predicate reintroduces the problem. The
|
|
193
|
+
takeaway client retries the same status set plus connection failures, but not a read
|
|
194
|
+
timeout: a slow page would spend the whole timeout again on every attempt.
|
|
190
195
|
|
|
191
196
|
**Langfuse trace linking is deliberately off the item critical path.** Polling for trace
|
|
192
197
|
ingestion has no pass/fail signal and inflates or misattributes per-item latency, so
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gooddata-eval
|
|
3
|
-
Version: 1.78.
|
|
3
|
+
Version: 1.78.1.dev2
|
|
4
4
|
Summary: Evaluate the GoodData AI agent against your own questions and models.
|
|
5
5
|
Project-URL: Source, https://github.com/gooddata/gooddata-python-sdk
|
|
6
6
|
Author-email: GoodData <support@gooddata.com>
|
|
@@ -17,7 +17,7 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
17
17
|
Classifier: Topic :: Software Development
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
|
-
Requires-Dist: gooddata-sdk~=1.78.
|
|
20
|
+
Requires-Dist: gooddata-sdk~=1.78.1.dev2
|
|
21
21
|
Requires-Dist: httpx<1.0,>=0.27
|
|
22
22
|
Requires-Dist: jsonpatch<2.0,>=1.33
|
|
23
23
|
Requires-Dist: orjson<4.0.0,>=3.9.15
|
|
@@ -161,8 +161,8 @@ gd-eval run \
|
|
|
161
161
|
| `--runs K` | `2` | Independent runs per item. |
|
|
162
162
|
| `--gate` | `any` | Which verdict decides an item: `any` = pass@K (a run passing is enough), `power` = pass^K (every run must pass, so the verdict measures stability). Identical at `--runs 1`. Kinds that repeat K runs only — `power` is refused when the dataset also has non-agentic items or `agentic_conversation`, which are always decided on pass@K. |
|
|
163
163
|
| `--concurrency K` | `1` | Number of items evaluated concurrently. `1` = sequential (default). Increase to load-test the agent under simultaneous requests — see *Concurrency and workspace safety* below. |
|
|
164
|
-
| `--judge-model MODEL` | `gpt-4o` | Model used for LLM-as-judge scoring — `agentic_general_question`, `agentic_guardrail`, `general_question`, `guardrail` and `
|
|
165
|
-
| `--reasoning-effort LEVEL` | server default | `LOW`, `MEDIUM` or `HIGH`, sent as `options.reasoningEffort` on every chat message. Requires the `enableGenAiReasoningEffort` feature flag on the target organization — without it the server ignores the value. Applies to chat items only; `dashboard_summary` items go through
|
|
164
|
+
| `--judge-model MODEL` | `gpt-4o` | Model used for LLM-as-judge scoring — `agentic_general_question`, `agentic_guardrail`, `general_question`, `guardrail`, `dashboard_summary` and `document_takeaway`. Also settable via `GD_EVAL_JUDGE_MODEL`. Two things to weigh before changing it: the gpt-5 family rejects `temperature=0`, so verdicts stop being reproducible (the run warns when this happens); and choosing the same model the agent runs means the judge grades its own family's output. |
|
|
165
|
+
| `--reasoning-effort LEVEL` | server default | `LOW`, `MEDIUM` or `HIGH`, sent as `options.reasoningEffort` on every chat message. Requires the `enableGenAiReasoningEffort` feature flag on the target organization — without it the server ignores the value. Applies to chat items only; `dashboard_summary` and `document_takeaway` items go through their own endpoints, which have no such option. |
|
|
166
166
|
|
|
167
167
|
**Concurrency and workspace safety.** Agentic kinds that create workspace objects
|
|
168
168
|
(`agentic_metric_skill`, `agentic_alert_skill`, `agentic_conversation`, `agentic_kda_skill`) always run one at a
|
|
@@ -736,7 +736,64 @@ A dataset is a folder of `.json` files, one per question:
|
|
|
736
736
|
```
|
|
737
737
|
|
|
738
738
|
Supported `test_kind` values: `visualization`, `metric_skill`, `alert_skill`,
|
|
739
|
-
`search_tool`, `general_question`, `guardrail`, `dashboard_summary`.
|
|
739
|
+
`search_tool`, `general_question`, `guardrail`, `dashboard_summary`, `document_takeaway`.
|
|
740
|
+
|
|
741
|
+
### `visualization` items
|
|
742
|
+
|
|
743
|
+
The `expected_output` lists the candidate charts in the AAC format, and the item passes when
|
|
744
|
+
the agent's chart matches one of them. Keys beside the candidates add item-level checks:
|
|
745
|
+
|
|
746
|
+
```json
|
|
747
|
+
{
|
|
748
|
+
"id": "sankey-001",
|
|
749
|
+
"dataset_name": "agent_visualization_extended_charts",
|
|
750
|
+
"test_kind": "visualization",
|
|
751
|
+
"question": "Show how net sales flow from product category to customer country.",
|
|
752
|
+
"expected_output": {
|
|
753
|
+
"visualization": {
|
|
754
|
+
"type": "sankey_chart",
|
|
755
|
+
"query": {"fields": {"m": {"using": "metric/net_sales"}, "c": {"using": "label/product_category"},
|
|
756
|
+
"d": {"using": "label/customer_country"}}},
|
|
757
|
+
"metrics": ["m"],
|
|
758
|
+
"from": "c",
|
|
759
|
+
"to": "d",
|
|
760
|
+
"buckets_strict": true
|
|
761
|
+
},
|
|
762
|
+
"requires_visualization_part": true
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
Item-level keys, all optional except the candidates:
|
|
768
|
+
|
|
769
|
+
- `visualization` — one candidate chart, or a list of them. `expected_outputs`
|
|
770
|
+
(`[{"visualization": {...}}, ...]`) is the same list in the agentic fixture form.
|
|
771
|
+
- `requires_execution` — the agent must also run the chart it built.
|
|
772
|
+
- `expects_visualization: false` — the agent must answer without a chart, for example by asking
|
|
773
|
+
for a part the request leaves out. The item then lists no candidates.
|
|
774
|
+
- `visualization_optional: true` — either an answer without a chart, or a chart that matches a
|
|
775
|
+
candidate, is right.
|
|
776
|
+
- `answer_must_include` / `answer_must_not_match` — regular expressions, matched without regard
|
|
777
|
+
to case against the final answer. Every `answer_must_include` pattern must match, and no
|
|
778
|
+
`answer_must_not_match` pattern may.
|
|
779
|
+
- `format_intents` — per metric uri, how the metric's numbers must read, for example
|
|
780
|
+
`{"metric/net_sales": {"divide_by": 1000000, "decimals": 1, "suffix": "M"}}`.
|
|
781
|
+
- `requires_visualization_part` — the chart must come back as a part of the answer, not only as
|
|
782
|
+
the arguments of the chart tool call.
|
|
783
|
+
|
|
784
|
+
Switches on a candidate chart:
|
|
785
|
+
|
|
786
|
+
- `buckets_strict` — compare bucket by bucket, not only the union of the buckets, so swapped
|
|
787
|
+
heatmap rows and columns or a reversed sankey fail.
|
|
788
|
+
- `metrics_ordered` — compare the metrics by position, for bullet roles and bubble axes.
|
|
789
|
+
|
|
790
|
+
Any other key, at item level or on a candidate chart, makes the item error before any request
|
|
791
|
+
is sent, because a misspelled key would otherwise be dropped and its check would never run:
|
|
792
|
+
|
|
793
|
+
```
|
|
794
|
+
ValueError: the expectation states ['answer_must_inlcude'], which is not one of ['answer_must_include', ...]
|
|
795
|
+
ValueError: a candidate chart states ['bucket_strict'], which the chart model does not read
|
|
796
|
+
```
|
|
740
797
|
|
|
741
798
|
### `dashboard_summary` items
|
|
742
799
|
|
|
@@ -775,6 +832,64 @@ The `expected_output` rubric:
|
|
|
775
832
|
Each criterion is scored independently by the LLM judge, so `quality_score`
|
|
776
833
|
is the fraction of satisfied criteria.
|
|
777
834
|
|
|
835
|
+
### `document_takeaway` items
|
|
836
|
+
|
|
837
|
+
Takeaway items call the AI Publisher takeaway endpoint
|
|
838
|
+
(`POST /api/v1/ai/workspaces/{ws}/takeaway`) with a `takeaway_input` block, authored in
|
|
839
|
+
snake_case and sent as the endpoint's camelCase body. In a Langfuse dataset the block goes
|
|
840
|
+
in the item's `metadata` (or its `input` object), and `input` can stay a plain case label;
|
|
841
|
+
an item carrying `takeaway_input` is a `document_takeaway` item unless it declares another
|
|
842
|
+
`test_kind`. The endpoint needs the `enableGenAiReportTakeaways` feature flag.
|
|
843
|
+
|
|
844
|
+
```json
|
|
845
|
+
{
|
|
846
|
+
"id": "takeaway-001",
|
|
847
|
+
"dataset_name": "agent_document_takeaway",
|
|
848
|
+
"test_kind": "document_takeaway",
|
|
849
|
+
"question": "P01 default format, Sales page, no instructions",
|
|
850
|
+
"takeaway_input": {
|
|
851
|
+
"visualizations": ["<visualization id>", {"visualization_id": "<id>", "filters": []}],
|
|
852
|
+
"filter_context": [],
|
|
853
|
+
"instructions": {"document": null, "slot": null, "format_hint": null},
|
|
854
|
+
"max_characters": 300
|
|
855
|
+
},
|
|
856
|
+
"expected_output": {
|
|
857
|
+
"no_data": false,
|
|
858
|
+
"format": {"no_lists": false},
|
|
859
|
+
"must_include": ["States one finding that connects at least two of the charts"],
|
|
860
|
+
"must_not_include": [],
|
|
861
|
+
"rubric": []
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
`takeaway_input` requires `visualizations`; unknown keys are rejected. Every
|
|
867
|
+
`expected_output` key is optional:
|
|
868
|
+
|
|
869
|
+
- `no_data` — whether the endpoint must answer `noData`; when either side says no data,
|
|
870
|
+
nothing else is graded.
|
|
871
|
+
- `format` — overrides of the deterministic checks, which gate pass/fail: `bold_headline`,
|
|
872
|
+
`max_headline_words` (10), `max_sentences` (2), `max_words` (50), `single_paragraph`,
|
|
873
|
+
`no_lists`, `no_headings`. `false` or `null` switches a check off; unknown keys are
|
|
874
|
+
rejected. `length` (`max_characters`), `no_ids` and `no_meta` always apply.
|
|
875
|
+
- `default_rules` — `false` drops the default judge criteria (one finding; no executive
|
|
876
|
+
summary, chart walk-through, background opener, figures, dates, causes the data cannot
|
|
877
|
+
show, chart description).
|
|
878
|
+
- `must_include`, `must_not_include`, `rubric` — the item's own judge criteria, graded like
|
|
879
|
+
`dashboard_summary`.
|
|
880
|
+
|
|
881
|
+
`word_count` and `headline_word_count` are reported in the item detail. The default judge
|
|
882
|
+
criteria are calibrated with `make calibrate-takeaway` (needs `OPENAI_API_KEY`); re-run it
|
|
883
|
+
after changing their wording.
|
|
884
|
+
|
|
885
|
+
The client retries 429/5xx answers and connection failures. A request error that survives
|
|
886
|
+
the retries is handled differently by the two entry points: `gd-eval run` aborts the item
|
|
887
|
+
with that error, whatever `--runs` is, while the CI entry point
|
|
888
|
+
(`evaluate_document_takeaway`) records it as an errored run, keeps the item's other runs,
|
|
889
|
+
and raises only when no run completed. A read timeout is not retried, and the CI entry
|
|
890
|
+
point records it as an errored run too. Any other 4xx and an unsupported URL scheme are
|
|
891
|
+
never retried and fail the item in both.
|
|
892
|
+
|
|
778
893
|
### Items asked in a dashboard context (`user_context`)
|
|
779
894
|
|
|
780
895
|
An item can be asked the way a user asks from an open dashboard or an attached widget.
|
|
@@ -782,7 +897,8 @@ Its `user_context` is sent verbatim as `userContext` on every chat message of th
|
|
|
782
897
|
follow-up and clarification messages included — the server treats a message without one
|
|
783
898
|
as a cleared context. Requires the `enableAiContextSetup` feature flag on the target
|
|
784
899
|
organization — without it the server ignores the dashboard view. Applies to chat items
|
|
785
|
-
only; `dashboard_summary` items carry their scope in
|
|
900
|
+
only; `dashboard_summary` and `document_takeaway` items carry their scope in
|
|
901
|
+
`summary_input` / `takeaway_input` instead. No
|
|
786
902
|
`userContext` key is sent at all when neither the item nor, for `agentic_conversation`,
|
|
787
903
|
the current turn sets one.
|
|
788
904
|
|
|
@@ -835,6 +951,7 @@ from that turn on until another turn changes it, as the attached context does in
|
|
|
835
951
|
| `general_question` | Text answer judged by LLM | `[llm-judge]` |
|
|
836
952
|
| `guardrail` | Refusal/redirect (visualization response auto-fails) | `[llm-judge]` |
|
|
837
953
|
| `dashboard_summary` | Dashboard summary (via `/summary` endpoint) scored against a rubric by LLM | `[llm-judge]` |
|
|
954
|
+
| `document_takeaway` | One short takeaway for a page (via `/takeaway` endpoint): deterministic format checks plus a rubric scored by LLM | `[llm-judge]` |
|
|
838
955
|
|
|
839
956
|
## Optional extras
|
|
840
957
|
|
|
@@ -132,8 +132,8 @@ gd-eval run \
|
|
|
132
132
|
| `--runs K` | `2` | Independent runs per item. |
|
|
133
133
|
| `--gate` | `any` | Which verdict decides an item: `any` = pass@K (a run passing is enough), `power` = pass^K (every run must pass, so the verdict measures stability). Identical at `--runs 1`. Kinds that repeat K runs only — `power` is refused when the dataset also has non-agentic items or `agentic_conversation`, which are always decided on pass@K. |
|
|
134
134
|
| `--concurrency K` | `1` | Number of items evaluated concurrently. `1` = sequential (default). Increase to load-test the agent under simultaneous requests — see *Concurrency and workspace safety* below. |
|
|
135
|
-
| `--judge-model MODEL` | `gpt-4o` | Model used for LLM-as-judge scoring — `agentic_general_question`, `agentic_guardrail`, `general_question`, `guardrail` and `
|
|
136
|
-
| `--reasoning-effort LEVEL` | server default | `LOW`, `MEDIUM` or `HIGH`, sent as `options.reasoningEffort` on every chat message. Requires the `enableGenAiReasoningEffort` feature flag on the target organization — without it the server ignores the value. Applies to chat items only; `dashboard_summary` items go through
|
|
135
|
+
| `--judge-model MODEL` | `gpt-4o` | Model used for LLM-as-judge scoring — `agentic_general_question`, `agentic_guardrail`, `general_question`, `guardrail`, `dashboard_summary` and `document_takeaway`. Also settable via `GD_EVAL_JUDGE_MODEL`. Two things to weigh before changing it: the gpt-5 family rejects `temperature=0`, so verdicts stop being reproducible (the run warns when this happens); and choosing the same model the agent runs means the judge grades its own family's output. |
|
|
136
|
+
| `--reasoning-effort LEVEL` | server default | `LOW`, `MEDIUM` or `HIGH`, sent as `options.reasoningEffort` on every chat message. Requires the `enableGenAiReasoningEffort` feature flag on the target organization — without it the server ignores the value. Applies to chat items only; `dashboard_summary` and `document_takeaway` items go through their own endpoints, which have no such option. |
|
|
137
137
|
|
|
138
138
|
**Concurrency and workspace safety.** Agentic kinds that create workspace objects
|
|
139
139
|
(`agentic_metric_skill`, `agentic_alert_skill`, `agentic_conversation`, `agentic_kda_skill`) always run one at a
|
|
@@ -707,7 +707,64 @@ A dataset is a folder of `.json` files, one per question:
|
|
|
707
707
|
```
|
|
708
708
|
|
|
709
709
|
Supported `test_kind` values: `visualization`, `metric_skill`, `alert_skill`,
|
|
710
|
-
`search_tool`, `general_question`, `guardrail`, `dashboard_summary`.
|
|
710
|
+
`search_tool`, `general_question`, `guardrail`, `dashboard_summary`, `document_takeaway`.
|
|
711
|
+
|
|
712
|
+
### `visualization` items
|
|
713
|
+
|
|
714
|
+
The `expected_output` lists the candidate charts in the AAC format, and the item passes when
|
|
715
|
+
the agent's chart matches one of them. Keys beside the candidates add item-level checks:
|
|
716
|
+
|
|
717
|
+
```json
|
|
718
|
+
{
|
|
719
|
+
"id": "sankey-001",
|
|
720
|
+
"dataset_name": "agent_visualization_extended_charts",
|
|
721
|
+
"test_kind": "visualization",
|
|
722
|
+
"question": "Show how net sales flow from product category to customer country.",
|
|
723
|
+
"expected_output": {
|
|
724
|
+
"visualization": {
|
|
725
|
+
"type": "sankey_chart",
|
|
726
|
+
"query": {"fields": {"m": {"using": "metric/net_sales"}, "c": {"using": "label/product_category"},
|
|
727
|
+
"d": {"using": "label/customer_country"}}},
|
|
728
|
+
"metrics": ["m"],
|
|
729
|
+
"from": "c",
|
|
730
|
+
"to": "d",
|
|
731
|
+
"buckets_strict": true
|
|
732
|
+
},
|
|
733
|
+
"requires_visualization_part": true
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
Item-level keys, all optional except the candidates:
|
|
739
|
+
|
|
740
|
+
- `visualization` — one candidate chart, or a list of them. `expected_outputs`
|
|
741
|
+
(`[{"visualization": {...}}, ...]`) is the same list in the agentic fixture form.
|
|
742
|
+
- `requires_execution` — the agent must also run the chart it built.
|
|
743
|
+
- `expects_visualization: false` — the agent must answer without a chart, for example by asking
|
|
744
|
+
for a part the request leaves out. The item then lists no candidates.
|
|
745
|
+
- `visualization_optional: true` — either an answer without a chart, or a chart that matches a
|
|
746
|
+
candidate, is right.
|
|
747
|
+
- `answer_must_include` / `answer_must_not_match` — regular expressions, matched without regard
|
|
748
|
+
to case against the final answer. Every `answer_must_include` pattern must match, and no
|
|
749
|
+
`answer_must_not_match` pattern may.
|
|
750
|
+
- `format_intents` — per metric uri, how the metric's numbers must read, for example
|
|
751
|
+
`{"metric/net_sales": {"divide_by": 1000000, "decimals": 1, "suffix": "M"}}`.
|
|
752
|
+
- `requires_visualization_part` — the chart must come back as a part of the answer, not only as
|
|
753
|
+
the arguments of the chart tool call.
|
|
754
|
+
|
|
755
|
+
Switches on a candidate chart:
|
|
756
|
+
|
|
757
|
+
- `buckets_strict` — compare bucket by bucket, not only the union of the buckets, so swapped
|
|
758
|
+
heatmap rows and columns or a reversed sankey fail.
|
|
759
|
+
- `metrics_ordered` — compare the metrics by position, for bullet roles and bubble axes.
|
|
760
|
+
|
|
761
|
+
Any other key, at item level or on a candidate chart, makes the item error before any request
|
|
762
|
+
is sent, because a misspelled key would otherwise be dropped and its check would never run:
|
|
763
|
+
|
|
764
|
+
```
|
|
765
|
+
ValueError: the expectation states ['answer_must_inlcude'], which is not one of ['answer_must_include', ...]
|
|
766
|
+
ValueError: a candidate chart states ['bucket_strict'], which the chart model does not read
|
|
767
|
+
```
|
|
711
768
|
|
|
712
769
|
### `dashboard_summary` items
|
|
713
770
|
|
|
@@ -746,6 +803,64 @@ The `expected_output` rubric:
|
|
|
746
803
|
Each criterion is scored independently by the LLM judge, so `quality_score`
|
|
747
804
|
is the fraction of satisfied criteria.
|
|
748
805
|
|
|
806
|
+
### `document_takeaway` items
|
|
807
|
+
|
|
808
|
+
Takeaway items call the AI Publisher takeaway endpoint
|
|
809
|
+
(`POST /api/v1/ai/workspaces/{ws}/takeaway`) with a `takeaway_input` block, authored in
|
|
810
|
+
snake_case and sent as the endpoint's camelCase body. In a Langfuse dataset the block goes
|
|
811
|
+
in the item's `metadata` (or its `input` object), and `input` can stay a plain case label;
|
|
812
|
+
an item carrying `takeaway_input` is a `document_takeaway` item unless it declares another
|
|
813
|
+
`test_kind`. The endpoint needs the `enableGenAiReportTakeaways` feature flag.
|
|
814
|
+
|
|
815
|
+
```json
|
|
816
|
+
{
|
|
817
|
+
"id": "takeaway-001",
|
|
818
|
+
"dataset_name": "agent_document_takeaway",
|
|
819
|
+
"test_kind": "document_takeaway",
|
|
820
|
+
"question": "P01 default format, Sales page, no instructions",
|
|
821
|
+
"takeaway_input": {
|
|
822
|
+
"visualizations": ["<visualization id>", {"visualization_id": "<id>", "filters": []}],
|
|
823
|
+
"filter_context": [],
|
|
824
|
+
"instructions": {"document": null, "slot": null, "format_hint": null},
|
|
825
|
+
"max_characters": 300
|
|
826
|
+
},
|
|
827
|
+
"expected_output": {
|
|
828
|
+
"no_data": false,
|
|
829
|
+
"format": {"no_lists": false},
|
|
830
|
+
"must_include": ["States one finding that connects at least two of the charts"],
|
|
831
|
+
"must_not_include": [],
|
|
832
|
+
"rubric": []
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
`takeaway_input` requires `visualizations`; unknown keys are rejected. Every
|
|
838
|
+
`expected_output` key is optional:
|
|
839
|
+
|
|
840
|
+
- `no_data` — whether the endpoint must answer `noData`; when either side says no data,
|
|
841
|
+
nothing else is graded.
|
|
842
|
+
- `format` — overrides of the deterministic checks, which gate pass/fail: `bold_headline`,
|
|
843
|
+
`max_headline_words` (10), `max_sentences` (2), `max_words` (50), `single_paragraph`,
|
|
844
|
+
`no_lists`, `no_headings`. `false` or `null` switches a check off; unknown keys are
|
|
845
|
+
rejected. `length` (`max_characters`), `no_ids` and `no_meta` always apply.
|
|
846
|
+
- `default_rules` — `false` drops the default judge criteria (one finding; no executive
|
|
847
|
+
summary, chart walk-through, background opener, figures, dates, causes the data cannot
|
|
848
|
+
show, chart description).
|
|
849
|
+
- `must_include`, `must_not_include`, `rubric` — the item's own judge criteria, graded like
|
|
850
|
+
`dashboard_summary`.
|
|
851
|
+
|
|
852
|
+
`word_count` and `headline_word_count` are reported in the item detail. The default judge
|
|
853
|
+
criteria are calibrated with `make calibrate-takeaway` (needs `OPENAI_API_KEY`); re-run it
|
|
854
|
+
after changing their wording.
|
|
855
|
+
|
|
856
|
+
The client retries 429/5xx answers and connection failures. A request error that survives
|
|
857
|
+
the retries is handled differently by the two entry points: `gd-eval run` aborts the item
|
|
858
|
+
with that error, whatever `--runs` is, while the CI entry point
|
|
859
|
+
(`evaluate_document_takeaway`) records it as an errored run, keeps the item's other runs,
|
|
860
|
+
and raises only when no run completed. A read timeout is not retried, and the CI entry
|
|
861
|
+
point records it as an errored run too. Any other 4xx and an unsupported URL scheme are
|
|
862
|
+
never retried and fail the item in both.
|
|
863
|
+
|
|
749
864
|
### Items asked in a dashboard context (`user_context`)
|
|
750
865
|
|
|
751
866
|
An item can be asked the way a user asks from an open dashboard or an attached widget.
|
|
@@ -753,7 +868,8 @@ Its `user_context` is sent verbatim as `userContext` on every chat message of th
|
|
|
753
868
|
follow-up and clarification messages included — the server treats a message without one
|
|
754
869
|
as a cleared context. Requires the `enableAiContextSetup` feature flag on the target
|
|
755
870
|
organization — without it the server ignores the dashboard view. Applies to chat items
|
|
756
|
-
only; `dashboard_summary` items carry their scope in
|
|
871
|
+
only; `dashboard_summary` and `document_takeaway` items carry their scope in
|
|
872
|
+
`summary_input` / `takeaway_input` instead. No
|
|
757
873
|
`userContext` key is sent at all when neither the item nor, for `agentic_conversation`,
|
|
758
874
|
the current turn sets one.
|
|
759
875
|
|
|
@@ -806,6 +922,7 @@ from that turn on until another turn changes it, as the attached context does in
|
|
|
806
922
|
| `general_question` | Text answer judged by LLM | `[llm-judge]` |
|
|
807
923
|
| `guardrail` | Refusal/redirect (visualization response auto-fails) | `[llm-judge]` |
|
|
808
924
|
| `dashboard_summary` | Dashboard summary (via `/summary` endpoint) scored against a rubric by LLM | `[llm-judge]` |
|
|
925
|
+
| `document_takeaway` | One short takeaway for a page (via `/takeaway` endpoint): deterministic format checks plus a rubric scored by LLM | `[llm-judge]` |
|
|
809
926
|
|
|
810
927
|
## Optional extras
|
|
811
928
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# (C) 2026 GoodData Corporation
|
|
2
2
|
[project]
|
|
3
3
|
name = "gooddata-eval"
|
|
4
|
-
version = "1.78.
|
|
4
|
+
version = "1.78.1.dev2"
|
|
5
5
|
description = "Evaluate the GoodData AI agent against your own questions and models."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "MIT"
|
|
@@ -11,7 +11,7 @@ authors = [
|
|
|
11
11
|
keywords = ["gooddata", "ai", "evaluation", "llm", "analytics", "cli"]
|
|
12
12
|
requires-python = ">=3.10"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"gooddata-sdk~=1.78.
|
|
14
|
+
"gooddata-sdk~=1.78.1.dev2",
|
|
15
15
|
"httpx>=0.27,<1.0",
|
|
16
16
|
"jsonpatch>=1.33,<2.0",
|
|
17
17
|
"orjson>=3.9.15,<4.0.0",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# (C) 2026 GoodData Corporation
|
|
2
|
+
"""Does the real judge put each calibration text on the right side of every default criterion?
|
|
3
|
+
|
|
4
|
+
tests/test_takeaway_evaluator.py pins the wiring and the criterion wording; it cannot pin
|
|
5
|
+
the verdict, because the verdict is an LLM's. This does, against the texts in
|
|
6
|
+
tests/fixtures/takeaway_calibration.json:
|
|
7
|
+
|
|
8
|
+
- a "pass" text must satisfy every default judge criterion on every run;
|
|
9
|
+
- a "fail" text must violate each criterion it names on every run.
|
|
10
|
+
|
|
11
|
+
Run it after changing any DEFAULT_MUST_INCLUDE / DEFAULT_MUST_NOT_INCLUDE wording:
|
|
12
|
+
|
|
13
|
+
OPENAI_API_KEY=... make calibrate-takeaway
|
|
14
|
+
|
|
15
|
+
It is a script rather than a test because CI has no OPENAI_API_KEY. Exits 1 on any mismatch.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from gooddata_eval.core.evaluators.takeaway import (
|
|
24
|
+
DEFAULT_MUST_INCLUDE,
|
|
25
|
+
DEFAULT_MUST_NOT_INCLUDE,
|
|
26
|
+
DocumentTakeawayEvaluator,
|
|
27
|
+
)
|
|
28
|
+
from gooddata_eval.core.models import ChatResult, DatasetItem, TakeawayInput
|
|
29
|
+
|
|
30
|
+
RUNS = 3
|
|
31
|
+
FIXTURE = Path(__file__).resolve().parent.parent / "tests" / "fixtures" / "takeaway_calibration.json"
|
|
32
|
+
|
|
33
|
+
CRITERIA = {f"default_include_{k}": k for k in DEFAULT_MUST_INCLUDE} | {
|
|
34
|
+
f"default_exclude_{k}": k for k in DEFAULT_MUST_NOT_INCLUDE
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main() -> int:
|
|
39
|
+
if not os.environ.get("OPENAI_API_KEY"):
|
|
40
|
+
raise SystemExit("OPENAI_API_KEY required")
|
|
41
|
+
fixture = json.loads(FIXTURE.read_text())
|
|
42
|
+
evaluator = DocumentTakeawayEvaluator()
|
|
43
|
+
print(f"judge model: {evaluator._positive_judge.model}, runs: {RUNS}")
|
|
44
|
+
mismatches = 0
|
|
45
|
+
for case in fixture["texts"]:
|
|
46
|
+
item = DatasetItem(
|
|
47
|
+
id=case["label"],
|
|
48
|
+
dataset_name="takeaway_calibration",
|
|
49
|
+
test_kind="document_takeaway",
|
|
50
|
+
question=fixture["question"],
|
|
51
|
+
expected_output={},
|
|
52
|
+
takeaway_input=TakeawayInput(visualizations=["calibration"]),
|
|
53
|
+
)
|
|
54
|
+
chat = ChatResult(text_response=case["text"], raw_response={"takeaway": case["text"], "noData": False})
|
|
55
|
+
satisfied = dict.fromkeys(CRITERIA.values(), 0)
|
|
56
|
+
for _ in range(RUNS):
|
|
57
|
+
detail = evaluator.evaluate(item, chat).detail
|
|
58
|
+
for key, name in CRITERIA.items():
|
|
59
|
+
satisfied[name] += detail.get(key) is True
|
|
60
|
+
want_pass = case["want"] == "pass"
|
|
61
|
+
must = dict.fromkeys(satisfied, RUNS) if want_pass else dict.fromkeys(case["criteria"], 0)
|
|
62
|
+
print(f"\n{case['label']} (want {case['want']})")
|
|
63
|
+
for name, count in satisfied.items():
|
|
64
|
+
flag = ""
|
|
65
|
+
if name in must:
|
|
66
|
+
ok = count == must[name]
|
|
67
|
+
mismatches += not ok
|
|
68
|
+
flag = "OK" if ok else "MISMATCH"
|
|
69
|
+
print(f" {name:20} satisfied {count}/{RUNS} {flag}")
|
|
70
|
+
print(f"\n{'all calibration texts on the right side' if not mismatches else f'{mismatches} mismatch(es)'}")
|
|
71
|
+
return 1 if mismatches else 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
sys.exit(main())
|
|
@@ -26,7 +26,7 @@ from gooddata_eval.core.agentic.visualization import evaluate_agentic_visualizat
|
|
|
26
26
|
from gooddata_eval.core.agentic.what_if import evaluate_agentic_what_if
|
|
27
27
|
from gooddata_eval.core.config import ReasoningEffort
|
|
28
28
|
from gooddata_eval.core.evaluators._llm_judge import JudgeResponseError
|
|
29
|
-
from gooddata_eval.core.evaluators.visualization import
|
|
29
|
+
from gooddata_eval.core.evaluators.visualization import expectation_options
|
|
30
30
|
from gooddata_eval.core.models import AgenticEvalOutcome, CreatedVisualization, DatasetItem
|
|
31
31
|
from gooddata_eval.core.runner import EvalReport, ItemReport
|
|
32
32
|
from gooddata_eval.core.timing import TraceRecorder
|
|
@@ -119,6 +119,8 @@ def _parse_visualization_expected(expected_output: Any) -> list[CreatedVisualiza
|
|
|
119
119
|
{"visualization": {...}} or {"visualization": [{...}]} <- single/multi candidate
|
|
120
120
|
[{"visualization": {...}}, ...] <- bare list
|
|
121
121
|
"""
|
|
122
|
+
if isinstance(expected_output, dict) and expected_output.get("expects_visualization") is False:
|
|
123
|
+
return []
|
|
122
124
|
if isinstance(expected_output, dict):
|
|
123
125
|
raw_list = expected_output.get("expected_outputs")
|
|
124
126
|
if raw_list is not None:
|
|
@@ -187,7 +189,7 @@ def _dispatch_agentic(
|
|
|
187
189
|
workspace_id=workspace_id,
|
|
188
190
|
question=item.question,
|
|
189
191
|
expected_outputs=_parse_visualization_expected(eo),
|
|
190
|
-
|
|
192
|
+
options=expectation_options(eo),
|
|
191
193
|
k=k,
|
|
192
194
|
gate=gate,
|
|
193
195
|
agent_id=agent_id,
|
|
@@ -7,7 +7,7 @@ import sys
|
|
|
7
7
|
import threading
|
|
8
8
|
from datetime import datetime, timezone
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import get_args
|
|
10
|
+
from typing import Any, get_args
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
13
|
from gooddata_api_client.exceptions import ApiException, ApiTypeError
|
|
@@ -36,32 +36,31 @@ from gooddata_eval.core.reporting.html_report import load_report_files, write_ht
|
|
|
36
36
|
from gooddata_eval.core.reporting.json_report import build_multi_model_report, write_multi_model_report
|
|
37
37
|
from gooddata_eval.core.runner import ItemReport, run_items
|
|
38
38
|
from gooddata_eval.core.summary.http_client import SummaryClient
|
|
39
|
+
from gooddata_eval.core.takeaway.http_client import TakeawayClient
|
|
39
40
|
from gooddata_eval.core.timing import TIMERS_ENV_VAR
|
|
40
41
|
from gooddata_eval.core.workspace import ModelResolutionError, WorkspaceModelController
|
|
41
42
|
|
|
42
43
|
_EXIT_OK = 0
|
|
43
44
|
_EXIT_OPERATIONAL_ERROR = 2
|
|
44
|
-
_SUMMARY_TEST_KIND = "dashboard_summary"
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class _RoutingBackend:
|
|
48
48
|
"""Dispatch each item to the right backend by test_kind.
|
|
49
49
|
|
|
50
|
-
`
|
|
51
|
-
|
|
50
|
+
Kinds in `by_kind` go to their dedicated endpoint client (`dashboard_summary` to the
|
|
51
|
+
summary endpoint, `document_takeaway` to the takeaway endpoint); everything else uses
|
|
52
|
+
the conversational chat endpoint.
|
|
52
53
|
"""
|
|
53
54
|
|
|
54
|
-
def __init__(self, chat: ChatClient,
|
|
55
|
+
def __init__(self, chat: ChatClient, by_kind: dict[str, Any]):
|
|
55
56
|
self._chat = chat
|
|
56
|
-
self.
|
|
57
|
+
self._by_kind = by_kind
|
|
57
58
|
|
|
58
59
|
def ask(self, item: DatasetItem) -> ChatResult:
|
|
59
|
-
|
|
60
|
-
return self._summary.ask(item)
|
|
61
|
-
return self._chat.ask(item)
|
|
60
|
+
return self._by_kind.get(item.test_kind, self._chat).ask(item)
|
|
62
61
|
|
|
63
62
|
def close(self) -> None:
|
|
64
|
-
for backend in (self._chat, self.
|
|
63
|
+
for backend in (self._chat, *self._by_kind.values()):
|
|
65
64
|
if hasattr(backend, "close"):
|
|
66
65
|
backend.close()
|
|
67
66
|
|
|
@@ -614,7 +613,14 @@ def _run(config: RunConfig) -> int:
|
|
|
614
613
|
turn_timeout_s=config.turn_timeout_s,
|
|
615
614
|
item_timeout_s=config.item_timeout_s,
|
|
616
615
|
),
|
|
617
|
-
|
|
616
|
+
{
|
|
617
|
+
"dashboard_summary": SummaryClient(
|
|
618
|
+
host=config.host, token=config.token, workspace_id=config.workspace_id
|
|
619
|
+
),
|
|
620
|
+
"document_takeaway": TakeawayClient(
|
|
621
|
+
host=config.host, token=config.token, workspace_id=config.workspace_id
|
|
622
|
+
),
|
|
623
|
+
},
|
|
618
624
|
)
|
|
619
625
|
try:
|
|
620
626
|
single_report = run_items(
|