gooddata-eval 1.76.1.dev3__tar.gz → 1.77.0__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 (143) hide show
  1. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/AGENTS.md +9 -1
  2. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/PKG-INFO +2 -2
  3. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/pyproject.toml +5 -2
  4. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/cli/agentic_runner.py +55 -6
  5. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/cli/main.py +22 -0
  6. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/__init__.py +22 -8
  7. gooddata_eval-1.77.0/src/gooddata_eval/core/agentic/_failed_runs.py +62 -0
  8. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/_langfuse.py +201 -15
  9. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/_trace_linker.py +72 -27
  10. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/alert_skill.py +71 -43
  11. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/anomaly_detection.py +40 -26
  12. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/conversation.py +28 -27
  13. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/dashboard_skill.py +618 -62
  14. gooddata_eval-1.76.1.dev3/src/gooddata_eval/core/agentic/report_skill.py → gooddata_eval-1.77.0/src/gooddata_eval/core/agentic/document_skill.py +372 -176
  15. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/general_question.py +105 -49
  16. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/guardrail.py +102 -45
  17. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/kda_skill.py +67 -39
  18. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/metric_skill.py +63 -37
  19. gooddata_eval-1.77.0/src/gooddata_eval/core/agentic/report_skill.py +50 -0
  20. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/search_tool.py +56 -30
  21. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/visualization.py +63 -34
  22. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/what_if.py +56 -28
  23. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/chat/sse_client.py +47 -5
  24. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/config.py +2 -0
  25. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/_llm_judge.py +12 -0
  26. gooddata_eval-1.77.0/src/gooddata_eval/core/langfuse/_env.py +78 -0
  27. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/langfuse/client.py +93 -20
  28. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/langfuse/experiment.py +23 -2
  29. gooddata_eval-1.77.0/src/gooddata_eval/core/langfuse/item_scope.py +162 -0
  30. gooddata_eval-1.77.0/src/gooddata_eval/core/langfuse/observations.py +194 -0
  31. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/langfuse/otlp.py +3 -3
  32. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/langfuse/sink.py +14 -10
  33. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/models.py +9 -0
  34. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/reporting/html_report.py +27 -1
  35. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/reporting/json_report.py +6 -0
  36. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/runner.py +40 -0
  37. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/conftest.py +3 -0
  38. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_anomaly_detection.py +1 -1
  39. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_dashboard_skill.py +779 -0
  40. gooddata_eval-1.77.0/tests/test_agentic_document_skill.py +1324 -0
  41. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_general_question.py +8 -0
  42. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_guardrail.py +118 -0
  43. gooddata_eval-1.77.0/tests/test_agentic_join_trace.py +481 -0
  44. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_observe_experiment.py +22 -0
  45. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_runner.py +211 -4
  46. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_what_if.py +50 -1
  47. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_chat_render.py +12 -10
  48. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_cli.py +90 -1
  49. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_html_report.py +69 -0
  50. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_langfuse_client.py +131 -5
  51. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_langfuse_e2e_fake_server.py +11 -7
  52. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_langfuse_env.py +75 -1
  53. gooddata_eval-1.77.0/tests/test_langfuse_item_scope.py +59 -0
  54. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_langfuse_observations.py +63 -3
  55. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_langfuse_otlp.py +2 -1
  56. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_langfuse_sink.py +28 -0
  57. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_reporting.py +46 -0
  58. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_runner.py +83 -0
  59. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_sse_client.py +139 -0
  60. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_trace_linker.py +1 -1
  61. gooddata_eval-1.76.1.dev3/src/gooddata_eval/core/langfuse/_env.py +0 -39
  62. gooddata_eval-1.76.1.dev3/src/gooddata_eval/core/langfuse/observations.py +0 -125
  63. gooddata_eval-1.76.1.dev3/tests/test_agentic_report_skill.py +0 -843
  64. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/.gitignore +0 -0
  65. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/CLAUDE.md +0 -0
  66. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/LICENSE.txt +0 -0
  67. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/Makefile +0 -0
  68. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/README.md +0 -0
  69. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/scripts/verify_guardrail_refusal_criteria.py +0 -0
  70. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/__init__.py +0 -0
  71. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/_version.py +0 -0
  72. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/cli/__init__.py +0 -0
  73. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/__init__.py +0 -0
  74. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/_output.py +0 -0
  75. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/_catalog.py +0 -0
  76. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/_conversation_context.py +0 -0
  77. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/agentic/_gate.py +0 -0
  78. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/chat/__init__.py +0 -0
  79. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/chat/render.py +0 -0
  80. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/connection.py +0 -0
  81. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/dataset/__init__.py +0 -0
  82. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/dataset/from_insights.py +0 -0
  83. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/dataset/langfuse_source.py +0 -0
  84. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/dataset/local.py +0 -0
  85. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/__init__.py +0 -0
  86. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/_deep_subset.py +0 -0
  87. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/_guardrail_criteria.py +0 -0
  88. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/_maql.py +0 -0
  89. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/_text_utils.py +0 -0
  90. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/alert_skill.py +0 -0
  91. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/base.py +0 -0
  92. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/general_question.py +0 -0
  93. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/guardrail.py +0 -0
  94. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/metric_skill.py +0 -0
  95. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/search_tool.py +0 -0
  96. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/summary.py +0 -0
  97. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/evaluators/visualization.py +0 -0
  98. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/granularity.py +0 -0
  99. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/langfuse/__init__.py +0 -0
  100. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/reporting/__init__.py +0 -0
  101. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/reporting/console.py +0 -0
  102. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/reporting/report_template.html +0 -0
  103. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/scoring.py +0 -0
  104. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/summary/__init__.py +0 -0
  105. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/summary/http_client.py +0 -0
  106. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/timing.py +0 -0
  107. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/src/gooddata_eval/core/workspace.py +0 -0
  108. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/__init__.py +0 -0
  109. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/_fake_langfuse.py +0 -0
  110. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/fixtures/sample_dataset/metric_skill_create.json +0 -0
  111. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/fixtures/sample_dataset/visualization_revenue.json +0 -0
  112. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/fixtures/sse_visualization_stream.txt +0 -0
  113. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_alert_skill.py +0 -0
  114. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_conversation.py +0 -0
  115. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_gate.py +0 -0
  116. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_kda_skill.py +0 -0
  117. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_langfuse_trace.py +0 -0
  118. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_metric_skill.py +0 -0
  119. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_run_context.py +0 -0
  120. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_search_tool.py +0 -0
  121. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_agentic_visualization.py +0 -0
  122. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_alert_skill_evaluator.py +0 -0
  123. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_connection.py +0 -0
  124. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_deep_subset.py +0 -0
  125. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_fake_langfuse.py +0 -0
  126. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_from_insights.py +0 -0
  127. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_guardrail_criteria.py +0 -0
  128. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_langfuse_experiment.py +0 -0
  129. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_langfuse_source.py +0 -0
  130. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_llm_judge.py +0 -0
  131. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_local_loader.py +0 -0
  132. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_maql_normalize.py +0 -0
  133. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_metric_skill_evaluator.py +0 -0
  134. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_models.py +0 -0
  135. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_scoring.py +0 -0
  136. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_search_tool_evaluator.py +0 -0
  137. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_summary_client.py +0 -0
  138. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_summary_evaluator.py +0 -0
  139. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_text_evaluators.py +0 -0
  140. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_timing.py +0 -0
  141. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_visualization_evaluator.py +0 -0
  142. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tests/test_workspace.py +0 -0
  143. {gooddata_eval-1.76.1.dev3 → gooddata_eval-1.77.0}/tox.ini +0 -0
