gooddata-eval 1.75.1.dev4__tar.gz → 1.76.1.dev1__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 (135) hide show
  1. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/PKG-INFO +2 -2
  2. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/pyproject.toml +2 -2
  3. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/cli/agentic_runner.py +30 -1
  4. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/__init__.py +14 -0
  5. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/alert_skill.py +58 -5
  6. gooddata_eval-1.76.1.dev1/src/gooddata_eval/core/agentic/anomaly_detection.py +642 -0
  7. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/conversation.py +63 -7
  8. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/kda_skill.py +30 -2
  9. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/metric_skill.py +43 -2
  10. gooddata_eval-1.76.1.dev1/src/gooddata_eval/core/agentic/report_skill.py +842 -0
  11. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/visualization.py +55 -6
  12. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/chat/sse_client.py +1 -0
  13. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/models.py +41 -0
  14. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_alert_skill.py +251 -1
  15. gooddata_eval-1.76.1.dev1/tests/test_agentic_anomaly_detection.py +530 -0
  16. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_conversation.py +121 -6
  17. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_kda_skill.py +12 -1
  18. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_metric_skill.py +127 -1
  19. gooddata_eval-1.76.1.dev1/tests/test_agentic_report_skill.py +843 -0
  20. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_runner.py +2 -0
  21. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_visualization.py +90 -1
  22. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_chat_render.py +42 -1
  23. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_trace_linker.py +2 -0
  24. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/.gitignore +0 -0
  25. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/AGENTS.md +0 -0
  26. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/CLAUDE.md +0 -0
  27. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/LICENSE.txt +0 -0
  28. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/Makefile +0 -0
  29. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/README.md +0 -0
  30. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/scripts/verify_guardrail_refusal_criteria.py +0 -0
  31. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/__init__.py +0 -0
  32. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/_version.py +0 -0
  33. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/cli/__init__.py +0 -0
  34. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/cli/main.py +0 -0
  35. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/__init__.py +0 -0
  36. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/_output.py +0 -0
  37. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/_catalog.py +0 -0
  38. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/_conversation_context.py +0 -0
  39. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/_gate.py +0 -0
  40. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/_langfuse.py +0 -0
  41. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/_trace_linker.py +0 -0
  42. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/dashboard_skill.py +0 -0
  43. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/general_question.py +0 -0
  44. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/guardrail.py +0 -0
  45. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/search_tool.py +0 -0
  46. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/agentic/what_if.py +0 -0
  47. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/chat/__init__.py +0 -0
  48. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/chat/render.py +0 -0
  49. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/config.py +0 -0
  50. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/connection.py +0 -0
  51. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/dataset/__init__.py +0 -0
  52. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/dataset/from_insights.py +0 -0
  53. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/dataset/langfuse_source.py +0 -0
  54. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/dataset/local.py +0 -0
  55. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/__init__.py +0 -0
  56. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/_deep_subset.py +0 -0
  57. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/_guardrail_criteria.py +0 -0
  58. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/_llm_judge.py +0 -0
  59. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/_maql.py +0 -0
  60. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/_text_utils.py +0 -0
  61. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/alert_skill.py +0 -0
  62. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/base.py +0 -0
  63. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/general_question.py +0 -0
  64. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/guardrail.py +0 -0
  65. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/metric_skill.py +0 -0
  66. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/search_tool.py +0 -0
  67. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/summary.py +0 -0
  68. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/evaluators/visualization.py +0 -0
  69. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/granularity.py +0 -0
  70. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/langfuse/__init__.py +0 -0
  71. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/langfuse/_env.py +0 -0
  72. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/langfuse/client.py +0 -0
  73. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/langfuse/experiment.py +0 -0
  74. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/langfuse/observations.py +0 -0
  75. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/langfuse/otlp.py +0 -0
  76. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/langfuse/sink.py +0 -0
  77. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/reporting/__init__.py +0 -0
  78. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/reporting/console.py +0 -0
  79. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/reporting/html_report.py +0 -0
  80. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/reporting/json_report.py +0 -0
  81. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/reporting/report_template.html +0 -0
  82. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/runner.py +0 -0
  83. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/scoring.py +0 -0
  84. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/summary/__init__.py +0 -0
  85. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/summary/http_client.py +0 -0
  86. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/timing.py +0 -0
  87. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/src/gooddata_eval/core/workspace.py +0 -0
  88. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/__init__.py +0 -0
  89. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/_fake_langfuse.py +0 -0
  90. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/conftest.py +0 -0
  91. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/fixtures/sample_dataset/metric_skill_create.json +0 -0
  92. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/fixtures/sample_dataset/visualization_revenue.json +0 -0
  93. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/fixtures/sse_visualization_stream.txt +0 -0
  94. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_dashboard_skill.py +0 -0
  95. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_gate.py +0 -0
  96. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_general_question.py +0 -0
  97. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_guardrail.py +0 -0
  98. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_langfuse_trace.py +0 -0
  99. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_observe_experiment.py +0 -0
  100. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_run_context.py +0 -0
  101. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_search_tool.py +0 -0
  102. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_agentic_what_if.py +0 -0
  103. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_alert_skill_evaluator.py +0 -0
  104. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_cli.py +0 -0
  105. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_connection.py +0 -0
  106. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_deep_subset.py +0 -0
  107. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_fake_langfuse.py +0 -0
  108. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_from_insights.py +0 -0
  109. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_guardrail_criteria.py +0 -0
  110. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_html_report.py +0 -0
  111. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_langfuse_client.py +0 -0
  112. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_langfuse_e2e_fake_server.py +0 -0
  113. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_langfuse_env.py +0 -0
  114. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_langfuse_experiment.py +0 -0
  115. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_langfuse_observations.py +0 -0
  116. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_langfuse_otlp.py +0 -0
  117. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_langfuse_sink.py +0 -0
  118. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_langfuse_source.py +0 -0
  119. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_llm_judge.py +0 -0
  120. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_local_loader.py +0 -0
  121. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_maql_normalize.py +0 -0
  122. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_metric_skill_evaluator.py +0 -0
  123. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_models.py +0 -0
  124. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_reporting.py +0 -0
  125. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_runner.py +0 -0
  126. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_scoring.py +0 -0
  127. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_search_tool_evaluator.py +0 -0
  128. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_sse_client.py +0 -0
  129. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_summary_client.py +0 -0
  130. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_summary_evaluator.py +0 -0
  131. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_text_evaluators.py +0 -0
  132. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_timing.py +0 -0
  133. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_visualization_evaluator.py +0 -0
  134. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tests/test_workspace.py +0 -0
  135. {gooddata_eval-1.75.1.dev4 → gooddata_eval-1.76.1.dev1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gooddata-eval
3
- Version: 1.75.1.dev4
3
+ Version: 1.76.1.dev1
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.75.1.dev4
20
+ Requires-Dist: gooddata-sdk~=1.76.1.dev1
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.75.1.dev4"
4
+ version = "1.76.1.dev1"
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.75.1.dev4",
14
+ "gooddata-sdk~=1.76.1.dev1",
15
15
  "httpx>=0.27,<1.0",
16
16
  "jsonpatch>=1.33,<2.0",
17
17
  "orjson>=3.9.15,<4.0.0",
@@ -12,12 +12,14 @@ from gooddata_eval.core.agentic._gate import DEFAULT_GATE, EvalGate, normalize_g
12
12
  from gooddata_eval.core.agentic._langfuse import make_langfuse_client
13
13
  from gooddata_eval.core.agentic._trace_linker import BackgroundTraceLinker, SubmitTraceLink, run_trace_link_inline
14
14
  from gooddata_eval.core.agentic.alert_skill import evaluate_agentic_alert_skill
15
+ from gooddata_eval.core.agentic.anomaly_detection import evaluate_agentic_anomaly_detection
15
16
  from gooddata_eval.core.agentic.conversation import ConversationFixture, evaluate_agentic_conversation
16
17
  from gooddata_eval.core.agentic.dashboard_skill import evaluate_agentic_dashboard_skill
17
18
  from gooddata_eval.core.agentic.general_question import evaluate_agentic_general_question
18
19
  from gooddata_eval.core.agentic.guardrail import evaluate_agentic_guardrail
19
20
  from gooddata_eval.core.agentic.kda_skill import evaluate_agentic_kda_skill
20
21
  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
21
23
  from gooddata_eval.core.agentic.search_tool import evaluate_agentic_search_tool
22
24
  from gooddata_eval.core.agentic.visualization import evaluate_agentic_visualization
23
25
  from gooddata_eval.core.agentic.what_if import evaluate_agentic_what_if
@@ -43,11 +45,13 @@ AGENTIC_TEST_KINDS = frozenset(
43
45
  "agentic_metric_skill",
44
46
  "agentic_alert_skill",
45
47
  "agentic_dashboard_skill",
48
+ "agentic_report_skill",
46
49
  "agentic_search",
47
50
  "agentic_general_question",
48
51
  "agentic_guardrail",
49
52
  "agentic_conversation",
50
53
  "agentic_kda_skill",
54
+ "agentic_anomaly_detection",
51
55
  "agentic_what_if",
52
56
  }
53
57
  )
