mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
Research: add configurable run timeout
Surfaces the research_run_timeout_seconds setting (added in #783) in Settings → Research as a "Max Time" field, and lets 0 disable the wall-clock cap entirely for long deep-research runs. - settings.py: document that 0 disables the cap; default stays 1800s. - research_handler.py: resolve 0 (or negative) to no timeout (asyncio.wait_for timeout=None); other values stay bounded to [60, 86400] as before. - index.html / settings.js: "Max Time" input bound to research_run_timeout_seconds, validated to {0} ∪ [60, 86400], with copy making explicit that 0 = no limit (unbounded model/API cost). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-7
@@ -216,15 +216,25 @@ class ResearchHandler:
|
|||||||
"""
|
"""
|
||||||
# Resolve the hard wall-clock timeout from settings when the caller
|
# Resolve the hard wall-clock timeout from settings when the caller
|
||||||
# didn't pin one. Local / edge models routinely need more than the
|
# didn't pin one. Local / edge models routinely need more than the
|
||||||
# old 600s default to finish a deep-research synthesis.
|
# old 600s default to finish a deep-research synthesis. A setting of
|
||||||
|
# 0 disables the cap entirely (unlimited run); any other value is
|
||||||
|
# bounded to [60, 86400] so a misconfigured settings.json can't
|
||||||
|
# explode into a multi-day hang.
|
||||||
if hard_timeout is None:
|
if hard_timeout is None:
|
||||||
from src.settings import get_setting
|
from src.settings import get_setting
|
||||||
hard_timeout = _bounded_int(
|
try:
|
||||||
get_setting("research_run_timeout_seconds", 1800),
|
raw_timeout = int(get_setting("research_run_timeout_seconds", 1800))
|
||||||
default=1800,
|
except (TypeError, ValueError):
|
||||||
minimum=60,
|
raw_timeout = 1800
|
||||||
maximum=86400,
|
if raw_timeout <= 0:
|
||||||
)
|
hard_timeout = None # 0 = no wall-clock cap (asyncio.wait_for timeout=None)
|
||||||
|
else:
|
||||||
|
hard_timeout = _bounded_int(
|
||||||
|
raw_timeout,
|
||||||
|
default=1800,
|
||||||
|
minimum=60,
|
||||||
|
maximum=86400,
|
||||||
|
)
|
||||||
|
|
||||||
# Cancel any existing research for this session
|
# Cancel any existing research for this session
|
||||||
if session_id in self._active_tasks:
|
if session_id in self._active_tasks:
|
||||||
|
|||||||
+4
-1
@@ -89,7 +89,10 @@ DEFAULT_SETTINGS = {
|
|||||||
# Hard wall-clock cap on a single deep-research run. The previous 600s
|
# Hard wall-clock cap on a single deep-research run. The previous 600s
|
||||||
# (10 min) default cut off slow local / edge LLMs mid-synthesis; 1800s
|
# (10 min) default cut off slow local / edge LLMs mid-synthesis; 1800s
|
||||||
# (30 min) is comfortable for most local setups while still bounding
|
# (30 min) is comfortable for most local setups while still bounding
|
||||||
# runaway jobs. Tune via Settings or by editing data/settings.json.
|
# runaway jobs. Set to 0 to disable the cap entirely (unlimited) — only
|
||||||
|
# for very long deep-research runs, since a stalled job then runs an
|
||||||
|
# unbounded model/API bill. Other values are bounded to [60, 86400].
|
||||||
|
# Tune via Settings or by editing data/settings.json.
|
||||||
"research_run_timeout_seconds": 1800,
|
"research_run_timeout_seconds": 1800,
|
||||||
"agent_max_tool_calls": 0,
|
"agent_max_tool_calls": 0,
|
||||||
"agent_input_token_budget": 6000,
|
"agent_input_token_budget": 6000,
|
||||||
|
|||||||
@@ -1463,6 +1463,10 @@
|
|||||||
<label class="settings-label">Extract Parallel</label>
|
<label class="settings-label">Extract Parallel</label>
|
||||||
<input id="set-researchExtractConcurrency" type="text" inputmode="numeric" placeholder="3" class="settings-select" style="width:120px;">
|
<input id="set-researchExtractConcurrency" type="text" inputmode="numeric" placeholder="3" class="settings-select" style="width:120px;">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-row">
|
||||||
|
<label class="settings-label">Max Time</label>
|
||||||
|
<input id="set-researchRunTimeout" type="text" inputmode="numeric" placeholder="1800 sec (0 = no limit)" class="settings-select" style="width:120px;">
|
||||||
|
</div>
|
||||||
<div id="set-researchMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
|
<div id="set-researchMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1402,6 +1402,7 @@ async function initResearchSettings() {
|
|||||||
var tokensInput = el('set-researchMaxTokens');
|
var tokensInput = el('set-researchMaxTokens');
|
||||||
var extractTimeoutInput = el('set-researchExtractTimeout');
|
var extractTimeoutInput = el('set-researchExtractTimeout');
|
||||||
var extractConcurrencyInput = el('set-researchExtractConcurrency');
|
var extractConcurrencyInput = el('set-researchExtractConcurrency');
|
||||||
|
var runTimeoutInput = el('set-researchRunTimeout');
|
||||||
var msg = el('set-researchMsg');
|
var msg = el('set-researchMsg');
|
||||||
var endpoints = [];
|
var endpoints = [];
|
||||||
|
|
||||||
@@ -1424,6 +1425,9 @@ async function initResearchSettings() {
|
|||||||
if (settings.research_max_tokens) tokensInput.value = settings.research_max_tokens;
|
if (settings.research_max_tokens) tokensInput.value = settings.research_max_tokens;
|
||||||
if (settings.research_extraction_timeout_seconds) extractTimeoutInput.value = settings.research_extraction_timeout_seconds;
|
if (settings.research_extraction_timeout_seconds) extractTimeoutInput.value = settings.research_extraction_timeout_seconds;
|
||||||
if (settings.research_extraction_concurrency) extractConcurrencyInput.value = settings.research_extraction_concurrency;
|
if (settings.research_extraction_concurrency) extractConcurrencyInput.value = settings.research_extraction_concurrency;
|
||||||
|
if (settings.research_run_timeout_seconds !== undefined && settings.research_run_timeout_seconds !== null) {
|
||||||
|
runTimeoutInput.value = settings.research_run_timeout_seconds;
|
||||||
|
}
|
||||||
} catch (e) { console.warn('Failed to load research settings', e); }
|
} catch (e) { console.warn('Failed to load research settings', e); }
|
||||||
|
|
||||||
function showStatus() {
|
function showStatus() {
|
||||||
@@ -1442,6 +1446,12 @@ async function initResearchSettings() {
|
|||||||
if (extractConcurrencyInput.value) {
|
if (extractConcurrencyInput.value) {
|
||||||
parts.push('Parallel: ' + extractConcurrencyInput.value);
|
parts.push('Parallel: ' + extractConcurrencyInput.value);
|
||||||
}
|
}
|
||||||
|
if (runTimeoutInput.value !== '') {
|
||||||
|
var rtv = parseInt(runTimeoutInput.value, 10);
|
||||||
|
if (!isNaN(rtv)) {
|
||||||
|
parts.push(rtv === 0 ? 'Max time: no limit' : 'Max time: ' + rtv + 's');
|
||||||
|
}
|
||||||
|
}
|
||||||
if (parts.length) {
|
if (parts.length) {
|
||||||
msg.textContent = parts.join(' · ');
|
msg.textContent = parts.join(' · ');
|
||||||
msg.style.color = 'var(--fg)';
|
msg.style.color = 'var(--fg)';
|
||||||
@@ -1463,6 +1473,13 @@ async function initResearchSettings() {
|
|||||||
if (et && et >= 15 && et <= 3600) payload.research_extraction_timeout_seconds = et;
|
if (et && et >= 15 && et <= 3600) payload.research_extraction_timeout_seconds = et;
|
||||||
var ec = parseInt(extractConcurrencyInput.value, 10);
|
var ec = parseInt(extractConcurrencyInput.value, 10);
|
||||||
if (ec && ec >= 1 && ec <= 12) payload.research_extraction_concurrency = ec;
|
if (ec && ec >= 1 && ec <= 12) payload.research_extraction_concurrency = ec;
|
||||||
|
if (runTimeoutInput.value !== '') {
|
||||||
|
var rt = parseInt(runTimeoutInput.value, 10);
|
||||||
|
// 0 = no limit (disables the hard timeout); otherwise 60s..86400s (24h)
|
||||||
|
if (!isNaN(rt) && (rt === 0 || (rt >= 60 && rt <= 86400))) {
|
||||||
|
payload.research_run_timeout_seconds = rt;
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await fetch('/api/auth/settings', { method: 'POST', credentials: 'same-origin',
|
await fetch('/api/auth/settings', { method: 'POST', credentials: 'same-origin',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -1481,6 +1498,7 @@ async function initResearchSettings() {
|
|||||||
tokensInput.addEventListener('change', saveResearch);
|
tokensInput.addEventListener('change', saveResearch);
|
||||||
extractTimeoutInput.addEventListener('change', saveResearch);
|
extractTimeoutInput.addEventListener('change', saveResearch);
|
||||||
extractConcurrencyInput.addEventListener('change', saveResearch);
|
extractConcurrencyInput.addEventListener('change', saveResearch);
|
||||||
|
runTimeoutInput.addEventListener('change', saveResearch);
|
||||||
|
|
||||||
_registerAiEndpointRefresh(function(nextEndpoints) {
|
_registerAiEndpointRefresh(function(nextEndpoints) {
|
||||||
endpoints = nextEndpoints;
|
endpoints = nextEndpoints;
|
||||||
|
|||||||
Reference in New Issue
Block a user