Skip to content

Commit 2b4fec9

Browse files
committed
integrate keywords eval
1 parent 36f7bc2 commit 2b4fec9

File tree

6 files changed

+54
-0
lines changed

6 files changed

+54
-0
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ lightspeed-eval --system-config config/system_api_disabled.yaml --eval-data conf
8686
- Response Evaluation
8787
- [`answer_correctness`](src/lightspeed_evaluation/core/metrics/custom.py)
8888
- [`intent_eval`](src/lightspeed_evaluation/core/metrics/custom.py) - Evaluates whether the response demonstrates the expected intent or purpose
89+
- [`keywords_eval`](src/lightspeed_evaluation/core/metrics/custom/keywords_eval.py) - Keywords evaluation with alternatives (ALL keywords must match, case insensitive)
8990
- Tool Evaluation
9091
- [`tool_eval`](src/lightspeed_evaluation/core/metrics/custom.py) - Validates tool calls and arguments with regex pattern matching
9192
- **Script-based**
@@ -149,6 +150,10 @@ metrics_metadata:
149150

150151
"custom:tool_eval":
151152
description: "Tool call evaluation comparing expected vs actual tool calls (regex for arguments)"
153+
154+
"custom:keywords_eval":
155+
threshold: 1 # Binary evaluation (0 or 1)
156+
description: "Keywords evaluation (ALL match) with sequential alternate checking (case insensitive)"
152157

153158
conversation_level:
154159
"deepeval:conversation_completeness":
@@ -226,12 +231,14 @@ embedding:
226231
contexts:
227232
- OpenShift Virtualization is an extension of the OpenShift ...
228233
attachments: [] # Attachments (Optional)
234+
expected_keywords: [["virtualization"], ["openshift"]] # For keywords_eval evaluation
229235
expected_response: OpenShift Virtualization is an extension of the OpenShift Container Platform that allows running virtual machines alongside containers
230236
expected_intent: "explain a concept" # Expected intent for intent evaluation
231237

232238
# Per-turn metrics (overrides system defaults)
233239
turn_metrics:
234240
- "ragas:faithfulness"
241+
- "custom:keywords_eval"
235242
- "custom:answer_correctness"
236243
- "custom:intent_eval"
237244

@@ -291,6 +298,7 @@ embedding:
291298
| `attachments` | list[string] | ❌ | Attachments | ❌ |
292299
| `expected_response` | string | 📋 | Expected response for comparison | ❌ |
293300
| `expected_intent` | string | 📋 | Expected intent for intent evaluation| ❌ |
301+
| `expected_keywords` | list[list[string]] | 📋 | Expected keywords for keyword evaluation (list of alternatives) | ❌ |
294302
| `expected_tool_calls` | list[list[list[dict]]] | 📋 | Expected tool call sequences (multiple alternative sets) | ❌ |
295303
| `tool_calls` | list[list[dict]] | ❌ | Actual tool calls from API | ✅ (if API enabled) |
296304
| `verify_script` | string | 📋 | Path to verification script | ❌ |
@@ -303,6 +311,7 @@ Examples
303311
> - `expected_response`: Required for `custom:answer_correctness`
304312
> - `expected_intent`: Required for `custom:intent_eval`
305313
> - `expected_tool_calls`: Required for `custom:tool_eval` (multiple alternative sets format)
314+
> - `expected_keywords`: Required for `custom:keywords_eval` (case insensitive matching)
306315
> - `verify_script`: Required for `script:action_eval` (used when API is enabled)
307316
> - `response`: Required for most metrics (auto-populated if API enabled)
308317

config/system.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ metrics_metadata:
7676
description: "Is what we retrieved actually relevant to user query?"
7777

7878
# Custom metrics
79+
"custom:keywords_eval": # boolean eval (either 0 or 1)
80+
description: "Keywords (ALL) matching evaluation with alternative sets"
81+
7982
"custom:answer_correctness":
8083
threshold: 0.75
8184
description: "Correctness vs expected answer using custom LLM evaluation"

