gooddata-eval 1.76.1.dev2__tar.gz → 1.76.1.dev3__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.dev2 → gooddata_eval-1.76.1.dev3}/AGENTS.md +1 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/PKG-INFO +2 -2
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/pyproject.toml +2 -2
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/chat/sse_client.py +43 -1
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_sse_client.py +77 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/.gitignore +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/CLAUDE.md +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/LICENSE.txt +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/Makefile +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/README.md +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/scripts/verify_guardrail_refusal_criteria.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/_version.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/cli/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/cli/agentic_runner.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/cli/main.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/_output.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/_catalog.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/_conversation_context.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/_gate.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/_langfuse.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/_trace_linker.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/alert_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/anomaly_detection.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/conversation.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/dashboard_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/general_question.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/guardrail.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/kda_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/metric_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/report_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/search_tool.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/visualization.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/what_if.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/chat/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/chat/render.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/config.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/connection.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/dataset/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/dataset/from_insights.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/dataset/langfuse_source.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/dataset/local.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/_deep_subset.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/_guardrail_criteria.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/_llm_judge.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/_maql.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/_text_utils.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/alert_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/base.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/general_question.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/guardrail.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/metric_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/search_tool.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/summary.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/visualization.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/granularity.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/_env.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/client.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/experiment.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/observations.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/otlp.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/sink.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/models.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/reporting/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/reporting/console.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/reporting/html_report.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/reporting/json_report.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/reporting/report_template.html +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/runner.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/scoring.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/summary/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/summary/http_client.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/timing.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/workspace.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/__init__.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/_fake_langfuse.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/conftest.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/fixtures/sample_dataset/metric_skill_create.json +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/fixtures/sample_dataset/visualization_revenue.json +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/fixtures/sse_visualization_stream.txt +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_alert_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_anomaly_detection.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_conversation.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_dashboard_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_gate.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_general_question.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_guardrail.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_kda_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_langfuse_trace.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_metric_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_observe_experiment.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_report_skill.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_run_context.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_runner.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_search_tool.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_visualization.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_what_if.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_alert_skill_evaluator.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_chat_render.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_cli.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_connection.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_deep_subset.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_fake_langfuse.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_from_insights.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_guardrail_criteria.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_html_report.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_langfuse_client.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_langfuse_e2e_fake_server.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_langfuse_env.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_langfuse_experiment.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_langfuse_observations.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_langfuse_otlp.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_langfuse_sink.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_langfuse_source.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_llm_judge.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_local_loader.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_maql_normalize.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_metric_skill_evaluator.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_models.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_reporting.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_runner.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_scoring.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_search_tool_evaluator.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_summary_client.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_summary_evaluator.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_text_evaluators.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_timing.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_trace_linker.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_visualization_evaluator.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_workspace.py +0 -0
- {gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tox.ini +0 -0
|
@@ -156,6 +156,7 @@ Run this before guessing a `--model` string.
|
|
|
156
156
|
| `GD_EVAL_AGENT_ID` | which agent to drive, same as `--agent-id` |
|
|
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
|
+
| `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 |
|
|
159
160
|
| `GD_EVAL_TIMERS` | same as `--timers` |
|
|
160
161
|
|
|
161
162
|
A gitignored `.env` at the repo root is the normal place for these; load it with
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gooddata-eval
|
|
3
|
-
Version: 1.76.1.
|
|
3
|
+
Version: 1.76.1.dev3
|
|
4
4
|
Summary: Evaluate the GoodData AI agent against your own questions and models.
|
|
5
5
|
Project-URL: Source, https://github.com/gooddata/gooddata-python-sdk
|
|
6
6
|
Author-email: GoodData <support@gooddata.com>
|
|
@@ -17,7 +17,7 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
17
17
|
Classifier: Topic :: Software Development
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
|
-
Requires-Dist: gooddata-sdk~=1.76.1.
|
|
20
|
+
Requires-Dist: gooddata-sdk~=1.76.1.dev3
|
|
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.76.1.
|
|
4
|
+
version = "1.76.1.dev3"
|
|
5
5
|
description = "Evaluate the GoodData AI agent against your own questions and models."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "MIT"
|
|
@@ -11,7 +11,7 @@ authors = [
|
|
|
11
11
|
keywords = ["gooddata", "ai", "evaluation", "llm", "analytics", "cli"]
|
|
12
12
|
requires-python = ">=3.10"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"gooddata-sdk~=1.76.1.
|
|
14
|
+
"gooddata-sdk~=1.76.1.dev3",
|
|
15
15
|
"httpx>=0.27,<1.0",
|
|
16
16
|
"jsonpatch>=1.33,<2.0",
|
|
17
17
|
"orjson>=3.9.15,<4.0.0",
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/chat/sse_client.py
RENAMED
|
@@ -13,12 +13,15 @@ module is the single place to swap — the runner only depends on the ChatBacken
|
|
|
13
13
|
protocol, not on this class.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
+
import functools
|
|
16
17
|
import json
|
|
17
18
|
import logging
|
|
18
19
|
import os
|
|
20
|
+
import re
|
|
19
21
|
import time
|
|
20
22
|
from dataclasses import dataclass, field
|
|
21
23
|
from typing import Any, Callable, Iterable, TypeVar
|
|
24
|
+
from urllib.parse import quote
|
|
22
25
|
|
|
23
26
|
import httpx
|
|
24
27
|
|
|
@@ -135,6 +138,45 @@ _TURN_TIMEOUT_S = _float_env("GOODDATA_EVAL_CHAT_TURN_TIMEOUT_S", 0.0)
|
|
|
135
138
|
# 4-turn conversation run to 4x the budget, which is not what a user would sit through.
|
|
136
139
|
_ITEM_TIMEOUT_S = _float_env("GOODDATA_EVAL_CHAT_ITEM_TIMEOUT_S", 0.0)
|
|
137
140
|
|
|
141
|
+
# Comma-separated `key=value` labels, e.g. `model_version=gpt-5.2,github_run_id=42`, sent as
|
|
142
|
+
# W3C baggage on every chat request. gen-ai's Langfuse span processor copies `langfuse_*`
|
|
143
|
+
# baggage onto each span it starts, so every observation of the conversation -- the cost-bearing
|
|
144
|
+
# generations, retries and the title trace included -- carries them from creation. Langfuse v4
|
|
145
|
+
# cannot add them to an observation afterwards. A value cannot contain a comma, and a key must be
|
|
146
|
+
# a plain token (letters, digits, `_`, `-`); any other key is skipped with a warning, because httpx
|
|
147
|
+
# refuses a non-ASCII header and gen-ai's OTel propagator drops or rewrites the rest.
|
|
148
|
+
_TRACE_LABELS_ENV = "GOODDATA_EVAL_TRACE_LABELS"
|
|
149
|
+
_TRACE_LABEL_KEY = re.compile(r"[A-Za-z0-9_-]+")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@functools.cache
|
|
153
|
+
def _warn_skipped_trace_label(key: str) -> None:
|
|
154
|
+
"""Warn once per key per process: every ChatClient re-reads the labels."""
|
|
155
|
+
_log.warning("%s: skipping label with key %r, keys must match [A-Za-z0-9_-]+", _TRACE_LABELS_ENV, key)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _trace_baggage() -> dict[str, str]:
|
|
159
|
+
"""The `baggage` header for the labels in ``GOODDATA_EVAL_TRACE_LABELS``, or none.
|
|
160
|
+
|
|
161
|
+
Each label becomes trace metadata, and ``model_version`` -- the combo the report groups
|
|
162
|
+
on -- also becomes the observation ``version``, the dimension Langfuse groups cost by.
|
|
163
|
+
Values are percent-encoded: a space or comma would otherwise end the baggage entry.
|
|
164
|
+
"""
|
|
165
|
+
labels: dict[str, str] = {}
|
|
166
|
+
for pair in os.getenv(_TRACE_LABELS_ENV, "").split(","):
|
|
167
|
+
key, sep, value = pair.partition("=")
|
|
168
|
+
key = key.strip()
|
|
169
|
+
if not sep or not key:
|
|
170
|
+
continue
|
|
171
|
+
if _TRACE_LABEL_KEY.fullmatch(key):
|
|
172
|
+
labels[key] = value.strip()
|
|
173
|
+
else:
|
|
174
|
+
_warn_skipped_trace_label(key)
|
|
175
|
+
entries = [f"langfuse_metadata_{k}={quote(v, safe='')}" for k, v in labels.items()]
|
|
176
|
+
if labels.get("model_version"):
|
|
177
|
+
entries.insert(0, f"langfuse_version={quote(labels['model_version'], safe='')}")
|
|
178
|
+
return {"baggage": ",".join(entries)} if entries else {}
|
|
179
|
+
|
|
138
180
|
|
|
139
181
|
def set_default_turn_timeout(seconds: float | None) -> None:
|
|
140
182
|
"""Set the per-turn budget every ChatClient built afterwards inherits.
|
|
@@ -470,7 +512,7 @@ class ChatClient:
|
|
|
470
512
|
the ``enableAiContextSetup`` feature flag is on for the organization.
|
|
471
513
|
"""
|
|
472
514
|
self._base = f"{host.rstrip('/')}/api/v1/ai/workspaces/{workspace_id}/chat/conversations"
|
|
473
|
-
self._auth = {"Authorization": f"Bearer {token}"}
|
|
515
|
+
self._auth = {"Authorization": f"Bearer {token}", **_trace_baggage()}
|
|
474
516
|
# 0/None disables the cap. Also lowered onto the read timeout: the wall-clock check
|
|
475
517
|
# fires between events, so a turn that goes silent needs the transport to give up too.
|
|
476
518
|
budget = _TURN_TIMEOUT_S if turn_timeout_s is None else turn_timeout_s
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# (C) 2026 GoodData Corporation
|
|
2
2
|
import json
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from urllib.parse import unquote
|
|
3
6
|
|
|
4
7
|
import httpx
|
|
5
8
|
import pytest
|
|
@@ -1053,3 +1056,77 @@ def test_ask_puts_the_item_attachment_on_the_wire():
|
|
|
1053
1056
|
)
|
|
1054
1057
|
client.ask(item)
|
|
1055
1058
|
assert captured["body"]["userContext"] == _ATTACHMENT
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
def _baggage_of(requests: list[httpx.Request]) -> list[dict[str, str] | None]:
|
|
1062
|
+
"""Each request's `baggage` header as {key: decoded value}, or None when absent."""
|
|
1063
|
+
out: list[dict[str, str] | None] = []
|
|
1064
|
+
for r in requests:
|
|
1065
|
+
raw = r.headers.get("baggage")
|
|
1066
|
+
out.append(None if raw is None else {k: unquote(v) for k, v in (e.split("=", 1) for e in raw.split(","))})
|
|
1067
|
+
return out
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def _record_requests(requests: list[httpx.Request]) -> Callable[[httpx.Request], httpx.Response]:
|
|
1071
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
1072
|
+
requests.append(request)
|
|
1073
|
+
if request.url.path.endswith("/conversations"):
|
|
1074
|
+
return httpx.Response(200, json={"conversationId": "c1"})
|
|
1075
|
+
return httpx.Response(200, content=_OK_SSE)
|
|
1076
|
+
|
|
1077
|
+
return handler
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def test_trace_labels_ride_every_request_as_langfuse_baggage(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
1081
|
+
"""gen-ai's Langfuse span processor copies `langfuse_*` baggage onto every span it starts, so
|
|
1082
|
+
the labels reach the root generation and its cost-bearing children at creation."""
|
|
1083
|
+
monkeypatch.setenv("GOODDATA_EVAL_TRACE_LABELS", "model_version=gpt-5.2 openai,github_run_id=42")
|
|
1084
|
+
requests: list[httpx.Request] = []
|
|
1085
|
+
client = _client_with_handler(_record_requests(requests))
|
|
1086
|
+
|
|
1087
|
+
client.send_message(client.create_conversation(), "q")
|
|
1088
|
+
|
|
1089
|
+
expected = {
|
|
1090
|
+
"langfuse_version": "gpt-5.2 openai",
|
|
1091
|
+
"langfuse_metadata_model_version": "gpt-5.2 openai",
|
|
1092
|
+
"langfuse_metadata_github_run_id": "42",
|
|
1093
|
+
}
|
|
1094
|
+
assert _baggage_of(requests) == [expected, expected]
|
|
1095
|
+
# Encoded, not raw: a space or comma in a value would otherwise split or end the entry.
|
|
1096
|
+
assert " " not in requests[0].headers["baggage"]
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
def test_trace_labels_without_a_model_version_set_no_langfuse_version(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
1100
|
+
monkeypatch.setenv("GOODDATA_EVAL_TRACE_LABELS", "github_run_id=42")
|
|
1101
|
+
requests: list[httpx.Request] = []
|
|
1102
|
+
_client_with_handler(_record_requests(requests)).create_conversation()
|
|
1103
|
+
|
|
1104
|
+
assert _baggage_of(requests) == [{"langfuse_metadata_github_run_id": "42"}]
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
@pytest.mark.parametrize("key", ["modèle", "run id", "c++"])
|
|
1108
|
+
def test_trace_label_with_a_non_token_key_is_skipped_with_one_warning(
|
|
1109
|
+
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture, key: str
|
|
1110
|
+
) -> None:
|
|
1111
|
+
"""httpx refuses a non-ASCII header, gen-ai's OTel propagator drops a key with a space, and
|
|
1112
|
+
it reads `+` as a space; each would otherwise fail the run or mislabel the trace."""
|
|
1113
|
+
monkeypatch.setenv("GOODDATA_EVAL_TRACE_LABELS", f"github_run_id=42,{key}=1")
|
|
1114
|
+
requests: list[httpx.Request] = []
|
|
1115
|
+
|
|
1116
|
+
for _ in range(2):
|
|
1117
|
+
_client_with_handler(_record_requests(requests)).create_conversation()
|
|
1118
|
+
|
|
1119
|
+
assert _baggage_of(requests) == [{"langfuse_metadata_github_run_id": "42"}] * 2
|
|
1120
|
+
assert [r.levelno for r in caplog.records if repr(key) in r.getMessage()] == [logging.WARNING]
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
@pytest.mark.parametrize("raw", [None, "", "no-equals-sign", " , "])
|
|
1124
|
+
def test_no_trace_labels_send_no_baggage(monkeypatch: pytest.MonkeyPatch, raw: str | None) -> None:
|
|
1125
|
+
if raw is None:
|
|
1126
|
+
monkeypatch.delenv("GOODDATA_EVAL_TRACE_LABELS", raising=False)
|
|
1127
|
+
else:
|
|
1128
|
+
monkeypatch.setenv("GOODDATA_EVAL_TRACE_LABELS", raw)
|
|
1129
|
+
requests: list[httpx.Request] = []
|
|
1130
|
+
_client_with_handler(_record_requests(requests)).create_conversation()
|
|
1131
|
+
|
|
1132
|
+
assert _baggage_of(requests) == [None]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/scripts/verify_guardrail_refusal_criteria.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/cli/agentic_runner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/__init__.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/_catalog.py
RENAMED
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/_gate.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/_langfuse.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/guardrail.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/kda_skill.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/agentic/what_if.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/chat/__init__.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/chat/render.py
RENAMED
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/connection.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/dataset/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/dataset/local.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/_maql.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/evaluators/summary.py
RENAMED
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/granularity.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/__init__.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/_env.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/otlp.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/langfuse/sink.py
RENAMED
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/reporting/__init__.py
RENAMED
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/reporting/console.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/src/gooddata_eval/core/summary/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/fixtures/sse_visualization_stream.txt
RENAMED
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_anomaly_detection.py
RENAMED
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_dashboard_skill.py
RENAMED
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_general_question.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_langfuse_trace.py
RENAMED
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_agentic_observe_experiment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_langfuse_e2e_fake_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_metric_skill_evaluator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gooddata_eval-1.76.1.dev2 → gooddata_eval-1.76.1.dev3}/tests/test_visualization_evaluator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|