gooddata-eval 1.76.1.dev1__tar.gz → 1.76.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 (135) hide show
  1. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/PKG-INFO +51 -2
  2. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/README.md +49 -0
  3. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/pyproject.toml +2 -2
  4. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/cli/agentic_runner.py +13 -0
  5. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/alert_skill.py +9 -1
  6. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/anomaly_detection.py +9 -1
  7. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/conversation.py +31 -2
  8. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/dashboard_skill.py +9 -1
  9. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/general_question.py +9 -9
  10. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/guardrail.py +9 -1
  11. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/kda_skill.py +9 -1
  12. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/metric_skill.py +55 -1
  13. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/report_skill.py +9 -1
  14. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/search_tool.py +9 -1
  15. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/visualization.py +37 -3
  16. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/what_if.py +9 -1
  17. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/chat/sse_client.py +25 -9
  18. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/dataset/langfuse_source.py +12 -3
  19. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/visualization.py +145 -1
  20. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/conftest.py +7 -0
  21. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_conversation.py +93 -2
  22. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_general_question.py +0 -18
  23. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_metric_skill.py +57 -0
  24. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_runner.py +90 -9
  25. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_visualization.py +75 -0
  26. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_source.py +13 -0
  27. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_sse_client.py +32 -9
  28. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_visualization_evaluator.py +160 -2
  29. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/.gitignore +0 -0
  30. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/AGENTS.md +0 -0
  31. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/CLAUDE.md +0 -0
  32. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/LICENSE.txt +0 -0
  33. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/Makefile +0 -0
  34. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/scripts/verify_guardrail_refusal_criteria.py +0 -0
  35. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/__init__.py +0 -0
  36. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/_version.py +0 -0
  37. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/cli/__init__.py +0 -0
  38. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/cli/main.py +0 -0
  39. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/__init__.py +0 -0
  40. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/_output.py +0 -0
  41. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/__init__.py +0 -0
  42. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_catalog.py +0 -0
  43. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_conversation_context.py +0 -0
  44. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_gate.py +0 -0
  45. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_langfuse.py +0 -0
  46. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_trace_linker.py +0 -0
  47. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/chat/__init__.py +0 -0
  48. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/chat/render.py +0 -0
  49. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/config.py +0 -0
  50. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/connection.py +0 -0
  51. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/dataset/__init__.py +0 -0
  52. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/dataset/from_insights.py +0 -0
  53. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/dataset/local.py +0 -0
  54. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/__init__.py +0 -0
  55. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_deep_subset.py +0 -0
  56. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_guardrail_criteria.py +0 -0
  57. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_llm_judge.py +0 -0
  58. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_maql.py +0 -0
  59. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_text_utils.py +0 -0
  60. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/alert_skill.py +0 -0
  61. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/base.py +0 -0
  62. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/general_question.py +0 -0
  63. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/guardrail.py +0 -0
  64. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/metric_skill.py +0 -0
  65. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/search_tool.py +0 -0
  66. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/summary.py +0 -0
  67. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/granularity.py +0 -0
  68. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/__init__.py +0 -0
  69. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/_env.py +0 -0
  70. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/client.py +0 -0
  71. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/experiment.py +0 -0
  72. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/observations.py +0 -0
  73. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/otlp.py +0 -0
  74. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/sink.py +0 -0
  75. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/models.py +0 -0
  76. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/__init__.py +0 -0
  77. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/console.py +0 -0
  78. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/html_report.py +0 -0
  79. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/json_report.py +0 -0
  80. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/report_template.html +0 -0
  81. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/runner.py +0 -0
  82. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/scoring.py +0 -0
  83. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/summary/__init__.py +0 -0
  84. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/summary/http_client.py +0 -0
  85. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/timing.py +0 -0
  86. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/workspace.py +0 -0
  87. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/__init__.py +0 -0
  88. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/_fake_langfuse.py +0 -0
  89. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/fixtures/sample_dataset/metric_skill_create.json +0 -0
  90. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/fixtures/sample_dataset/visualization_revenue.json +0 -0
  91. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/fixtures/sse_visualization_stream.txt +0 -0
  92. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_alert_skill.py +0 -0
  93. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_anomaly_detection.py +0 -0
  94. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_dashboard_skill.py +0 -0
  95. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_gate.py +0 -0
  96. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_guardrail.py +0 -0
  97. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_kda_skill.py +0 -0
  98. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_langfuse_trace.py +0 -0
  99. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_observe_experiment.py +0 -0
  100. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_report_skill.py +0 -0
  101. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_run_context.py +0 -0
  102. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_search_tool.py +0 -0
  103. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_what_if.py +0 -0
  104. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_alert_skill_evaluator.py +0 -0
  105. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_chat_render.py +0 -0
  106. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_cli.py +0 -0
  107. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_connection.py +0 -0
  108. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_deep_subset.py +0 -0
  109. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_fake_langfuse.py +0 -0
  110. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_from_insights.py +0 -0
  111. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_guardrail_criteria.py +0 -0
  112. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_html_report.py +0 -0
  113. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_client.py +0 -0
  114. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_e2e_fake_server.py +0 -0
  115. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_env.py +0 -0
  116. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_experiment.py +0 -0
  117. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_observations.py +0 -0
  118. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_otlp.py +0 -0
  119. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_sink.py +0 -0
  120. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_llm_judge.py +0 -0
  121. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_local_loader.py +0 -0
  122. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_maql_normalize.py +0 -0
  123. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_metric_skill_evaluator.py +0 -0
  124. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_models.py +0 -0
  125. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_reporting.py +0 -0
  126. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_runner.py +0 -0
  127. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_scoring.py +0 -0
  128. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_search_tool_evaluator.py +0 -0
  129. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_summary_client.py +0 -0
  130. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_summary_evaluator.py +0 -0
  131. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_text_evaluators.py +0 -0
  132. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_timing.py +0 -0
  133. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_trace_linker.py +0 -0
  134. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tests/test_workspace.py +0 -0
  135. {gooddata_eval-1.76.1.dev1 → gooddata_eval-1.76.1.dev2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gooddata-eval
3
- Version: 1.76.1.dev1
3
+ Version: 1.76.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.76.1.dev1
20
+ Requires-Dist: gooddata-sdk~=1.76.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
@@ -603,6 +603,55 @@ The `expected_output` rubric:
603
603
  Each criterion is scored independently by the LLM judge, so `quality_score`
604
604
  is the fraction of satisfied criteria.
605
605
 
606
+ ### Items asked in a dashboard context (`user_context`)
607
+
608
+ An item can be asked the way a user asks from an open dashboard or an attached widget.
609
+ Its `user_context` is sent verbatim as `userContext` on every chat message of the item,
610
+ follow-up and clarification messages included — the server treats a message without one
611
+ as a cleared context. Requires the `enableAiContextSetup` feature flag on the target
612
+ organization — without it the server ignores the dashboard view. Applies to chat items
613
+ only; `dashboard_summary` items carry their scope in `summary_input` instead. No
614
+ `userContext` key is sent at all when neither the item nor, for `agentic_conversation`,
615
+ the current turn sets one.
616
+
617
+ ```json
618
+ {
619
+ "id": "ctx-001",
620
+ "dataset_name": "my_dataset_ctx",
621
+ "test_kind": "general_question",
622
+ "question": "Which dashboard am I looking at, and what does it cover?",
623
+ "user_context": {
624
+ "view": {
625
+ "dashboard": {
626
+ "id": "sales_overview",
627
+ "title": "Sales Overview",
628
+ "widgets": [
629
+ {"widgetType": "insight", "widgetId": "w1", "title": "Revenue by Month", "visualizationId": "revenue_by_month"}
630
+ ]
631
+ }
632
+ }
633
+ },
634
+ "expected_output": "Names the Sales Overview dashboard and summarizes its charts."
635
+ }
636
+ ```
637
+
638
+ The schema belongs to the AI chat API (`UserContext`: `view.dashboard`,
639
+ `referencedObjects`, `activeObject`), so it is not validated here beyond being an object.
640
+ In a Langfuse dataset, put it in the item `metadata` (or in the `input` object) under
641
+ `user_context`. An object in `input` wins and `metadata` is then not read; a missing or
642
+ `null` value in `input` falls back to `metadata`. The value used must be an object or `null`;
643
+ anything else fails the dataset load.
644
+
645
+ `agentic_conversation` items take the item's `user_context` as the context of the first
646
+ turn. A turn in `expected_output.turns` can set its own `user_context`, which applies
647
+ from that turn on until another turn changes it, as the attached context does in the UI:
648
+
649
+ | Turn | Context sent |
650
+ |---|---|
651
+ | no `user_context` key | the current one, unchanged |
652
+ | `"user_context": {...}` | this one, from this turn on |
653
+ | `"user_context": null` | none, from this turn on |
654
+
606
655
  ## Supported test kinds
607
656
 
608
657
  | test_kind | What the agent must produce | Extra required |
@@ -574,6 +574,55 @@ The `expected_output` rubric:
574
574
  Each criterion is scored independently by the LLM judge, so `quality_score`
575
575
  is the fraction of satisfied criteria.
576
576
 
577
+ ### Items asked in a dashboard context (`user_context`)
578
+
579
+ An item can be asked the way a user asks from an open dashboard or an attached widget.
580
+ Its `user_context` is sent verbatim as `userContext` on every chat message of the item,
581
+ follow-up and clarification messages included — the server treats a message without one
582
+ as a cleared context. Requires the `enableAiContextSetup` feature flag on the target
583
+ organization — without it the server ignores the dashboard view. Applies to chat items
584
+ only; `dashboard_summary` items carry their scope in `summary_input` instead. No
585
+ `userContext` key is sent at all when neither the item nor, for `agentic_conversation`,
586
+ the current turn sets one.
587
+
588
+ ```json
589
+ {
590
+ "id": "ctx-001",
591
+ "dataset_name": "my_dataset_ctx",
592
+ "test_kind": "general_question",
593
+ "question": "Which dashboard am I looking at, and what does it cover?",
594
+ "user_context": {
595
+ "view": {
596
+ "dashboard": {
597
+ "id": "sales_overview",
598
+ "title": "Sales Overview",
599
+ "widgets": [
600
+ {"widgetType": "insight", "widgetId": "w1", "title": "Revenue by Month", "visualizationId": "revenue_by_month"}
601
+ ]
602
+ }
603
+ }
604
+ },
605
+ "expected_output": "Names the Sales Overview dashboard and summarizes its charts."
606
+ }
607
+ ```
608
+
609
+ The schema belongs to the AI chat API (`UserContext`: `view.dashboard`,
610
+ `referencedObjects`, `activeObject`), so it is not validated here beyond being an object.
611
+ In a Langfuse dataset, put it in the item `metadata` (or in the `input` object) under
612
+ `user_context`. An object in `input` wins and `metadata` is then not read; a missing or
613
+ `null` value in `input` falls back to `metadata`. The value used must be an object or `null`;
614
+ anything else fails the dataset load.
615
+
616
+ `agentic_conversation` items take the item's `user_context` as the context of the first
617
+ turn. A turn in `expected_output.turns` can set its own `user_context`, which applies
618
+ from that turn on until another turn changes it, as the attached context does in the UI:
619
+
620
+ | Turn | Context sent |
621
+ |---|---|
622
+ | no `user_context` key | the current one, unchanged |
623
+ | `"user_context": {...}` | this one, from this turn on |
624
+ | `"user_context": null` | none, from this turn on |
625
+
577
626
  ## Supported test kinds
578
627
 
579
628
  | test_kind | What the agent must produce | Extra required |
@@ -1,7 +1,7 @@
1
1
  # (C) 2026 GoodData Corporation
2
2
  [project]
3
3
  name = "gooddata-eval"
4
- version = "1.76.1.dev1"
4
+ version = "1.76.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.76.1.dev1",
14
+ "gooddata-sdk~=1.76.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",
@@ -24,6 +24,7 @@ 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.visualization import requires_execution_of
27
28
  from gooddata_eval.core.models import AgenticEvalOutcome, CreatedVisualization, DatasetItem
28
29
  from gooddata_eval.core.runner import EvalReport, ItemReport
29
30
 
@@ -179,9 +180,11 @@ def _dispatch_agentic(
179
180
  workspace_id=workspace_id,
180
181
  question=item.question,
181
182
  expected_outputs=_parse_visualization_expected(eo),
183
+ requires_execution=requires_execution_of(eo),
182
184
  k=k,
183
185
  gate=gate,
184
186
  agent_id=agent_id,
187
+ user_context=item.user_context,
185
188
  **lf_kw,
186
189
  )
187
190
  elif kind == "agentic_metric_skill":
@@ -194,6 +197,7 @@ def _dispatch_agentic(
194
197
  k=k,
195
198
  gate=gate,
196
199
  agent_id=agent_id,
200
+ user_context=item.user_context,
197
201
  **lf_kw,
198
202
  )
199
203
  elif kind == "agentic_dashboard_skill":
@@ -206,6 +210,7 @@ def _dispatch_agentic(
206
210
  k=k,
207
211
  gate=gate,
208
212
  agent_id=agent_id,
213
+ user_context=item.user_context,
209
214
  **lf_kw,
210
215
  )
211
216
  elif kind == "agentic_report_skill":
@@ -218,6 +223,7 @@ def _dispatch_agentic(
218
223
  k=k,
219
224
  gate=gate,
220
225
  agent_id=agent_id,
226
+ user_context=item.user_context,
221
227
  **lf_kw,
222
228
  )
223
229
  elif kind == "agentic_alert_skill":
@@ -230,6 +236,7 @@ def _dispatch_agentic(
230
236
  k=k,
231
237
  gate=gate,
232
238
  agent_id=agent_id,
239
+ user_context=item.user_context,
233
240
  **lf_kw,
234
241
  )
235
242
  elif kind == "agentic_search":
@@ -245,6 +252,7 @@ def _dispatch_agentic(
245
252
  k=k,
246
253
  gate=gate,
247
254
  agent_id=agent_id,
255
+ user_context=item.user_context,
248
256
  **lf_kw,
249
257
  )
250
258
  elif kind == "agentic_general_question":
@@ -270,6 +278,7 @@ def _dispatch_agentic(
270
278
  k=k,
271
279
  gate=gate,
272
280
  agent_id=agent_id,
281
+ user_context=item.user_context,
273
282
  **lf_kw,
274
283
  )
275
284
  elif kind == "agentic_kda_skill":
@@ -282,6 +291,7 @@ def _dispatch_agentic(
282
291
  k=k,
283
292
  gate=gate,
284
293
  agent_id=agent_id,
294
+ user_context=item.user_context,
285
295
  **lf_kw,
286
296
  )
287
297
  elif kind == "agentic_what_if":
@@ -293,6 +303,7 @@ def _dispatch_agentic(
293
303
  expected_output=eo if isinstance(eo, dict) else {},
294
304
  k=k,
295
305
  agent_id=agent_id,
306
+ user_context=item.user_context,
296
307
  **lf_kw,
297
308
  )
298
309
  elif kind == "agentic_anomaly_detection":
@@ -305,6 +316,7 @@ def _dispatch_agentic(
305
316
  k=k,
306
317
  gate=gate,
307
318
  agent_id=agent_id,
319
+ user_context=item.user_context,
308
320
  **lf_kw,
309
321
  )
310
322
  elif kind == "agentic_conversation":
@@ -315,6 +327,7 @@ def _dispatch_agentic(
315
327
  workspace_id=workspace_id,
316
328
  fixture=ConversationFixture.model_validate(fixture_data),
317
329
  agent_id=agent_id,
330
+ user_context=item.user_context,
318
331
  **lf_kw,
319
332
  )
320
333
  else:
@@ -659,12 +659,18 @@ def run_agentic_alert_skill(
659
659
  initial_conversation_id: str | None = None,
660
660
  reasoning_effort: ReasoningEffort | None = None,
661
661
  agent_id: str | None = None,
662
+ user_context: dict | None = None,
662
663
  ) -> AgenticAlertSummary:
663
664
  """Run the alert-skill agentic evaluation K times and return a summary."""
664
665
  expected = _normalize_expected_output(expected_output)
665
666
  run_results: list[AlertRunResult] = []
666
667
  client = ChatClient(
667
- host=host, token=token, workspace_id=workspace_id, reasoning_effort=reasoning_effort, agent_id=agent_id
668
+ host=host,
669
+ token=token,
670
+ workspace_id=workspace_id,
671
+ reasoning_effort=reasoning_effort,
672
+ agent_id=agent_id,
673
+ user_context=user_context,
668
674
  )
669
675
  sdk = GoodDataSdk.create(host, token)
670
676
 
@@ -858,6 +864,7 @@ def evaluate_agentic_alert_skill(
858
864
  run_metadata_extra: dict | None = None,
859
865
  reasoning_effort: ReasoningEffort | None = None,
860
866
  submit_trace_link: SubmitTraceLink = run_trace_link_inline,
867
+ user_context: dict | None = None,
861
868
  gate: EvalGate = DEFAULT_GATE,
862
869
  ) -> AgenticEvalOutcome:
863
870
  """Run alert-skill evaluation, log to Langfuse, and raise AlertSkillAssertionError on failure.
@@ -881,6 +888,7 @@ def evaluate_agentic_alert_skill(
881
888
  initial_conversation_id=initial_conversation_id,
882
889
  reasoning_effort=reasoning_effort,
883
890
  agent_id=agent_id,
891
+ user_context=user_context,
884
892
  )
885
893
 
886
894
  if langfuse is not None and dataset_item_id:
@@ -344,6 +344,7 @@ def run_agentic_anomaly_detection(
344
344
  initial_conversation_id: str | None = None,
345
345
  reasoning_effort: ReasoningEffort | None = None,
346
346
  agent_id: str | None = None,
347
+ user_context: dict | None = None,
347
348
  ) -> AgenticAnomalySummary:
348
349
  """Run the anomaly-detection agentic evaluation K times and return a summary.
349
350
 
@@ -356,7 +357,12 @@ def run_agentic_anomaly_detection(
356
357
  raise ValueError(f"k must be >= 1, got {k}")
357
358
  run_results: list[AnomalyRunResult] = []
358
359
  client = ChatClient(
359
- host=host, token=token, workspace_id=workspace_id, reasoning_effort=reasoning_effort, agent_id=agent_id
360
+ host=host,
361
+ token=token,
362
+ workspace_id=workspace_id,
363
+ reasoning_effort=reasoning_effort,
364
+ agent_id=agent_id,
365
+ user_context=user_context,
360
366
  )
361
367
 
362
368
  def _run_once(conv_id: str) -> AnomalyRunResult:
@@ -520,6 +526,7 @@ def evaluate_agentic_anomaly_detection(
520
526
  run_metadata_extra: dict | None = None,
521
527
  reasoning_effort: ReasoningEffort | None = None,
522
528
  submit_trace_link: SubmitTraceLink = run_trace_link_inline,
529
+ user_context: dict | None = None,
523
530
  ) -> AgenticEvalOutcome:
524
531
  """Run anomaly-detection evaluation, log to Langfuse, and raise on failure."""
525
532
  langfuse, window_start = open_trace_window(langfuse)
@@ -534,6 +541,7 @@ def evaluate_agentic_anomaly_detection(
534
541
  initial_conversation_id=initial_conversation_id,
535
542
  reasoning_effort=reasoning_effort,
536
543
  agent_id=agent_id,
544
+ user_context=user_context,
537
545
  )
538
546
 
539
547
  if langfuse is not None and dataset_item_id:
@@ -24,7 +24,7 @@ from datetime import date
24
24
  from typing import ClassVar, Literal
25
25
 
26
26
  from gooddata_sdk import GoodDataSdk
27
- from pydantic import BaseModel, Field, ValidationError
27
+ from pydantic import BaseModel, Field, ValidationError, model_validator
28
28
 
29
29
  from gooddata_eval.core.agentic._conversation_context import (
30
30
  CONFIRMATION_REPLY,
@@ -100,6 +100,12 @@ class TurnDefinition(BaseModel):
100
100
  over. ``set_answers`` are what the user replies, in order, when the assistant asks a
101
101
  legitimate question on this turn -- the only knowledge the context-mode simulated user
102
102
  has beyond the conversation itself.
103
+
104
+ ``user_context`` is the ``userContext`` (dashboard/widget scope) the user has attached
105
+ from this turn on, as in the UI: it sticks to later turns until another turn sets it,
106
+ and setting it to null clears it. A turn that leaves it out keeps the current one. Its
107
+ clarification replies carry it too, since gen-ai treats a message without a context as
108
+ cleared.
103
109
  """
104
110
 
105
111
  turn_id: str
@@ -112,6 +118,7 @@ class TurnDefinition(BaseModel):
112
118
  expected_tool_args: dict | None = None
113
119
  depends_on: list[str] = Field(default_factory=list)
114
120
  set_answers: list[str] = Field(default_factory=list)
121
+ user_context: dict | None = None
115
122
 
116
123
 
117
124
  class ConversationFixture(BaseModel):
@@ -122,6 +129,17 @@ class ConversationFixture(BaseModel):
122
129
  expected_skills: list[str]
123
130
  turns: list[TurnDefinition]
124
131
 
132
+ @model_validator(mode="before")
133
+ @classmethod
134
+ def _reject_fixture_level_user_context(cls, data: object) -> object:
135
+ # Unknown keys are ignored, so a context placed here would be dropped without a trace.
136
+ if isinstance(data, dict) and "user_context" in data:
137
+ raise ValueError(
138
+ "user_context is not read from the conversation fixture; put it in the item "
139
+ "metadata or input (the context of the first turn), or on a turn"
140
+ )
141
+ return data
142
+
125
143
 
126
144
  ReplyRecordKind = Literal["confirmation", "question", "no_action", "turn_incomplete"]
127
145
 
@@ -696,6 +714,7 @@ def run_agentic_conversation(
696
714
  mode: ConversationMode | None = None,
697
715
  clarification_judge: ClarificationJudge | None = None,
698
716
  fresh_conversation_per_turn: bool = False,
717
+ user_context: dict | None = None,
699
718
  ) -> ConversationResult:
700
719
  """Run a multi-turn, multi-skill conversation evaluation (no K-runs).
701
720
 
@@ -706,6 +725,9 @@ def run_agentic_conversation(
706
725
  ``fresh_conversation_per_turn`` sends every turn into a new conversation instead, so the
707
726
  agent sees no history. It is the no-memory baseline a context score is calibrated
708
727
  against: context-dependent turns are expected to fail there.
728
+
729
+ ``user_context`` is the context attached when the conversation starts; a turn's own
730
+ ``user_context`` replaces it from that turn on.
709
731
  """
710
732
  resolved_mode = resolve_conversation_mode(mode)
711
733
  if fresh_conversation_per_turn and initial_conversation_id is not None:
@@ -747,6 +769,7 @@ def run_agentic_conversation(
747
769
  turn_offset = 0.0
748
770
  tool_index_offset = 0
749
771
  reasoning_index_offset = 0
772
+ current_context = user_context
750
773
 
751
774
  try:
752
775
  if initial_conversation_id is not None:
@@ -761,6 +784,10 @@ def run_agentic_conversation(
761
784
  conversation_id = client.create_conversation()
762
785
  transcript = []
763
786
  active_skills = set()
787
+ # Before the skip below: a turn whose expectation cannot resolve still changed what
788
+ # the user has attached. Only a turn that names the field changes it; null clears it.
789
+ if "user_context" in turn.model_fields_set:
790
+ current_context = turn.user_context
764
791
  try:
765
792
  resolved_expected = _resolve_refs(turn.expected_output, turn_outputs)
766
793
  resolved_alternatives = [
@@ -813,7 +840,7 @@ def run_agentic_conversation(
813
840
  transcript.append(TranscriptEntry("user", current_message))
814
841
  incomplete = False
815
842
  try:
816
- chat_result = client.send_message(conversation_id, current_message)
843
+ chat_result = client.send_message(conversation_id, current_message, user_context=current_context)
817
844
  except TurnIncompleteError as exc:
818
845
  chat_result = exc.partial_result or ChatResult()
819
846
  incomplete = True
@@ -1054,6 +1081,7 @@ def evaluate_agentic_conversation(
1054
1081
  submit_trace_link: SubmitTraceLink = run_trace_link_inline,
1055
1082
  mode: ConversationMode | None = None,
1056
1083
  clarification_judge: ClarificationJudge | None = None,
1084
+ user_context: dict | None = None,
1057
1085
  ) -> AgenticEvalOutcome:
1058
1086
  """Run conversation evaluation, log to Langfuse, and raise on failure.
1059
1087
 
@@ -1078,6 +1106,7 @@ def evaluate_agentic_conversation(
1078
1106
  agent_id=agent_id,
1079
1107
  mode=resolved_mode,
1080
1108
  clarification_judge=clarification_judge,
1109
+ user_context=user_context,
1081
1110
  )
1082
1111
  passed = result.context_success if resolved_mode == "context" else result.conversation_success
1083
1112
 
@@ -955,6 +955,7 @@ def run_agentic_dashboard_skill(
955
955
  initial_conversation_id: str | None = None,
956
956
  reasoning_effort: ReasoningEffort | None = None,
957
957
  agent_id: str | None = None,
958
+ user_context: dict | None = None,
958
959
  ) -> AgenticDashboardSummary:
959
960
  """Run the dashboard-skill agentic evaluation K times and return a summary.
960
961
 
@@ -964,7 +965,12 @@ def run_agentic_dashboard_skill(
964
965
  _validate_expectation(expected_output)
965
966
  run_results: list[DashboardRunResult] = []
966
967
  client = ChatClient(
967
- host=host, token=token, workspace_id=workspace_id, reasoning_effort=reasoning_effort, agent_id=agent_id
968
+ host=host,
969
+ token=token,
970
+ workspace_id=workspace_id,
971
+ reasoning_effort=reasoning_effort,
972
+ agent_id=agent_id,
973
+ user_context=user_context,
968
974
  )
969
975
 
970
976
  try:
@@ -1021,6 +1027,7 @@ def evaluate_agentic_dashboard_skill(
1021
1027
  run_metadata_extra: dict | None = None,
1022
1028
  reasoning_effort: ReasoningEffort | None = None,
1023
1029
  submit_trace_link: SubmitTraceLink = run_trace_link_inline,
1030
+ user_context: dict | None = None,
1024
1031
  gate: EvalGate = DEFAULT_GATE,
1025
1032
  ) -> AgenticEvalOutcome:
1026
1033
  """Run dashboard-skill evaluation, log to Langfuse, and raise on failure.
@@ -1045,6 +1052,7 @@ def evaluate_agentic_dashboard_skill(
1045
1052
  initial_conversation_id=initial_conversation_id,
1046
1053
  reasoning_effort=reasoning_effort,
1047
1054
  agent_id=agent_id,
1055
+ user_context=user_context,
1048
1056
  )
1049
1057
 
1050
1058
  if langfuse is not None and dataset_item_id:
@@ -116,11 +116,10 @@ def _run_single_general_question(
116
116
  conversation_id: str,
117
117
  question: str,
118
118
  expected_output: str,
119
- user_context: dict | None = None,
120
119
  ) -> GeneralQuestionResult:
121
120
  item_started = time.monotonic()
122
121
  agent_started = time.monotonic()
123
- chat_result = client.send_message(conversation_id, question, user_context=user_context)
122
+ chat_result = client.send_message(conversation_id, question)
124
123
  actual_output = render_answer_text(chat_result)
125
124
  agent_elapsed = time.monotonic() - agent_started
126
125
  log_timer(
@@ -169,16 +168,19 @@ def run_agentic_general_question(
169
168
  """Run the general-question agentic evaluation K times and return a summary."""
170
169
  run_results: list[GeneralQuestionResult] = []
171
170
  client = ChatClient(
172
- host=host, token=token, workspace_id=workspace_id, reasoning_effort=reasoning_effort, agent_id=agent_id
171
+ host=host,
172
+ token=token,
173
+ workspace_id=workspace_id,
174
+ reasoning_effort=reasoning_effort,
175
+ agent_id=agent_id,
176
+ user_context=user_context,
173
177
  )
174
178
  judge = LLMJudge(_GENERAL_QUESTION_EVALUATION_STEPS)
175
179
 
176
180
  try:
177
181
  conv_id_0 = initial_conversation_id if initial_conversation_id is not None else client.create_conversation()
178
182
  try:
179
- run_results.append(
180
- _run_single_general_question(client, judge, conv_id_0, question, expected_output, user_context)
181
- )
183
+ run_results.append(_run_single_general_question(client, judge, conv_id_0, question, expected_output))
182
184
  finally:
183
185
  if initial_conversation_id is None:
184
186
  client.delete_conversation(conv_id_0)
@@ -186,9 +188,7 @@ def run_agentic_general_question(
186
188
  for _ in range(1, k):
187
189
  conv_id = client.create_conversation()
188
190
  try:
189
- run_results.append(
190
- _run_single_general_question(client, judge, conv_id, question, expected_output, user_context)
191
- )
191
+ run_results.append(_run_single_general_question(client, judge, conv_id, question, expected_output))
192
192
  finally:
193
193
  client.delete_conversation(conv_id)
194
194
  finally:
@@ -144,11 +144,17 @@ def run_agentic_guardrail(
144
144
  initial_conversation_id: str | None = None,
145
145
  reasoning_effort: ReasoningEffort | None = None,
146
146
  agent_id: str | None = None,
147
+ user_context: dict | None = None,
147
148
  ) -> AgenticGuardrailSummary:
148
149
  """Run the guardrail agentic evaluation K times and return a summary."""
149
150
  run_results: list[GuardrailResult] = []
150
151
  client = ChatClient(
151
- host=host, token=token, workspace_id=workspace_id, reasoning_effort=reasoning_effort, agent_id=agent_id
152
+ host=host,
153
+ token=token,
154
+ workspace_id=workspace_id,
155
+ reasoning_effort=reasoning_effort,
156
+ agent_id=agent_id,
157
+ user_context=user_context,
152
158
  )
153
159
  judge = LLMJudge(_GUARDRAIL_EVALUATION_STEPS)
154
160
 
@@ -206,6 +212,7 @@ def evaluate_agentic_guardrail(
206
212
  run_metadata_extra: dict | None = None,
207
213
  reasoning_effort: ReasoningEffort | None = None,
208
214
  submit_trace_link: SubmitTraceLink = run_trace_link_inline,
215
+ user_context: dict | None = None,
209
216
  gate: EvalGate = DEFAULT_GATE,
210
217
  ) -> AgenticEvalOutcome:
211
218
  """Run guardrail evaluation, log to Langfuse, and raise GuardrailAssertionError on failure.
@@ -226,6 +233,7 @@ def evaluate_agentic_guardrail(
226
233
  initial_conversation_id=initial_conversation_id,
227
234
  reasoning_effort=reasoning_effort,
228
235
  agent_id=agent_id,
236
+ user_context=user_context,
229
237
  )
230
238
 
231
239
  if langfuse is not None and dataset_item_id:
@@ -239,6 +239,7 @@ def run_agentic_kda_skill(
239
239
  initial_conversation_id: str | None = None,
240
240
  reasoning_effort: ReasoningEffort | None = None,
241
241
  agent_id: str | None = None,
242
+ user_context: dict | None = None,
242
243
  ) -> AgenticKdaSummary:
243
244
  """Run the KDA-skill agentic evaluation K times and return a summary.
244
245
 
@@ -256,7 +257,12 @@ def run_agentic_kda_skill(
256
257
  raise ValueError(f"k must be >= 1, got {k}")
257
258
  run_results: list[KdaRunResult] = []
258
259
  client = ChatClient(
259
- host=host, token=token, workspace_id=workspace_id, reasoning_effort=reasoning_effort, agent_id=agent_id
260
+ host=host,
261
+ token=token,
262
+ workspace_id=workspace_id,
263
+ reasoning_effort=reasoning_effort,
264
+ agent_id=agent_id,
265
+ user_context=user_context,
260
266
  )
261
267
 
262
268
  def _run_once(conv_id: str) -> KdaRunResult:
@@ -424,6 +430,7 @@ def evaluate_agentic_kda_skill(
424
430
  run_metadata_extra: dict | None = None,
425
431
  reasoning_effort: ReasoningEffort | None = None,
426
432
  submit_trace_link: SubmitTraceLink = run_trace_link_inline,
433
+ user_context: dict | None = None,
427
434
  gate: EvalGate = DEFAULT_GATE,
428
435
  ) -> AgenticEvalOutcome:
429
436
  """Run KDA-skill evaluation, log to Langfuse, and raise KdaSkillAssertionError on failure.
@@ -444,6 +451,7 @@ def evaluate_agentic_kda_skill(
444
451
  initial_conversation_id=initial_conversation_id,
445
452
  reasoning_effort=reasoning_effort,
446
453
  agent_id=agent_id,
454
+ user_context=user_context,
447
455
  )
448
456
 
449
457
  if langfuse is not None and dataset_item_id: