mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 00:03:01 +03:00
fix(agent): time out no-tools model requests
This commit is contained in:
committed by
chengyongru
parent
04974b7607
commit
f5e467626d
+30
-13
@@ -922,18 +922,7 @@ class AgentRunner:
|
|||||||
conversation_state: ProviderConversationStateController,
|
conversation_state: ProviderConversationStateController,
|
||||||
provider_context: ProviderCallContext | None = None,
|
provider_context: ProviderCallContext | None = None,
|
||||||
) -> LLMResponse:
|
) -> LLMResponse:
|
||||||
timeout_s: float | None = spec.llm_timeout_s
|
timeout_s = self._resolve_llm_timeout_s(spec)
|
||||||
if timeout_s is None:
|
|
||||||
# Default to a finite timeout to avoid per-session lock starvation when an LLM
|
|
||||||
# request hangs indefinitely (e.g. gateway/network stall).
|
|
||||||
# Set NANOBOT_LLM_TIMEOUT_S=0 to disable.
|
|
||||||
raw = os.environ.get("NANOBOT_LLM_TIMEOUT_S", "300").strip()
|
|
||||||
try:
|
|
||||||
timeout_s = float(raw)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
timeout_s = 300.0
|
|
||||||
if timeout_s <= 0:
|
|
||||||
timeout_s = None
|
|
||||||
|
|
||||||
kwargs = self._build_request_kwargs(
|
kwargs = self._build_request_kwargs(
|
||||||
spec,
|
spec,
|
||||||
@@ -1302,10 +1291,38 @@ class AgentRunner:
|
|||||||
messages,
|
messages,
|
||||||
tools=None,
|
tools=None,
|
||||||
)
|
)
|
||||||
return await spec.runtime.provider.chat_with_retry(
|
coro = spec.runtime.provider.chat_with_retry(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
provider_context=provider_context,
|
provider_context=provider_context,
|
||||||
)
|
)
|
||||||
|
timeout_s = self._resolve_llm_timeout_s(spec)
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
await coro
|
||||||
|
if timeout_s is None
|
||||||
|
else await asyncio.wait_for(coro, timeout=timeout_s)
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
return LLMResponse(
|
||||||
|
content=f"Error calling LLM: timed out after {timeout_s:g}s",
|
||||||
|
finish_reason="error",
|
||||||
|
error_kind="timeout",
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_llm_timeout_s(spec: AgentRunSpec) -> float | None:
|
||||||
|
"""Resolve the wall-clock limit shared by every model request path."""
|
||||||
|
timeout_s = spec.llm_timeout_s
|
||||||
|
if timeout_s is None:
|
||||||
|
# Default to a finite timeout to avoid per-session lock starvation when an LLM
|
||||||
|
# request hangs indefinitely (e.g. gateway/network stall).
|
||||||
|
# Set NANOBOT_LLM_TIMEOUT_S=0 to disable.
|
||||||
|
raw = os.environ.get("NANOBOT_LLM_TIMEOUT_S", "300").strip()
|
||||||
|
try:
|
||||||
|
timeout_s = float(raw)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
timeout_s = 300.0
|
||||||
|
return timeout_s if timeout_s > 0 else None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _budget_exhausted_finalization_messages(
|
def _budget_exhausted_finalization_messages(
|
||||||
|
|||||||
@@ -499,6 +499,55 @@ async def test_runner_times_out_hung_llm_request():
|
|||||||
assert "timed out" in (result.final_content or "").lower()
|
assert "timed out" in (result.final_content or "").lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_runner_times_out_hung_max_iteration_finalization():
|
||||||
|
from nanobot.agent.runner import AgentRunner
|
||||||
|
|
||||||
|
provider = MagicMock()
|
||||||
|
calls = 0
|
||||||
|
|
||||||
|
async def chat_with_retry(**kwargs):
|
||||||
|
nonlocal calls
|
||||||
|
calls += 1
|
||||||
|
if calls == 1:
|
||||||
|
return LLMResponse(
|
||||||
|
content="",
|
||||||
|
tool_calls=[
|
||||||
|
ToolCallRequest(
|
||||||
|
id="call_1",
|
||||||
|
name="probe",
|
||||||
|
arguments={},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
finish_reason="tool_calls",
|
||||||
|
)
|
||||||
|
await asyncio.Event().wait()
|
||||||
|
|
||||||
|
provider.chat_with_retry = chat_with_retry
|
||||||
|
tools = MagicMock()
|
||||||
|
tools.get_definitions.return_value = []
|
||||||
|
tools.execute = AsyncMock(return_value="ok")
|
||||||
|
|
||||||
|
result = await asyncio.wait_for(
|
||||||
|
AgentRunner().run(make_run_spec(
|
||||||
|
provider,
|
||||||
|
initial_messages=[{"role": "user", "content": "run the probe"}],
|
||||||
|
tools=tools,
|
||||||
|
model="test-model",
|
||||||
|
max_iterations=1,
|
||||||
|
max_tool_result_chars=_MAX_TOOL_RESULT_CHARS,
|
||||||
|
max_iterations_message="fallback after {max_iterations} iteration",
|
||||||
|
llm_timeout_s=0.01,
|
||||||
|
)),
|
||||||
|
timeout=1.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert calls == 2
|
||||||
|
assert result.stop_reason == "max_iterations"
|
||||||
|
assert result.error is None
|
||||||
|
assert result.final_content == "fallback after 1 iteration"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_runner_applies_outer_wall_timeout_to_streaming_requests():
|
async def test_runner_applies_outer_wall_timeout_to_streaming_requests():
|
||||||
from nanobot.agent.hook import AgentHook, AgentHookContext
|
from nanobot.agent.hook import AgentHook, AgentHookContext
|
||||||
|
|||||||
Reference in New Issue
Block a user