@@ -92,7 +96,8 @@ PARALLEL_SAFE_TEST_KINDS = frozenset(
92
96
  #
93
97
  # agentic_dashboard_skill is absent by default rather than by evidence: gen-ai holds the draft and
94
98
  # any chart it authors in conversation state and writes neither until a user saves from the UI, so
95
- # it is a candidate for the allowlist once the dataset has runs behind it.
99
+ # it is a candidate for the allowlist once the dataset has runs behind it. agentic_report_skill is
100
+ # absent for the same reason: the report draft stays in conversation state until a user saves it.
96
101
  WORKSPACE_MUTATING_TEST_KINDS = frozenset(AGENTIC_TEST_KINDS) - PARALLEL_SAFE_TEST_KINDS
97
102
 
98
103
 
@@ -203,6 +208,18 @@ def _dispatch_agentic(
203
208
  agent_id=agent_id,
204
209
  **lf_kw,
205
210
  )
211
+ elif kind == "agentic_report_skill":
212
+ return evaluate_agentic_report_skill(
213
+ host=host,
214
+ token=token,
215
+ workspace_id=workspace_id,
216
+ question=item.question,
217
+ expected_output=eo,
218
+ k=k,
219
+ gate=gate,
220
+ agent_id=agent_id,
221
+ **lf_kw,
222
+ )
206
223
  elif kind == "agentic_alert_skill":
207
224
  return evaluate_agentic_alert_skill(
208
225
  host=host,
@@ -278,6 +295,18 @@ def _dispatch_agentic(
278
295
  agent_id=agent_id,
279
296
  **lf_kw,
280
297
  )
298
+ elif kind == "agentic_anomaly_detection":
299
+ return evaluate_agentic_anomaly_detection(
300
+ host=host,
301
+ token=token,
302
+ workspace_id=workspace_id,
303
+ question=item.question,
304
+ expected_output=eo if isinstance(eo, dict) else {},
305
+ k=k,
306
+ gate=gate,
307
+ agent_id=agent_id,
308
+ **lf_kw,
309
+ )
281
310
  elif kind == "agentic_conversation":
282
311
  fixture_data = eo.get("fixture") or eo if isinstance(eo, dict) else {}
283
312
  return evaluate_agentic_conversation(
@@ -53,6 +53,14 @@ from gooddata_eval.core.agentic.metric_skill import (
53
53
  evaluate_agentic_metric_skill,
54
54
  run_agentic_metric_skill,
55
55
  )
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
+ )
56
64
  from gooddata_eval.core.agentic.search_tool import (
57
65
  AgenticSearchSummary,
58
66
  SearchResult,
@@ -75,6 +83,7 @@ __all__ = [
75
83
  "AgenticGuardrailSummary",
76
84
  "AgenticKdaSummary",
77
85
  "AgenticMetricSummary",
86
+ "AgenticReportSummary",
78
87
  "AgenticSearchSummary",
79
88
  "AgenticRunSummary",
80
89
  "AlertEvaluation",
@@ -95,6 +104,9 @@ __all__ = [
95
104
  "KdaSkillAssertionError",
96
105
  "MetricRunResult",
97
106
  "MetricSkillAssertionError",
107
+ "ReportEvaluation",
108
+ "ReportRunResult",
109
+ "ReportSkillAssertionError",
98
110
  "RunResult",
99
111
  "SearchResult",
100
112
  "SearchToolAssertionError",
@@ -108,6 +120,7 @@ __all__ = [
108
120
  "evaluate_agentic_guardrail",
109
121
  "evaluate_agentic_kda_skill",
110
122
  "evaluate_agentic_metric_skill",
123
+ "evaluate_agentic_report_skill",
111
124
  "evaluate_agentic_search_tool",
112
125
  "evaluate_agentic_visualization",
113
126
  "run_agentic_alert_skill",
@@ -117,6 +130,7 @@ __all__ = [
117
130
  "run_agentic_guardrail",
118
131
  "run_agentic_kda_skill",
119
132
  "run_agentic_metric_skill",
133
+ "run_agentic_report_skill",
120
134
  "run_agentic_search_tool",
121
135
  "run_agentic_visualization",
122
136
  ]
@@ -30,11 +30,12 @@ from gooddata_eval.core.agentic._trace_linker import (
30
30
  utc_now,
31
31
  )
32
32
  from gooddata_eval.core.chat.render import render_answer_text
33
- from gooddata_eval.core.chat.sse_client import ChatClient
33
+ from gooddata_eval.core.chat.sse_client import ChatClient, ChatError
34
34
  from gooddata_eval.core.config import ReasoningEffort
35
35
  from gooddata_eval.core.models import (
36
36
  AgenticAssertionError,
37
37
  AgenticEvalOutcome,
38
+ LoopExit,
38
39
  ReasoningStepEvent,
39
40
  ToolCallEvent,
40
41
  shift_and_index_events,
@@ -483,6 +484,12 @@ class AlertRunResult:
483
484
  response_id: str | None = None
484
485
  tool_call_events: list[ToolCallEvent] = field(default_factory=list)
485
486
  reasoning_step_events: list[ReasoningStepEvent] = field(default_factory=list)
487
+ # Why the simulated-user loop stopped, and how many turns it took. Without these a run
488
+ # that ran out of turns is indistinguishable from one that refused: both land on
489
+ # alert_created=False, and every downstream check is `alert_created and ...`, so both
490
+ # also report operator/threshold/metric/recipients as False.
491
+ exit_reason: LoopExit = LoopExit.BUDGET_EXHAUSTED
492
+ turns_used: int = 0
486
493
 
487
494
 
488
495
  @dataclass
@@ -681,8 +688,35 @@ def run_agentic_alert_skill(
681
688
  turns = 0
682
689
  steps = 0
683
690
 
691
+ # Defaults to BUDGET_EXHAUSTED: every other exit sets it explicitly, so a loop
692
+ # that simply runs out of range() is correctly labelled without a trailing else.
693
+ exit_reason = LoopExit.BUDGET_EXHAUSTED
694
+ turns_used = 0
684
695
  for _iteration in range(max_iterations):
685
- chat_result = client.send_message(conv_id, current_question)
696
+ turns_used = _iteration + 1
697
+ try:
698
+ chat_result = client.send_message(conv_id, current_question)
699
+ except ChatError as exc:
700
+ # Without this the exception escapes run_agentic_alert_skill entirely,
701
+ # discarding every K-run already completed along with any exit_reason.
702
+ print(f"[CHAT] send_message failed for conversation {conv_id}: {exc}")
703
+ # The stream can break AFTER create_metric_alert already succeeded
704
+ # server-side. That id only ever reached alert_id_to_delete from the
705
+ # normal path below, so breaking here left the alert in the workspace for
706
+ # the `finally` cleanup to miss -- a real object leaking out of a failed
707
+ # run, not a reporting gap.
708
+ partial = exc.partial_result
709
+ if partial is not None:
710
+ reasoning_steps.extend(partial.reasoning_steps or [])
711
+ response_id = partial.response_id or response_id
712
+ partial_alert_id, _, partial_tool_called = _extract_alert_call(partial.tool_call_events or [])
713
+ if partial_tool_called:
714
+ alert_id_to_delete = partial_alert_id
715
+ exit_reason = LoopExit.CHAT_ERROR
716
+ break
717
+ # `turns_used` counts attempts (set above, so a failed send still shows the
718
+ # loop reached this iteration); `turns`/`steps` count completed work, so they
719
+ # advance only once send_message has actually returned.
686
720
  turns += 1
687
721
  steps += chat_result.reasoning_step_count
688
722
  reasoning_steps.extend(chat_result.reasoning_steps or [])
@@ -698,6 +732,7 @@ def run_agentic_alert_skill(
698
732
  alert_id, actual_args, tool_called = _extract_alert_call(chat_result.tool_call_events or [])
699
733
  if tool_called:
700
734
  alert_id_to_delete = alert_id
735
+ exit_reason = LoopExit.SUCCESS
701
736
  break
702
737
  response_text = (chat_result.text_response or "").strip()
703
738
  if not response_text and chat_result.alert_proposals:
@@ -706,13 +741,24 @@ def run_agentic_alert_skill(
706
741
  response_text = render_answer_text(chat_result)
707
742
  # Stop if agent gave a completely empty response (stuck)
708
743
  if not response_text and not chat_result.tool_call_events:
744
+ exit_reason = LoopExit.AGENT_SILENT
709
745
  break
710
746
  # Stop before generating a follow-up for the last iteration
711
747
  if _iteration >= max_iterations - 1:
712
748
  break
713
- follow_up = generate_simulated_alert_response(
714
- response_text, expected, conversation_history, question=question
715
- )
749
+ # Recorded rather than raised, matching metric_skill and kda_skill. Letting it
750
+ # propagate did keep a harness fault from being scored as a content failure,
751
+ # but it also discarded the K-runs already completed -- and SIMULATED_USER_FAILED
752
+ # achieves the same separation while keeping them, since reporting reads the
753
+ # exit reason to classify the run as an error rather than an agent failure.
754
+ try:
755
+ follow_up = generate_simulated_alert_response(
756
+ response_text, expected, conversation_history, question=question
757
+ )
758
+ except Exception as exc: # noqa: BLE001 -- harness-side fault; end only this run
759
+ print(f"[SIM-USER] Simulated reply failed for conversation {conv_id}: {exc}")
760
+ exit_reason = LoopExit.SIMULATED_USER_FAILED
761
+ break
716
762
  # Record this exchange so the next call has full history
717
763
  conversation_history.append({"role": "assistant", "content": response_text})
718
764
  conversation_history.append({"role": "user", "content": follow_up})
@@ -740,6 +786,8 @@ def run_agentic_alert_skill(
740
786
  response_id=response_id,
741
787
  tool_call_events=all_tool_call_events,
742
788
  reasoning_step_events=all_reasoning_step_events,
789
+ exit_reason=exit_reason,
790
+ turns_used=turns_used,
743
791
  )
744
792
  finally:
745
793
  if alert_id_to_delete:
@@ -908,6 +956,11 @@ def evaluate_agentic_alert_skill(
908
956
  "attributes_correct": ev.attributes_correct,
909
957
  "granularity_correct": ev.granularity_correct,
910
958
  "actual_alert_arguments": best.actual_alert_arguments,
959
+ # Why the loop stopped. alert_created=False alone cannot tell a refusal from a run
960
+ # that hit max_iterations while still on track -- see LoopExit.
961
+ "exit_reason": best.exit_reason.value,
962
+ "turns_used": best.turns_used,
963
+ "max_iterations": max_iterations,
911
964
  **timeline_detail(best.tool_call_events, best.reasoning_step_events),
912
965
  }
913
966