src/lightspeed_evaluation/core/metrics/custom/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Custom metrics components package."""
22

33
from lightspeed_evaluation.core.metrics.custom.custom import CustomMetrics
4+
from lightspeed_evaluation.core.metrics.custom.keywords_eval import evaluate_keywords
45
from lightspeed_evaluation.core.metrics.custom.prompts import (
56
ANSWER_CORRECTNESS_PROMPT,
67
INTENT_EVALUATION_PROMPT,
@@ -9,6 +10,7 @@
910

1011
__all__ = [
1112
"CustomMetrics",
13+
"evaluate_keywords",
1214
"evaluate_tool_calls",
1315
# Prompts
1416
"ANSWER_CORRECTNESS_PROMPT",

src/lightspeed_evaluation/core/metrics/custom/custom.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
ANSWER_CORRECTNESS_PROMPT,
1010
INTENT_EVALUATION_PROMPT,
1111
)
12+
from lightspeed_evaluation.core.metrics.custom.keywords_eval import evaluate_keywords
1213
from lightspeed_evaluation.core.metrics.custom.tool_eval import evaluate_tool_calls
1314
from lightspeed_evaluation.core.models import EvaluationScope, TurnData
1415
from lightspeed_evaluation.core.system.exceptions import LLMError
@@ -28,6 +29,7 @@ def __init__(self, llm_manager: LLMManager):
2829
)
2930

3031
self.supported_metrics = {
32+
"keywords_eval": evaluate_keywords,
3133
"answer_correctness": self._evaluate_answer_correctness,
3234
"intent_eval": self._evaluate_intent,
3335
"tool_eval": self._evaluate_tool_calls,

src/lightspeed_evaluation/core/models/data.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ class TurnData(BaseModel):
6262
expected_intent: Optional[str] = Field(
6363
default=None, min_length=1, description="Expected intent for intent evaluation"
6464
)
65+
expected_keywords: Optional[list[list[str]]] = Field(
66+
default=None,
67+
description="Expected keywords for keyword evaluation (list of alternatives)",
68+
)
6569
conversation_id: Optional[str] = Field(
6670
default=None, description="Conversation ID - populated by API if enabled"
6771
)
@@ -89,6 +93,36 @@ def validate_turn_metrics(cls, v: Optional[list[str]]) -> Optional[list[str]]:
8993
v = _validate_and_deduplicate_metrics(v, "Turn metric")
9094
return v
9195

96+
@field_validator("expected_keywords")
97+
@classmethod
98+
def validate_expected_keywords(
99+
cls, v: Optional[list[list[str]]]
100+
) -> Optional[list[list[str]]]:
101+
"""Validate expected keywords when provided."""
102+
if v is None:
103+
return None
104+
105+
if not isinstance(v, list):
106+
raise ValueError("expected_keywords must be a list of lists")
107+
108+
# Validate each alternative group
109+
for i, keyword_group in enumerate(v):
110+
if not isinstance(keyword_group, list):
111+
raise ValueError(f"expected_keywords[{i}] must be a list of strings")
112+
113+
if not keyword_group:
114+
raise ValueError(f"expected_keywords[{i}] cannot be empty")
115+
116+
for j, keyword in enumerate(keyword_group):
117+
if not isinstance(keyword, str):
118+
raise ValueError(f"expected_keywords[{i}][{j}] must be a string")
119+
if not keyword.strip():
120+
raise ValueError(
121+
f"expected_keywords[{i}][{j}] cannot be empty or whitespace"
122+
)
123+
124+
return v
125+
92126
@field_validator("expected_tool_calls", mode="before")
93127
@classmethod
94128
def validate_expected_tool_calls(

src/lightspeed_evaluation/core/system/validator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
"required_fields": ["response", "contexts"],
4141
"description": "requires 'response' and 'contexts' fields",
4242
},
43+
"custom:keywords_eval": {
44+
"required_fields": ["response", "expected_keywords"],
45+
"description": "requires 'response' and 'expected_keywords' fields",
46+
},
4347
"custom:answer_correctness": {
4448
"required_fields": ["response", "expected_response"],
4549
"description": "requires 'response' and 'expected_response' fields",

0 commit comments

Comments
 (0)