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
985 lines
31 KiB
Python
985 lines
31 KiB
Python
"""
|
|
Tests for the candidate exploration system.
|
|
|
|
Tests cover:
|
|
- BaseCandidateExplorer search execution
|
|
- Exploration time and candidate limits
|
|
- Query generation and result handling
|
|
"""
|
|
|
|
import time
|
|
from unittest.mock import Mock
|
|
|
|
|
|
class TestBaseCandidateExplorer:
|
|
"""Tests for the BaseCandidateExplorer class."""
|
|
|
|
def test_base_explorer_execute_search_list_results(self):
|
|
"""Test _execute_search with list results."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
# Create a concrete implementation for testing
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
mock_search.run.return_value = [
|
|
{"title": "Result 1", "snippet": "Snippet 1"},
|
|
{"title": "Result 2", "snippet": "Snippet 2"},
|
|
]
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
max_candidates=50,
|
|
max_search_time=60.0,
|
|
)
|
|
|
|
result = explorer._execute_search("test query")
|
|
|
|
assert "results" in result
|
|
assert len(result["results"]) == 2
|
|
assert result["query"] == "test query"
|
|
|
|
def test_base_explorer_execute_search_dict_results(self):
|
|
"""Test _execute_search with dict results."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
mock_search.run.return_value = {
|
|
"results": [
|
|
{"title": "Result 1", "snippet": "Snippet 1"},
|
|
],
|
|
"total": 1,
|
|
}
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
result = explorer._execute_search("test query")
|
|
|
|
assert "results" in result
|
|
assert len(result["results"]) == 1
|
|
|
|
def test_base_explorer_should_continue_time_limit(self):
|
|
"""Test exploration stops at time limit."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
max_candidates=100,
|
|
max_search_time=1.0, # 1 second limit
|
|
)
|
|
|
|
start_time = time.time() - 2.0 # Simulate 2 seconds elapsed
|
|
|
|
result = explorer._should_continue_exploration(start_time, 0)
|
|
|
|
assert result is False # Should stop due to time limit
|
|
|
|
def test_base_explorer_should_continue_candidate_limit(self):
|
|
"""Test exploration stops at max candidates."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
max_candidates=10,
|
|
max_search_time=60.0,
|
|
)
|
|
|
|
start_time = time.time() # Just started
|
|
|
|
result = explorer._should_continue_exploration(start_time, 15)
|
|
|
|
assert result is False # Should stop due to candidate limit
|
|
|
|
def test_base_explorer_deduplicate_candidates(self):
|
|
"""Test candidate deduplication."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
from local_deep_research.advanced_search_system.candidates.base_candidate import (
|
|
Candidate,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
candidates = [
|
|
Candidate(name="Apple Inc"),
|
|
Candidate(name="apple inc"), # Duplicate (case insensitive)
|
|
Candidate(name="Google"),
|
|
Candidate(name=" google "), # Duplicate (with whitespace)
|
|
Candidate(name="Microsoft"),
|
|
]
|
|
|
|
unique = explorer._deduplicate_candidates(candidates)
|
|
|
|
assert len(unique) == 3 # Only Apple, Google, Microsoft
|
|
|
|
def test_base_explorer_rank_candidates_by_relevance(self):
|
|
"""Test candidate ranking."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
from local_deep_research.advanced_search_system.candidates.base_candidate import (
|
|
Candidate,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
candidates = [
|
|
Candidate(
|
|
name="Random Thing", metadata={"query": "unrelated search"}
|
|
),
|
|
Candidate(
|
|
name="Python Language", metadata={"query": "python programming"}
|
|
),
|
|
]
|
|
|
|
ranked = explorer._rank_candidates_by_relevance(
|
|
candidates, "python programming"
|
|
)
|
|
|
|
# The candidate with matching query words should rank higher
|
|
assert ranked[0].name == "Python Language"
|
|
|
|
def test_base_explorer_extract_entity_names_empty(self):
|
|
"""Test entity name extraction with empty text."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
# Empty text should return empty list
|
|
names = explorer._extract_entity_names("")
|
|
assert names == []
|
|
|
|
names = explorer._extract_entity_names(" ")
|
|
assert names == []
|
|
|
|
def test_base_explorer_execute_search_unknown_format(self):
|
|
"""Test _execute_search with unknown result format."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
mock_search.run.return_value = "unexpected string result"
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
result = explorer._execute_search("test query")
|
|
|
|
assert "results" in result
|
|
assert result["results"] == []
|
|
|
|
def test_base_explorer_execute_search_exception(self):
|
|
"""Test _execute_search handles exceptions."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
mock_search.run.side_effect = Exception("Search error")
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
result = explorer._execute_search("test query")
|
|
|
|
assert "results" in result
|
|
assert result["results"] == []
|
|
|
|
def test_base_explorer_execute_search_tracks_queries(self):
|
|
"""Test _execute_search tracks explored queries."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
mock_search.run.return_value = []
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
explorer._execute_search("Test Query")
|
|
|
|
assert "test query" in explorer.explored_queries
|
|
|
|
def test_base_explorer_extract_candidates_empty_results(self):
|
|
"""Test _extract_candidates_from_results with empty results."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
candidates = explorer._extract_candidates_from_results(
|
|
{"results": []}, "test query"
|
|
)
|
|
|
|
assert candidates == []
|
|
|
|
def test_base_explorer_extract_candidates_no_query(self):
|
|
"""Test _extract_candidates_from_results without original query."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
results = {"results": [{"title": "Test", "snippet": "Content"}]}
|
|
candidates = explorer._extract_candidates_from_results(results)
|
|
|
|
assert candidates == []
|
|
|
|
def test_base_explorer_extract_candidates_with_results(self):
|
|
"""Test _extract_candidates_from_results with actual results."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.return_value = Mock(content="Answer 1\nAnswer 2")
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
results = {
|
|
"results": [
|
|
{"title": "Title 1", "snippet": "Snippet 1"},
|
|
{"title": "Title 2", "snippet": "Snippet 2"},
|
|
],
|
|
"query": "search query",
|
|
}
|
|
|
|
candidates = explorer._extract_candidates_from_results(
|
|
results, "original query"
|
|
)
|
|
|
|
assert len(candidates) == 2
|
|
assert candidates[0].name == "Answer 1"
|
|
|
|
def test_base_explorer_extract_candidates_skips_duplicates(self):
|
|
"""Test _extract_candidates_from_results skips already found candidates."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
from local_deep_research.advanced_search_system.candidates.base_candidate import (
|
|
Candidate,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.return_value = Mock(
|
|
content="Existing Answer\nNew Answer"
|
|
)
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
explorer.found_candidates["Existing Answer"] = Candidate(
|
|
name="Existing Answer"
|
|
)
|
|
|
|
results = {"results": [{"title": "Title", "snippet": "Snippet"}]}
|
|
candidates = explorer._extract_candidates_from_results(results, "query")
|
|
|
|
assert len(candidates) == 1
|
|
assert candidates[0].name == "New Answer"
|
|
|
|
def test_base_explorer_generate_answer_candidates(self):
|
|
"""Test _generate_answer_candidates method."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.return_value = Mock(
|
|
content="• Answer One\n- Answer Two\n1. Answer Three"
|
|
)
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
answers = explorer._generate_answer_candidates(
|
|
"Question?", "Search content"
|
|
)
|
|
|
|
assert len(answers) == 3
|
|
assert "Answer One" in answers
|
|
assert "Answer Two" in answers
|
|
assert "Answer Three" in answers
|
|
|
|
def test_base_explorer_generate_answer_candidates_exception(self):
|
|
"""Test _generate_answer_candidates handles exceptions."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.side_effect = Exception("Model error")
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
answers = explorer._generate_answer_candidates("Question?", "Content")
|
|
|
|
assert answers == []
|
|
|
|
def test_base_explorer_generate_answer_candidates_limits_to_five(self):
|
|
"""Test _generate_answer_candidates limits results to 5."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.return_value = Mock(
|
|
content="A1\nA2\nA3\nA4\nA5\nA6\nA7\nA8"
|
|
)
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
answers = explorer._generate_answer_candidates("Q?", "Content")
|
|
|
|
assert len(answers) <= 5
|
|
|
|
def test_base_explorer_generate_answer_candidates_skips_short(self):
|
|
"""Test _generate_answer_candidates skips very short answers."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.return_value = Mock(content="AB\nValid Answer\nXY")
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
answers = explorer._generate_answer_candidates("Q?", "Content")
|
|
|
|
assert "AB" not in answers
|
|
assert "XY" not in answers
|
|
assert "Valid Answer" in answers
|
|
|
|
def test_base_explorer_extract_entity_names_with_text(self):
|
|
"""Test _extract_entity_names with actual text."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.return_value = Mock(content="Apple Inc\nGoogle LLC")
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
names = explorer._extract_entity_names("Text about Apple and Google")
|
|
|
|
assert len(names) == 2
|
|
assert "Apple Inc" in names
|
|
|
|
def test_base_explorer_extract_entity_names_with_entity_type(self):
|
|
"""Test _extract_entity_names with entity type specified."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.return_value = Mock(content="New York\nLos Angeles")
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
explorer._extract_entity_names("Text about cities", "city")
|
|
|
|
# Should have invoked model with entity type in prompt
|
|
call_args = mock_model.invoke.call_args[0][0]
|
|
assert "city" in call_args
|
|
|
|
def test_base_explorer_extract_entity_names_exception(self):
|
|
"""Test _extract_entity_names handles exceptions."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.side_effect = Exception("Model error")
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
names = explorer._extract_entity_names("Some text")
|
|
|
|
assert names == []
|
|
|
|
def test_base_explorer_extract_entity_names_filters_articles(self):
|
|
"""Test _extract_entity_names filters names starting with articles."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.return_value = Mock(
|
|
content="The Company\na small thing\nan entity\nValid Name"
|
|
)
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
names = explorer._extract_entity_names("Text")
|
|
|
|
assert "The Company" not in names
|
|
assert "a small thing" not in names
|
|
assert "an entity" not in names
|
|
assert "Valid Name" in names
|
|
|
|
def test_base_explorer_extract_entity_names_limits_to_five(self):
|
|
"""Test _extract_entity_names limits results to 5."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_model.invoke.return_value = Mock(
|
|
content="Name1\nName2\nName3\nName4\nName5\nName6\nName7"
|
|
)
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
names = explorer._extract_entity_names("Text")
|
|
|
|
assert len(names) <= 5
|
|
|
|
def test_base_explorer_should_continue_returns_true(self):
|
|
"""Test _should_continue_exploration returns True when within limits."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
max_candidates=100,
|
|
max_search_time=60.0,
|
|
)
|
|
|
|
start_time = time.time()
|
|
|
|
result = explorer._should_continue_exploration(start_time, 5)
|
|
|
|
assert result is True
|
|
|
|
def test_base_explorer_rank_empty_candidates(self):
|
|
"""Test _rank_candidates_by_relevance with empty list."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
result = explorer._rank_candidates_by_relevance([], "query")
|
|
|
|
assert result == []
|
|
|
|
def test_base_explorer_rank_with_result_title(self):
|
|
"""Test _rank_candidates_by_relevance with result_title metadata."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
from local_deep_research.advanced_search_system.candidates.base_candidate import (
|
|
Candidate,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
candidates = [
|
|
Candidate(
|
|
name="Low Match",
|
|
metadata={
|
|
"query": "unrelated",
|
|
"result_title": "something else",
|
|
},
|
|
),
|
|
Candidate(
|
|
name="High Match",
|
|
metadata={
|
|
"query": "python programming",
|
|
"result_title": "python programming guide",
|
|
},
|
|
),
|
|
]
|
|
|
|
ranked = explorer._rank_candidates_by_relevance(
|
|
candidates, "python programming"
|
|
)
|
|
|
|
assert ranked[0].name == "High Match"
|
|
|
|
def test_base_explorer_init_defaults(self):
|
|
"""Test BaseCandidateExplorer initialization defaults."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
BaseCandidateExplorer,
|
|
)
|
|
|
|
class TestExplorer(BaseCandidateExplorer):
|
|
def explore(
|
|
self, initial_query, constraints=None, entity_type=None
|
|
):
|
|
pass
|
|
|
|
def generate_exploration_queries(
|
|
self, base_query, found_candidates, constraints=None
|
|
):
|
|
return []
|
|
|
|
mock_model = Mock()
|
|
mock_search = Mock()
|
|
|
|
explorer = TestExplorer(
|
|
model=mock_model,
|
|
search_engine=mock_search,
|
|
)
|
|
|
|
assert explorer.max_candidates == 50
|
|
assert explorer.max_search_time == 60.0
|
|
assert explorer.explored_queries == set()
|
|
assert explorer.found_candidates == {}
|
|
|
|
def test_exploration_strategy_enum(self):
|
|
"""Test ExplorationStrategy enum values."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
ExplorationStrategy,
|
|
)
|
|
|
|
assert ExplorationStrategy.BREADTH_FIRST.value == "breadth_first"
|
|
assert ExplorationStrategy.DEPTH_FIRST.value == "depth_first"
|
|
assert (
|
|
ExplorationStrategy.CONSTRAINT_GUIDED.value == "constraint_guided"
|
|
)
|
|
assert (
|
|
ExplorationStrategy.DIVERSITY_FOCUSED.value == "diversity_focused"
|
|
)
|
|
assert ExplorationStrategy.ADAPTIVE.value == "adaptive"
|
|
|
|
def test_exploration_result_dataclass(self):
|
|
"""Test ExplorationResult dataclass."""
|
|
from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import (
|
|
ExplorationResult,
|
|
ExplorationStrategy,
|
|
)
|
|
from local_deep_research.advanced_search_system.candidates.base_candidate import (
|
|
Candidate,
|
|
)
|
|
|
|
candidates = [Candidate(name="Test")]
|
|
result = ExplorationResult(
|
|
candidates=candidates,
|
|
total_searched=10,
|
|
unique_candidates=5,
|
|
exploration_paths=["path1", "path2"],
|
|
metadata={"key": "value"},
|
|
elapsed_time=5.5,
|
|
strategy_used=ExplorationStrategy.BREADTH_FIRST,
|
|
)
|
|
|
|
assert len(result.candidates) == 1
|
|
assert result.total_searched == 10
|
|
assert result.unique_candidates == 5
|
|
assert len(result.exploration_paths) == 2
|
|
assert result.metadata["key"] == "value"
|
|
assert result.elapsed_time == 5.5
|
|
assert result.strategy_used == ExplorationStrategy.BREADTH_FIRST
|