Skip to content

Commit 7543628

Browse files
committed
test(gooddata-eval): cover the turn and step counts on every skill
metric_skill and alert_skill had no test for the new counters at all, and no skill asserted that the counts reach Langfuse — the dataclass fields alone would pass with the score writes deleted. - counter tests for metric_skill and alert_skill - score tests for all four skills, driven through submit_trace_scoring - conversation pins `turns` as fixture turns plus clarification rounds, which is neither half on its own
1 parent 65cc58e commit 7543628

4 files changed

Lines changed: 240 additions & 0 deletions

File tree

packages/gooddata-eval/tests/test_agentic_alert_skill.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,3 +996,67 @@ def test_every_gen_ai_interval_is_accepted():
996996
assert AnomalyDetectionGranularity.parse(value.lower()) is AnomalyDetectionGranularity(value)
997997
assert AnomalyDetectionGranularity.parse(None) is None
998998
assert AnomalyDetectionGranularity.parse(" ") is None
999+
1000+
1001+
def test_run_agentic_alert_skill_counts_the_turns_and_reasoning_steps_it_used():
1002+
"""QA-29110: the effort comparison reads these. A refusal still took a turn, and the turn
1003+
count is what separates a wrong answer from a run max_iterations cut short."""
1004+
mock_client = MagicMock()
1005+
mock_client.create_conversation.return_value = "conv-1"
1006+
mock_client.send_message.return_value = _no_alert_chat_result()
1007+
mock_client._base = "http://host/api/v1/actions/workspaces/ws1/ai"
1008+
mock_client._auth = {"Authorization": "Bearer tok"}
1009+
1010+
with _patched(mock_client, simulated_reply="Yes please"):
1011+
summary = run_agentic_alert_skill(
1012+
host="http://host/api/v1/actions/workspaces/ws1/ai",
1013+
token="tok",
1014+
workspace_id="ws1",
1015+
question="Create alert",
1016+
expected_output={"operator": "GREATER_THAN", "threshold": 100},
1017+
k=1,
1018+
max_iterations=2,
1019+
)
1020+
1021+
# _no_alert_chat_result has no tool calls and non-empty text, so the run replies once and
1022+
# stops at max_iterations: 2 turns, 1 reasoning step each.
1023+
assert summary.best.total_turns == 2.0
1024+
assert summary.best.total_steps == 2.0
1025+
1026+
1027+
def test_alert_skill_writes_the_turn_and_step_counts_to_langfuse():
1028+
"""The counters exist to reach Langfuse; asserting only the dataclass would pass even if
1029+
the scores were never written."""
1030+
mock_client = MagicMock()
1031+
mock_client.create_conversation.return_value = "conv-1"
1032+
mock_client.send_message.return_value = _no_alert_chat_result()
1033+
mock_client._base = "http://host/api/v1/actions/workspaces/ws1/ai"
1034+
mock_client._auth = {"Authorization": "Bearer tok"}
1035+
captured = {}
1036+
1037+
def _capture(_submit, _identity, **kwargs):
1038+
captured["write_scores"] = kwargs["write_scores"]
1039+
1040+
with (
1041+
_patched(mock_client),
1042+
patch("gooddata_eval.core.agentic.alert_skill.submit_trace_scoring", _capture),
1043+
pytest.raises(AlertSkillAssertionError),
1044+
):
1045+
evaluate_agentic_alert_skill(
1046+
host="http://host/api/v1/actions/workspaces/ws1/ai",
1047+
token="tok",
1048+
workspace_id="ws1",
1049+
question="Create alert",
1050+
expected_output={"operator": "GREATER_THAN", "threshold": 100},
1051+
k=1,
1052+
max_iterations=1,
1053+
langfuse=MagicMock(),
1054+
dataset_item_id="item-1",
1055+
)
1056+
1057+
ctx = MagicMock()
1058+
captured["write_scores"](ctx)
1059+
scores = {c.kwargs["name"]: c.kwargs["value"] for c in ctx.score.call_args_list}
1060+
1061+
assert scores["turns"] == 1.0
1062+
assert scores["steps"] == 1.0

packages/gooddata-eval/tests/test_agentic_conversation.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,3 +1071,61 @@ def test_run_agentic_conversation_sums_the_reasoning_steps_of_every_turn():
10711071

