gooddata-eval 1.69.0__tar.gz → 1.69.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.69.0 → gooddata_eval-1.69.1.dev2}/PKG-INFO +2 -2
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/pyproject.toml +3 -2
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/cli/agentic_runner.py +9 -12
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/cli/main.py +14 -2
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/_langfuse.py +20 -3
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/alert_skill.py +22 -10
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/conversation.py +19 -1
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/general_question.py +5 -1
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/guardrail.py +5 -1
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/metric_skill.py +88 -30
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/search_tool.py +5 -1
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/visualization.py +5 -1
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/chat/sse_client.py +32 -5
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/config.py +1 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/alert_skill.py +3 -1
- gooddata_eval-1.69.1.dev2/src/gooddata_eval/core/evaluators/search_tool.py +59 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/models.py +2 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/reporting/json_report.py +2 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/runner.py +7 -1
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_agentic_alert_skill.py +20 -2
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_agentic_conversation.py +123 -2
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_agentic_general_question.py +0 -2
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_agentic_guardrail.py +0 -2
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_agentic_metric_skill.py +80 -0
- gooddata_eval-1.69.1.dev2/tests/test_agentic_run_context.py +32 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_agentic_search_tool.py +0 -2
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_agentic_visualization.py +5 -8
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_alert_skill_evaluator.py +17 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_cli.py +67 -8
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_langfuse_source.py +2 -1
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_runner.py +25 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_sse_client.py +118 -0
- gooddata_eval-1.69.1.dev2/tox.ini +16 -0
- gooddata_eval-1.69.0/src/gooddata_eval/core/evaluators/search_tool.py +0 -40
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/.gitignore +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/LICENSE.txt +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/Makefile +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/README.md +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/_version.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/cli/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/_catalog.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/chat/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/connection.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/dataset/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/dataset/langfuse_source.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/dataset/local.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/_deep_subset.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/_llm_judge.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/_text_utils.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/base.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/general_question.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/guardrail.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/metric_skill.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/summary.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/evaluators/visualization.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/langfuse/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/langfuse/sink.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/reporting/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/reporting/console.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/scoring.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/summary/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/summary/http_client.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/workspace.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/__init__.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/conftest.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/fixtures/sample_dataset/metric_skill_create.json +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/fixtures/sample_dataset/visualization_revenue.json +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/fixtures/sse_visualization_stream.txt +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_connection.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_deep_subset.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_langfuse_sink.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_llm_judge.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_local_loader.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_metric_skill_evaluator.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_models.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_reporting.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_scoring.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_search_tool_evaluator.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_summary_client.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_summary_evaluator.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_text_evaluators.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_visualization_evaluator.py +0 -0
- {gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gooddata-eval
|
|
3
|
-
Version: 1.69.
|
|
3
|
+
Version: 1.69.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.69.
|
|
20
|
+
Requires-Dist: gooddata-sdk~=1.69.1.dev2
|
|
21
21
|
Requires-Dist: httpx<1.0,>=0.27
|
|
22
22
|
Requires-Dist: orjson<4.0.0,>=3.9.15
|
|
23
23
|
Requires-Dist: pydantic<3.0,>=2.6
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# (C) 2026 GoodData Corporation
|
|
2
2
|
[project]
|
|
3
3
|
name = "gooddata-eval"
|
|
4
|
-
version = "1.69.
|
|
4
|
+
version = "1.69.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.69.
|
|
14
|
+
"gooddata-sdk~=1.69.1.dev2",
|
|
15
15
|
"httpx>=0.27,<1.0",
|
|
16
16
|
"orjson>=3.9.15,<4.0.0",
|
|
17
17
|
"pydantic>=2.6,<3.0",
|
|
@@ -45,6 +45,7 @@ dev = [
|
|
|
45
45
|
test = [
|
|
46
46
|
"pytest~=8.3.4",
|
|
47
47
|
"pytest-cov~=6.0.0",
|
|
48
|
+
"pytest-json-report==1.5.0",
|
|
48
49
|
"pytest-mock>=3.14.0",
|
|
49
50
|
]
|
|
50
51
|
|
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
import time
|
|
7
7
|
from typing import Any, TypedDict
|
|
8
8
|
|
|
9
|
-
from gooddata_eval.core.agentic._langfuse import
|
|
9
|
+
from gooddata_eval.core.agentic._langfuse import make_langfuse_client
|
|
10
10
|
from gooddata_eval.core.agentic.alert_skill import evaluate_agentic_alert_skill
|
|
11
11
|
from gooddata_eval.core.agentic.conversation import ConversationFixture, evaluate_agentic_conversation
|
|
12
12
|
from gooddata_eval.core.agentic.general_question import evaluate_agentic_general_question
|
|
@@ -17,17 +17,14 @@ from gooddata_eval.core.agentic.visualization import evaluate_agentic_visualizat
|
|
|
17
17
|
from gooddata_eval.core.models import CreatedVisualization, DatasetItem
|
|
18
18
|
from gooddata_eval.core.runner import EvalReport, ItemReport
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
},
|
|
29
|
-
total=False,
|
|
30
|
-
)
|
|
20
|
+
|
|
21
|
+
class _LfKw(TypedDict, total=False):
|
|
22
|
+
langfuse: Any
|
|
23
|
+
dataset_item_id: str
|
|
24
|
+
dataset_name: str
|
|
25
|
+
run_timestamp: str
|
|
26
|
+
model_version_override: str | None
|
|
27
|
+
|
|
31
28
|
|
|
32
29
|
AGENTIC_TEST_KINDS = frozenset(
|
|
33
30
|
{
|
|
@@ -12,6 +12,7 @@ from gooddata_api_client.exceptions import ApiException
|
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
|
|
15
|
+
from gooddata_eval.cli.agentic_runner import AGENTIC_TEST_KINDS, run_agentic_items
|
|
15
16
|
from gooddata_eval.core.chat.sse_client import ChatClient
|
|
16
17
|
from gooddata_eval.core.config import RunConfig
|
|
17
18
|
from gooddata_eval.core.connection import ConnectionError_, resolve_connection
|
|
@@ -20,7 +21,6 @@ from gooddata_eval.core.langfuse.sink import LangfuseSink
|
|
|
20
21
|
from gooddata_eval.core.models import ChatResult, DatasetItem
|
|
21
22
|
from gooddata_eval.core.reporting.console import render_comparison, render_console
|
|
22
23
|
from gooddata_eval.core.reporting.json_report import write_multi_model_report
|
|
23
|
-
from gooddata_eval.cli.agentic_runner import AGENTIC_TEST_KINDS, run_agentic_items
|
|
24
24
|
from gooddata_eval.core.runner import ItemReport, run_items
|
|
25
25
|
from gooddata_eval.core.summary.http_client import SummaryClient
|
|
26
26
|
from gooddata_eval.core.workspace import ModelResolutionError, WorkspaceModelController
|
|
@@ -98,6 +98,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
98
98
|
)
|
|
99
99
|
run.add_argument("--json", dest="json_path", help="Write a JSON report to this path.")
|
|
100
100
|
run.add_argument("--quiet", action="store_true", help="Suppress per-item progress output.")
|
|
101
|
+
run.add_argument(
|
|
102
|
+
"--preserve-failed",
|
|
103
|
+
action="store_true",
|
|
104
|
+
dest="preserve_failed",
|
|
105
|
+
help="Keep failed conversations on the server for post-mortem inspection.",
|
|
106
|
+
)
|
|
101
107
|
run.add_argument(
|
|
102
108
|
"--langfuse",
|
|
103
109
|
action="store_true",
|
|
@@ -331,7 +337,12 @@ def _run(config: RunConfig) -> int:
|
|
|
331
337
|
|
|
332
338
|
# --- non-agentic items (single-turn, use Evaluator) ---
|
|
333
339
|
backend = _RoutingBackend(
|
|
334
|
-
ChatClient(
|
|
340
|
+
ChatClient(
|
|
341
|
+
host=config.host,
|
|
342
|
+
token=config.token,
|
|
343
|
+
workspace_id=config.workspace_id,
|
|
344
|
+
preserve_failed=config.preserve_failed,
|
|
345
|
+
),
|
|
335
346
|
SummaryClient(host=config.host, token=config.token, workspace_id=config.workspace_id),
|
|
336
347
|
)
|
|
337
348
|
try:
|
|
@@ -421,6 +432,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
421
432
|
log_to_langfuse=args.langfuse,
|
|
422
433
|
quiet=args.quiet,
|
|
423
434
|
kind=args.kind,
|
|
435
|
+
preserve_failed=args.preserve_failed,
|
|
424
436
|
)
|
|
425
437
|
return _run(config)
|
|
426
438
|
except (
|
{gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/_langfuse.py
RENAMED
|
@@ -305,7 +305,7 @@ def observe(
|
|
|
305
305
|
run_name=run_name,
|
|
306
306
|
dataset_item_id=dataset_item_id,
|
|
307
307
|
trace_id=trace_id,
|
|
308
|
-
metadata=run_metadata or {
|
|
308
|
+
metadata=run_metadata or {},
|
|
309
309
|
run_description="",
|
|
310
310
|
)
|
|
311
311
|
_log.debug(
|
|
@@ -383,14 +383,31 @@ def build_run_context(
|
|
|
383
383
|
dataset_name: str,
|
|
384
384
|
run_timestamp: str | None,
|
|
385
385
|
model_version_override: str | None,
|
|
386
|
+
run_metadata_extra: dict[str, Any] | None = None,
|
|
386
387
|
) -> tuple[str, dict[str, Any]]:
|
|
387
|
-
"""Return (run_name_base, run_metadata) with model version resolved from workspace API.
|
|
388
|
+
"""Return (run_name_base, run_metadata) with model version resolved from workspace API.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
host: GoodData host URL.
|
|
392
|
+
token: API token used to resolve the workspace model version.
|
|
393
|
+
workspace_id: Workspace whose active LLM provider is read when no override is given.
|
|
394
|
+
dataset_name: Langfuse dataset name; used as the run-name prefix.
|
|
395
|
+
run_timestamp: Shared run timestamp for the run name (falls back to the current time).
|
|
396
|
+
model_version_override: Explicit model-version tag; when set it wins over the
|
|
397
|
+
workspace lookup.
|
|
398
|
+
run_metadata_extra: Optional extra key/values added to the dataset-run metadata
|
|
399
|
+
(e.g. a testing-framework tag or a CI run id for scoping). Default None keeps
|
|
400
|
+
behavior unchanged. The SDK-derived model_version is applied last and cannot
|
|
401
|
+
be overwritten by this dict.
|
|
402
|
+
"""
|
|
388
403
|
model = get_model_version(host, token, workspace_id, model_version_override)
|
|
389
404
|
ts = run_timestamp or datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
390
405
|
base = f"{dataset_name}_{ts}"
|
|
391
406
|
if model:
|
|
392
407
|
base = f"{base}_{model}"
|
|
393
|
-
|
|
408
|
+
# Caller supplies its own run tags (e.g. testing_framework); model_version is applied
|
|
409
|
+
# last so the SDK-derived value cannot be overwritten by run_metadata_extra.
|
|
410
|
+
metadata: dict[str, Any] = dict(run_metadata_extra) if run_metadata_extra else {}
|
|
394
411
|
if model:
|
|
395
412
|
metadata["model_version"] = model
|
|
396
413
|
return base, metadata
|
{gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/alert_skill.py
RENAMED
|
@@ -7,12 +7,12 @@ import json
|
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
|
|
11
10
|
from typing import Any
|
|
12
11
|
|
|
13
|
-
from
|
|
14
|
-
from gooddata_eval.core.agentic._catalog import CatalogMetricAlert
|
|
12
|
+
from gooddata_sdk import GoodDataSdk
|
|
15
13
|
|
|
14
|
+
from gooddata_eval.core.agentic._catalog import CatalogMetricAlert
|
|
15
|
+
from gooddata_eval.core.chat.sse_client import ChatClient
|
|
16
16
|
from gooddata_eval.core.models import ToolCallEvent
|
|
17
17
|
|
|
18
18
|
try:
|
|
@@ -80,7 +80,11 @@ def _check_trigger(expected: CatalogMetricAlert, actual_args: dict) -> bool:
|
|
|
80
80
|
if expected.operator == "ANOMALY":
|
|
81
81
|
return True
|
|
82
82
|
exp_trigger = expected.trigger
|
|
83
|
-
|
|
83
|
+
# A missing OR explicit-null trigger means "unset" -> the product persists the
|
|
84
|
+
# default ALWAYS ("Every time"). `.get(k, default)` only returns the default when
|
|
85
|
+
# the key is ABSENT, but create_metric_alert serialises unset params as
|
|
86
|
+
# `trigger: null`, so chain with `or` to also cover the present-but-None case.
|
|
87
|
+
act_trigger = actual_args.get("trigger") or actual_args.get("triggerMode") or "ALWAYS"
|
|
84
88
|
if exp_trigger in _ALWAYS_TRIGGER_VALUES:
|
|
85
89
|
return act_trigger in {"ALWAYS", "Every time"}
|
|
86
90
|
act_api = _TRIGGER_DISPLAY_TO_API.get(act_trigger, act_trigger)
|
|
@@ -181,11 +185,14 @@ def generate_simulated_alert_response(
|
|
|
181
185
|
return response.choices[0].message.content or ""
|
|
182
186
|
|
|
183
187
|
|
|
184
|
-
def _delete_alert(
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
def _delete_alert(sdk: GoodDataSdk, workspace_id: str, alert_id: str) -> None:
|
|
189
|
+
"""Best-effort delete of an alert (automation) created during evaluation.
|
|
190
|
+
|
|
191
|
+
Uses the GoodData SDK entities API rather than reimplementing the REST call.
|
|
192
|
+
Failures are logged, not raised.
|
|
193
|
+
"""
|
|
187
194
|
try:
|
|
188
|
-
|
|
195
|
+
sdk._client.entities_api.delete_entity_automations(workspace_id, alert_id)
|
|
189
196
|
except Exception as exc:
|
|
190
197
|
print(f"[CLEANUP] Failed to delete alert {alert_id}: {exc}")
|
|
191
198
|
|
|
@@ -325,6 +332,7 @@ def run_agentic_alert_skill(
|
|
|
325
332
|
expected = _normalize_expected_output(expected_output)
|
|
326
333
|
run_results: list[AlertRunResult] = []
|
|
327
334
|
client = ChatClient(host=host, token=token, workspace_id=workspace_id)
|
|
335
|
+
sdk = GoodDataSdk.create(host, token)
|
|
328
336
|
|
|
329
337
|
def _run_once(conv_id: str) -> AlertRunResult:
|
|
330
338
|
alert_id_to_delete: str | None = None
|
|
@@ -373,7 +381,7 @@ def run_agentic_alert_skill(
|
|
|
373
381
|
)
|
|
374
382
|
finally:
|
|
375
383
|
if alert_id_to_delete:
|
|
376
|
-
_delete_alert(
|
|
384
|
+
_delete_alert(sdk, workspace_id, alert_id_to_delete)
|
|
377
385
|
|
|
378
386
|
try:
|
|
379
387
|
conv_id_0 = initial_conversation_id if initial_conversation_id is not None else client.create_conversation()
|
|
@@ -436,9 +444,12 @@ def evaluate_agentic_alert_skill(
|
|
|
436
444
|
dataset_name: str = "alert_skill",
|
|
437
445
|
run_timestamp: str | None = None,
|
|
438
446
|
model_version_override: str | None = None,
|
|
447
|
+
run_metadata_extra: dict | None = None,
|
|
439
448
|
) -> None:
|
|
440
449
|
"""Run alert-skill evaluation, log to Langfuse, and raise AlertSkillAssertionError on failure."""
|
|
441
|
-
from datetime import datetime as _dt
|
|
450
|
+
from datetime import datetime as _dt # noqa: PLC0415
|
|
451
|
+
from datetime import timezone as _tz # noqa: PLC0415
|
|
452
|
+
|
|
442
453
|
from gooddata_eval.core.agentic._langfuse import try_make_langfuse_client # noqa: PLC0415
|
|
443
454
|
|
|
444
455
|
if langfuse is None:
|
|
@@ -471,6 +482,7 @@ def evaluate_agentic_alert_skill(
|
|
|
471
482
|
dataset_name,
|
|
472
483
|
run_timestamp,
|
|
473
484
|
model_version_override,
|
|
485
|
+
run_metadata_extra,
|
|
474
486
|
)
|
|
475
487
|
traces_by_conv = find_traces_per_conversation(
|
|
476
488
|
langfuse,
|
{gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/conversation.py
RENAMED
|
@@ -8,8 +8,10 @@ import re
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from typing import Literal
|
|
10
10
|
|
|
11
|
+
from gooddata_sdk import GoodDataSdk
|
|
11
12
|
from pydantic import BaseModel
|
|
12
13
|
|
|
14
|
+
from gooddata_eval.core.agentic.metric_skill import _delete_metric, _extract_created_metric_ids
|
|
13
15
|
from gooddata_eval.core.chat.sse_client import ChatClient
|
|
14
16
|
from gooddata_eval.core.models import ChatResult, ToolCallEvent
|
|
15
17
|
from gooddata_eval.core.scoring import (
|
|
@@ -283,11 +285,16 @@ def run_agentic_conversation(
|
|
|
283
285
|
replies before the agent produces the expected output.
|
|
284
286
|
"""
|
|
285
287
|
client = ChatClient(host=host, token=token, workspace_id=workspace_id)
|
|
288
|
+
sdk = GoodDataSdk.create(host, token)
|
|
286
289
|
turn_results: list[TurnResult] = []
|
|
287
290
|
turn_outputs: dict[str, dict] = {}
|
|
288
291
|
total_clarification_turns = 0
|
|
289
292
|
conversation_id: str = ""
|
|
290
293
|
owns_conversation = False
|
|
294
|
+
# Metrics created during this conversation, deleted after it completes so they do
|
|
295
|
+
# not persist in the (shared) workspace and get reused by a later test. Deferred to
|
|
296
|
+
# the end — a later turn may $ref a metric an earlier turn created.
|
|
297
|
+
created_metric_ids: list[str] = []
|
|
291
298
|
|
|
292
299
|
try:
|
|
293
300
|
if initial_conversation_id is not None:
|
|
@@ -335,6 +342,11 @@ def run_agentic_conversation(
|
|
|
335
342
|
if metric_data:
|
|
336
343
|
turn_outputs[turn.turn_id] = metric_data
|
|
337
344
|
|
|
345
|
+
# Track every metric created this turn (any turn may create one) for cleanup.
|
|
346
|
+
for metric_id in _extract_created_metric_ids(all_tool_calls):
|
|
347
|
+
if metric_id not in created_metric_ids:
|
|
348
|
+
created_metric_ids.append(metric_id)
|
|
349
|
+
|
|
338
350
|
turn_results.append(
|
|
339
351
|
TurnResult(
|
|
340
352
|
turn_id=turn.turn_id,
|
|
@@ -351,6 +363,8 @@ def run_agentic_conversation(
|
|
|
351
363
|
finally:
|
|
352
364
|
if owns_conversation and conversation_id:
|
|
353
365
|
client.delete_conversation(conversation_id)
|
|
366
|
+
for metric_id in created_metric_ids:
|
|
367
|
+
_delete_metric(sdk, workspace_id, metric_id)
|
|
354
368
|
client.close()
|
|
355
369
|
|
|
356
370
|
activated_all = {skill for tr in turn_results for skill in tr.activated_skills}
|
|
@@ -384,9 +398,12 @@ def evaluate_agentic_conversation(
|
|
|
384
398
|
dataset_name: str = "conversation",
|
|
385
399
|
run_timestamp: str | None = None,
|
|
386
400
|
model_version_override: str | None = None,
|
|
401
|
+
run_metadata_extra: dict | None = None,
|
|
387
402
|
) -> None:
|
|
388
403
|
"""Run conversation evaluation, log to Langfuse, and raise on failure."""
|
|
389
|
-
from datetime import datetime as _dt
|
|
404
|
+
from datetime import datetime as _dt # noqa: PLC0415
|
|
405
|
+
from datetime import timezone as _tz # noqa: PLC0415
|
|
406
|
+
|
|
390
407
|
from gooddata_eval.core.agentic._langfuse import try_make_langfuse_client # noqa: PLC0415
|
|
391
408
|
|
|
392
409
|
if langfuse is None:
|
|
@@ -417,6 +434,7 @@ def evaluate_agentic_conversation(
|
|
|
417
434
|
dataset_name or fixture.dataset_name,
|
|
418
435
|
run_timestamp,
|
|
419
436
|
model_version_override,
|
|
437
|
+
run_metadata_extra,
|
|
420
438
|
)
|
|
421
439
|
traces_by_conv = find_traces_per_conversation(
|
|
422
440
|
langfuse,
|
|
@@ -152,9 +152,12 @@ def evaluate_agentic_general_question(
|
|
|
152
152
|
dataset_name: str = "general_question",
|
|
153
153
|
run_timestamp: str | None = None,
|
|
154
154
|
model_version_override: str | None = None,
|
|
155
|
+
run_metadata_extra: dict | None = None,
|
|
155
156
|
) -> None:
|
|
156
157
|
"""Run general-question evaluation, log to Langfuse, and raise on failure."""
|
|
157
|
-
from datetime import datetime as _dt
|
|
158
|
+
from datetime import datetime as _dt # noqa: PLC0415
|
|
159
|
+
from datetime import timezone as _tz # noqa: PLC0415
|
|
160
|
+
|
|
158
161
|
from gooddata_eval.core.agentic._langfuse import try_make_langfuse_client # noqa: PLC0415
|
|
159
162
|
|
|
160
163
|
if langfuse is None:
|
|
@@ -186,6 +189,7 @@ def evaluate_agentic_general_question(
|
|
|
186
189
|
dataset_name,
|
|
187
190
|
run_timestamp,
|
|
188
191
|
model_version_override,
|
|
192
|
+
run_metadata_extra,
|
|
189
193
|
)
|
|
190
194
|
traces_by_conv = find_traces_per_conversation(
|
|
191
195
|
langfuse,
|
{gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/guardrail.py
RENAMED
|
@@ -149,9 +149,12 @@ def evaluate_agentic_guardrail(
|
|
|
149
149
|
dataset_name: str = "guardrail",
|
|
150
150
|
run_timestamp: str | None = None,
|
|
151
151
|
model_version_override: str | None = None,
|
|
152
|
+
run_metadata_extra: dict | None = None,
|
|
152
153
|
) -> None:
|
|
153
154
|
"""Run guardrail evaluation, log to Langfuse, and raise on failure."""
|
|
154
|
-
from datetime import datetime as _dt
|
|
155
|
+
from datetime import datetime as _dt # noqa: PLC0415
|
|
156
|
+
from datetime import timezone as _tz # noqa: PLC0415
|
|
157
|
+
|
|
155
158
|
from gooddata_eval.core.agentic._langfuse import try_make_langfuse_client # noqa: PLC0415
|
|
156
159
|
|
|
157
160
|
if langfuse is None:
|
|
@@ -183,6 +186,7 @@ def evaluate_agentic_guardrail(
|
|
|
183
186
|
dataset_name,
|
|
184
187
|
run_timestamp,
|
|
185
188
|
model_version_override,
|
|
189
|
+
run_metadata_extra,
|
|
186
190
|
)
|
|
187
191
|
traces_by_conv = find_traces_per_conversation(
|
|
188
192
|
langfuse,
|
{gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/metric_skill.py
RENAMED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
6
|
import os
|
|
9
7
|
import re
|
|
10
8
|
from dataclasses import dataclass
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from gooddata_sdk import GoodDataSdk
|
|
11
12
|
|
|
12
13
|
from gooddata_eval.core.chat.sse_client import ChatClient
|
|
13
14
|
from gooddata_eval.core.models import ToolCallEvent
|
|
@@ -128,6 +129,41 @@ def _extract_metric_result(tool_call_events: list[ToolCallEvent]) -> dict | None
|
|
|
128
129
|
return None
|
|
129
130
|
|
|
130
131
|
|
|
132
|
+
def _extract_created_metric_ids(tool_call_events: list[ToolCallEvent]) -> list[str]:
|
|
133
|
+
"""Ids of every metric created by ``create_metric`` calls (a turn may create more than one).
|
|
134
|
+
|
|
135
|
+
Used for cleanup so no created metric leaks — unlike ``_extract_metric_result``, which
|
|
136
|
+
returns only the first result for MAQL evaluation. Shared with conversation evaluation.
|
|
137
|
+
"""
|
|
138
|
+
metric_ids: list[str] = []
|
|
139
|
+
for tc in tool_call_events:
|
|
140
|
+
if tc.function_name != "create_metric" or not tc.result:
|
|
141
|
+
continue
|
|
142
|
+
result_data = tc.parsed_result()
|
|
143
|
+
if not result_data:
|
|
144
|
+
continue
|
|
145
|
+
data = result_data.get("data", result_data)
|
|
146
|
+
metric_id = data.get("metric_id") if isinstance(data, dict) else None
|
|
147
|
+
if metric_id and metric_id not in metric_ids:
|
|
148
|
+
metric_ids.append(metric_id)
|
|
149
|
+
return metric_ids
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _delete_metric(sdk: GoodDataSdk, workspace_id: str, metric_id: str) -> None:
|
|
153
|
+
"""Delete a metric created during evaluation.
|
|
154
|
+
|
|
155
|
+
Eval runs share a persistent workspace, so a metric left behind is picked up by
|
|
156
|
+
a later test — the agent reuses it (returning ``SELECT {id}`` instead of full
|
|
157
|
+
MAQL) and the assertion fails. Deleting the created metric on the way out keeps
|
|
158
|
+
the workspace clean for the next run. Best-effort: failures are logged, not raised.
|
|
159
|
+
Mirrors ``alert_skill._delete_alert``.
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
sdk._client.entities_api.delete_entity_metrics(workspace_id, metric_id)
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
print(f"[CLEANUP] Failed to delete metric {metric_id}: {exc}")
|
|
165
|
+
|
|
166
|
+
|
|
131
167
|
def _is_asking_clarification(text: str) -> bool:
|
|
132
168
|
if not text:
|
|
133
169
|
return False
|
|
@@ -137,41 +173,54 @@ def _is_asking_clarification(text: str) -> bool:
|
|
|
137
173
|
|
|
138
174
|
def _execute_single_metric_run(
|
|
139
175
|
client: ChatClient,
|
|
176
|
+
sdk: GoodDataSdk,
|
|
177
|
+
workspace_id: str,
|
|
140
178
|
conversation_id: str,
|
|
141
179
|
question: str,
|
|
142
180
|
expected_outputs: list[dict],
|
|
143
181
|
max_iterations: int,
|
|
144
182
|
) -> MetricRunResult:
|
|
145
|
-
"""Drive one full multi-turn metric-skill conversation and evaluate the result.
|
|
183
|
+
"""Drive one full multi-turn metric-skill conversation and evaluate the result.
|
|
184
|
+
|
|
185
|
+
Any metric the agent creates during this run is deleted on the way out (see
|
|
186
|
+
``_delete_metric``) so it cannot leak into — and be reused by — a later test
|
|
187
|
+
sharing the workspace.
|
|
188
|
+
"""
|
|
146
189
|
primary_expected = expected_outputs[0] if expected_outputs else {}
|
|
147
190
|
metric_result: dict | None = None
|
|
191
|
+
metric_id_to_delete: str | None = None
|
|
148
192
|
turns = 0
|
|
149
193
|
current_question = question
|
|
150
194
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
195
|
+
try:
|
|
196
|
+
for _iteration in range(max_iterations):
|
|
197
|
+
turns += 1
|
|
198
|
+
chat_result = client.send_message(conversation_id, current_question)
|
|
199
|
+
candidate = _extract_metric_result(chat_result.tool_call_events or [])
|
|
200
|
+
if candidate is not None:
|
|
201
|
+
metric_result = candidate
|
|
202
|
+
metric_id_to_delete = candidate.get("metric_id")
|
|
203
|
+
break
|
|
204
|
+
response_text = (chat_result.text_response or "").strip()
|
|
205
|
+
if _is_asking_clarification(response_text):
|
|
206
|
+
current_question = generate_simulated_response(response_text, primary_expected)
|
|
207
|
+
else:
|
|
208
|
+
break
|
|
209
|
+
|
|
210
|
+
actual_maql = (metric_result or {}).get("maql", "")
|
|
211
|
+
metric_created = metric_result is not None
|
|
212
|
+
maql_correct, _ = _best_maql_match(actual_maql, expected_outputs) if metric_created else (False, "")
|
|
213
|
+
return MetricRunResult(
|
|
214
|
+
conversation_id=conversation_id,
|
|
215
|
+
metric_result=metric_result,
|
|
216
|
+
metric_created=metric_created,
|
|
217
|
+
actual_maql=actual_maql,
|
|
218
|
+
maql_correct=maql_correct,
|
|
219
|
+
total_turns=float(turns),
|
|
220
|
+
)
|
|
221
|
+
finally:
|
|
222
|
+
if metric_id_to_delete:
|
|
223
|
+
_delete_metric(sdk, workspace_id, metric_id_to_delete)
|
|
175
224
|
|
|
176
225
|
|
|
177
226
|
def run_agentic_metric_skill(
|
|
@@ -192,12 +241,15 @@ def run_agentic_metric_skill(
|
|
|
192
241
|
expected_outputs: list[dict] = expected_output if isinstance(expected_output, list) else [expected_output]
|
|
193
242
|
run_results: list[MetricRunResult] = []
|
|
194
243
|
client = ChatClient(host=host, token=token, workspace_id=workspace_id)
|
|
244
|
+
sdk = GoodDataSdk.create(host, token)
|
|
195
245
|
|
|
196
246
|
try:
|
|
197
247
|
conv_id_0 = initial_conversation_id if initial_conversation_id is not None else client.create_conversation()
|
|
198
248
|
try:
|
|
199
249
|
run_results.append(
|
|
200
|
-
_execute_single_metric_run(
|
|
250
|
+
_execute_single_metric_run(
|
|
251
|
+
client, sdk, workspace_id, conv_id_0, question, expected_outputs, max_iterations
|
|
252
|
+
)
|
|
201
253
|
)
|
|
202
254
|
finally:
|
|
203
255
|
if initial_conversation_id is None: # only delete conversations we created
|
|
@@ -207,7 +259,9 @@ def run_agentic_metric_skill(
|
|
|
207
259
|
conv_id = client.create_conversation()
|
|
208
260
|
try:
|
|
209
261
|
run_results.append(
|
|
210
|
-
_execute_single_metric_run(
|
|
262
|
+
_execute_single_metric_run(
|
|
263
|
+
client, sdk, workspace_id, conv_id, question, expected_outputs, max_iterations
|
|
264
|
+
)
|
|
211
265
|
)
|
|
212
266
|
finally:
|
|
213
267
|
client.delete_conversation(conv_id)
|
|
@@ -245,9 +299,12 @@ def evaluate_agentic_metric_skill(
|
|
|
245
299
|
dataset_name: str = "metric_skill",
|
|
246
300
|
run_timestamp: str | None = None,
|
|
247
301
|
model_version_override: str | None = None,
|
|
302
|
+
run_metadata_extra: dict | None = None,
|
|
248
303
|
) -> None:
|
|
249
304
|
"""Run metric-skill evaluation, log to Langfuse, and raise MetricSkillAssertionError on failure."""
|
|
250
|
-
from datetime import datetime as _dt
|
|
305
|
+
from datetime import datetime as _dt # noqa: PLC0415
|
|
306
|
+
from datetime import timezone as _tz # noqa: PLC0415
|
|
307
|
+
|
|
251
308
|
from gooddata_eval.core.agentic._langfuse import try_make_langfuse_client # noqa: PLC0415
|
|
252
309
|
|
|
253
310
|
if langfuse is None:
|
|
@@ -280,6 +337,7 @@ def evaluate_agentic_metric_skill(
|
|
|
280
337
|
dataset_name,
|
|
281
338
|
run_timestamp,
|
|
282
339
|
model_version_override,
|
|
340
|
+
run_metadata_extra,
|
|
283
341
|
)
|
|
284
342
|
traces_by_conv = find_traces_per_conversation(
|
|
285
343
|
langfuse,
|
{gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/search_tool.py
RENAMED
|
@@ -143,9 +143,12 @@ def evaluate_agentic_search_tool(
|
|
|
143
143
|
dataset_name: str = "search",
|
|
144
144
|
run_timestamp: str | None = None,
|
|
145
145
|
model_version_override: str | None = None,
|
|
146
|
+
run_metadata_extra: dict | None = None,
|
|
146
147
|
) -> None:
|
|
147
148
|
"""Run search-tool evaluation, log to Langfuse, and raise SearchToolAssertionError on failure."""
|
|
148
|
-
from datetime import datetime as _dt
|
|
149
|
+
from datetime import datetime as _dt # noqa: PLC0415
|
|
150
|
+
from datetime import timezone as _tz # noqa: PLC0415
|
|
151
|
+
|
|
149
152
|
from gooddata_eval.core.agentic._langfuse import try_make_langfuse_client # noqa: PLC0415
|
|
150
153
|
|
|
151
154
|
if langfuse is None:
|
|
@@ -177,6 +180,7 @@ def evaluate_agentic_search_tool(
|
|
|
177
180
|
dataset_name,
|
|
178
181
|
run_timestamp,
|
|
179
182
|
model_version_override,
|
|
183
|
+
run_metadata_extra,
|
|
180
184
|
)
|
|
181
185
|
traces_by_conv = find_traces_per_conversation(
|
|
182
186
|
langfuse,
|
{gooddata_eval-1.69.0 → gooddata_eval-1.69.1.dev2}/src/gooddata_eval/core/agentic/visualization.py
RENAMED
|
@@ -263,11 +263,14 @@ def evaluate_agentic_visualization(
|
|
|
263
263
|
dataset_name: str = "visualization",
|
|
264
264
|
run_timestamp: str | None = None,
|
|
265
265
|
model_version_override: str | None = None,
|
|
266
|
+
run_metadata_extra: dict | None = None,
|
|
266
267
|
record_output_path: str | None = None,
|
|
267
268
|
) -> None:
|
|
268
269
|
"""Run visualization evaluation, log to Langfuse, and raise VisualizationAssertionError on failure."""
|
|
269
270
|
import json as _json # noqa: PLC0415
|
|
270
|
-
from datetime import datetime as _dt
|
|
271
|
+
from datetime import datetime as _dt # noqa: PLC0415
|
|
272
|
+
from datetime import timezone as _tz # noqa: PLC0415
|
|
273
|
+
|
|
271
274
|
from gooddata_eval.core.agentic._langfuse import try_make_langfuse_client # noqa: PLC0415
|
|
272
275
|
|
|
273
276
|
if langfuse is None:
|
|
@@ -300,6 +303,7 @@ def evaluate_agentic_visualization(
|
|
|
300
303
|
dataset_name,
|
|
301
304
|
run_timestamp,
|
|
302
305
|
model_version_override,
|
|
306
|
+
run_metadata_extra,
|
|
303
307
|
)
|
|
304
308
|
K = len(summary.run_results)
|
|
305
309
|
traces_by_conv = find_traces_per_conversation(
|