Files
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

546 lines
19 KiB
Python

"""Extra coverage tests for benchmarks/graders.py targeting uncovered branches.
Targets:
- LLM invoke __call__ fallback (line 203)
- BrowseComp extraction edge cases (missing fields, multiline, malformed)
- SimpleQA "no" judgment
- Batch _grade_results_inner per-item exception
- grade_single_result chat_messages path
- extract_answer_from_response: SimpleQA path, BrowseComp with/without fields
- grade_single_result: exception path (grading error)
- _grade_results_inner: browsecomp dataset, progress callback, with existing output file
- get_evaluation_llm: custom config override, openai_endpoint API key from snapshot
"""
import json
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch
MODULE = "local_deep_research.benchmarks.graders"
def _make_llm_with_invoke(response_text):
"""Create a mock LLM with invoke() returning an object with .content."""
llm = MagicMock()
result = MagicMock()
result.content = response_text
llm.invoke.return_value = result
# Ensure callable interface also exists
llm.return_value = response_text
return llm
def _make_llm_callable_only(response_text):
"""Create a mock LLM without invoke attribute — uses __call__ fallback."""
llm = MagicMock(spec=[]) # No attributes
llm.return_value = response_text
return llm
def _make_llm_with_chat_messages(response_text):
"""Create a mock LLM with both invoke and chat_messages."""
llm = MagicMock()
llm.chat_messages = True
result = MagicMock()
result.content = response_text
llm.invoke.return_value = result
return llm
# ---------------------------------------------------------------------------
# LLM invoke __call__ fallback
# ---------------------------------------------------------------------------
class TestLLMCallFallback:
def test_callable_only_llm(self):
"""LLM without invoke uses __call__ fallback in grade_single_result."""
from local_deep_research.benchmarks.graders import grade_single_result
llm = _make_llm_callable_only(
"Extracted Answer: Paris\nReasoning: Capital of France\nCorrect: yes"
)
with patch(f"{MODULE}.get_evaluation_llm", return_value=llm):
result = grade_single_result(
{
"problem": "Capital of France?",
"correct_answer": "Paris",
"response": "Paris is the capital.",
},
dataset_type="simpleqa",
)
assert result["is_correct"] is True
assert result["extracted_by_grader"] == "Paris"
# ---------------------------------------------------------------------------
# BrowseComp extraction edge cases
# ---------------------------------------------------------------------------
class TestBrowseCompExtraction:
def test_missing_reasoning_field(self):
"""Response without reasoning → still extracts answer and correct."""
from local_deep_research.benchmarks.graders import grade_single_result
response = "extracted_final_answer: The Moon\ncorrect: yes"
llm = _make_llm_with_invoke(response)
with patch(f"{MODULE}.get_evaluation_llm", return_value=llm):
result = grade_single_result(
{
"problem": "What orbits Earth?",
"correct_answer": "The Moon",
"response": "The Moon orbits Earth",
},
dataset_type="browsecomp",
)
assert result["is_correct"] is True
assert result["extracted_by_grader"] == "The Moon"
def test_multiline_reasoning(self):
"""Response with multiline reasoning (re.DOTALL) → extracts correctly."""
from local_deep_research.benchmarks.graders import grade_single_result
response = (
"extracted_final_answer: 42\n"
"reasoning: The answer is 42\nbecause of the ultimate question\n\n"
"correct: yes\nconfidence: 95"
)
llm = _make_llm_with_invoke(response)
with patch(f"{MODULE}.get_evaluation_llm", return_value=llm):
result = grade_single_result(
{
"problem": "Answer to life?",
"correct_answer": "42",
"response": "42",
},
dataset_type="browsecomp",
)
assert result["is_correct"] is True
assert result["graded_confidence"] == "95"
def test_completely_malformed_response(self):
"""Malformed response → returns defaults (None/False)."""
from local_deep_research.benchmarks.graders import grade_single_result
llm = _make_llm_with_invoke("This is totally garbled output xyz")
with patch(f"{MODULE}.get_evaluation_llm", return_value=llm):
result = grade_single_result(
{
"problem": "Test?",
"correct_answer": "A",
"response": "B",
},
dataset_type="browsecomp",
)
assert result["is_correct"] is False
assert result["extracted_by_grader"] == "None"
# ---------------------------------------------------------------------------
# SimpleQA "no" judgment
# ---------------------------------------------------------------------------
class TestSimpleQANoJudgment:
def test_correct_no(self):
"""SimpleQA with 'Correct: no' → is_correct=False."""
from local_deep_research.benchmarks.graders import grade_single_result
response = (
"Extracted Answer: Wrong\nReasoning: Completely wrong\nCorrect: no"
)
llm = _make_llm_with_invoke(response)
with patch(f"{MODULE}.get_evaluation_llm", return_value=llm):
result = grade_single_result(
{
"problem": "What is 2+2?",
"correct_answer": "4",
"response": "5",
},
dataset_type="simpleqa",
)
assert result["is_correct"] is False
assert result["extracted_by_grader"] == "Wrong"
# ---------------------------------------------------------------------------
# Batch _grade_results_inner per-item exception
# ---------------------------------------------------------------------------
class TestBatchGradePerItemException:
def test_one_item_throws_others_still_graded(self):
"""One result throwing during grading → error recorded, others graded."""
from local_deep_research.benchmarks.graders import _grade_results_inner
# Create results file with 2 entries
with tempfile.NamedTemporaryFile(
mode="w", suffix=".jsonl", delete=False
) as f:
f.write(
json.dumps(
{
"problem": "Q1",
"correct_answer": "A1",
"response": "R1",
}
)
+ "\n"
)
f.write(
json.dumps(
{
"problem": "Q2",
"correct_answer": "A2",
"response": "R2",
}
)
+ "\n"
)
results_file = f.name
output_file = results_file + ".graded"
# LLM that fails on first call, succeeds on second
llm = MagicMock()
call_count = 0
def invoke_side_effect(prompt):
nonlocal call_count
call_count += 1
if call_count == 1:
raise RuntimeError("LLM failed")
result = MagicMock()
result.content = (
"Extracted Answer: A2\nReasoning: Correct\nCorrect: yes"
)
return result
llm.invoke = invoke_side_effect
try:
results = _grade_results_inner(
llm, results_file, output_file, "simpleqa", None
)
assert len(results) == 2
assert "grading_error" in results[0]
assert results[1].get("is_correct") is True
finally:
Path(results_file).unlink(missing_ok=True)
Path(output_file).unlink(missing_ok=True)
# ---------------------------------------------------------------------------
# grade_single_result chat_messages path
# ---------------------------------------------------------------------------
class TestChatMessagesPath:
def test_chat_messages_format(self):
"""LLM with chat_messages attribute → uses HumanMessage format."""
from local_deep_research.benchmarks.graders import grade_single_result
response_text = (
"Extracted Answer: Paris\nReasoning: Capital\nCorrect: yes"
)
llm = _make_llm_with_chat_messages(response_text)
with patch(f"{MODULE}.get_evaluation_llm", return_value=llm):
result = grade_single_result(
{
"problem": "Capital of France?",
"correct_answer": "Paris",
"response": "Paris",
},
dataset_type="simpleqa",
)
assert result["is_correct"] is True
# Verify HumanMessage was used (invoke called with list)
call_args = llm.invoke.call_args[0][0]
assert isinstance(call_args, list)
# ---------------------------------------------------------------------------
# extract_answer_from_response
# ---------------------------------------------------------------------------
class TestExtractAnswerFromResponse:
def test_simpleqa_strips_citations(self):
"""SimpleQA: citations [1] [2] removed, whole response returned."""
from local_deep_research.benchmarks.graders import (
extract_answer_from_response,
)
result = extract_answer_from_response(
"Paris is the capital [1] of France [2].", "simpleqa"
)
assert result["extracted_answer"] == "Paris is the capital of France ."
assert result["confidence"] == "100"
def test_browsecomp_extracts_answer_and_confidence(self):
"""BrowseComp: extracts Exact Answer and Confidence."""
from local_deep_research.benchmarks.graders import (
extract_answer_from_response,
)
result = extract_answer_from_response(
"Exact Answer: The Moon\nConfidence: 95%", "browsecomp"
)
assert result["extracted_answer"] == "The Moon"
assert result["confidence"] == "95"
def test_browsecomp_no_match(self):
"""BrowseComp: no matching fields → defaults."""
from local_deep_research.benchmarks.graders import (
extract_answer_from_response,
)
result = extract_answer_from_response("Random text here", "browsecomp")
assert result["extracted_answer"] == "None"
assert result["confidence"] == "100"
def test_browsecomp_no_confidence(self):
"""BrowseComp: answer found but no confidence → default 100."""
from local_deep_research.benchmarks.graders import (
extract_answer_from_response,
)
result = extract_answer_from_response(
"Exact Answer: Jupiter", "browsecomp"
)
assert result["extracted_answer"] == "Jupiter"
assert result["confidence"] == "100"
# ---------------------------------------------------------------------------
# grade_single_result — exception path
# ---------------------------------------------------------------------------
class TestGradeSingleResultException:
def test_llm_exception_returns_error(self):
"""LLM raises exception → returns grading_error dict."""
from local_deep_research.benchmarks.graders import grade_single_result
llm = MagicMock()
llm.invoke.side_effect = RuntimeError("API down")
with patch(f"{MODULE}.get_evaluation_llm", return_value=llm):
result = grade_single_result(
{
"problem": "Q?",
"correct_answer": "A",
"response": "R",
},
dataset_type="simpleqa",
)
assert "grading_error" in result
assert result["is_correct"] is False
assert result["graded_confidence"] == "0"
# ---------------------------------------------------------------------------
# _grade_results_inner — browsecomp + progress callback + existing output
# ---------------------------------------------------------------------------
class TestGradeResultsInnerBrowsecomp:
def test_browsecomp_batch(self):
"""Batch grading with browsecomp dataset type."""
from local_deep_research.benchmarks.graders import _grade_results_inner
with tempfile.NamedTemporaryFile(
mode="w", suffix=".jsonl", delete=False
) as f:
f.write(
json.dumps(
{
"problem": "Q1",
"correct_answer": "A1",
"response": "R1",
}
)
+ "\n"
)
results_file = f.name
output_file = results_file + ".graded"
llm = MagicMock()
result_obj = MagicMock()
result_obj.content = (
"extracted_final_answer: A1\n"
"reasoning: Looks correct\n\n"
"correct: yes\nconfidence: 90"
)
llm.invoke.return_value = result_obj
try:
results = _grade_results_inner(
llm, results_file, output_file, "browsecomp", None
)
assert len(results) == 1
assert results[0]["is_correct"] is True
assert results[0]["graded_confidence"] == "90"
finally:
Path(results_file).unlink(missing_ok=True)
Path(output_file).unlink(missing_ok=True)
def test_with_progress_callback(self):
"""Progress callback called for each result."""
from local_deep_research.benchmarks.graders import _grade_results_inner
with tempfile.NamedTemporaryFile(
mode="w", suffix=".jsonl", delete=False
) as f:
f.write(
json.dumps(
{
"problem": "Q1",
"correct_answer": "A1",
"response": "R1",
}
)
+ "\n"
)
results_file = f.name
output_file = results_file + ".graded"
llm = MagicMock()
result_obj = MagicMock()
result_obj.content = (
"Extracted Answer: A1\nReasoning: Good\nCorrect: yes"
)
llm.invoke.return_value = result_obj
callback_calls = []
def progress_cb(idx, total, meta):
callback_calls.append((idx, total, meta["status"]))
try:
_grade_results_inner(
llm, results_file, output_file, "simpleqa", progress_cb
)
# Should be called twice per item: "grading" and "graded"
assert len(callback_calls) == 2
assert callback_calls[0][2] == "grading"
assert callback_calls[1][2] == "graded"
finally:
Path(results_file).unlink(missing_ok=True)
Path(output_file).unlink(missing_ok=True)
def test_existing_output_file_removed(self):
"""Existing output file gets removed before grading starts."""
from local_deep_research.benchmarks.graders import _grade_results_inner
with tempfile.NamedTemporaryFile(
mode="w", suffix=".jsonl", delete=False
) as f:
f.write(
json.dumps(
{
"problem": "Q",
"correct_answer": "A",
"response": "R",
}
)
+ "\n"
)
results_file = f.name
output_file = results_file + ".graded"
# Create pre-existing output file
Path(output_file).write_text("old data\n")
llm = MagicMock()
result_obj = MagicMock()
result_obj.content = (
"Extracted Answer: A\nReasoning: Correct\nCorrect: yes"
)
llm.invoke.return_value = result_obj
try:
_grade_results_inner(
llm, results_file, output_file, "simpleqa", None
)
# Output file should only contain the new result, not "old data"
content = Path(output_file).read_text()
assert "old data" not in content
finally:
Path(results_file).unlink(missing_ok=True)
Path(output_file).unlink(missing_ok=True)
# ---------------------------------------------------------------------------
# get_evaluation_llm
# ---------------------------------------------------------------------------
class TestGetEvaluationLLM:
def test_custom_config_override(self):
"""Custom config merges with defaults."""
from local_deep_research.benchmarks.graders import get_evaluation_llm
with patch(f"{MODULE}.get_llm") as mock_get_llm:
mock_get_llm.return_value = MagicMock()
get_evaluation_llm(custom_config={"model_name": "custom-model"})
call_kwargs = mock_get_llm.call_args.kwargs
assert call_kwargs["model_name"] == "custom-model"
def test_api_key_from_settings_snapshot(self):
"""API key extracted from settings snapshot dict format."""
from local_deep_research.benchmarks.graders import get_evaluation_llm
snapshot = {"llm.openai_endpoint.api_key": {"value": "sk-test-key-123"}}
with patch(f"{MODULE}.get_llm") as mock_get_llm:
mock_get_llm.return_value = MagicMock()
get_evaluation_llm(settings_snapshot=snapshot)
# Should not raise or warn about missing API key
mock_get_llm.assert_called_once()
def test_api_key_from_settings_snapshot_plain_string(self):
"""API key as plain string (not dict) from snapshot."""
from local_deep_research.benchmarks.graders import get_evaluation_llm
snapshot = {"llm.openai_endpoint.api_key": "sk-plain-key"}
with patch(f"{MODULE}.get_llm") as mock_get_llm:
mock_get_llm.return_value = MagicMock()
get_evaluation_llm(settings_snapshot=snapshot)
mock_get_llm.assert_called_once()
def test_no_snapshot_no_api_key(self):
"""No snapshot, no API key → warns but proceeds."""
from local_deep_research.benchmarks.graders import get_evaluation_llm
with patch(f"{MODULE}.get_llm") as mock_get_llm:
mock_get_llm.return_value = MagicMock()
get_evaluation_llm()
mock_get_llm.assert_called_once()