10721072
assert result.total_steps == 5.0
10731073
assert result.total_clarification_turns == 1
1074+
1075+
1076+
def test_conversation_writes_the_turn_step_and_clarification_counts_to_langfuse():
1077+
"""`turns` is not the clarification count: it is one per fixture turn plus every
1078+
simulated-user round, so a test has to pin the sum rather than either half."""
1079+
proposal_turn = ChatResult.model_validate(
1080+
{
1081+
"text_response": None,
1082+
"alertProposals": [{"cta": "Should I create this alert?", "recipients": [{"email": "a@b.com"}]}],
1083+
"reasoningStepCount": 2,
1084+
"toolCallEvents": [
1085+
{"functionName": "set_skills", "functionArguments": '{"skills": ["alert"]}', "result": None},
1086+
{"functionName": "prepare_metric_alert_proposal", "functionArguments": "{}", "result": None},
1087+
],
1088+
}
1089+
)
1090+
created_turn = ChatResult.model_validate(
1091+
{
1092+
"text_response": "Alert created.",
1093+
"reasoningStepCount": 3,
1094+
"toolCallEvents": [
1095+
{"functionName": "create_metric_alert", "functionArguments": "{}", "result": '{"id": "alert-1"}'}
1096+
],
1097+
}
1098+
)
1099+
mock_client = MagicMock()
1100+
mock_client.create_conversation.return_value = "conv-1"
1101+
mock_client.send_message.side_effect = [proposal_turn, created_turn]
1102+
captured = {}
1103+
1104+
def _capture(_submit, _identity, **kwargs):
1105+
captured["write_scores"] = kwargs["write_scores"]
1106+
1107+
with (
1108+
patch("gooddata_eval.core.agentic.conversation.ChatClient", return_value=mock_client),
1109+
patch("gooddata_eval.core.agentic.conversation.GoodDataSdk"),
1110+
patch("gooddata_eval.core.agentic.conversation.submit_trace_scoring", _capture),
1111+
patch(
1112+
"gooddata_eval.core.agentic.conversation._get_sim_user_response",
1113+
return_value="Yes, please create it.",
1114+
),
1115+
):
1116+
evaluate_agentic_conversation(
1117+
host="http://host/api/v1/actions/workspaces/ws1/ai",
1118+
token="tok",
1119+
workspace_id="ws1",
1120+
fixture=_alert_turn_fixture(),
1121+
langfuse=MagicMock(),
1122+
dataset_item_id="item-1",
1123+
)
1124+
1125+
ctx = MagicMock()
1126+
captured["write_scores"](ctx)
1127+
scores = {c.kwargs["name"]: c.kwargs["value"] for c in ctx.score.call_args_list}
1128+
1129+
assert scores["clarification_turns"] == 1.0
1130+
assert scores["turns"] == 2.0 # 1 fixture turn + 1 clarification round
1131+
assert scores["steps"] == 5.0

packages/gooddata-eval/tests/test_agentic_kda_skill.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,3 +1245,38 @@ def test_run_agentic_kda_skill_reports_no_turns_when_the_first_send_fails():
12451245

12461246
assert summary.best.total_turns == 0.0
12471247
assert summary.best.total_steps == 0.0
1248+
1249+
1250+
def test_kda_skill_writes_the_turn_and_step_counts_to_langfuse():
1251+
"""The counters exist to reach Langfuse; asserting only the dataclass would pass even if
1252+
the scores were never written."""
1253+
mock_client = MagicMock()
1254+
mock_client.create_conversation.return_value = "conv-1"
1255+
mock_client.send_message.return_value = _kda_chat_result(success=True)
1256+
captured = {}
1257+
1258+
def _capture(_submit, _identity, **kwargs):
1259+
captured["write_scores"] = kwargs["write_scores"]
1260+
1261+
with (
1262+
patch("gooddata_eval.core.agentic.kda_skill.ChatClient", return_value=mock_client),
1263+
patch("gooddata_eval.core.agentic.kda_skill.submit_trace_scoring", _capture),
1264+
):
1265+
evaluate_agentic_kda_skill(
1266+
host="http://host/api/v1/actions/workspaces/ws1/ai",
1267+
token="tok",
1268+
workspace_id="ws1",
1269+
question="What drove the change?",
1270+
expected_output=_EXPECTED,
1271+
k=1,
1272+
max_iterations=1,
1273+
langfuse=MagicMock(),
1274+
dataset_item_id="item-1",
1275+
)
1276+
1277+
ctx = MagicMock()
1278+
captured["write_scores"](ctx)
1279+
scores = {c.kwargs["name"]: c.kwargs["value"] for c in ctx.score.call_args_list}
1280+
1281+
assert scores["turns"] == 1.0
1282+
assert scores["steps"] == 1.0

