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.
Files changed (157) hide show
  1. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/AGENTS.md +14 -9
  2. gooddata_eval-1.78.1.dev2/Makefile +6 -0
  3. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/PKG-INFO +123 -6
  4. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/README.md +121 -4
  5. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/pyproject.toml +2 -2
  6. gooddata_eval-1.78.1.dev2/scripts/calibrate_takeaway_judge.py +75 -0
  7. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/cli/agentic_runner.py +4 -2
  8. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/cli/main.py +17 -11
  9. gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/_retry.py +71 -0
  10. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_conversation_context.py +59 -10
  11. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/dashboard_skill.py +80 -1
  12. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/visualization.py +132 -22
  13. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/chat/sse_client.py +17 -62
  14. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/from_insights.py +10 -6
  15. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/langfuse_source.py +29 -2
  16. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/__init__.py +5 -2
  17. gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/evaluators/_rubric.py +94 -0
  18. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/summary.py +12 -64
  19. gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/evaluators/takeaway.py +245 -0
  20. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/visualization.py +296 -6
  21. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/models.py +61 -4
  22. gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/number_format.py +179 -0
  23. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/scoring.py +148 -8
  24. gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/takeaway/evaluate.py +275 -0
  25. gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/takeaway/http_client.py +104 -0
  26. gooddata_eval-1.78.1.dev2/tests/__init__.py +1 -0
  27. gooddata_eval-1.78.1.dev2/tests/fixtures/takeaway_calibration.json +68 -0
  28. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_conversation.py +115 -0
  29. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_dashboard_skill.py +113 -0
  30. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_runner.py +47 -5
  31. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_visualization.py +272 -0
  32. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_cli.py +51 -0
  33. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_from_insights.py +24 -0
  34. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_source.py +96 -0
  35. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_models.py +50 -0
  36. gooddata_eval-1.78.1.dev2/tests/test_number_format.py +195 -0
  37. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_runner.py +1 -0
  38. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_sse_client.py +23 -0
  39. gooddata_eval-1.78.1.dev2/tests/test_takeaway_client.py +257 -0
  40. gooddata_eval-1.78.1.dev2/tests/test_takeaway_evaluate.py +349 -0
  41. gooddata_eval-1.78.1.dev2/tests/test_takeaway_evaluator.py +425 -0
  42. gooddata_eval-1.78.1.dev2/tests/test_visualization_evaluator.py +765 -0
  43. gooddata_eval-1.78.0/Makefile +0 -2
  44. gooddata_eval-1.78.0/tests/test_visualization_evaluator.py +0 -369
  45. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/.gitignore +0 -0
  46. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/CLAUDE.md +0 -0
  47. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/LICENSE.txt +0 -0
  48. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/scripts/verify_guardrail_refusal_criteria.py +0 -0
  49. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/__init__.py +0 -0
  50. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/_version.py +0 -0
  51. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/cli/__init__.py +0 -0
  52. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/__init__.py +0 -0
  53. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/_output.py +0 -0
  54. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/__init__.py +0 -0
  55. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_catalog.py +0 -0
  56. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_failed_runs.py +0 -0
  57. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_gate.py +0 -0
  58. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_langfuse.py +0 -0
  59. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_trace_linker.py +0 -0
  60. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/alert_skill.py +0 -0
  61. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/anomaly_detection.py +0 -0
  62. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/conversation.py +0 -0
  63. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/document_skill.py +0 -0
  64. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/general_question.py +0 -0
  65. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/guardrail.py +0 -0
  66. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/kda_skill.py +0 -0
  67. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/metric_skill.py +0 -0
  68. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/report_skill.py +0 -0
  69. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/search_tool.py +0 -0
  70. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/what_if.py +0 -0
  71. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/chat/__init__.py +0 -0
  72. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/chat/render.py +0 -0
  73. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/config.py +0 -0
  74. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/connection.py +0 -0
  75. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/__init__.py +0 -0
  76. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/local.py +0 -0
  77. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_deep_subset.py +0 -0
  78. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_guardrail_criteria.py +0 -0
  79. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_llm_judge.py +0 -0
  80. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_maql.py +0 -0
  81. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_text_utils.py +0 -0
  82. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/alert_skill.py +0 -0
  83. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/base.py +0 -0
  84. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/general_question.py +0 -0
  85. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/guardrail.py +0 -0
  86. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/metric_skill.py +0 -0
  87. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/search_tool.py +0 -0
  88. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/granularity.py +0 -0
  89. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/interaction.py +0 -0
  90. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/__init__.py +0 -0
  91. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/_env.py +0 -0
  92. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/client.py +0 -0
  93. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/experiment.py +0 -0
  94. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/item_scope.py +0 -0
  95. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/observations.py +0 -0
  96. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/otlp.py +0 -0
  97. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/sink.py +0 -0
  98. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/__init__.py +0 -0
  99. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/console.py +0 -0
  100. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/html_report.py +0 -0
  101. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/json_report.py +0 -0
  102. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/report_template.html +0 -0
  103. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/runner.py +0 -0
  104. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/summary/__init__.py +0 -0
  105. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/summary/http_client.py +0 -0
  106. {gooddata_eval-1.78.0/tests → gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/takeaway}/__init__.py +0 -0
  107. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/timing.py +0 -0
  108. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/workspace.py +0 -0
  109. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/_fake_langfuse.py +0 -0
  110. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/conftest.py +0 -0
  111. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/fixtures/sample_dataset/metric_skill_create.json +0 -0
  112. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/fixtures/sample_dataset/visualization_revenue.json +0 -0
  113. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/fixtures/sse_visualization_stream.txt +0 -0
  114. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_alert_skill.py +0 -0
  115. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_anomaly_detection.py +0 -0
  116. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_document_skill.py +0 -0
  117. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_gate.py +0 -0
  118. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_general_question.py +0 -0
  119. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_guardrail.py +0 -0
  120. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_join_trace.py +0 -0
  121. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_kda_skill.py +0 -0
  122. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_langfuse_trace.py +0 -0
  123. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_metric_skill.py +0 -0
  124. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_observe_experiment.py +0 -0
  125. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_run_context.py +0 -0
  126. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_search_tool.py +0 -0
  127. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_what_if.py +0 -0
  128. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_alert_skill_evaluator.py +0 -0
  129. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_chat_render.py +0 -0
  130. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_connection.py +0 -0
  131. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_deep_subset.py +0 -0
  132. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_fake_langfuse.py +0 -0
  133. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_guardrail_criteria.py +0 -0
  134. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_html_report.py +0 -0
  135. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_interaction.py +0 -0
  136. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_client.py +0 -0
  137. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_e2e_fake_server.py +0 -0
  138. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_env.py +0 -0
  139. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_experiment.py +0 -0
  140. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_item_scope.py +0 -0
  141. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_observations.py +0 -0
  142. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_otlp.py +0 -0
  143. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_sink.py +0 -0
  144. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_llm_judge.py +0 -0
  145. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_local_loader.py +0 -0
  146. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_maql_normalize.py +0 -0
  147. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_metric_skill_evaluator.py +0 -0
  148. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_reporting.py +0 -0
  149. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_scoring.py +0 -0
  150. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_search_tool_evaluator.py +0 -0
  151. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_summary_client.py +0 -0
  152. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_summary_evaluator.py +0 -0
  153. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_text_evaluators.py +0 -0
  154. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_timing.py +0 -0
  155. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_trace_linker.py +0 -0
  156. {gooddata_eval-1.78.0 → gooddata_eval-1.78.1.dev2}/tests/test_workspace.py +0 -0
  157. {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_*` | 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 |
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.** `core/chat/sse_client.py` retries
185
- 429/502/503/504 and `httpx.RemoteProtocolError` (a mid-stream disconnect) with exponential
186
- backoff, and treats a `METADATA_SYNC_IN_PROGRESS` payload as transient. The
187
- `RemoteProtocolError` case was added after it was confirmed live to contaminate a small
188
- percentage of visualization runs with a hard fail and no retry. Narrowing that predicate
189
- reintroduces the problem.
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
@@ -0,0 +1,6 @@
1
+ # (C) 2026 GoodData Corporation
2
+ include ../../project_common.mk
3
+
4
+ .PHONY: calibrate-takeaway
5
+ calibrate-takeaway:
6
+ uv run --package gooddata-eval --extra llm-judge python scripts/calibrate_takeaway_judge.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gooddata-eval
3
- Version: 1.78.0
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.0
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 `dashboard_summary`. 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` items go through the summary endpoint, which has no such option. |
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 `summary_input` instead. No
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 `dashboard_summary`. 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` items go through the summary endpoint, which has no such option. |
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 `summary_input` instead. No
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.0"
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.0",
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 requires_execution_of
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
- requires_execution=requires_execution_of(eo),
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
- `dashboard_summary` items go to the dedicated summary endpoint; everything
51
- else uses the conversational chat endpoint.
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, summary: SummaryClient):
55
+ def __init__(self, chat: ChatClient, by_kind: dict[str, Any]):
55
56
  self._chat = chat
56
- self._summary = summary
57
+ self._by_kind = by_kind
57
58
 
58
59
  def ask(self, item: DatasetItem) -> ChatResult:
59
- if item.test_kind == _SUMMARY_TEST_KIND:
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._summary):
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
- SummaryClient(host=config.host, token=config.token, workspace_id=config.workspace_id),
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(