chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
"""End-to-end tests for API settings in research workflows."""
|
||||
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from local_deep_research.api import quick_summary, detailed_research
|
||||
from local_deep_research.api.settings_utils import create_settings_snapshot
|
||||
|
||||
|
||||
class TestE2EResearchWithSettings:
|
||||
"""Test end-to-end research workflows with various settings."""
|
||||
|
||||
@patch("local_deep_research.api.research_functions.get_llm")
|
||||
@patch("local_deep_research.api.research_functions.get_search")
|
||||
def test_quick_summary_full_flow(self, mock_get_search, mock_get_llm):
|
||||
"""Test quick_summary with full settings propagation.
|
||||
|
||||
Patches happen on `api.research_functions` because the names are
|
||||
imported into that module at import time — patching the original
|
||||
module (`config.llm_config.get_llm`) would not affect already-bound
|
||||
references.
|
||||
"""
|
||||
# Mock LLM
|
||||
mock_llm = MagicMock()
|
||||
mock_llm.invoke.return_value = MagicMock(content="Test summary")
|
||||
mock_get_llm.return_value = mock_llm
|
||||
|
||||
# Mock search engine
|
||||
mock_search = MagicMock()
|
||||
mock_search.search.return_value = {
|
||||
"results": [
|
||||
{
|
||||
"title": "Result 1",
|
||||
"url": "http://example.com/1",
|
||||
"snippet": "Snippet 1",
|
||||
},
|
||||
{
|
||||
"title": "Result 2",
|
||||
"url": "http://example.com/2",
|
||||
"snippet": "Snippet 2",
|
||||
},
|
||||
]
|
||||
}
|
||||
mock_get_search.return_value = mock_search
|
||||
|
||||
# Run quick summary with custom settings
|
||||
result = quick_summary(
|
||||
"What is quantum computing?",
|
||||
provider="anthropic",
|
||||
api_key="test-key",
|
||||
temperature=0.5,
|
||||
max_search_results=10,
|
||||
settings_override={
|
||||
"llm.anthropic.model": "claude-3-opus-20240229",
|
||||
"search.tool": "duckduckgo",
|
||||
"search.region": "us-en",
|
||||
},
|
||||
)
|
||||
|
||||
# Verify LLM was configured correctly
|
||||
mock_get_llm.assert_called()
|
||||
|
||||
# Verify search was configured correctly. get_search() takes the
|
||||
# tool name as the first positional arg; everything else is in
|
||||
# settings_snapshot rather than dedicated kwargs.
|
||||
mock_get_search.assert_called()
|
||||
search_args, search_kwargs = mock_get_search.call_args
|
||||
assert search_args[0] == "duckduckgo"
|
||||
snapshot = search_kwargs["settings_snapshot"]
|
||||
assert snapshot["search.region"]["value"] == "us-en"
|
||||
assert snapshot["search.max_results"]["value"] == 10
|
||||
|
||||
# Verify result structure
|
||||
assert "summary" in result
|
||||
assert "findings" in result
|
||||
assert "iterations" in result
|
||||
|
||||
@patch("local_deep_research.api.research_functions.get_llm")
|
||||
@patch("local_deep_research.api.research_functions.get_search")
|
||||
def test_detailed_research_full_flow(self, mock_get_search, mock_get_llm):
|
||||
"""Test detailed_research with comprehensive settings.
|
||||
|
||||
detailed_research accepts only `settings_snapshot` for configuration
|
||||
(unlike quick_summary which has provider/api_key shortcuts), so the
|
||||
test builds a snapshot via create_settings_snapshot and threads it
|
||||
in directly. Patch targets follow `api.research_functions` because
|
||||
that is where get_llm / get_search are imported and bound.
|
||||
"""
|
||||
# Mock LLM with different responses
|
||||
mock_llm = MagicMock()
|
||||
mock_llm.invoke.side_effect = [
|
||||
MagicMock(content="Initial analysis"),
|
||||
MagicMock(content="Deeper analysis"),
|
||||
MagicMock(content="Final synthesis"),
|
||||
]
|
||||
mock_get_llm.return_value = mock_llm
|
||||
|
||||
mock_search = MagicMock()
|
||||
mock_get_search.return_value = mock_search
|
||||
|
||||
snapshot = create_settings_snapshot(
|
||||
provider="openai",
|
||||
overrides={
|
||||
"search.iterations": 3,
|
||||
"search.max_results": 20,
|
||||
"search.engines.arxiv.enabled": True,
|
||||
"llm.max_tokens": 4000,
|
||||
},
|
||||
)
|
||||
|
||||
# Run detailed research with custom settings
|
||||
result = detailed_research(
|
||||
"Explain the applications of quantum computing in cryptography",
|
||||
settings_snapshot=snapshot,
|
||||
)
|
||||
|
||||
# The settings_snapshot values should be threaded through to
|
||||
# get_search.
|
||||
mock_get_search.assert_called()
|
||||
passed_snapshot = mock_get_search.call_args[1]["settings_snapshot"]
|
||||
assert passed_snapshot["search.max_results"]["value"] == 20
|
||||
assert passed_snapshot["search.iterations"]["value"] == 3
|
||||
|
||||
# Verify the structured-report shape is returned (detailed_research
|
||||
# returns a report dict, not a summary dict).
|
||||
assert isinstance(result, dict)
|
||||
|
||||
def test_settings_isolation_between_calls(self):
|
||||
"""Test that settings don't leak between API calls."""
|
||||
with patch(
|
||||
"local_deep_research.api.research_functions._init_search_system"
|
||||
) as mock_init:
|
||||
mock_system = MagicMock()
|
||||
mock_system.analyze_topic.return_value = {
|
||||
"current_knowledge": "Summary",
|
||||
"findings": [],
|
||||
"iterations": 1,
|
||||
"questions": {},
|
||||
"formatted_findings": "Findings",
|
||||
"all_links_of_system": [],
|
||||
}
|
||||
mock_init.return_value = mock_system
|
||||
|
||||
# First call with one set of settings
|
||||
_ = quick_summary("Query 1", provider="openai", temperature=0.9)
|
||||
|
||||
# Second call with different settings
|
||||
_ = quick_summary("Query 2", provider="anthropic", temperature=0.1)
|
||||
|
||||
# Verify each call got its own settings
|
||||
call1_settings = mock_init.call_args_list[0][1]["settings_snapshot"]
|
||||
call2_settings = mock_init.call_args_list[1][1]["settings_snapshot"]
|
||||
|
||||
assert call1_settings["llm.provider"]["value"] == "openai"
|
||||
assert call1_settings["llm.temperature"]["value"] == 0.9
|
||||
|
||||
assert call2_settings["llm.provider"]["value"] == "anthropic"
|
||||
assert call2_settings["llm.temperature"]["value"] == 0.1
|
||||
|
||||
|
||||
class TestMultiProviderScenarios:
|
||||
"""Test scenarios with multiple LLM providers."""
|
||||
|
||||
def test_multi_model_research(self):
|
||||
"""Test research using multiple models for comparison."""
|
||||
models = [
|
||||
("openai", "gpt-4", 0.3),
|
||||
("anthropic", "claude-3-opus-20240229", 0.3),
|
||||
("openai", "gpt-3.5-turbo", 0.5),
|
||||
]
|
||||
|
||||
results = []
|
||||
|
||||
with patch(
|
||||
"local_deep_research.api.research_functions._init_search_system"
|
||||
) as mock_init:
|
||||
mock_system = MagicMock()
|
||||
mock_system.analyze_topic.return_value = {
|
||||
"current_knowledge": "Model-specific summary",
|
||||
"findings": [],
|
||||
"iterations": 1,
|
||||
"questions": {},
|
||||
"formatted_findings": "Findings",
|
||||
"all_links_of_system": [],
|
||||
}
|
||||
mock_init.return_value = mock_system
|
||||
|
||||
for provider, model, temp in models:
|
||||
result = quick_summary(
|
||||
"Compare quantum and classical computing",
|
||||
provider=provider,
|
||||
temperature=temp,
|
||||
settings_override={
|
||||
f"llm.{provider}.model": model,
|
||||
},
|
||||
)
|
||||
results.append((provider, model, result))
|
||||
|
||||
# Verify each call used different settings
|
||||
assert mock_init.call_count == len(models)
|
||||
|
||||
for i, (provider, model, _) in enumerate(models):
|
||||
call_settings = mock_init.call_args_list[i][1][
|
||||
"settings_snapshot"
|
||||
]
|
||||
assert call_settings["llm.provider"]["value"] == provider
|
||||
assert call_settings[f"llm.{provider}.model"]["value"] == model
|
||||
|
||||
|
||||
class TestSearchEngineIntegration:
|
||||
"""Test integration with various search engines through settings."""
|
||||
|
||||
@patch("local_deep_research.api.research_functions.get_llm")
|
||||
@patch("local_deep_research.api.research_functions.get_search")
|
||||
def test_search_engine_specific_settings(
|
||||
self, mock_get_search, mock_get_llm
|
||||
):
|
||||
"""Test that search engine specific settings are applied.
|
||||
|
||||
Each call's positional first argument identifies the chosen tool;
|
||||
engine-specific settings should appear in `settings_snapshot`.
|
||||
Patches target `api.research_functions` since that is where the
|
||||
names are bound.
|
||||
"""
|
||||
search_configs = [
|
||||
{
|
||||
"engine": "searxng",
|
||||
"settings": {
|
||||
"search.tool": "searxng",
|
||||
"search.engines.searxng.base_url": "https://searx.example.com",
|
||||
"search.engines.searxng.timeout": 15,
|
||||
},
|
||||
},
|
||||
{
|
||||
"engine": "duckduckgo",
|
||||
"settings": {
|
||||
"search.tool": "duckduckgo",
|
||||
"search.engines.duckduckgo.region": "uk-en",
|
||||
"search.engines.duckduckgo.safe_search": False,
|
||||
},
|
||||
},
|
||||
{
|
||||
"engine": "wikipedia",
|
||||
"settings": {
|
||||
"search.tool": "wikipedia",
|
||||
"search.engines.wikipedia.language": "es",
|
||||
"search.engines.wikipedia.max_chars": 2000,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
mock_get_llm.return_value = MagicMock()
|
||||
mock_get_search.return_value = MagicMock()
|
||||
|
||||
for config in search_configs:
|
||||
_ = quick_summary(
|
||||
f"Test with {config['engine']}",
|
||||
provider="openai",
|
||||
api_key="test-key",
|
||||
settings_override=config["settings"],
|
||||
)
|
||||
|
||||
# Verify the chosen search engine appears as the first positional
|
||||
# argument (the get_search public signature).
|
||||
last_args, last_kwargs = mock_get_search.call_args
|
||||
assert last_args[0] == config["engine"]
|
||||
|
||||
# Engine-specific overrides must propagate through settings_snapshot.
|
||||
snapshot = last_kwargs["settings_snapshot"]
|
||||
for key, value in config["settings"].items():
|
||||
assert key in snapshot, (
|
||||
f"missing snapshot key {key} for {config['engine']}"
|
||||
)
|
||||
assert snapshot[key]["value"] == value
|
||||
|
||||
|
||||
class TestPerformanceSettings:
|
||||
"""Test performance-related settings."""
|
||||
|
||||
def test_concurrent_research_settings(self):
|
||||
"""Test settings for concurrent research operations."""
|
||||
# Settings optimized for concurrent operations
|
||||
concurrent_settings = create_settings_snapshot(
|
||||
overrides={
|
||||
"research.concurrent.enabled": True,
|
||||
"research.concurrent.max_workers": 5,
|
||||
"llm.request_timeout": 30,
|
||||
"llm.max_retries": 2,
|
||||
"search.request_timeout": 10,
|
||||
"search.concurrent_searches": 3,
|
||||
}
|
||||
)
|
||||
|
||||
# Verify concurrent settings
|
||||
assert (
|
||||
concurrent_settings["research.concurrent.enabled"]["value"] is True
|
||||
)
|
||||
assert (
|
||||
concurrent_settings["research.concurrent.max_workers"]["value"] == 5
|
||||
)
|
||||
assert concurrent_settings["llm.request_timeout"]["value"] == 30
|
||||
|
||||
def test_rate_limiting_settings(self):
|
||||
"""Test rate limiting settings for API calls."""
|
||||
rate_limit_settings = create_settings_snapshot(
|
||||
overrides={
|
||||
"llm.rate_limit.enabled": True,
|
||||
"llm.rate_limit.requests_per_minute": 60,
|
||||
"llm.rate_limit.tokens_per_minute": 90000,
|
||||
"llm.rate_limit.concurrent_requests": 5,
|
||||
"search.rate_limit.requests_per_second": 10,
|
||||
}
|
||||
)
|
||||
|
||||
# Verify rate limiting configuration
|
||||
assert rate_limit_settings["llm.rate_limit.enabled"]["value"] is True
|
||||
assert (
|
||||
rate_limit_settings["llm.rate_limit.requests_per_minute"]["value"]
|
||||
== 60
|
||||
)
|
||||
assert (
|
||||
rate_limit_settings["search.rate_limit.requests_per_second"][
|
||||
"value"
|
||||
]
|
||||
== 10
|
||||
)
|
||||
|
||||
|
||||
class TestDebugAndMonitoringSettings:
|
||||
"""Test debug and monitoring related settings."""
|
||||
|
||||
def test_debug_mode_settings(self):
|
||||
"""Test settings for debug mode."""
|
||||
debug_settings = create_settings_snapshot(
|
||||
overrides={
|
||||
"debug.enabled": True,
|
||||
"debug.log_level": "DEBUG",
|
||||
"debug.log_api_calls": True,
|
||||
"debug.log_search_queries": True,
|
||||
"debug.save_intermediate_results": True,
|
||||
"debug.pretty_print_responses": True,
|
||||
}
|
||||
)
|
||||
|
||||
# Verify debug settings
|
||||
assert debug_settings["debug.enabled"]["value"] is True
|
||||
assert debug_settings["debug.log_level"]["value"] == "DEBUG"
|
||||
assert debug_settings["debug.log_api_calls"]["value"] is True
|
||||
|
||||
def test_monitoring_settings(self):
|
||||
"""Test settings for monitoring and metrics."""
|
||||
monitoring_settings = create_settings_snapshot(
|
||||
overrides={
|
||||
"monitoring.enabled": True,
|
||||
"monitoring.metrics.api_latency": True,
|
||||
"monitoring.metrics.token_usage": True,
|
||||
"monitoring.metrics.search_performance": True,
|
||||
"monitoring.export.format": "prometheus",
|
||||
"monitoring.export.endpoint": "http://metrics.example.com",
|
||||
}
|
||||
)
|
||||
|
||||
# Verify monitoring configuration
|
||||
assert monitoring_settings["monitoring.enabled"]["value"] is True
|
||||
assert (
|
||||
monitoring_settings["monitoring.metrics.token_usage"]["value"]
|
||||
is True
|
||||
)
|
||||
assert (
|
||||
monitoring_settings["monitoring.export.format"]["value"]
|
||||
== "prometheus"
|
||||
)
|
||||
Reference in New Issue
Block a user