""" Full coverage tests for benchmarks/graders.py. Targets uncovered paths: - get_evaluation_llm: API key from settings_snapshot, missing API key warning - grade_single_result: chat_messages branch - _grade_results_inner: callable fallback, error with progress_callback - grade_results: cleanup with safe_close """ import json from unittest.mock import Mock, patch import pytest class TestGetEvaluationLlm: """Cover get_evaluation_llm API key handling branches.""" @patch("local_deep_research.benchmarks.graders.get_llm") def test_api_key_from_settings_snapshot_dict(self, mock_get_llm): """Extracts API key from settings_snapshot when value is a dict.""" from local_deep_research.benchmarks.graders import get_evaluation_llm snapshot = { "llm.openai_endpoint.api_key": { "value": "sk-test-key", "type": "string", } } get_evaluation_llm(settings_snapshot=snapshot) mock_get_llm.assert_called_once() @patch("local_deep_research.benchmarks.graders.get_llm") def test_api_key_from_settings_snapshot_string(self, mock_get_llm): """Extracts API key from settings_snapshot when value is a string.""" from local_deep_research.benchmarks.graders import get_evaluation_llm snapshot = {"llm.openai_endpoint.api_key": "sk-direct-key"} get_evaluation_llm(settings_snapshot=snapshot) mock_get_llm.assert_called_once() @patch("local_deep_research.benchmarks.graders.get_llm") def test_no_settings_snapshot_warns(self, mock_get_llm): """Warns when no settings_snapshot provided for openai_endpoint.""" from local_deep_research.benchmarks.graders import get_evaluation_llm get_evaluation_llm() mock_get_llm.assert_called_once() @patch("local_deep_research.benchmarks.graders.get_llm") def test_missing_api_key_warns(self, mock_get_llm): """Warns when settings_snapshot has no API key.""" from local_deep_research.benchmarks.graders import get_evaluation_llm snapshot = {"llm.openai_endpoint.api_key": None} get_evaluation_llm(settings_snapshot=snapshot) mock_get_llm.assert_called_once() @patch("local_deep_research.benchmarks.graders.get_llm") def test_custom_config_overrides_defaults(self, mock_get_llm): """Custom config overrides default evaluation config.""" from local_deep_research.benchmarks.graders import get_evaluation_llm custom = {"provider": "anthropic", "model_name": "claude-3"} get_evaluation_llm(custom_config=custom) call_kwargs = mock_get_llm.call_args assert call_kwargs[1]["provider"] == "anthropic" assert call_kwargs[1]["model_name"] == "claude-3" @patch("local_deep_research.benchmarks.graders.get_llm") def test_unsupported_params_filtered(self, mock_get_llm): """Parameters not in ldr_supported_params are filtered out.""" from local_deep_research.benchmarks.graders import get_evaluation_llm custom = {"max_tokens": 1000, "model_name": "test"} get_evaluation_llm(custom_config=custom) call_kwargs = mock_get_llm.call_args[1] assert "max_tokens" not in call_kwargs class TestGradeSingleResultChatMessages: """Cover grade_single_result chat_messages branch.""" @patch("local_deep_research.benchmarks.graders.get_evaluation_llm") @patch("local_deep_research.utilities.resource_utils.safe_close") def test_chat_messages_branch(self, mock_close, mock_get_llm): """Uses HumanMessage when LLM has chat_messages attribute.""" from local_deep_research.benchmarks.graders import grade_single_result mock_llm = Mock() mock_llm.chat_messages = True # Has chat_messages attribute mock_response = Mock() mock_response.content = ( "Extracted Answer: 42\nReasoning: ok\nCorrect: yes" ) mock_llm.invoke.return_value = mock_response mock_get_llm.return_value = mock_llm result = grade_single_result( { "problem": "Q", "correct_answer": "42", "response": "42", }, dataset_type="simpleqa", ) assert result["is_correct"] is True # Verify invoke was called with a list (HumanMessage) call_args = mock_llm.invoke.call_args[0][0] assert isinstance(call_args, list) class TestGradeResultsInnerCallableFallback: """Cover _grade_results_inner callable fallback path.""" def test_callable_fallback_in_batch(self, tmp_path): """Falls back to calling LLM as callable when no invoke method.""" from local_deep_research.benchmarks.graders import _grade_results_inner input_file = tmp_path / "results.jsonl" input_file.write_text( json.dumps( { "problem": "Q", "correct_answer": "A", "response": "R", } ) ) output_file = tmp_path / "graded.jsonl" mock_llm = Mock() del mock_llm.invoke # Remove invoke to trigger callable mock_llm.return_value = ( "Extracted Answer: A\nReasoning: matches\nCorrect: yes" ) result = _grade_results_inner( mock_llm, str(input_file), str(output_file), "simpleqa", None ) assert len(result) == 1 assert result[0]["is_correct"] is True def test_chat_messages_branch_in_batch(self, tmp_path): """Batch grading uses HumanMessage when LLM has chat_messages.""" from local_deep_research.benchmarks.graders import _grade_results_inner input_file = tmp_path / "results.jsonl" input_file.write_text( json.dumps({"problem": "Q", "correct_answer": "A", "response": "R"}) ) output_file = tmp_path / "graded.jsonl" mock_llm = Mock() mock_llm.chat_messages = True mock_response = Mock() mock_response.content = ( "Extracted Answer: A\nReasoning: ok\nCorrect: yes" ) mock_llm.invoke.return_value = mock_response result = _grade_results_inner( mock_llm, str(input_file), str(output_file), "simpleqa", None ) assert len(result) == 1 assert result[0]["is_correct"] is True class TestGradeResultsInnerErrorWithCallback: """Cover error handling with progress_callback in _grade_results_inner.""" def test_error_callback_called_on_exception(self, tmp_path): """Progress callback receives error status when grading fails.""" from local_deep_research.benchmarks.graders import _grade_results_inner input_file = tmp_path / "results.jsonl" input_file.write_text( json.dumps({"problem": "Q", "correct_answer": "A", "response": "R"}) ) output_file = tmp_path / "graded.jsonl" mock_llm = Mock() mock_llm.invoke.side_effect = Exception("LLM crashed") del mock_llm.chat_messages callback_calls = [] def callback(idx, total, data): callback_calls.append(data) result = _grade_results_inner( mock_llm, str(input_file), str(output_file), "simpleqa", callback, ) assert len(result) == 1 assert "grading_error" in result[0] # Should have grading + error callbacks error_calls = [c for c in callback_calls if c["status"] == "error"] assert len(error_calls) == 1 assert "LLM crashed" in error_calls[0]["error"] # Output file should contain error result assert output_file.exists() class TestGradeResults: """Cover grade_results wrapper with safe_close.""" @patch("local_deep_research.benchmarks.graders.get_evaluation_llm") @patch("local_deep_research.utilities.resource_utils.safe_close") def test_safe_close_called_after_grading( self, mock_close, mock_get_llm, tmp_path ): """safe_close is called on the LLM after grading completes.""" from local_deep_research.benchmarks.graders import grade_results input_file = tmp_path / "results.jsonl" input_file.write_text( json.dumps({"problem": "Q", "correct_answer": "A", "response": "R"}) ) output_file = tmp_path / "graded.jsonl" mock_llm = Mock() mock_response = Mock() mock_response.content = ( "Extracted Answer: A\nReasoning: ok\nCorrect: yes" ) mock_llm.invoke.return_value = mock_response del mock_llm.chat_messages mock_get_llm.return_value = mock_llm grade_results(str(input_file), str(output_file)) mock_close.assert_called_once_with(mock_llm, "grader LLM") @patch("local_deep_research.benchmarks.graders.get_evaluation_llm") @patch("local_deep_research.utilities.resource_utils.safe_close") def test_safe_close_called_even_on_error( self, mock_close, mock_get_llm, tmp_path ): """safe_close is called even when grading raises.""" from local_deep_research.benchmarks.graders import grade_results mock_llm = Mock() mock_get_llm.return_value = mock_llm # Non-existent input file will cause an error with pytest.raises(Exception): grade_results( str(tmp_path / "nonexistent.jsonl"), str(tmp_path / "output.jsonl"), ) mock_close.assert_called_once_with(mock_llm, "grader LLM") class TestGradeResultsEmptyFile: """Cover accuracy calculation with empty results.""" def test_empty_results_zero_accuracy(self, tmp_path): """Empty results file produces zero accuracy without division error.""" from local_deep_research.benchmarks.graders import _grade_results_inner input_file = tmp_path / "results.jsonl" input_file.write_text("") output_file = tmp_path / "graded.jsonl" mock_llm = Mock() result = _grade_results_inner( mock_llm, str(input_file), str(output_file), "simpleqa", None ) assert result == []