@@ -29,7 +29,7 @@ experiment. The newest and most actively developed package in the repo.
29
29
  | `core/summary/` | HTTP client for the dedicated dashboard-summary endpoint — a single-shot chat backend, not reporting |
30
30
  | `core/dataset/` | dataset format, loading, and `from_insights.py` — dataset generation from a workspace's real insights |
31
31
  | `core/evaluators/` | single-shot evaluators and their registry |
32
- | `core/langfuse/` | the whole Langfuse v4 client: `_env` (base URL + credentials), `otlp` (OTLP/JSON encoding), `experiment` (root-span construction, score targets), `observations` (trace reads), `client` (httpx calls), `sink` (single-shot results as experiments) |
32
+ | `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) |
33
33
  | `core/reporting/` | console and JSON output rendering |
34
34
  | `core/scoring.py`, `core/runner.py` | scoring and orchestration |
35
35
  | `core/models.py` | `DatasetItem`, `ChatResult`, `ItemReport` and friends |
@@ -157,6 +157,7 @@ Run this before guessing a `--model` string.
157
157
  | `LANGFUSE_HOST`, `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` | `--langfuse`, `--langfuse-dataset` |
158
158
  | `GOODDATA_EVAL_CHAT_*` | SSE retry, backoff and timeout knobs |
159
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_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 |
160
161
  | `GD_EVAL_TIMERS` | same as `--timers` |
161
162
 
162
163
  A gitignored `.env` at the repo root is the normal place for these; load it with
@@ -190,6 +191,13 @@ ingestion has no pass/fail signal and inflates or misattributes per-item latency
190
191
  (`run_trace_link_inline` is the synchronous alternative). Do not "fix" a slow item by
191
192
  making trace scoring synchronous again.
192
193
 
194
+ **Turn `GOODDATA_EVAL_JOIN_GENAI_TRACE` off against a gen-ai that accepts caller baggage
195
+ but cannot join.** The trace and root ids are derived from the conversation id, and a conversation
196
+ counts as joined only when gen-ai reports that trace id on `response_started`. A gen-ai that
197
+ reads the baggage without that support tags its own traces with experiment ids under a root
198
+ gd-eval never exports. The same orphan is left when an item raises before its deferred
199
+ scoring runs: its turns sit in environment `sdk-experiment` without a root or scores.
200
+
193
201
  **A generated item's `expected_output` is copied, never invented — keep it that way.**
194
202
  `core/dataset/from_insights.py` converts each insight with the platform's own
195
203
  `declarative_visualization_to_aac()` (from `gooddata-code-convertors`, via `gooddata-sdk`),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gooddata-eval
3
- Version: 1.76.1.dev3
3
+ Version: 1.77.0
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.76.1.dev3
20
+ Requires-Dist: gooddata-sdk~=1.77.0
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
@@ -1,7 +1,7 @@
1
1
  # (C) 2026 GoodData Corporation
2
2
  [project]
3
3
  name = "gooddata-eval"
4
- version = "1.76.1.dev3"
4
+ version = "1.77.0"
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.76.1.dev3",
14
+ "gooddata-sdk~=1.77.0",
15
15
  "httpx>=0.27,<1.0",
16
16
  "jsonpatch>=1.33,<2.0",
17
17
  "orjson>=3.9.15,<4.0.0",
@@ -71,3 +71,6 @@ allowed-unresolved-imports = ["openai.**", "gooddata_api_client.**"]
71
71
  [build-system]
72
72
  requires = ["hatchling"]
73
73
  build-backend = "hatchling.build"
74
+
75
+ [tool.pytest]
76
+ strict = true
@@ -15,15 +15,16 @@ from gooddata_eval.core.agentic.alert_skill import evaluate_agentic_alert_skill
15
15
  from gooddata_eval.core.agentic.anomaly_detection import evaluate_agentic_anomaly_detection
16
16
  from gooddata_eval.core.agentic.conversation import ConversationFixture, evaluate_agentic_conversation
17
17
  from gooddata_eval.core.agentic.dashboard_skill import evaluate_agentic_dashboard_skill
18
+ from gooddata_eval.core.agentic.document_skill import evaluate_agentic_document_skill
18
19
  from gooddata_eval.core.agentic.general_question import evaluate_agentic_general_question
19
20
  from gooddata_eval.core.agentic.guardrail import evaluate_agentic_guardrail
20
21
  from gooddata_eval.core.agentic.kda_skill import evaluate_agentic_kda_skill
21
22
  from gooddata_eval.core.agentic.metric_skill import evaluate_agentic_metric_skill
22
- from gooddata_eval.core.agentic.report_skill import evaluate_agentic_report_skill
23
23
  from gooddata_eval.core.agentic.search_tool import evaluate_agentic_search_tool
24
24
  from gooddata_eval.core.agentic.visualization import evaluate_agentic_visualization
25
25
  from gooddata_eval.core.agentic.what_if import evaluate_agentic_what_if
26
26
  from gooddata_eval.core.config import ReasoningEffort
27
+ from gooddata_eval.core.evaluators._llm_judge import JudgeResponseError
27
28
  from gooddata_eval.core.evaluators.visualization import requires_execution_of
28
29
  from gooddata_eval.core.models import AgenticEvalOutcome, CreatedVisualization, DatasetItem
29
30
  from gooddata_eval.core.runner import EvalReport, ItemReport
@@ -37,6 +38,7 @@ class _LfKw(TypedDict, total=False):
37
38
  model_version_override: str | None
38
39
  reasoning_effort: ReasoningEffort | None
39
40
  submit_trace_link: SubmitTraceLink
41
+ run_metadata_extra: dict[str, Any] | None
40
42
 
41
43
 
42
44
  AGENTIC_TEST_KINDS = frozenset(
@@ -46,7 +48,7 @@ AGENTIC_TEST_KINDS = frozenset(
46
48
  "agentic_metric_skill",
47
49
  "agentic_alert_skill",
48
50
  "agentic_dashboard_skill",
49
- "agentic_report_skill",
51
+ "agentic_document_skill",
50
52
  "agentic_search",
51
53
  "agentic_general_question",
52
54
  "agentic_guardrail",
@@ -97,8 +99,8 @@ PARALLEL_SAFE_TEST_KINDS = frozenset(
97
99
  #
98
100
  # agentic_dashboard_skill is absent by default rather than by evidence: gen-ai holds the draft and
99
101
  # any chart it authors in conversation state and writes neither until a user saves from the UI, so
100
- # it is a candidate for the allowlist once the dataset has runs behind it. agentic_report_skill is
101
- # absent for the same reason: the report draft stays in conversation state until a user saves it.
102
+ # it is a candidate for the allowlist once the dataset has runs behind it. agentic_document_skill is
103
+ # absent for the same reason: the document draft stays in conversation state until a user saves it.
102
104
  WORKSPACE_MUTATING_TEST_KINDS = frozenset(AGENTIC_TEST_KINDS) - PARALLEL_SAFE_TEST_KINDS
103
105
 
104
106
 
@@ -151,11 +153,13 @@ def _dispatch_agentic(
151
153
  agent_id: str | None = None,
152
154
  submit_trace_link: SubmitTraceLink = run_trace_link_inline,
153
155
  gate: EvalGate = DEFAULT_GATE,
156
+ run_metadata_extra: dict[str, Any] | None = None,
154
157
  ) -> AgenticEvalOutcome:
155
158
  """Call the appropriate evaluate_agentic_* function for the item's test_kind.
156
159
 
157
160
  `gate` reaches every kind except those in UNGATED_AGENTIC_TEST_KINDS, which have no K
158
161
  to gate over; the CLI refuses --gate power for a dataset containing one.
162
+ `run_metadata_extra` reaches every kind unchanged.
159
163
 
160
164
  Every evaluate_agentic_* function returns an AgenticEvalOutcome (reasoning_steps,
161
165
  conversation_id, response_id, detail) on success and attaches the same four attributes
@@ -171,6 +175,7 @@ def _dispatch_agentic(
171
175
  "model_version_override": model_version_override,
172
176
  "reasoning_effort": reasoning_effort,
173
177
  "submit_trace_link": submit_trace_link,
178
+ "run_metadata_extra": run_metadata_extra,
174
179
  }
175
180
 
176
181
  if kind in ("vis_agentic", "agentic_visualization"):
@@ -213,8 +218,8 @@ def _dispatch_agentic(
213
218
  user_context=item.user_context,
214
219
  **lf_kw,
215
220
  )
216
- elif kind == "agentic_report_skill":
217
- return evaluate_agentic_report_skill(
221
+ elif kind == "agentic_document_skill":
222
+ return evaluate_agentic_document_skill(
218
223
  host=host,
219
224
  token=token,
220
225
  workspace_id=workspace_id,
@@ -302,6 +307,7 @@ def _dispatch_agentic(
302
307
  question=item.question,
303
308
  expected_output=eo if isinstance(eo, dict) else {},
304
309
  k=k,
310
+ gate=gate,
305
311
  agent_id=agent_id,
306
312
  user_context=item.user_context,
307
313
  **lf_kw,
@@ -355,6 +361,20 @@ def _apply_run_counts(item_report: ItemReport, source: Any) -> None:
355
361
  item_report.runs_ungraded = unscored
356
362
 
357
363
 
364
+ def _apply_failed_runs(item_report: ItemReport, source: Any) -> None:
365
+ """Copy the per-run failure records off an outcome or a failure, if the kind built any.
366
+
367
+ Read the same way from both, like the run counts above: a partially passing item raises,
368
+ so an item's failing runs reach the report through the exception at least as often as
369
+ through the outcome. A kind that builds none keeps the empty default, which reads as
370
+ "not instrumented" rather than "nothing failed" -- ``runs_passed``/``runs`` already say
371
+ how many failed.
372
+ """
373
+ failed_runs = getattr(source, "failed_runs", None)
374
+ if failed_runs:
375
+ item_report.failed_runs = list(failed_runs)
376
+
377
+
358
378
  def _apply_timings(item_report: ItemReport, timings: Any) -> None:
359
379
  """Copy an outcome's phase breakdown onto the item report, if the kind recorded one.
360
380
 
@@ -384,6 +404,7 @@ def run_agentic_items(
384
404
  agent_id: str | None = None,
385
405
  concurrency: int = 1,
386
406
  gate: EvalGate = DEFAULT_GATE,
407
+ tags: tuple[str, ...] = (),
387
408
  ) -> EvalReport:
388
409
  """Run agentic items through evaluate_agentic_* and return an EvalReport.
389
410
 
@@ -392,9 +413,14 @@ def run_agentic_items(
392
413
  from the parallel ones -- a metric being created and dropped mid-run would otherwise be
393
414
  visible to a catalog-reading item running alongside it.
394
415
 
416
+ ``tags`` are added to every Langfuse trace gd-eval writes for these items, after the
417
+ automatic ones. They reach Langfuse whenever trace linking runs, with or without
418
+ ``use_langfuse``.
419
+
395
420
  Results are collected in dataset order regardless of completion order.
396
421
  """
397
422
  langfuse = make_langfuse_client() if use_langfuse else None
423
+ run_metadata_extra: dict[str, Any] | None = {"tags": list(tags)} if tags else None
398
424
 
399
425
  report = EvalReport(model=model_version, gate=normalize_gate(gate))
400
426
  total = len(items)
@@ -436,6 +462,7 @@ def run_agentic_items(
436
462
  agent_id,
437
463
  submit_trace_link=linker.submit,
438
464
  gate=gate,
465
+ run_metadata_extra=run_metadata_extra,
439
466
  )
440
467
  if isinstance(outcome, AgenticEvalOutcome):
441
468
  reasoning_steps = outcome.reasoning_steps
@@ -454,6 +481,7 @@ def run_agentic_items(
454
481
  item_report.best_detail = detail or {}
455
482
  _apply_timings(item_report, getattr(outcome, "timings", None))
456
483
  _apply_run_counts(item_report, outcome)
484
+ _apply_failed_runs(item_report, outcome)
457
485
  except AssertionError as exc:
458
486
  item_report.gate_passed = False if gated else None
459
487
  item_report.runs = k
@@ -463,11 +491,32 @@ def run_agentic_items(
463
491
  item_report.best_detail = getattr(exc, "detail", None) or {}
464
492
  _apply_timings(item_report, getattr(exc, "timings", None))
465
493
  _apply_run_counts(item_report, exc)
494
+ _apply_failed_runs(item_report, exc)
466
495
  # Read off the counts, not off the gate: pass^K fails items where runs did pass,
467
496
  # and reporting those as pass_at_k False would contradict the Langfuse score of
468
497
  # the same name. Kinds that report no count read as 0, i.e. a clean failure.
469
498
  item_report.pass_at_k = item_report.runs_passed > 0
470
499
  print(f"[agentic] {item.id} FAIL: {exc}", flush=True)
500
+ except JudgeResponseError as exc:
501
+ # Errored, like the branch below -- there is no verdict for any run, so this is
502
+ # not K failures -- but NOT diagnostically empty. This is the one failure mode
503
+ # where the per-run records are most worth having: the judge broke, so what the
504
+ # agent actually said is still there to read, and the conversation ids are how
505
+ # anyone gets to it. Reported with the runs it really drove rather than 0.
506
+ item_report.error = f"{type(exc).__name__}: {exc}"
507
+ item_report.runs = getattr(exc, "runs_effective", None) or k
508
+ item_report.reasoning_steps = getattr(exc, "reasoning_steps", None) or []
509
+ item_report.conversation_id = getattr(exc, "conversation_id", None)
510
+ item_report.response_id = getattr(exc, "response_id", None)
511
+ item_report.best_detail = getattr(exc, "detail", None) or {}
512
+ _apply_timings(item_report, getattr(exc, "timings", None))
513
+ _apply_run_counts(item_report, exc)
514
+ _apply_failed_runs(item_report, exc)
515
+ # Every run ungraded, by definition of this error -- said explicitly rather than
516
+ # left to _apply_run_counts, whose source is detail["unscored_runs"] and which a
517
+ # kind that attaches no diagnostics would leave at 0.
518
+ item_report.runs_ungraded = item_report.runs
519
+ print(f"[agentic] {item.id} UNGRADED: {exc}", flush=True)
471
520
  except Exception as exc:
472
521
  item_report.error = f"{type(exc).__name__}: {exc}"
473
522
  item_report.runs = 0
@@ -183,6 +183,17 @@ def _build_parser() -> argparse.ArgumentParser:
183
183
  action="store_true",
184
184
  help="Log scores and traces to Langfuse (requires --langfuse-dataset and LANGFUSE_* env vars).",
185
185
  )
186
+ run.add_argument(
187
+ "--tag",
188
+ dest="tags",
189
+ action="append",
190
+ default=[],
191
+ type=_parse_tag,
192
+ metavar="TAG",
193
+ help="Custom tag for every Langfuse trace gd-eval writes, next to the automatic ones. "
194
+ "Repeat for several tags. Applied wherever traces are written: single-turn kinds only "
195
+ "with --langfuse, agentic kinds whenever LANGFUSE_* env vars are set.",
196
+ )
186
197
  run.add_argument(
187
198
  "--agent-id",
188
199
  dest="agent_id",
@@ -377,6 +388,14 @@ def _parse_model_arg(val: str) -> tuple[str | None, str]:
377
388
  return None, val
378
389
 
379
390
 
391
+ def _parse_tag(val: str) -> str:
392
+ """Strip one --tag value and reject a blank one, which Langfuse would store as an empty tag."""
393
+ tag = val.strip()
394
+ if not tag:
395
+ raise argparse.ArgumentTypeError("tag must not be blank")
396
+ return tag
397
+
398
+
380
399
  def _make_progress_callbacks(console: Console):
381
400
  """Build (on_item_start, on_run_done, on_item_done) callbacks that stream progress.
382
401
 
@@ -549,6 +568,7 @@ def _run(config: RunConfig) -> int:
549
568
  model_id=resolved.model_id,
550
569
  provider_type=resolved.provider_type,
551
570
  reasoning_effort=config.reasoning_effort,
571
+ tags=config.tags,
552
572
  )
553
573
 
554
574
  def on_langfuse_item_done(
@@ -574,6 +594,7 @@ def _run(config: RunConfig) -> int:
574
594
  model_version=resolved.model_id,
575
595
  reasoning_effort=config.reasoning_effort,
576
596
  use_langfuse=config.log_to_langfuse,
597
+ tags=config.tags,
577
598
  run_ts=run_ts,
578
599
  on_item_start=on_item_start,
579
600
  on_item_done=on_item_done,
@@ -728,6 +749,7 @@ def main(argv: list[str] | None = None) -> int:
728
749
  agent_id=args.agent_id or os.environ.get("GD_EVAL_AGENT_ID"),
729
750
  turn_timeout_s=args.turn_timeout,
730
751
  item_timeout_s=args.item_timeout,
752
+ tags=tuple(dict.fromkeys(args.tags)),
731
753
  )
732
754
  return _run(config)
733
755
  except (
@@ -24,6 +24,14 @@ from gooddata_eval.core.agentic.dashboard_skill import (
24
24
  evaluate_agentic_dashboard_skill,
25
25
  run_agentic_dashboard_skill,
26
26
  )
27
+ from gooddata_eval.core.agentic.document_skill import (
28
+ AgenticDocumentSummary,
29
+ DocumentEvaluation,
30
+ DocumentRunResult,
31
+ DocumentSkillAssertionError,
32
+ evaluate_agentic_document_skill,
33
+ run_agentic_document_skill,
34
+ )
27
35
  from gooddata_eval.core.agentic.general_question import (
28
36
  AgenticGeneralQuestionSummary,
29
37
  GeneralQuestionAssertionError,
@@ -53,14 +61,6 @@ from gooddata_eval.core.agentic.metric_skill import (
53
61
  evaluate_agentic_metric_skill,
54
62
  run_agentic_metric_skill,
55
63
  )
56
- from gooddata_eval.core.agentic.report_skill import (
57
- AgenticReportSummary,
58
- ReportEvaluation,
59
- ReportRunResult,
60
- ReportSkillAssertionError,
61
- evaluate_agentic_report_skill,
62
- run_agentic_report_skill,
63
- )
64
64
  from gooddata_eval.core.agentic.search_tool import (
65
65
  AgenticSearchSummary,
66
66
  SearchResult,
@@ -76,6 +76,14 @@ from gooddata_eval.core.agentic.visualization import (
76
76
  run_agentic_visualization,
77
77
  )
78
78
 
79
+ # Deprecated names of the document skill, kept for existing callers.
80
+ AgenticReportSummary = AgenticDocumentSummary
81
+ ReportEvaluation = DocumentEvaluation
82
+ ReportRunResult = DocumentRunResult
83
+ ReportSkillAssertionError = DocumentSkillAssertionError
84
+ evaluate_agentic_report_skill = evaluate_agentic_document_skill
85
+ run_agentic_report_skill = run_agentic_document_skill
86
+
79
87
  __all__ = [
80
88
  "AgenticAlertSummary",
81
89
  "AgenticDashboardSummary",
@@ -83,6 +91,7 @@ __all__ = [
83
91
  "AgenticGuardrailSummary",
84
92
  "AgenticKdaSummary",
85
93
  "AgenticMetricSummary",
94
+ "AgenticDocumentSummary",
86
95
  "AgenticReportSummary",
87
96
  "AgenticSearchSummary",
88
97
  "AgenticRunSummary",
@@ -95,6 +104,9 @@ __all__ = [
95
104
  "DashboardEvaluation",
96
105
  "DashboardRunResult",
97
106
  "DashboardSkillAssertionError",
107
+ "DocumentEvaluation",
108
+ "DocumentRunResult",
109
+ "DocumentSkillAssertionError",
98
110
  "GeneralQuestionAssertionError",
99
111
  "GeneralQuestionResult",
100
112
  "GuardrailAssertionError",
@@ -116,6 +128,7 @@ __all__ = [
116
128
  "evaluate_agentic_alert_skill",
117
129
  "evaluate_agentic_conversation",
118
130
  "evaluate_agentic_dashboard_skill",
131
+ "evaluate_agentic_document_skill",
119
132
  "evaluate_agentic_general_question",
120
133
  "evaluate_agentic_guardrail",
121
134
  "evaluate_agentic_kda_skill",
@@ -126,6 +139,7 @@ __all__ = [
126
139
  "run_agentic_alert_skill",
127
140
  "run_agentic_conversation",
128
141
  "run_agentic_dashboard_skill",
142
+ "run_agentic_document_skill",
129
143
  "run_agentic_general_question",
130
144
  "run_agentic_guardrail",
131
145
  "run_agentic_kda_skill",
@@ -0,0 +1,62 @@
1
+ # (C) 2026 GoodData Corporation. All rights reserved.
2
+ """Per-run failure records for the agentic evaluators.
3
+
4
+ ``core/runner.py`` records one of these for every failing run of a single-shot item. The
5
+ agentic kinds cannot reuse it: each of them drives its own K-loop inside the evaluator and
6
+ hands ``cli/agentic_runner`` a single aggregate per item, so by the time the runner sees
7
+ the result the individual attempts are already collapsed into ``best``. The runs themselves
8
+ are not lost -- every kind keeps its ``run_results`` list -- they simply never leave the
9
+ evaluator. This turns that list into the same records the single-shot path writes.
10
+
11
+ Keys mirror ``core.runner._failed_run_record`` so one consumer can read ``failed_runs`` from
12
+ either path without branching on test kind. ``tool_call_count``/``tool_names`` are the
13
+ addition: the agentic kinds capture tool calls per run, and an agent that produced a final
14
+ answer having made no tool call at all answered from the model's own knowledge rather than
15
+ from the workspace -- a distinction no other recorded field exposes.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from collections.abc import Callable, Sequence
21
+ from typing import Any
22
+
23
+
24
+ def build_failed_runs(
25
+ run_results: Sequence[Any],
26
+ *,
27
+ passed: Callable[[Any], bool],
28
+ detail: Callable[[Any], dict],
29
+ ) -> list[dict]:
30
+ """One record per run that did not pass, in the order the runs happened.
31
+
32
+ ``passed`` and ``detail`` are supplied by the caller because neither is uniform across
33
+ kinds: guardrail reads ``run.passed`` while visualization reads
34
+ ``run.eval_result.strict_pass``, and each kind's diagnostic dict is its own. Everything
35
+ read here directly goes through ``getattr`` with a default, so a kind whose run result
36
+ lacks a field (no response id, no tool capture) records a null instead of raising.
37
+
38
+ A run the judge could not grade is recorded like any other non-passing run, with its
39
+ ``error`` set. That matches ``core/runner.py``, which also records ungraded runs; the
40
+ verdict-level accounting of ungraded runs stays with the caller's ``unscored_runs``.
41
+ """
42
+ records: list[dict] = []
43
+ for run_index, run in enumerate(run_results, start=1):
44
+ if passed(run):
45
+ continue
46
+ tool_calls = list(getattr(run, "tool_call_events", None) or [])
47
+ reasoning_steps = list(getattr(run, "reasoning_steps", None) or [])
48
+ records.append(
49
+ {
50
+ "run_index": run_index,
51
+ "passed": False,
52
+ "error": getattr(run, "judge_error", None),
53
+ "detail": detail(run),
54
+ "conversation_id": getattr(run, "conversation_id", None),
55
+ "response_id": getattr(run, "response_id", None),
56
+ "reasoning_step_count": len(reasoning_steps),
57
+ "reasoning_steps": reasoning_steps,
58
+ "tool_call_count": len(tool_calls),
59
+ "tool_names": [getattr(e, "function_name", None) for e in tool_calls],
60
+ }
61
+ )
62
+ return records