|
1 | 1 | import pytest |
2 | | -from traceloop.sdk.evaluator.evaluator import validate_and_normalize_task_output |
| 2 | +from traceloop.sdk.evaluator.evaluator import ( |
| 3 | + validate_and_normalize_task_output, |
| 4 | + _validate_evaluator_input, |
| 5 | +) |
3 | 6 | from traceloop.sdk.evaluator.config import EvaluatorDetails |
4 | 7 |
|
5 | 8 |
|
@@ -221,3 +224,106 @@ def test_validate_task_output_duplicate_required_fields(self): |
221 | 224 | assert "pii-detector requires:" in error_message |
222 | 225 | assert "tone-analyzer requires:" in error_message |
223 | 226 | assert "sentiment-analyzer requires:" in error_message |
| 227 | + |
| 228 | + |
| 229 | +class TestValidateEvaluatorInput: |
| 230 | + """Tests for _validate_evaluator_input function""" |
| 231 | + |
| 232 | + def test_validate_input_no_request_model(self): |
| 233 | + """Validation passes for unknown slugs (no request model registered).""" |
| 234 | + # Should not raise - unknown slug has no model to validate against |
| 235 | + _validate_evaluator_input("unknown-evaluator", {"text": "hello"}) |
| 236 | + |
| 237 | + def test_validate_input_valid_input_only(self): |
| 238 | + """Validation passes for evaluators that only require input (no config).""" |
| 239 | + _validate_evaluator_input( |
| 240 | + "pii-detector", |
| 241 | + {"text": "Please contact John at john@email.com"}, |
| 242 | + ) |
| 243 | + |
| 244 | + def test_validate_input_missing_required_input_field(self): |
| 245 | + """Validation fails when a required input field is missing.""" |
| 246 | + with pytest.raises(ValueError, match="Invalid input for 'pii-detector'"): |
| 247 | + _validate_evaluator_input("pii-detector", {"wrong_field": "value"}) |
| 248 | + |
| 249 | + def test_validate_input_with_optional_config(self): |
| 250 | + """Validation passes for evaluators with optional config when config is provided.""" |
| 251 | + _validate_evaluator_input( |
| 252 | + "pii-detector", |
| 253 | + {"text": "Some text"}, |
| 254 | + evaluator_config={"probability_threshold": 0.8}, |
| 255 | + ) |
| 256 | + |
| 257 | + def test_validate_input_with_optional_config_omitted(self): |
| 258 | + """Validation passes for evaluators with optional config when config is omitted.""" |
| 259 | + _validate_evaluator_input( |
| 260 | + "toxicity-detector", |
| 261 | + {"text": "Some text"}, |
| 262 | + ) |
| 263 | + |
| 264 | + def test_validate_agent_flow_quality_with_required_config(self): |
| 265 | + """Validation passes for agent-flow-quality when config is provided.""" |
| 266 | + _validate_evaluator_input( |
| 267 | + "agent-flow-quality", |
| 268 | + { |
| 269 | + "trajectory_completions": '["Found 5 flights"]', |
| 270 | + "trajectory_prompts": '["Search for flights"]', |
| 271 | + }, |
| 272 | + evaluator_config={ |
| 273 | + "conditions": ["no tools called"], |
| 274 | + "threshold": 0.5, |
| 275 | + }, |
| 276 | + ) |
| 277 | + |
| 278 | + def test_validate_agent_flow_quality_missing_config_fails(self): |
| 279 | + """Validation fails for agent-flow-quality when required config is missing.""" |
| 280 | + with pytest.raises(ValueError, match="Invalid input for 'agent-flow-quality'"): |
| 281 | + _validate_evaluator_input( |
| 282 | + "agent-flow-quality", |
| 283 | + { |
| 284 | + "trajectory_completions": '["Found 5 flights"]', |
| 285 | + "trajectory_prompts": '["Search for flights"]', |
| 286 | + }, |
| 287 | + ) |
| 288 | + |
| 289 | + def test_validate_agent_flow_quality_missing_input_fields(self): |
| 290 | + """Validation fails for agent-flow-quality when required input fields are missing.""" |
| 291 | + with pytest.raises(ValueError, match="Invalid input for 'agent-flow-quality'"): |
| 292 | + _validate_evaluator_input( |
| 293 | + "agent-flow-quality", |
| 294 | + {"wrong_field": "value"}, |
| 295 | + evaluator_config={ |
| 296 | + "conditions": ["no tools called"], |
| 297 | + "threshold": 0.5, |
| 298 | + }, |
| 299 | + ) |
| 300 | + |
| 301 | + def test_validate_agent_goal_completeness_optional_config(self): |
| 302 | + """agent-goal-completeness has optional config - passes with or without it.""" |
| 303 | + input_data = { |
| 304 | + "trajectory_completions": '["Account created"]', |
| 305 | + "trajectory_prompts": '["Create new account"]', |
| 306 | + } |
| 307 | + # Without config |
| 308 | + _validate_evaluator_input("agent-goal-completeness", input_data) |
| 309 | + # With config |
| 310 | + _validate_evaluator_input( |
| 311 | + "agent-goal-completeness", |
| 312 | + input_data, |
| 313 | + evaluator_config={"threshold": 0.5}, |
| 314 | + ) |
| 315 | + |
| 316 | + def test_validate_agent_tool_trajectory_optional_config(self): |
| 317 | + """agent-tool-trajectory has optional config - passes with or without it.""" |
| 318 | + input_data = { |
| 319 | + "executed_tool_calls": '[{"name": "search", "input": {"query": "weather"}}]', |
| 320 | + "expected_tool_calls": '[{"name": "search", "input": {"query": "weather"}}]', |
| 321 | + } |
| 322 | + # Without config |
| 323 | + _validate_evaluator_input("agent-tool-trajectory", input_data) |
| 324 | + # With config |
| 325 | + _validate_evaluator_input( |
| 326 | + "agent-tool-trajectory", |
| 327 | + input_data, |
| 328 | + evaluator_config={"order_sensitive": True, "threshold": 0.5}, |
| 329 | + ) |
0 commit comments