packages/gooddata-eval/tests/test_agentic_metric_skill.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,3 +764,86 @@ def test_no_timer_output_by_default(monkeypatch, capsys):
764764
assert "[timer]" not in capsys.readouterr().out
765765
# Silenced, not un-measured.
766766
assert summary.run_results[0].timings.agent_s == 3.0
767+
768+
769+
def test_run_agentic_metric_skill_counts_the_turns_and_reasoning_steps_it_used():
770+
"""QA-29110: the effort comparison reads these. A clarification round is part of the work
771+
the effort setting changes, so its steps count with the rest."""
772+
clarify_turn = ChatResult.model_validate(
773+
{"textResponse": "Which foo?", "toolCallEvents": [], "reasoningStepCount": 2}
774+
)
775+
created_turn = ChatResult.model_validate(
776+
{
777+
"textResponse": "done",
778+
"reasoningStepCount": 3,
779+
"toolCallEvents": [
780+
{
781+
"functionName": "create_metric",
782+
"functionArguments": "{}",
783+
"result": '{"data": {"maql": "SELECT {metric/foo}"}}',
784+
}
785+
],
786+
}
787+
)
788+
mock_client = _client()
789+
mock_client.send_message.side_effect = [clarify_turn, created_turn]
790+
791+
with _patched(mock_client, simulated_reply="It's foo"):
792+
summary = run_agentic_metric_skill(
793+
host="http://host/api/v1/actions/workspaces/ws1/ai",
794+
token="tok",
795+
workspace_id="ws1",
796+
question="Create metric foo",
797+
expected_output={"maql": "SELECT {metric/foo}"},
798+
k=1,
799+
max_iterations=2,
800+
)
801+
802+
assert summary.best.total_turns == 2.0
803+
assert summary.best.total_steps == 5.0
804+
805+
806+
def test_metric_skill_writes_the_turn_and_step_counts_to_langfuse():
807+
"""The counters exist to reach Langfuse; asserting only the dataclass would pass even if
808+
the scores were never written."""
809+
mock_client = _client()
810+
mock_client.send_message.return_value = ChatResult.model_validate(
811+
{
812+
"textResponse": "done",
813+
"reasoningStepCount": 4,
814+
"toolCallEvents": [
815+
{
816+
"functionName": "create_metric",
817+
"functionArguments": "{}",
818+
"result": '{"data": {"maql": "SELECT {metric/foo}"}}',
819+
}
820+
],
821+
}
822+
)
823+
captured = {}
824+
825+
def _capture(_submit, _identity, **kwargs):
826+
captured["write_scores"] = kwargs["write_scores"]
827+
828+
with (
829+
_patched(mock_client),
830+
patch("gooddata_eval.core.agentic.metric_skill.submit_trace_scoring", _capture),
831+
):
832+
evaluate_agentic_metric_skill(
833+
host="http://host/api/v1/actions/workspaces/ws1/ai",
834+
token="tok",
835+
workspace_id="ws1",
836+
question="Create metric foo",
837+
expected_output={"maql": "SELECT {metric/foo}"},
838+
k=1,
839+
max_iterations=1,
840+
langfuse=MagicMock(),
841+
dataset_item_id="item-1",
842+
)
843+
844+
ctx = MagicMock()
845+
captured["write_scores"](ctx)
846+
scores = {c.kwargs["name"]: c.kwargs["value"] for c in ctx.score.call_args_list}
847+
848+
assert scores["turns"] == 1.0
849+
assert scores["steps"] == 4.0

0 commit comments

Comments
 (0)