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