gooddata-eval 1.76.0__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.
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/PKG-INFO +51 -2
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/README.md +49 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/pyproject.toml +2 -2
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/cli/agentic_runner.py +43 -1
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/__init__.py +14 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/alert_skill.py +67 -6
- gooddata_eval-1.76.1.dev2/src/gooddata_eval/core/agentic/anomaly_detection.py +650 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/conversation.py +94 -9
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/dashboard_skill.py +9 -1
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/general_question.py +9 -9
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/guardrail.py +9 -1
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/kda_skill.py +39 -3
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/metric_skill.py +98 -3
- gooddata_eval-1.76.1.dev2/src/gooddata_eval/core/agentic/report_skill.py +850 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/search_tool.py +9 -1
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/visualization.py +92 -9
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/what_if.py +9 -1
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/chat/sse_client.py +26 -9
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/dataset/langfuse_source.py +12 -3
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/visualization.py +145 -1
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/models.py +41 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/conftest.py +7 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_alert_skill.py +251 -1
- gooddata_eval-1.76.1.dev2/tests/test_agentic_anomaly_detection.py +530 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_conversation.py +214 -8
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_general_question.py +0 -18
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_kda_skill.py +12 -1
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_metric_skill.py +184 -1
- gooddata_eval-1.76.1.dev2/tests/test_agentic_report_skill.py +843 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_runner.py +92 -9
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_visualization.py +165 -1
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_chat_render.py +42 -1
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_source.py +13 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_sse_client.py +32 -9
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_trace_linker.py +2 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_visualization_evaluator.py +160 -2
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/.gitignore +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/AGENTS.md +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/CLAUDE.md +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/LICENSE.txt +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/Makefile +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/scripts/verify_guardrail_refusal_criteria.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/_version.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/cli/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/cli/main.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/_output.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_catalog.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_conversation_context.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_gate.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_langfuse.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/_trace_linker.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/chat/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/chat/render.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/config.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/connection.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/dataset/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/dataset/from_insights.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/dataset/local.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_deep_subset.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_guardrail_criteria.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_llm_judge.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_maql.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/_text_utils.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/alert_skill.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/base.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/general_question.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/guardrail.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/metric_skill.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/search_tool.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/evaluators/summary.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/granularity.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/_env.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/client.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/experiment.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/observations.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/otlp.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/langfuse/sink.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/console.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/html_report.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/json_report.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/reporting/report_template.html +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/runner.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/scoring.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/summary/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/summary/http_client.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/timing.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/workspace.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/__init__.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/_fake_langfuse.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/fixtures/sample_dataset/metric_skill_create.json +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/fixtures/sample_dataset/visualization_revenue.json +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/fixtures/sse_visualization_stream.txt +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_dashboard_skill.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_gate.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_guardrail.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_langfuse_trace.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_observe_experiment.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_run_context.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_search_tool.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_agentic_what_if.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_alert_skill_evaluator.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_cli.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_connection.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_deep_subset.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_fake_langfuse.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_from_insights.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_guardrail_criteria.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_html_report.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_client.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_e2e_fake_server.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_env.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_experiment.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_observations.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_otlp.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_langfuse_sink.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_llm_judge.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_local_loader.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_maql_normalize.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_metric_skill_evaluator.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_models.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_reporting.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_runner.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_scoring.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_search_tool_evaluator.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_summary_client.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_summary_evaluator.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_text_evaluators.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_timing.py +0 -0
- {gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/tests/test_workspace.py +0 -0
- {gooddata_eval-1.76.0 → 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
|
@@ -12,16 +12,19 @@ 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
|
|
24
26
|
from gooddata_eval.core.config import ReasoningEffort
|
|
27
|
+
from gooddata_eval.core.evaluators.visualization import requires_execution_of
|
|
25
28
|
from gooddata_eval.core.models import AgenticEvalOutcome, CreatedVisualization, DatasetItem
|
|
26
29
|
from gooddata_eval.core.runner import EvalReport, ItemReport
|
|
27
30
|
|
|
@@ -43,11 +46,13 @@ AGENTIC_TEST_KINDS = frozenset(
|
|
|
43
46
|
"agentic_metric_skill",
|
|
44
47
|
"agentic_alert_skill",
|
|
45
48
|
"agentic_dashboard_skill",
|
|
49
|
+
"agentic_report_skill",
|
|
46
50
|
"agentic_search",
|
|
47
51
|
"agentic_general_question",
|
|
48
52
|
"agentic_guardrail",
|
|
49
53
|
"agentic_conversation",
|
|
50
54
|
"agentic_kda_skill",
|
|
55
|
+
"agentic_anomaly_detection",
|
|
51
56
|
"agentic_what_if",
|
|
52
57
|
}
|
|
53
58
|
)
|
|
@@ -92,7 +97,8 @@ PARALLEL_SAFE_TEST_KINDS = frozenset(
|
|
|
92
97
|
#
|
|
93
98
|
# agentic_dashboard_skill is absent by default rather than by evidence: gen-ai holds the draft and
|
|
94
99
|
# 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.
|
|
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.
|
|
96
102
|
WORKSPACE_MUTATING_TEST_KINDS = frozenset(AGENTIC_TEST_KINDS) - PARALLEL_SAFE_TEST_KINDS
|
|
97
103
|
|
|
98
104
|
|
|
@@ -174,9 +180,11 @@ def _dispatch_agentic(
|
|
|
174
180
|
workspace_id=workspace_id,
|
|
175
181
|
question=item.question,
|
|
176
182
|
expected_outputs=_parse_visualization_expected(eo),
|
|
183
|
+
requires_execution=requires_execution_of(eo),
|
|
177
184
|
k=k,
|
|
178
185
|
gate=gate,
|
|
179
186
|
agent_id=agent_id,
|
|
187
|
+
user_context=item.user_context,
|
|
180
188
|
**lf_kw,
|
|
181
189
|
)
|
|
182
190
|
elif kind == "agentic_metric_skill":
|
|
@@ -189,6 +197,7 @@ def _dispatch_agentic(
|
|
|
189
197
|
k=k,
|
|
190
198
|
gate=gate,
|
|
191
199
|
agent_id=agent_id,
|
|
200
|
+
user_context=item.user_context,
|
|
192
201
|
**lf_kw,
|
|
193
202
|
)
|
|
194
203
|
elif kind == "agentic_dashboard_skill":
|
|
@@ -201,6 +210,20 @@ def _dispatch_agentic(
|
|
|
201
210
|
k=k,
|
|
202
211
|
gate=gate,
|
|
203
212
|
agent_id=agent_id,
|
|
213
|
+
user_context=item.user_context,
|
|
214
|
+
**lf_kw,
|
|
215
|
+
)
|
|
216
|
+
elif kind == "agentic_report_skill":
|
|
217
|
+
return evaluate_agentic_report_skill(
|
|
218
|
+
host=host,
|
|
219
|
+
token=token,
|
|
220
|
+
workspace_id=workspace_id,
|
|
221
|
+
question=item.question,
|
|
222
|
+
expected_output=eo,
|
|
223
|
+
k=k,
|
|
224
|
+
gate=gate,
|
|
225
|
+
agent_id=agent_id,
|
|
226
|
+
user_context=item.user_context,
|
|
204
227
|
**lf_kw,
|
|
205
228
|
)
|
|
206
229
|
elif kind == "agentic_alert_skill":
|
|
@@ -213,6 +236,7 @@ def _dispatch_agentic(
|
|
|
213
236
|
k=k,
|
|
214
237
|
gate=gate,
|
|
215
238
|
agent_id=agent_id,
|
|
239
|
+
user_context=item.user_context,
|
|
216
240
|
**lf_kw,
|
|
217
241
|
)
|
|
218
242
|
elif kind == "agentic_search":
|
|
@@ -228,6 +252,7 @@ def _dispatch_agentic(
|
|
|
228
252
|
k=k,
|
|
229
253
|
gate=gate,
|
|
230
254
|
agent_id=agent_id,
|
|
255
|
+
user_context=item.user_context,
|
|
231
256
|
**lf_kw,
|
|
232
257
|
)
|
|
233
258
|
elif kind == "agentic_general_question":
|
|
@@ -253,6 +278,7 @@ def _dispatch_agentic(
|
|
|
253
278
|
k=k,
|
|
254
279
|
gate=gate,
|
|
255
280
|
agent_id=agent_id,
|
|
281
|
+
user_context=item.user_context,
|
|
256
282
|
**lf_kw,
|
|
257
283
|
)
|
|
258
284
|
elif kind == "agentic_kda_skill":
|
|
@@ -265,6 +291,7 @@ def _dispatch_agentic(
|
|
|
265
291
|
k=k,
|
|
266
292
|
gate=gate,
|
|
267
293
|
agent_id=agent_id,
|
|
294
|
+
user_context=item.user_context,
|
|
268
295
|
**lf_kw,
|
|
269
296
|
)
|
|
270
297
|
elif kind == "agentic_what_if":
|
|
@@ -276,6 +303,20 @@ def _dispatch_agentic(
|
|
|
276
303
|
expected_output=eo if isinstance(eo, dict) else {},
|
|
277
304
|
k=k,
|
|
278
305
|
agent_id=agent_id,
|
|
306
|
+
user_context=item.user_context,
|
|
307
|
+
**lf_kw,
|
|
308
|
+
)
|
|
309
|
+
elif kind == "agentic_anomaly_detection":
|
|
310
|
+
return evaluate_agentic_anomaly_detection(
|
|
311
|
+
host=host,
|
|
312
|
+
token=token,
|
|
313
|
+
workspace_id=workspace_id,
|
|
314
|
+
question=item.question,
|
|
315
|
+
expected_output=eo if isinstance(eo, dict) else {},
|
|
316
|
+
k=k,
|
|
317
|
+
gate=gate,
|
|
318
|
+
agent_id=agent_id,
|
|
319
|
+
user_context=item.user_context,
|
|
279
320
|
**lf_kw,
|
|
280
321
|
)
|
|
281
322
|
elif kind == "agentic_conversation":
|
|
@@ -286,6 +327,7 @@ def _dispatch_agentic(
|
|
|
286
327
|
workspace_id=workspace_id,
|
|
287
328
|
fixture=ConversationFixture.model_validate(fixture_data),
|
|
288
329
|
agent_id=agent_id,
|
|
330
|
+
user_context=item.user_context,
|
|
289
331
|
**lf_kw,
|
|
290
332
|
)
|
|
291
333
|
else:
|
{gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/__init__.py
RENAMED
|
@@ -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
|
]
|
{gooddata_eval-1.76.0 → gooddata_eval-1.76.1.dev2}/src/gooddata_eval/core/agentic/alert_skill.py
RENAMED
|
@@ -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
|
|
@@ -652,12 +659,18 @@ def run_agentic_alert_skill(
|
|
|
652
659
|
initial_conversation_id: str | None = None,
|
|
653
660
|
reasoning_effort: ReasoningEffort | None = None,
|
|
654
661
|
agent_id: str | None = None,
|
|
662
|
+
user_context: dict | None = None,
|
|
655
663
|
) -> AgenticAlertSummary:
|
|
656
664
|
"""Run the alert-skill agentic evaluation K times and return a summary."""
|
|
657
665
|
expected = _normalize_expected_output(expected_output)
|
|
658
666
|
run_results: list[AlertRunResult] = []
|
|
659
667
|
client = ChatClient(
|
|
660
|
-
host=host,
|
|
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,
|
|
661
674
|
)
|
|
662
675
|
sdk = GoodDataSdk.create(host, token)
|
|
663
676
|
|
|
@@ -681,8 +694,35 @@ def run_agentic_alert_skill(
|
|
|
681
694
|
turns = 0
|
|
682
695
|
steps = 0
|
|
683
696
|
|
|
697
|
+
# Defaults to BUDGET_EXHAUSTED: every other exit sets it explicitly, so a loop
|
|
698
|
+
# that simply runs out of range() is correctly labelled without a trailing else.
|
|
699
|
+
exit_reason = LoopExit.BUDGET_EXHAUSTED
|
|
700
|
+
turns_used = 0
|
|
684
701
|
for _iteration in range(max_iterations):
|
|
685
|
-
|
|
702
|
+
turns_used = _iteration + 1
|
|
703
|
+
try:
|
|
704
|
+
chat_result = client.send_message(conv_id, current_question)
|
|
705
|
+
except ChatError as exc:
|
|
706
|
+
# Without this the exception escapes run_agentic_alert_skill entirely,
|
|
707
|
+
# discarding every K-run already completed along with any exit_reason.
|
|
708
|
+
print(f"[CHAT] send_message failed for conversation {conv_id}: {exc}")
|
|
709
|
+
# The stream can break AFTER create_metric_alert already succeeded
|
|
710
|
+
# server-side. That id only ever reached alert_id_to_delete from the
|
|
711
|
+
# normal path below, so breaking here left the alert in the workspace for
|
|
712
|
+
# the `finally` cleanup to miss -- a real object leaking out of a failed
|
|
713
|
+
# run, not a reporting gap.
|
|
714
|
+
partial = exc.partial_result
|
|
715
|
+
if partial is not None:
|
|
716
|
+
reasoning_steps.extend(partial.reasoning_steps or [])
|
|
717
|
+
response_id = partial.response_id or response_id
|
|
718
|
+
partial_alert_id, _, partial_tool_called = _extract_alert_call(partial.tool_call_events or [])
|
|
719
|
+
if partial_tool_called:
|
|
720
|
+
alert_id_to_delete = partial_alert_id
|
|
721
|
+
exit_reason = LoopExit.CHAT_ERROR
|
|
722
|
+
break
|
|
723
|
+
# `turns_used` counts attempts (set above, so a failed send still shows the
|
|
724
|
+
# loop reached this iteration); `turns`/`steps` count completed work, so they
|
|
725
|
+
# advance only once send_message has actually returned.
|
|
686
726
|
turns += 1
|
|
687
727
|
steps += chat_result.reasoning_step_count
|
|
688
728
|
reasoning_steps.extend(chat_result.reasoning_steps or [])
|
|
@@ -698,6 +738,7 @@ def run_agentic_alert_skill(
|
|
|
698
738
|
alert_id, actual_args, tool_called = _extract_alert_call(chat_result.tool_call_events or [])
|
|
699
739
|
if tool_called:
|
|
700
740
|
alert_id_to_delete = alert_id
|
|
741
|
+
exit_reason = LoopExit.SUCCESS
|
|
701
742
|
break
|
|
702
743
|
response_text = (chat_result.text_response or "").strip()
|
|
703
744
|
if not response_text and chat_result.alert_proposals:
|
|
@@ -706,13 +747,24 @@ def run_agentic_alert_skill(
|
|
|
706
747
|
response_text = render_answer_text(chat_result)
|
|
707
748
|
# Stop if agent gave a completely empty response (stuck)
|
|
708
749
|
if not response_text and not chat_result.tool_call_events:
|
|
750
|
+
exit_reason = LoopExit.AGENT_SILENT
|
|
709
751
|
break
|
|
710
752
|
# Stop before generating a follow-up for the last iteration
|
|
711
753
|
if _iteration >= max_iterations - 1:
|
|
712
754
|
break
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
755
|
+
# Recorded rather than raised, matching metric_skill and kda_skill. Letting it
|
|
756
|
+
# propagate did keep a harness fault from being scored as a content failure,
|
|
757
|
+
# but it also discarded the K-runs already completed -- and SIMULATED_USER_FAILED
|
|
758
|
+
# achieves the same separation while keeping them, since reporting reads the
|
|
759
|
+
# exit reason to classify the run as an error rather than an agent failure.
|
|
760
|
+
try:
|
|
761
|
+
follow_up = generate_simulated_alert_response(
|
|
762
|
+
response_text, expected, conversation_history, question=question
|
|
763
|
+
)
|
|
764
|
+
except Exception as exc: # noqa: BLE001 -- harness-side fault; end only this run
|
|
765
|
+
print(f"[SIM-USER] Simulated reply failed for conversation {conv_id}: {exc}")
|
|
766
|
+
exit_reason = LoopExit.SIMULATED_USER_FAILED
|
|
767
|
+
break
|
|
716
768
|
# Record this exchange so the next call has full history
|
|
717
769
|
conversation_history.append({"role": "assistant", "content": response_text})
|
|
718
770
|
conversation_history.append({"role": "user", "content": follow_up})
|
|
@@ -740,6 +792,8 @@ def run_agentic_alert_skill(
|
|
|
740
792
|
response_id=response_id,
|
|
741
793
|
tool_call_events=all_tool_call_events,
|
|
742
794
|
reasoning_step_events=all_reasoning_step_events,
|
|
795
|
+
exit_reason=exit_reason,
|
|
796
|
+
turns_used=turns_used,
|
|
743
797
|
)
|
|
744
798
|
finally:
|
|
745
799
|
if alert_id_to_delete:
|
|
@@ -810,6 +864,7 @@ def evaluate_agentic_alert_skill(
|
|
|
810
864
|
run_metadata_extra: dict | None = None,
|
|
811
865
|
reasoning_effort: ReasoningEffort | None = None,
|
|
812
866
|
submit_trace_link: SubmitTraceLink = run_trace_link_inline,
|
|
867
|
+
user_context: dict | None = None,
|
|
813
868
|
gate: EvalGate = DEFAULT_GATE,
|
|
814
869
|
) -> AgenticEvalOutcome:
|
|
815
870
|
"""Run alert-skill evaluation, log to Langfuse, and raise AlertSkillAssertionError on failure.
|
|
@@ -833,6 +888,7 @@ def evaluate_agentic_alert_skill(
|
|
|
833
888
|
initial_conversation_id=initial_conversation_id,
|
|
834
889
|
reasoning_effort=reasoning_effort,
|
|
835
890
|
agent_id=agent_id,
|
|
891
|
+
user_context=user_context,
|
|
836
892
|
)
|
|
837
893
|
|
|
838
894
|
if langfuse is not None and dataset_item_id:
|
|
@@ -908,6 +964,11 @@ def evaluate_agentic_alert_skill(
|
|
|
908
964
|
"attributes_correct": ev.attributes_correct,
|
|
909
965
|
"granularity_correct": ev.granularity_correct,
|
|
910
966
|
"actual_alert_arguments": best.actual_alert_arguments,
|
|
967
|
+
# Why the loop stopped. alert_created=False alone cannot tell a refusal from a run
|
|
968
|
+
# that hit max_iterations while still on track -- see LoopExit.
|
|
969
|
+
"exit_reason": best.exit_reason.value,
|
|
970
|
+
"turns_used": best.turns_used,
|
|
971
|
+
"max_iterations": max_iterations,
|
|
911
972
|
**timeline_detail(best.tool_call_events, best.reasoning_step_events),
|
|
912
973
|
}
|
|
913
974
|
|