diff --git a/routes/research_routes.py b/routes/research_routes.py index a04f76298..bfcbc0422 100644 --- a/routes/research_routes.py +++ b/routes/research_routes.py @@ -315,7 +315,7 @@ def setup_research_routes(research_handler, session_manager=None) -> APIRouter: endpoint_id: Optional[str] = None model: Optional[str] = None max_time: int = Field(default=300, ge=60, le=1800) - extraction_timeout: Optional[int] = Field(default=None, ge=15, le=600) + extraction_timeout: Optional[int] = Field(default=None, ge=15, le=3600) extraction_concurrency: Optional[int] = Field(default=None, ge=1, le=12) category: Optional[str] = None diff --git a/src/deep_research.py b/src/deep_research.py index 2de0c2269..f60ca3b07 100644 --- a/src/deep_research.py +++ b/src/deep_research.py @@ -199,7 +199,7 @@ class DeepResearcher: self.max_urls_per_round = max_urls_per_round self.max_content_chars = max_content_chars self.max_report_tokens = max_report_tokens - self.extraction_timeout = min(600, max(15, int(extraction_timeout or 90))) + self.extraction_timeout = min(3600, max(15, int(extraction_timeout or 90))) self.extraction_concurrency = min(12, max(1, int(extraction_concurrency or 3))) self.min_rounds = min_rounds self.max_empty_rounds = max_empty_rounds diff --git a/src/research_handler.py b/src/research_handler.py index 4ad39af8e..5550bdef4 100644 --- a/src/research_handler.py +++ b/src/research_handler.py @@ -689,7 +689,7 @@ class ResearchHandler: extraction_timeout if extraction_timeout is not None else get_setting("research_extraction_timeout_seconds", 90), default=90, minimum=15, - maximum=600, + maximum=3600, ) _extraction_concurrency = _bounded_int( extraction_concurrency if extraction_concurrency is not None else get_setting("research_extraction_concurrency", 3), diff --git a/static/js/settings.js b/static/js/settings.js index 0a63dd258..69602fa25 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -1426,7 +1426,7 @@ async function initResearchSettings() { var tv = parseInt(tokensInput.value, 10); if (tv && tv >= 1024) payload.research_max_tokens = tv; var et = parseInt(extractTimeoutInput.value, 10); - if (et && et >= 15 && et <= 600) payload.research_extraction_timeout_seconds = et; + if (et && et >= 15 && et <= 3600) payload.research_extraction_timeout_seconds = et; var ec = parseInt(extractConcurrencyInput.value, 10); if (ec && ec >= 1 && ec <= 12) payload.research_extraction_concurrency = ec; try { diff --git a/tests/test_deep_research_extraction_controls.py b/tests/test_deep_research_extraction_controls.py index bdbbae374..3317ddc76 100644 --- a/tests/test_deep_research_extraction_controls.py +++ b/tests/test_deep_research_extraction_controls.py @@ -86,3 +86,13 @@ async def test_fetch_and_extract_uses_configured_timeout(monkeypatch): assert result["summary"] == "useful page content" assert captured["timeout"] == 123 + + +def test_extraction_timeout_allows_long_local_model_runs(): + researcher = DeepResearcher( + llm_endpoint="http://local.test/v1/chat/completions", + llm_model="local-model", + extraction_timeout=1800, + ) + + assert researcher.extraction_timeout == 1800