7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
289 lines
10 KiB
Python
289 lines
10 KiB
Python
"""
|
|
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 == []
|