Skip to content

Commit 40634f7

Browse files
authored
Merge pull request #1763 from gooddata/feat/knowledge-question-evaluator
feat(gooddata-eval): register knowledge_question evaluator
2 parents fb6852d + f213331 commit 40634f7

4 files changed

Lines changed: 46 additions & 5 deletions

File tree

‎packages/gooddata-eval/src/gooddata_eval/core/evaluators/__init__.py‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,26 @@
2020
)
2121
}
2222

23-
# LLM-judge evaluators (general_question, guardrail, dashboard_summary) require the
24-
# [llm-judge] extra. Their modules are imported lazily on first use so the CLI
23+
# LLM-judge evaluators (general_question, guardrail, dashboard_summary, knowledge_question)
24+
# require the [llm-judge] extra. Their modules are imported lazily on first use so the CLI
2525
# starts without openai.
26+
#
27+
# knowledge_question reuses GeneralQuestionEvaluator directly: both are free-text-rubric,
28+
# LLM-judged prose answers -- knowledge_question just covers platform/product/policy facts
29+
# instead of LDM-grounded ones. `ItemReport.test_kind` is tagged from the dataset item's own
30+
# `test_kind` field (see runner.py), not from the evaluator class, so sharing one class
31+
# across both kinds does not mislabel results.
2632
_LAZY_EVALUATOR_MODULES: dict[str, str] = {
2733
"general_question": "gooddata_eval.core.evaluators.general_question",
2834
"guardrail": "gooddata_eval.core.evaluators.guardrail",
2935
"dashboard_summary": "gooddata_eval.core.evaluators.summary",
36+
"knowledge_question": "gooddata_eval.core.evaluators.general_question",
3037
}
3138
_LAZY_EVALUATOR_CLASSES: dict[str, str] = {
3239
"general_question": "GeneralQuestionEvaluator",
3340
"guardrail": "GuardrailEvaluator",
3441
"dashboard_summary": "DashboardSummaryEvaluator",
42+
"knowledge_question": "GeneralQuestionEvaluator",
3543
}
3644

3745

@@ -57,9 +65,9 @@ def _openai_available() -> bool:
5765
def supported_test_kinds() -> set[str]:
5866
"""Return all supported test_kind values.
5967
60-
LLM-judge kinds (general_question, guardrail) are excluded when the
61-
[llm-judge] extra (openai) is not installed — those items are skipped
62-
rather than erroring out mid-run.
68+
LLM-judge kinds (general_question, guardrail, dashboard_summary,
69+
knowledge_question) are excluded when the [llm-judge] extra (openai) is
70+
not installed — those items are skipped rather than erroring out mid-run.
6371
"""
6472
kinds = set(_EAGER_EVALUATORS)
6573
if _openai_available():

‎packages/gooddata-eval/src/gooddata_eval/core/evaluators/general_question.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515

1616

1717
class GeneralQuestionEvaluator:
18+
# Also registered under "knowledge_question" (core/evaluators/__init__.py) -- this
19+
# attribute is unused by that registration (which keys off item.test_kind, not this
20+
# class attribute, see the comment there), so it's harmless as-is, but don't derive
21+
# an output label from it without accounting for the second kind it now backs.
1822
test_kind = "general_question"
1923

2024
def __init__(self):

‎packages/gooddata-eval/tests/test_runner.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def test_run_items_routes_all_supported_kinds():
122122
"general_question",
123123
"guardrail",
124124
"dashboard_summary",
125+
"knowledge_question",
125126
}
126127
assert expected_kinds == supported_test_kinds()
127128

‎packages/gooddata-eval/tests/test_text_evaluators.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# (C) 2026 GoodData Corporation
22
from unittest.mock import MagicMock, patch
33

4+
from gooddata_eval.core.evaluators import get_evaluator
45
from gooddata_eval.core.evaluators._llm_judge import JudgeResponseError
56
from gooddata_eval.core.evaluators.general_question import GeneralQuestionEvaluator
67
from gooddata_eval.core.evaluators.guardrail import GuardrailEvaluator
@@ -17,6 +18,16 @@ def _gq_item() -> DatasetItem:
1718
)
1819

1920

21+
def _kq_item() -> DatasetItem:
22+
return DatasetItem(
23+
id="kq-001",
24+
dataset_name="d",
25+
test_kind="knowledge_question",
26+
question="How can I get access to more ICAs?",
27+
expected_output="Must explain the process for requesting additional ICAs, e.g. contacting an account manager.",
28+
)
29+
30+
2031
def _gr_item() -> DatasetItem:
2132
return DatasetItem(
2233
id="gr-001",
@@ -60,6 +71,23 @@ def test_general_question_fails_when_judge_scores_0():
6071
assert result.passed is False
6172

6273

74+
def test_knowledge_question_dispatches_to_general_question_evaluator():
75+
with patch("gooddata_eval.core.evaluators.general_question.LLMJudge", return_value=_make_judge(True)):
76+
assert isinstance(get_evaluator("knowledge_question"), GeneralQuestionEvaluator)
77+
78+
79+
def test_knowledge_question_passes_when_judge_scores_1():
80+
with patch("gooddata_eval.core.evaluators.general_question.LLMJudge", return_value=_make_judge(True)):
81+
result = GeneralQuestionEvaluator().evaluate(_kq_item(), _chat_text("Contact your account manager."))
82+
assert result.passed is True
83+
84+
85+
def test_knowledge_question_fails_when_judge_scores_0():
86+
with patch("gooddata_eval.core.evaluators.general_question.LLMJudge", return_value=_make_judge(False)):
87+
result = GeneralQuestionEvaluator().evaluate(_kq_item(), _chat_text("I don't know."))
88+
assert result.passed is False
89+
90+
6391
def test_guardrail_fails_when_viz_returned():
6492
with patch("gooddata_eval.core.evaluators.guardrail.LLMJudge", return_value=_make_judge(True)):
6593
result = GuardrailEvaluator().evaluate(_gr_item(), _chat_viz())

0 commit comments

Comments
 (0)