Skip to content

Commit 8d9838a

Browse files
Yaohua-LeoLehaoLinSoulter
authored
fix(agent): pass tool_call_timeout to subagent handsoff, cron and background task execution, and increase default timeout from 60 to 120 (AstrBotDevs#6713)
* fix(agent): pass tool_call_timeout to SubAgent handoff execution - Add tool_call_timeout parameter to _execute_handoff method - Pass run_context.tool_call_timeout to ctx.tool_loop_agent - Add unit test to verify tool_call_timeout is correctly passed - Fixes AstrBotDevs#6711: SubAgent MCP tool call timeout now respects configured timeout The SubAgent handoff execution was using the default 60-second timeout instead of the configured tool_call_timeout from provider settings. This change ensures that SubAgent MCP tool calls respect the user's configured timeout settings. * test: add unit test for tool_call_timeout in SubAgent handoff * fix: restore deleted test and fix test assertion - Restore test_collect_handoff_image_urls_filters_extensionless_missing_event_file - Fix test_collect_handoff_image_urls_keeps_extensionless_existing_event_file assertion - Keep new test_execute_handoff_passes_tool_call_timeout_to_tool_loop_agent * refactor: simplify tool_call_timeout passing in _execute_handoff - Pass run_context.tool_call_timeout directly to ctx.tool_loop_agent - Remove unnecessary local variable assignment - Addresses review feedback from Sourcery AI * fix(config): increase default tool call timeout from 60 to 120 seconds --------- Co-authored-by: LehaoLin <linlehao@cuhk.edu.cn> Co-authored-by: Soulter <905617992@qq.com>
1 parent b273ba2 commit 8d9838a

10 files changed

Lines changed: 62 additions & 9 deletions

File tree

astrbot/core/agent/run_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ContextWrapper(Generic[TContext]):
1616
context: TContext
1717
messages: list[Message] = Field(default_factory=list)
1818
"""This field stores the llm message context for the agent run, agent runners will maintain this field automatically."""
19-
tool_call_timeout: int = 60 # Default tool call timeout in seconds
19+
tool_call_timeout: int = 120 # Default tool call timeout in seconds
2020

2121

2222
NoContext = ContextWrapper[None]

astrbot/core/astr_agent_tool_exec.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ async def _execute_handoff(
303303
tools=toolset,
304304
contexts=contexts,
305305
max_steps=agent_max_step,
306+
tool_call_timeout=run_context.tool_call_timeout,
306307
stream=stream,
307308
)
308309
yield mcp.types.CallToolResult(
@@ -481,7 +482,7 @@ async def _wake_main_agent_for_background_result(
481482
)
482483
cron_event.role = event.role
483484
config = MainAgentBuildConfig(
484-
tool_call_timeout=3600,
485+
tool_call_timeout=run_context.tool_call_timeout,
485486
streaming_response=ctx.get_config()
486487
.get("provider_settings", {})
487488
.get("stream", False),

astrbot/core/config/default.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
"unsupported_streaming_strategy": "realtime_segmenting",
118118
"reachability_check": False,
119119
"max_agent_step": 30,
120-
"tool_call_timeout": 60,
120+
"tool_call_timeout": 120,
121121
"tool_schema_mode": "full",
122122
"llm_safety_mode": True,
123123
"safety_mode_strategy": "system_prompt", # TODO: llm judge

astrbot/core/cron/manager.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,11 @@ async def _woke_main_agent(
307307
if cron_payload.get("origin", "tool") == "api":
308308
cron_event.role = "admin"
309309

310+
tool_call_timeout = cfg.get("provider_settings", {}).get(
311+
"tool_call_timeout", 120
312+
)
310313
config = MainAgentBuildConfig(
311-
tool_call_timeout=3600,
314+
tool_call_timeout=tool_call_timeout,
312315
llm_safety_mode=False,
313316
streaming_response=False,
314317
)

astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ def mark_stream_consumed() -> None:
378378
request=req,
379379
run_context=AgentContextWrapper(
380380
context=astr_agent_ctx,
381-
tool_call_timeout=60,
381+
tool_call_timeout=120,
382382
),
383383
agent_hooks=MAIN_AGENT_HOOKS,
384384
provider_config=self.prov_cfg,

astrbot/core/star/context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ async def tool_loop_agent(
153153
system_prompt: str | None = None,
154154
contexts: list[Message] | None = None,
155155
max_steps: int = 30,
156-
tool_call_timeout: int = 60,
156+
tool_call_timeout: int = 120,
157157
**kwargs: Any,
158158
) -> LLMResponse:
159159
"""Run an agent loop that allows the LLM to call tools iteratively until a final answer is produced.

docs/en/dev/astrbot-config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ The default AstrBot configuration is as follows:
7474
"show_tool_use_status": False,
7575
"streaming_segmented": False,
7676
"max_agent_step": 30,
77-
"tool_call_timeout": 60,
77+
"tool_call_timeout": 120,
7878
},
7979
"provider_stt_settings": {
8080
"enable": False,

docs/en/dev/star/guides/ai.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ llm_resp = await self.context.tool_loop_agent(
8484
prompt="Search for videos related to AstrBot on Bilibili.",
8585
tools=ToolSet([BilibiliTool()]),
8686
max_steps=30, # Maximum agent execution steps
87-
tool_call_timeout=60, # Tool invocation timeout
87+
tool_call_timeout=120, # Tool invocation timeout
8888
)
8989
# print(llm_resp.completion_text) # Get the returned text
9090
```

docs/zh/dev/astrbot-config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ AstrBot 默认配置如下:
7474
"show_tool_use_status": False,
7575
"streaming_segmented": False,
7676
"max_agent_step": 30,
77-
"tool_call_timeout": 60,
77+
"tool_call_timeout": 120,
7878
},
7979
"provider_stt_settings": {
8080
"enable": False,

tests/unit/test_astr_agent_tool_exec.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,55 @@ async def _fake_convert_to_file_path(self):
272272
assert image_urls == []
273273

274274

275+
@pytest.mark.asyncio
276+
async def test_execute_handoff_passes_tool_call_timeout_to_tool_loop_agent(
277+
monkeypatch: pytest.MonkeyPatch,
278+
):
279+
captured: dict = {}
280+
281+
async def _fake_get_current_chat_provider_id(_umo):
282+
return "provider-id"
283+
284+
async def _fake_tool_loop_agent(**kwargs):
285+
captured.update(kwargs)
286+
return SimpleNamespace(completion_text="ok")
287+
288+
context = SimpleNamespace(
289+
get_current_chat_provider_id=_fake_get_current_chat_provider_id,
290+
tool_loop_agent=_fake_tool_loop_agent,
291+
get_config=lambda **_kwargs: {"provider_settings": {}},
292+
)
293+
event = _DummyEvent([])
294+
run_context = ContextWrapper(
295+
context=SimpleNamespace(event=event, context=context),
296+
tool_call_timeout=120,
297+
)
298+
tool = SimpleNamespace(
299+
name="transfer_to_subagent",
300+
provider_id=None,
301+
agent=SimpleNamespace(
302+
name="subagent",
303+
tools=[],
304+
instructions="subagent-instructions",
305+
begin_dialogs=[],
306+
run_hooks=None,
307+
),
308+
)
309+
310+
results = []
311+
async for result in FunctionToolExecutor._execute_handoff(
312+
tool,
313+
run_context,
314+
image_urls_prepared=True,
315+
input="hello",
316+
image_urls=[],
317+
):
318+
results.append(result)
319+
320+
assert len(results) == 1
321+
assert captured["tool_call_timeout"] == 120
322+
323+
275324
@pytest.mark.asyncio
276325
async def test_collect_handoff_image_urls_filters_extensionless_file_outside_temp_root(
277326
monkeypatch: pytest.MonkeyPatch,

0 commit comments

Comments
 (0)